diff options
author | Taybin Rutkin <taybin@taybin.com> | 2005-05-13 20:47:18 +0000 |
---|---|---|
committer | Taybin Rutkin <taybin@taybin.com> | 2005-05-13 20:47:18 +0000 |
commit | d09f6b3016bacbc2871a8946cbb24ad705076509 (patch) | |
tree | f27312839c2a772cb2ce068a4f28b2449ad869df /libs/midi++2 |
Initial revision
git-svn-id: svn://localhost/trunk/ardour2@4 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/midi++2')
42 files changed, 5852 insertions, 0 deletions
diff --git a/libs/midi++2/.cvsignore b/libs/midi++2/.cvsignore new file mode 100644 index 0000000000..b1322ae10d --- /dev/null +++ b/libs/midi++2/.cvsignore @@ -0,0 +1,3 @@ +libmidi++.pc +libmidi++.spec +version.cc diff --git a/libs/midi++2/AUTHORS b/libs/midi++2/AUTHORS new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/libs/midi++2/AUTHORS diff --git a/libs/midi++2/COPYING b/libs/midi++2/COPYING new file mode 100644 index 0000000000..d60c31a97a --- /dev/null +++ b/libs/midi++2/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/libs/midi++2/ChangeLog b/libs/midi++2/ChangeLog new file mode 100644 index 0000000000..c40b4b5e82 --- /dev/null +++ b/libs/midi++2/ChangeLog @@ -0,0 +1,9 @@ +2005-05-08 Taybin Rutkin <taybin@earthlink.net> + * Brought up Jesse's CoreMIDI threading fix. + +2005-04-14 Taybin Rutkin <taybin@earthlink.net> + * Brought up Paul's parser fix from midi++ 1.16. + +2005-04-01 Taybin Rutkin <taybin@earthlink.net> + * Updated for sigc++-2.0. + * Incremented version number to 2.0.0. diff --git a/libs/midi++2/NEWS b/libs/midi++2/NEWS new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/libs/midi++2/NEWS diff --git a/libs/midi++2/README b/libs/midi++2/README new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/libs/midi++2/README diff --git a/libs/midi++2/SConscript b/libs/midi++2/SConscript new file mode 100644 index 0000000000..7c2d3964ce --- /dev/null +++ b/libs/midi++2/SConscript @@ -0,0 +1,48 @@ +# -*- python -*- + +import glob + +Import('env libraries') + +midi2 = env.Copy() +midi2.Merge([ libraries['sigc2'], libraries['xml'], libraries['pbd3'] ]) + +domain = 'midipp' + +midi2.Append(DOMAIN=domain,MAJOR=2,MINOR=1,MICRO=1) + +sources = Split(""" +fd_midiport.cc +fifomidi.cc +midi.cc +midichannel.cc +midicontrollable.cc +midifactory.cc +midimanager.cc +midiparser.cc +midiport.cc +mmc.cc +mtc.cc +port_request.cc +version.cc +""") + +sysdep_sources = Split (""" +alsa_sequencer_midiport.cc +coremidi_midiport.cc +""") + +if env['SYSMIDI'] == 'CoreMIDI': + sysdep_src = [ 'coremidi_midiport.cc' ] + midi2.Append (CCFLAGS="-DWITH_COREMIDI") +else: + sysdep_src = [ 'alsa_sequencer_midiport.cc' ] + midi2.Append (CCFLAGS="-DWITH_ALSA") + +midi2.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE") +midi2.Append(CCFLAGS="-DLIBSIGC_DISABLE_DEPRECATED") + +midi2.VersionBuild(['version.cc','midi++/version.h'], 'SConscript') + +libmidi2 = midi2.StaticLibrary('midi++', [ sources, sysdep_src ]) +Default(libmidi2) diff --git a/libs/midi++2/alsa_sequencer_midiport.cc b/libs/midi++2/alsa_sequencer_midiport.cc new file mode 100644 index 0000000000..e0f6ad33e9 --- /dev/null +++ b/libs/midi++2/alsa_sequencer_midiport.cc @@ -0,0 +1,156 @@ +/* + Copyright (C) 2004 Paul Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <fcntl.h> +#include <cerrno> + +#include <midi++/types.h> +#include <midi++/alsa_sequencer.h> +#include <midi++/port_request.h> + +//#define DOTRACE 1 + +#ifdef DOTRACE +#define TR_FN() (cerr << __FUNCTION__ << endl) +#define TR_VAL(v) (cerr << __FILE__ " " << __LINE__ << " " #v "=" << v << endl) +#else +#define TR_FN() +#define TR_VAL(v) +#endif + + + + +using namespace std; +using namespace MIDI; + +ALSA_SequencerMidiPort::ALSA_SequencerMidiPort (PortRequest &req) + : Port (req) + , seq (0) + , decoder (0) + , encoder (0) +{ + TR_FN(); + int err; + if (0 <= (err = CreatePorts (req)) && + 0 <= (err = snd_midi_event_new (1024, &decoder)) && // Length taken from ARDOUR::Session::midi_read () + 0 <= (err = snd_midi_event_new (64, &encoder))) { // Length taken from ARDOUR::Session::mmc_buffer + snd_midi_event_init (decoder); + snd_midi_event_init (encoder); + _ok = true; + req.status = PortRequest::OK; + } else + req.status = PortRequest::Unknown; +} + +ALSA_SequencerMidiPort::~ALSA_SequencerMidiPort () +{ + if (decoder) + snd_midi_event_free (decoder); + if (encoder) + snd_midi_event_free (encoder); + if (seq) + snd_seq_close (seq); +} + +int ALSA_SequencerMidiPort::selectable () const +{ + struct pollfd pfd[1]; + if (0 <= snd_seq_poll_descriptors (seq, pfd, 1, POLLIN | POLLOUT)) { + return pfd[0].fd; + } + return -1; +} + +int ALSA_SequencerMidiPort::write (byte *msg, size_t msglen) +{ + TR_FN (); + int R; + snd_midi_event_reset_encode (encoder); + int nwritten = snd_midi_event_encode (encoder, msg, msglen, &SEv); + TR_VAL (nwritten); + if (0 < nwritten) { + if (0 <= (R = snd_seq_event_output (seq, &SEv)) && + 0 <= (R = snd_seq_drain_output (seq))) { + bytes_written += nwritten; + if (output_parser) { + output_parser->raw_preparse (*output_parser, msg, nwritten); + for (int i = 0; i < nwritten; i++) { + output_parser->scanner (msg[i]); + } + output_parser->raw_postparse (*output_parser, msg, nwritten); + } + return nwritten; + } else { + TR_VAL(R); + return R; + } + } else + return nwritten; +} + +int ALSA_SequencerMidiPort::read (byte *buf, size_t max) +{ + TR_FN(); + int err; + snd_seq_event_t *ev; + if (0 <= (err = snd_seq_event_input (seq, &ev))) { + TR_VAL(err); + err = snd_midi_event_decode (decoder, buf, max, ev); + } + + if (err > 0) { + bytes_read += err; + + if (input_parser) { + input_parser->raw_preparse (*input_parser, buf, err); + for (int i = 0; i < err; i++) { + input_parser->scanner (buf[i]); + } + input_parser->raw_postparse (*input_parser, buf, err); + } + } + return -ENOENT == err ? 0 : err; +} + +int ALSA_SequencerMidiPort::CreatePorts (PortRequest &req) +{ + int err; + if (0 <= (err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, + (req.mode & O_NONBLOCK) ? SND_SEQ_NONBLOCK : 0))) { + snd_seq_set_client_name (seq, req.devname); + unsigned int caps = 0; + if (req.mode == O_WRONLY || req.mode == O_RDWR) + caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; + if (req.mode == O_RDONLY || req.mode == O_RDWR) + caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; + err = snd_seq_create_simple_port (seq, req.tagname, caps, SND_SEQ_PORT_TYPE_MIDI_GENERIC); + if (err >= 0) { + port_id = err; + snd_seq_ev_clear (&SEv); + snd_seq_ev_set_source (&SEv, port_id); + snd_seq_ev_set_subs (&SEv); + snd_seq_ev_set_direct (&SEv); + } else + snd_seq_close (seq); + } + return err; +} + diff --git a/libs/midi++2/coremidi_midiport.cc b/libs/midi++2/coremidi_midiport.cc new file mode 100644 index 0000000000..8d1d927b7b --- /dev/null +++ b/libs/midi++2/coremidi_midiport.cc @@ -0,0 +1,144 @@ +/* + Copyright (C) 2004 Paul Davis + Copyright (C) 2004 Grame + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <fcntl.h> +#include <cerrno> + +#include <midi++/coremidi_midiport.h> +#include <midi++/types.h> +#include <midi++/port_request.h> +#include <mach/mach_time.h> + +#include <pbd/pthread_utils.h> + +using namespace std; +using namespace MIDI; + +MIDITimeStamp CoreMidi_MidiPort::MIDIGetCurrentHostTime() +{ + return mach_absolute_time(); +} + +CoreMidi_MidiPort::CoreMidi_MidiPort (PortRequest &req) : Port (req) +{ + firstrecv = true; + int err; + if (0 == (err = Open(req))) { + _ok = true; + req.status = PortRequest::OK; + } else + req.status = PortRequest::Unknown; +} + +CoreMidi_MidiPort::~CoreMidi_MidiPort () {Close();} + +void CoreMidi_MidiPort::Close () +{ + if (midi_destination) MIDIEndpointDispose(midi_destination); + if (midi_source) MIDIEndpointDispose(midi_source); + if (midi_client) MIDIClientDispose(midi_client); +} + +int CoreMidi_MidiPort::write (byte *msg, size_t msglen) +{ + OSStatus err; + MIDIPacketList* pktlist = (MIDIPacketList*)midi_buffer; + MIDIPacket* packet = MIDIPacketListInit(pktlist); + packet = MIDIPacketListAdd(pktlist,sizeof(midi_buffer),packet,MIDIGetCurrentHostTime(),msglen,msg); + + if (packet) { + + err = MIDIReceived(midi_source,pktlist); + if (err != noErr) { + //error << "MIDIReceived error" << err << endmsg. + } + + bytes_written += msglen; + return msglen; + }else{ + return 0; + } +} + +int CoreMidi_MidiPort::Open (PortRequest &req) +{ + OSStatus err; + CFStringRef coutputStr; + string str; + + coutputStr = CFStringCreateWithCString(0, req.devname, CFStringGetSystemEncoding()); + err = MIDIClientCreate(coutputStr, 0, 0, &midi_client); + CFRelease(coutputStr); + if (!midi_client) { + //error << "Cannot open CoreMidi client : " << err << endmsg. + goto error; + } + + str = req.tagname + string("_in"); + coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding()); + err = MIDIDestinationCreate(midi_client, coutputStr, read_proc, this, &midi_destination); + CFRelease(coutputStr); + if (!midi_destination) { + //error << "Cannot create CoreMidi destination : " << err << endmsg. + goto error; + } + + str = req.tagname + string("_out"); + coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding()); + err = MIDISourceCreate(midi_client, coutputStr, &midi_source); + CFRelease(coutputStr); + if (!midi_source) { + //error << "Cannot create CoreMidi source : " << err << endmsg. + goto error; + } + + return err; + +error: + Close(); + return err; +} + +void CoreMidi_MidiPort::read_proc (const MIDIPacketList *pktlist, void *refCon, void *connRefCon) +{ + CoreMidi_MidiPort* driver = (CoreMidi_MidiPort*)refCon; + MIDIPacket *packet = (MIDIPacket *)pktlist->packet; + + if (driver->firstrecv) { + driver->firstrecv = false; + PBD::ThreadCreated (pthread_self(), "COREMIDI"); + } + + for (unsigned int i = 0; i < pktlist->numPackets; ++i) { + + driver->bytes_read += packet->length; + + if (driver->input_parser) { + driver->input_parser->raw_preparse (*driver->input_parser, packet->data, packet->length); + for (int i = 0; i < packet->length; i++) { + driver->input_parser->scanner (packet->data[i]); + } + driver->input_parser->raw_postparse (*driver->input_parser, packet->data, packet->length); + } + + packet = MIDIPacketNext(packet); + } +} + diff --git a/libs/midi++2/fd_midiport.cc b/libs/midi++2/fd_midiport.cc new file mode 100644 index 0000000000..2ced63c259 --- /dev/null +++ b/libs/midi++2/fd_midiport.cc @@ -0,0 +1,181 @@ +/* + Copyright (C) 1999 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <fcntl.h> +#include <cerrno> + +#include <pbd/error.h> +#include <pbd/pathscanner.h> + +#include <midi++/types.h> +#include <midi++/fd_midiport.h> + +using namespace std; +using namespace MIDI; + +string *FD_MidiPort::midi_dirpath = 0; +string *FD_MidiPort::midi_filename_pattern = 0; + +FD_MidiPort::FD_MidiPort (PortRequest &req, + const string &dirpath, + const string &pattern) + : Port (req) +{ + open (req); + + if (_fd < 0) { + switch (errno) { + case EBUSY: + error << "MIDI: port device in use" << endmsg; + req.status = PortRequest::Busy; + break; + case ENOENT: + error << "MIDI: no such port device" << endmsg; + req.status = PortRequest::NoSuchFile; + break; + case EACCES: + error << "MIDI: access to port denied" << endmsg; + req.status = PortRequest::NotAllowed; + break; + default: + req.status = PortRequest::Unknown; + } + } else { + _ok = true; + req.status = PortRequest::OK; + + if (midi_dirpath == 0) { + midi_dirpath = new string (dirpath); + midi_filename_pattern = new string (pattern); + } + + if (req.mode & O_NONBLOCK == 0) { + /* we unconditionally set O_NONBLOCK during + open, but the request didn't ask for it, + so remove it. + */ + + int flags = fcntl (_fd, F_GETFL, 0); + fcntl (_fd, F_SETFL, flags & ~(O_NONBLOCK)); + } + } +} + +void +FD_MidiPort::open (PortRequest &req) + +{ + int mode = req.mode | O_NONBLOCK; + _fd = ::open (req.devname, mode); +} + +vector<string *> * +FD_MidiPort::list_devices () + +{ + PathScanner scanner; + + return scanner (*midi_dirpath, *midi_filename_pattern, false, true); +} + +int +FD_MidiPort::selectable () const + +{ + long flags; + + /* turn on non-blocking mode, since we plan to use select/poll + to tell us when there is data to read. + */ + + flags = fcntl (_fd, F_GETFL); + flags |= O_NONBLOCK; + + if (fcntl (_fd, F_SETFL, flags)) { + error << "FD_MidiPort: could not turn on non-blocking mode" + << " (" << strerror (errno) + << ')' + << endmsg; + + return -1; + } + + return _fd; +} + + +int +FD_MidiPort::do_slow_write (byte *msg, unsigned int msglen) + +{ + size_t n; + size_t i; + + for (n = 0; n < msglen; n++) { + + if (::write (_fd, &msg[n], 1) != 1) { + break; + } + + bytes_written++; + for (i = 0; i < slowdown * 10000; i++); + } + + + if (n && output_parser) { + output_parser->raw_preparse (*output_parser, msg, n); + for (unsigned int i = 0; i < n; i++) { + output_parser->scanner (msg[i]); + } + output_parser->raw_postparse (*output_parser, msg, n); + } + + return n; +} + +int +FD_MidiPort::read (byte* buf, size_t max) +{ + int nread; + + if ((_mode & O_ACCMODE) == O_WRONLY) { + return -EACCES; + } + + // cerr << "MIDI: read up to " << max << " from " << _fd << " (" << name() << ')' << endl; + + if ((nread = ::read (_fd, buf, max)) > 0) { + bytes_read += nread; + + // cerr << " read " << nread << endl; + + if (input_parser) { + input_parser->raw_preparse + (*input_parser, buf, nread); + for (int i = 0; i < nread; i++) { + input_parser->scanner (buf[i]); + } + input_parser->raw_postparse + (*input_parser, buf, nread); + } + } + + return nread; +} diff --git a/libs/midi++2/fifomidi.cc b/libs/midi++2/fifomidi.cc new file mode 100644 index 0000000000..7bb126ddeb --- /dev/null +++ b/libs/midi++2/fifomidi.cc @@ -0,0 +1,44 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <midi++/types.h> +#include <midi++/fifomidi.h> + +using namespace MIDI; + +FIFO_MidiPort::FIFO_MidiPort (PortRequest &req) + : FD_MidiPort (req, ".", "midi") + +{ +} + +void +FIFO_MidiPort::open (PortRequest &req) + +{ + /* This is a placeholder for the fun-and-games I think we will + need to do with FIFO's. + */ + + _fd = ::open (req.devname, req.mode|O_NDELAY); +} diff --git a/libs/midi++2/libmidi++.pc.in b/libs/midi++2/libmidi++.pc.in new file mode 100644 index 0000000000..10be08c7a7 --- /dev/null +++ b/libs/midi++2/libmidi++.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/midi++ + +Name: libmidi++ +Version: @VERSION@ +Description: libmidi++, a C++ library for handling MIDI I/O, including MMC and MTC +Requires: libpbd +Libs: -L${libdir} -lmidipp @AUDIO_LIBS@ @NON_PKG_LIBS@ +Cflags: -I${includedir} @NON_PKG_CFLAGS@ diff --git a/libs/midi++2/libmidi++.spec.in b/libs/midi++2/libmidi++.spec.in new file mode 100644 index 0000000000..fbeb6a263a --- /dev/null +++ b/libs/midi++2/libmidi++.spec.in @@ -0,0 +1,71 @@ +Summary: A high level MIDI handling library. +%define lib_name midi++ +Name: lib%{lib_name} +Version: @VERSION@ +Release: 2 +Copyright: GPL +Source: ftp://ftp.quasimodo.org/pub/libs/midi++/current/%{name}-%{version}.tar.gz +Url: http://www.quasimodo.org +Vendor: Paul Barton Davis <pbd@op.net> +Packager: jfm3 <jfm3@acm.org> +Group: System Environment/Libraries +Prefix: %{_prefix} +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description + +libmidi++ is a C++ library that uses libsigc++ to make handling MIDI +I/O from MIDI hardware trivial. You can attach "callbacks" to any +MIDI input or output event, ranging from each individual byte, to a +particular message type on a particular channel. It also provides +channel "state". + +%prep +%setup -q + +%build +CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" ./configure $ARCH_FLAGS --prefix=%{prefix} +make + +%install +rm -rf $RPM_BUILD_ROOT +install -d -m 755 $RPM_BUILD_ROOT%{prefix}/{{include,lib}/%{lib_name}} +make install INSTALL="%(which install) -p" prefix=$RPM_BUILD_ROOT%{prefix} + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%doc README AUTHORS NEWS COPYING* +%{prefix}/lib/libmidipp.so* + +%package devel +Summary: A high level MIDI handling library -- develper version. +Group: System Environment/Libraries + +%description devel + +libmidi++ is a C++ library that uses libsigc++ to make handling MIDI +I/O from MIDI hardware trivial. You can attach "callbacks" to any +MIDI input or output event, ranging from each individual byte, to a +particular message type on a particular channel. It also provides +channel "state". + +This package holds static libraries and headers needed by developers +who wish to use libmidi++ in their programs. + +%files devel +%defattr(-,root,root) +%{prefix}/include/midi++/* +%{prefix}/lib/libmidipp.a +%{prefix}/lib/libmidipp.la +%{prefix}/bin/midi++-config +%{prefix}/share/aclocal/midi++.m4 +%{prefix}/share/aclocal/midi.m4 diff --git a/libs/midi++2/midi++/.DS_Store b/libs/midi++2/midi++/.DS_Store Binary files differnew file mode 100644 index 0000000000..5008ddfcf5 --- /dev/null +++ b/libs/midi++2/midi++/.DS_Store diff --git a/libs/midi++2/midi++/.cvsignore b/libs/midi++2/midi++/.cvsignore new file mode 100644 index 0000000000..67020331ba --- /dev/null +++ b/libs/midi++2/midi++/.cvsignore @@ -0,0 +1 @@ +version.h diff --git a/libs/midi++2/midi++/alsa_rawmidi.h b/libs/midi++2/midi++/alsa_rawmidi.h new file mode 100644 index 0000000000..655b673174 --- /dev/null +++ b/libs/midi++2/midi++/alsa_rawmidi.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __alsa_rawmidi_h__ +#define __alsa_rawmidi_h__ + +#include <vector> +#include <string> + +#include <fcntl.h> +#include <unistd.h> + +#include <midi++/port.h> +#include <midi++/fd_midiport.h> + +class ALSA_RawMidiPort : public MIDI::FD_MidiPort + +{ + public: + ALSA_RawMidiPort (MIDI::PortRequest &req) + : FD_MidiPort (req, "/dev/snd", "midi") {} + virtual ~ALSA_RawMidiPort () {} +}; + + +#endif // __alsa_rawmidi_h__ + diff --git a/libs/midi++2/midi++/alsa_sequencer.h b/libs/midi++2/midi++/alsa_sequencer.h new file mode 100644 index 0000000000..8ddb2a7dd7 --- /dev/null +++ b/libs/midi++2/midi++/alsa_sequencer.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2004 Paul Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __alsa_sequencer_midiport_h__ +#define __alsa_sequencer_midiport_h__ + +#include <vector> +#include <string> + +#include <fcntl.h> +#include <unistd.h> + +#include <alsa/asoundlib.h> +#include <midi++/port.h> + +namespace MIDI { + +class ALSA_SequencerMidiPort : public Port + +{ + public: + ALSA_SequencerMidiPort (PortRequest &req); + virtual ~ALSA_SequencerMidiPort (); + + /* select(2)/poll(2)-based I/O */ + + virtual int selectable() const; + + protected: + /* Direct I/O */ + + int write (byte *msg, size_t msglen); + int read (byte *buf, size_t max); + + private: + snd_seq_t *seq; + snd_midi_event_t *decoder, *encoder; + int port_id; + snd_seq_event_t SEv; + int CreatePorts(PortRequest &req); + +}; + +}; /* namespace MIDI */ + +#endif // __alsa_sequencer_midiport_h__ + diff --git a/libs/midi++2/midi++/channel.h b/libs/midi++2/midi++/channel.h new file mode 100644 index 0000000000..1efde3cb93 --- /dev/null +++ b/libs/midi++2/midi++/channel.h @@ -0,0 +1,161 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midichannel_h__ +#define __midichannel_h__ + +#include <queue> + +#include <sigc++/sigc++.h> + +#include <midi++/types.h> +#include <midi++/parser.h> + +namespace MIDI { + +class Port; + +class Channel : public sigc::trackable { + + public: + Channel (byte channel_number, Port &); + + Port &midi_port() { return port; } + byte channel() { return channel_number; } + byte program() { return program_number; } + byte bank() { return bank_number; } + byte pressure () { return chanpress; } + byte poly_pressure (byte n) { return polypress[n]; } + + byte last_note_on () { + return _last_note_on; + } + byte last_on_velocity () { + return _last_on_velocity; + } + byte last_note_off () { + return _last_note_off; + } + byte last_off_velocity () { + return _last_off_velocity; + } + + pitchbend_t pitchbend () { + return pitch_bend; + } + + controller_value_t controller_value (byte n) { + return controller_val[n%128]; + } + + controller_value_t *controller_addr (byte n) { + return &controller_val[n%128]; + } + + void set_controller (byte n, byte val) { + controller_val[n%128] = val; + } + + int channel_msg (byte id, byte val1, byte val2); + + int all_notes_off () { + return channel_msg (MIDI::controller, 123, 0); + } + + int control (byte id, byte value) { + return channel_msg (MIDI::controller, id, value); + } + + int note_on (byte note, byte velocity) { + return channel_msg (MIDI::on, note, velocity); + } + + int note_off (byte note, byte velocity) { + return channel_msg (MIDI::off, note, velocity); + } + + int aftertouch (byte value) { + return channel_msg (MIDI::chanpress, value, 0); + } + + int poly_aftertouch (byte note, byte value) { + return channel_msg (MIDI::polypress, note, value); + } + + int program_change (byte value) { + return channel_msg (MIDI::program, value, 0); + } + + int pitchbend (byte msb, byte lsb) { + return channel_msg (MIDI::pitchbend, lsb, msb); + } + + protected: + friend class Port; + void connect_input_signals (); + void connect_output_signals (); + + private: + Port &port; + + /* Current channel values */ + + byte channel_number; + byte bank_number; + byte program_number; + byte rpn_msb; + byte rpn_lsb; + byte nrpn_msb; + byte nrpn_lsb; + byte chanpress; + byte polypress[128]; + bool controller_14bit[128]; + controller_value_t controller_val[128]; + byte controller_msb[128]; + byte controller_lsb[128]; + byte _last_note_on; + byte _last_on_velocity; + byte _last_note_off; + byte _last_off_velocity; + pitchbend_t pitch_bend; + bool _omni; + bool _poly; + bool _mono; + size_t _notes_on; + + void reset (bool notes_off = true); + + void process_note_off (Parser &, EventTwoBytes *); + void process_note_on (Parser &, EventTwoBytes *); + void process_controller (Parser &, EventTwoBytes *); + void process_polypress (Parser &, EventTwoBytes *); + void process_program_change (Parser &, byte); + void process_chanpress (Parser &, byte); + void process_pitchbend (Parser &, pitchbend_t); + void process_reset (Parser &); +}; + +}; /* namespace MIDI */ + +#endif // __midichannel_h__ + + + + diff --git a/libs/midi++2/midi++/controllable.h b/libs/midi++2/midi++/controllable.h new file mode 100644 index 0000000000..3fa108bb46 --- /dev/null +++ b/libs/midi++2/midi++/controllable.h @@ -0,0 +1,92 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __qm_midicontrollable_h__ +#define __qm_midicontrollable_h__ + +#include <string> + +#include <sigc++/sigc++.h> + +#include <midi++/types.h> + +namespace MIDI { + +class Channel; +class Port; +class Parser; + +class Controllable : public sigc::trackable +{ + public: + Controllable (Port *, bool bistate = false); + virtual ~Controllable (); + + void midi_rebind (Port *, channel_t channel=-1); + void midi_forget (); + void learn_about_external_control (); + void stop_learning (); + void drop_external_control (); + + virtual void set_value (float) = 0; + + sigc::signal<void> learning_started; + sigc::signal<void> learning_stopped; + + bool get_control_info (channel_t&, eventType&, byte&); + void set_control_type (channel_t, eventType, byte); + + bool get_midi_feedback () { return feedback; } + void set_midi_feedback (bool val) { feedback = val; } + + Port * get_port() { return port; } + + std::string control_description() const { return _control_description; } + + void send_midi_feedback (float); + + private: + bool bistate; + int midi_msg_id; /* controller ID or note number */ + sigc::connection midi_sense_connection[2]; + sigc::connection midi_learn_connection; + size_t connections; + Port* port; + eventType control_type; + byte control_additional; + channel_t control_channel; + std::string _control_description; + bool feedback; + + void midi_receiver (Parser &p, byte *, size_t); + void midi_sense_note (Parser &, EventTwoBytes *, bool is_on); + void midi_sense_note_on (Parser &p, EventTwoBytes *tb); + void midi_sense_note_off (Parser &p, EventTwoBytes *tb); + void midi_sense_controller (Parser &, EventTwoBytes *); + void midi_sense_program_change (Parser &, byte); + void midi_sense_pitchbend (Parser &, pitchbend_t); + + void bind_midi (channel_t, eventType, byte); +}; + +}; /* namespace MIDI */ + +#endif // __qm_midicontrollable_h__ + diff --git a/libs/midi++2/midi++/coremidi_midiport.h b/libs/midi++2/midi++/coremidi_midiport.h new file mode 100644 index 0000000000..e02a225784 --- /dev/null +++ b/libs/midi++2/midi++/coremidi_midiport.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2004 Paul Davis + Copyright (C) 2004 Grame + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#ifndef __coremidi_midiport_h__ +#define __coremidi_midiport_h__ + +#include <list> +#include <string> + +#include <fcntl.h> +#include <unistd.h> + +#include <midi++/port.h> + +#include <CoreMIDI/CoreMIDI.h> + +namespace MIDI { + + class CoreMidi_MidiPort:public Port { + public: + CoreMidi_MidiPort(PortRequest & req); + virtual ~ CoreMidi_MidiPort(); + + virtual int selectable() const { + return -1; + } + protected: + /* Direct I/O */ + int write(byte * msg, size_t msglen); + int read(byte * buf, size_t max) { + return 0; + } /* CoreMidi callback */ + static void read_proc(const MIDIPacketList * pktlist, + void *refCon, void *connRefCon); + + private: + byte midi_buffer[1024]; + MIDIClientRef midi_client; + MIDIEndpointRef midi_destination; + MIDIEndpointRef midi_source; + + int Open(PortRequest & req); + void Close(); + static MIDITimeStamp MIDIGetCurrentHostTime(); + + bool firstrecv; + }; + +}; /* namespace MIDI */ + +#endif // __coremidi_midiport_h__ diff --git a/libs/midi++2/midi++/factory.h b/libs/midi++2/midi++/factory.h new file mode 100644 index 0000000000..1543f68cdc --- /dev/null +++ b/libs/midi++2/midi++/factory.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midi_factory_h__ +#define __midi_factory_h__ + +#include <vector> +#include <string> + +#include <midi++/port.h> + +namespace MIDI { + +class PortFactory { + public: + Port *create_port (PortRequest &req); + + static void add_port_request (std::vector<PortRequest *> &reqs, + const std::string &reqstr); +}; + +}; /* namespace MIDI */ + +#endif // __midi_factory_h__ diff --git a/libs/midi++2/midi++/fd_midiport.h b/libs/midi++2/midi++/fd_midiport.h new file mode 100644 index 0000000000..853af9d7b4 --- /dev/null +++ b/libs/midi++2/midi++/fd_midiport.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 1999 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __fd_midiport_h__ +#define __fd_midiport_h__ + +#include <vector> +#include <string> +#include <cerrno> + +#include <cerrno> +#include <fcntl.h> +#include <unistd.h> + +#include <midi++/port.h> +#include <midi++/port_request.h> + +namespace MIDI { + +class FD_MidiPort : public Port + +{ + public: + FD_MidiPort (PortRequest &req, + const std::string &dirpath, + const std::string &pattern); + + virtual ~FD_MidiPort () { + ::close (_fd); + } + + virtual int selectable() const; + static std::vector<std::string *> *list_devices (); + + protected: + int _fd; + virtual void open (PortRequest &req); + + virtual int write (byte *msg, size_t msglen) { + int nwritten; + + if ((_mode & O_ACCMODE) == O_RDONLY) { + return -EACCES; + } + + if (slowdown) { + return do_slow_write (msg, msglen); + } + + if ((nwritten = ::write (_fd, msg, msglen)) > 0) { + bytes_written += nwritten; + + if (output_parser) { + output_parser->raw_preparse + (*output_parser, msg, nwritten); + for (int i = 0; i < nwritten; i++) { + output_parser->scanner (msg[i]); + } + output_parser->raw_postparse + (*output_parser, msg, nwritten); + } + } + return nwritten; + } + + virtual int read (byte *buf, size_t max); + + private: + static std::string *midi_dirpath; + static std::string *midi_filename_pattern; + + int do_slow_write (byte *msg, unsigned int msglen); +}; + +}; /*namespace MIDI */ + +#endif // __fd_midiport_h__ diff --git a/libs/midi++2/midi++/fifomidi.h b/libs/midi++2/midi++/fifomidi.h new file mode 100644 index 0000000000..eb8778d4d5 --- /dev/null +++ b/libs/midi++2/midi++/fifomidi.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __fifomidi_h__ +#define __fifomidi_h__ + +#include <fcntl.h> +#include <vector> +#include <string> +#include <unistd.h> + +#include <midi++/port.h> +#include <midi++/port_request.h> +#include <midi++/fd_midiport.h> + +namespace MIDI { + +class FIFO_MidiPort : public MIDI::FD_MidiPort + +{ + public: + FIFO_MidiPort (PortRequest &req); + ~FIFO_MidiPort () {}; + + private: + void open (PortRequest &req); +}; + +}; /* namespace MIDI */ + +#endif // __fifomidi_h__ diff --git a/libs/midi++2/midi++/manager.h b/libs/midi++2/midi++/manager.h new file mode 100644 index 0000000000..4889aad8c9 --- /dev/null +++ b/libs/midi++2/midi++/manager.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midi_manager_h__ +#define __midi_manager_h__ + +#include <map> +#include <string> + +#include <midi++/types.h> +#include <midi++/port.h> + +namespace MIDI { + +class Manager { + public: + ~Manager (); + + Port *add_port (PortRequest &); + int remove_port (std::string port); + + Port *port (std::string name); + Port *port (size_t number); + + size_t nports () { return ports_by_device.size(); } + + /* defaults for clients who are not picky */ + + Port *inputPort; + Port *outputPort; + channel_t inputChannelNumber; + channel_t outputChannelNumber; + + int set_input_port (size_t port); + int set_input_port (std::string); + int set_output_port (size_t port); + int set_output_port (std::string); + int set_input_channel (channel_t); + int set_output_channel (channel_t); + + int foreach_port (int (*func)(const Port &, size_t n, void *), + void *arg); + + typedef std::map<std::string, Port *> PortMap; + + const PortMap& get_midi_ports() const { return ports_by_tag; } + + static Manager *instance () { + if (theManager == 0) { + theManager = new Manager; + } + return theManager; + } + + static int parse_port_request (std::string str, Port::Type type); + + private: + /* This is a SINGLETON pattern */ + + Manager (); + + static Manager *theManager; + PortMap ports_by_device; /* canonical */ + PortMap ports_by_tag; /* may contain duplicate Ports */ + + void close_ports (); +}; + +}; /* namespace MIDI */ + +#endif // __midi_manager_h__ diff --git a/libs/midi++2/midi++/mmc.h b/libs/midi++2/midi++/mmc.h new file mode 100644 index 0000000000..7b51b33a72 --- /dev/null +++ b/libs/midi++2/midi++/mmc.h @@ -0,0 +1,261 @@ +/* + Copyright (C) 2000 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midipp_mmc_h_h__ +#define __midipp_mmc_h_h__ + +#include <sigc++/sigc++.h> +#include <midi++/types.h> + +namespace MIDI { + +class Port; +class Parser; + +class MachineControl : public sigc::trackable + +{ + public: + typedef byte CommandSignature[60]; + typedef byte ResponseSignature[60]; + + enum Command { + cmdStop = 0x1, + cmdPlay = 0x2, + cmdDeferredPlay = 0x3, + cmdFastForward = 0x4, + cmdRewind = 0x5, + cmdRecordStrobe = 0x6, + + cmdRecordExit = 0x7, + cmdRecordPause = 0x8, + cmdPause = 0x9, + cmdEject = 0xA, + cmdChase = 0xB, + cmdCommandErrorReset = 0xC, + cmdMmcReset = 0xD, + + cmdIllegalMackieJogStart = 0x20, + cmdIllegalMackieJogStop = 0x21, + + cmdWrite = 0x40, + cmdMaskedWrite = 0x41, + cmdRead = 0x42, + cmdUpdate = 0x43, + cmdLocate = 0x44, + cmdVariablePlay = 0x45, + cmdSearch = 0x46, + + cmdShuttle = 0x47, + cmdStep = 0x48, + cmdAssignSystemMaster = 0x49, + cmdGeneratorCommand = 0x4A, + cmdMtcCommand = 0x4B, + cmdMove = 0x4C, + cmdAdd = 0x4D, + + cmdSubtract = 0x4E, + cmdDropFrameAdjust = 0x4F, + cmdProcedure = 0x50, + cmdEvent = 0x51, + cmdGroup = 0x52, + cmdCommandSegment = 0x53, + cmdDeferredVariablePlay = 0x54, + + cmdRecordStrobeVariable = 0x55, + + cmdWait = 0x7C, + cmdResume = 0x7F, + }; + + MachineControl (Port &port, + float MMCVersion, + CommandSignature &cs, + ResponseSignature &rs); + + Port &port() { return _port; } + + void set_device_id (byte id); + static bool is_mmc (byte *sysex_buf, size_t len); + + /* Signals to connect to if you want to run "callbacks" + when certain MMC commands are received. + */ + + sigc::signal<void,MachineControl &> Stop; + sigc::signal<void,MachineControl &> Play; + sigc::signal<void,MachineControl &> DeferredPlay; + sigc::signal<void,MachineControl &> FastForward; + sigc::signal<void,MachineControl &> Rewind; + sigc::signal<void,MachineControl &> RecordStrobe; + sigc::signal<void,MachineControl &> RecordExit; + sigc::signal<void,MachineControl &> RecordPause; + sigc::signal<void,MachineControl &> Pause; + sigc::signal<void,MachineControl &> Eject; + sigc::signal<void,MachineControl &> Chase; + sigc::signal<void,MachineControl &> CommandErrorReset; + sigc::signal<void,MachineControl &> MmcReset; + + sigc::signal<void,MachineControl &> JogStart; + sigc::signal<void,MachineControl &> JogStop; + + sigc::signal<void,MachineControl &> Write; + sigc::signal<void,MachineControl &> MaskedWrite; + sigc::signal<void,MachineControl &> Read; + sigc::signal<void,MachineControl &> Update; + sigc::signal<void,MachineControl &> VariablePlay; + sigc::signal<void,MachineControl &> Search; + sigc::signal<void,MachineControl &> AssignSystemMaster; + sigc::signal<void,MachineControl &> GeneratorCommand; + sigc::signal<void,MachineControl &> MidiTimeCodeCommand; + sigc::signal<void,MachineControl &> Move; + sigc::signal<void,MachineControl &> Add; + sigc::signal<void,MachineControl &> Subtract; + sigc::signal<void,MachineControl &> DropFrameAdjust; + sigc::signal<void,MachineControl &> Procedure; + sigc::signal<void,MachineControl &> Event; + sigc::signal<void,MachineControl &> Group; + sigc::signal<void,MachineControl &> CommandSegment; + sigc::signal<void,MachineControl &> DeferredVariablePlay; + sigc::signal<void,MachineControl &> RecordStrobeVariable; + sigc::signal<void,MachineControl &> Wait; + sigc::signal<void,MachineControl &> Resume; + + /* The second argument is the shuttle speed, the third is + true if the direction is "forwards", false for "reverse" + */ + + sigc::signal<void,MachineControl &,float,bool> Shuttle; + + /* The second argument specifies the desired track record enabled + status. + */ + + sigc::signal<void,MachineControl &,size_t,bool> + TrackRecordStatusChange; + + /* The second argument points to a byte array containing + the locate target value in MMC Standard Time Code + format (5 bytes, roughly: hrs/mins/secs/frames/subframes) + */ + + sigc::signal<void,MachineControl &, const byte *> Locate; + + /* The second argument is the number of steps to jump */ + + sigc::signal<void,MachineControl &, int> Step; + + protected: + +#define MMC_NTRACKS 48 + + /* MMC Information fields (think "registers") */ + + CommandSignature commandSignature; + ResponseSignature responseSignature; + + byte updateRate; + byte responseError; + byte commandError; + byte commandErrorLevel; + + byte motionControlTally; + byte velocityTally; + byte stopMode; + byte fastMode; + byte recordMode; + byte recordStatus; + bool trackRecordStatus[MMC_NTRACKS]; + bool trackRecordReady[MMC_NTRACKS]; + byte globalMonitor; + byte recordMonitor; + byte trackSyncMonitor; + byte trackInputMonitor; + byte stepLength; + byte playSpeedReference; + byte fixedSpeed; + byte lifterDefeat; + byte controlDisable; + byte trackMute; + byte failure; + byte selectedTimeCode; + byte shortSelectedTimeCode; + byte timeStandard; + byte selectedTimeCodeSource; + byte selectedTimeCodeUserbits; + byte selectedMasterCode; + byte requestedOffset; + byte actualOffset; + byte lockDeviation; + byte shortSelectedMasterCode; + byte shortRequestedOffset; + byte shortActualOffset; + byte shortLockDeviation; + byte resolvedPlayMode; + byte chaseMode; + byte generatorTimeCode; + byte shortGeneratorTimeCode; + byte generatorCommandTally; + byte generatorSetUp; + byte generatorUserbits; + byte vitcInsertEnable; + byte midiTimeCodeInput; + byte shortMidiTimeCodeInput; + byte midiTimeCodeCommandTally; + byte midiTimeCodeSetUp; + byte gp0; + byte gp1; + byte gp2; + byte gp3; + byte gp4; + byte gp5; + byte gp6; + byte gp7; + byte shortGp0; + byte shortGp1; + byte shortGp2; + byte shortGp3; + byte shortGp4; + byte shortGp5; + byte shortGp6; + byte shortGp7; + byte procedureResponse; + byte eventResponse; + byte responseSegment; + byte wait; + byte resume; + + private: + byte _device_id; + MIDI::Port &_port; + + void process_mmc_message (Parser &p, byte *, size_t len); + + int do_masked_write (byte *, size_t len); + int do_locate (byte *, size_t len); + int do_step (byte *, size_t len); + int do_shuttle (byte *, size_t len); + + void write_track_record_ready (byte *, size_t len); +}; + +}; /* namespace MIDI */ + +#endif /* __midipp_mmc_h_h__ */ diff --git a/libs/midi++2/midi++/nullmidi.h b/libs/midi++2/midi++/nullmidi.h new file mode 100644 index 0000000000..a94b1015b0 --- /dev/null +++ b/libs/midi++2/midi++/nullmidi.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __nullmidi_h__ +#define __nullmidi_h__ + +#include <fcntl.h> +#include <vector> +#include <string> + +#include <midi++/port.h> +#include <midi++/port_request.h> + +namespace MIDI { + +class Null_MidiPort : public Port + +{ + public: + Null_MidiPort (PortRequest &req) + : Port (req) { + + /* reset devname and tagname */ + + _devname = "nullmidi"; + _tagname = "null"; + _type = Port::Null; + _ok = true; + } + + virtual ~Null_MidiPort () {}; + + virtual int write (byte *msg, size_t msglen) { + return msglen; + } + + virtual int read (byte *buf, size_t max) { + return 0; + } + + virtual int selectable() const { return -1; } +}; + +}; /* namespace MIDI */ + +#endif // __nullmidi_h__ diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h new file mode 100644 index 0000000000..4ac07cc15d --- /dev/null +++ b/libs/midi++2/midi++/parser.h @@ -0,0 +1,189 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midi_parse_h__ +#define __midi_parse_h__ + +#include <string> +#include <iostream> + +#include <sigc++/sigc++.h> + +#include <midi++/types.h> + +namespace MIDI { + +class Port; +class Parser; + +typedef sigc::signal<void, Parser &, byte> OneByteSignal; +typedef sigc::signal<void, Parser &, EventTwoBytes *> TwoByteSignal; +typedef sigc::signal<void, Parser &, pitchbend_t> PitchBendSignal; +typedef sigc::signal<void, Parser &, byte *, size_t> Signal; + +class Parser : public sigc::trackable { + public: + Parser (Port &p); + ~Parser (); + + /* signals that anyone can connect to */ + + OneByteSignal bank_change; + TwoByteSignal note_on; + TwoByteSignal note_off; + TwoByteSignal poly_pressure; + OneByteSignal pressure; + OneByteSignal program_change; + PitchBendSignal pitchbend; + TwoByteSignal controller; + + OneByteSignal channel_bank_change[16]; + TwoByteSignal channel_note_on[16]; + TwoByteSignal channel_note_off[16]; + TwoByteSignal channel_poly_pressure[16]; + OneByteSignal channel_pressure[16]; + OneByteSignal channel_program_change[16]; + PitchBendSignal channel_pitchbend[16]; + TwoByteSignal channel_controller[16]; + sigc::signal<void, Parser &> channel_active_preparse[16]; + sigc::signal<void, Parser &> channel_active_postparse[16]; + + OneByteSignal mtc_quarter_frame; + + Signal raw_preparse; + Signal raw_postparse; + Signal any; + Signal sysex; + Signal mmc; + Signal position; + Signal song; + + Signal mtc; + sigc::signal<void,Parser&> mtc_qtr; + + sigc::signal<void, Parser &> all_notes_off; + sigc::signal<void, Parser &> tune; + sigc::signal<void, Parser &> timing; + sigc::signal<void, Parser &> start; + sigc::signal<void, Parser &> stop; + sigc::signal<void, Parser &> contineu; /* note spelling */ + sigc::signal<void, Parser &> active_sense; + sigc::signal<void, Parser &> reset; + sigc::signal<void, Parser &> eox; + + /* This should really be protected, but then derivatives of Port + can't access it. + */ + + void scanner (byte c); + + size_t *message_counts() { return message_counter; } + const char *midi_event_type_name (MIDI::eventType); + void trace (bool onoff, std::ostream *o, const std::string &prefix = ""); + bool tracing() { return trace_stream != 0; } + Port &port() { return _port; } + + void set_offline (bool); + bool offline() const { return _offline; } + sigc::signal<void> OfflineStatusChanged; + + sigc::signal<int, byte *, size_t> edit; + + void set_mmc_forwarding (bool yn) { + _mmc_forward = yn; + } + + /* MTC */ + + enum MTC_Status { + MTC_Stopped = 0, + MTC_Forward, + MTC_Backward + }; + + MTC_FPS mtc_fps() const { return _mtc_fps; } + MTC_Status mtc_running() const { return _mtc_running; } + const byte *mtc_current() const { return _mtc_time; } + bool mtc_locked() const { return _mtc_locked; } + + sigc::signal<void,MTC_Status> mtc_status; + sigc::signal<bool> mtc_skipped; + sigc::signal<void,const byte*,bool> mtc_time; + + void set_mtc_forwarding (bool yn) { + _mtc_forward = yn; + } + + void reset_mtc_state (); + + private: + Port &_port; + /* tracing */ + + std::ostream *trace_stream; + std::string trace_prefix; + void trace_event (Parser &p, byte *msg, size_t len); + sigc::connection trace_connection; + + size_t message_counter[256]; + + enum ParseState { + NEEDSTATUS, + NEEDONEBYTE, + NEEDTWOBYTES, + VARIABLELENGTH + }; + ParseState state; + unsigned char *msgbuf; + int msglen; + int msgindex; + MIDI::eventType msgtype; + channel_t channel; + bool _offline; + bool runnable; + bool was_runnable; + bool _mmc_forward; + bool _mtc_forward; + int expected_mtc_quarter_frame_code; + byte _mtc_time[4]; + byte _qtr_mtc_time[4]; + unsigned long consecutive_qtr_frame_cnt; + MTC_FPS _mtc_fps; + MTC_Status _mtc_running; + bool _mtc_locked; + byte last_qtr_frame; + + ParseState pre_variable_state; + MIDI::eventType pre_variable_msgtype; + byte last_status_byte; + + void channel_msg (byte); + void realtime_msg (byte); + void system_msg (byte); + void signal (byte *msg, size_t msglen); + bool possible_mmc (byte *msg, size_t msglen); + bool possible_mtc (byte *msg, size_t msglen); + void process_mtc_quarter_frame (byte *msg); +}; + +}; /* namespace MIDI */ + +#endif // __midi_parse_h__ + diff --git a/libs/midi++2/midi++/port.h b/libs/midi++2/midi++/port.h new file mode 100644 index 0000000000..81e28615d0 --- /dev/null +++ b/libs/midi++2/midi++/port.h @@ -0,0 +1,147 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __libmidi_port_h__ +#define __libmidi_port_h__ + +#include <string> + +#include <sigc++/sigc++.h> + +#include <pbd/selectable.h> +#include <midi++/types.h> +#include <midi++/parser.h> + +namespace MIDI { + +class Channel; +class PortRequest; + +class Port : public sigc::trackable { + public: + enum Type { + Unknown, + ALSA_RawMidi, + ALSA_Sequencer, + CoreMidi_MidiPort, + Null, + FIFO, + }; + + + Port (PortRequest &); + virtual ~Port (); + + /* Direct I/O */ + + virtual int write (byte *msg, size_t msglen) = 0; + virtual int read (byte *buf, size_t max) = 0; + + /* slowdown i/o to a loop of single byte emissions + interspersed with a busy loop of 10000 * this value. + + This may be ignored by a particular instance + of this virtual class. See FD_MidiPort for an + example of where it used. + */ + + void set_slowdown (size_t n) { slowdown = n; } + + /* select(2)/poll(2)-based I/O */ + + virtual int selectable() const = 0; + + void selector_read_callback (Select::Selectable *, Select::Condition); + + static void xforms_read_callback (int cond, int fd, void *ptr); + static void gtk_read_callback (void *ptr, int fd, int cond); + + static void write_callback (byte *msg, unsigned int len, void *); + + Channel *channel (channel_t chn) { + return _channel[chn&0x7F]; + } + + Parser *input() { return input_parser; } + Parser *output() { return output_parser; } + + void iostat (int *written, int *read, + const size_t **in_counts, + const size_t **out_counts) { + + *written = bytes_written; + *read = bytes_read; + if (input_parser) { + *in_counts = input_parser->message_counts(); + } else { + *in_counts = 0; + } + if (output_parser) { + *out_counts = output_parser->message_counts(); + } else { + *out_counts = 0; + } + } + + int midimsg (byte *msg, size_t len) { + return !(write (msg, len) == (int) len); + } + + int three_byte_msg (byte a, byte b, byte c) { + byte msg[3]; + + msg[0] = a; + msg[1] = b; + msg[2] = c; + + return !(write (msg, 3) == 3); + } + + int clock (); + + const char *device () const { return _devname.c_str(); } + const char *name () const { return _tagname.c_str(); } + Type type () const { return _type; } + int mode () const { return _mode; } + bool ok () const { return _ok; } + size_t number () const { return _number; } + + protected: + bool _ok; + Type _type; + std::string _devname; + std::string _tagname; + int _mode; + size_t _number; + Channel *_channel[16]; + sigc::connection thru_connection; + unsigned int bytes_written; + unsigned int bytes_read; + Parser *input_parser; + Parser *output_parser; + size_t slowdown; + + private: + static size_t nports; +}; + +}; /* namespace MIDI */ + +#endif // __libmidi_port_h__ + diff --git a/libs/midi++2/midi++/port_request.h b/libs/midi++2/midi++/port_request.h new file mode 100644 index 0000000000..28a0d1d70b --- /dev/null +++ b/libs/midi++2/midi++/port_request.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 1999 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __midi_port_request_h__ +#define __midi_port_request_h__ + +#include <string> + +namespace MIDI { + +struct PortRequest { + enum Status { + Unknown, + OK, + Busy, + NoSuchFile, + TypeUnsupported, + NotAllowed + }; + const char *devname; + const char *tagname; + int mode; + Port::Type type; + Status status; + + PortRequest () { + devname = 0; + tagname = 0; + mode = 0; + type = Port::Unknown; + status = Unknown; + } + + PortRequest (const std::string &xdev, + const std::string &xtag, + const std::string &xmode, + const std::string &xtype); +}; + +}; /* namespace MIDI */ + +#endif // __midi_port_request_h__ + diff --git a/libs/midi++2/midi++/types.h b/libs/midi++2/midi++/types.h new file mode 100644 index 0000000000..b9d9bf33e7 --- /dev/null +++ b/libs/midi++2/midi++/types.h @@ -0,0 +1,69 @@ +#ifndef __midi_types_h__ +#define __midi_types_h__ + +namespace MIDI { + + typedef char channel_t; + typedef float controller_value_t; + typedef unsigned char byte; + typedef unsigned short pitchbend_t; + + enum eventType { + none = 0x0, + raw = 0xF4, /* undefined in MIDI spec */ + any = 0xF5, /* undefined in MIDI spec */ + off = 0x80, + on = 0x90, + controller = 0xB0, + program = 0xC0, + chanpress = 0xD0, + polypress = 0xA0, + pitchbend = 0xE0, + sysex = 0xF0, + mtc_quarter = 0xF1, + position = 0xF2, + song = 0xF3, + tune = 0xF6, + eox = 0xF7, + timing = 0xF8, + start = 0xFA, + contineu = 0xFB, /* note spelling */ + stop = 0xFC, + active = 0xFE, + reset = 0xFF + }; + + extern const char *controller_names[]; + byte decode_controller_name (const char *name); + + struct EventTwoBytes { + union { + byte note_number; + byte controller_number; + }; + union { + byte velocity; + byte value; + }; + }; + + enum MTC_FPS { + MTC_24_FPS = 0, + MTC_25_FPS = 1, + MTC_30_FPS_DROP = 2, + MTC_30_FPS = 3 + }; + + enum MTC_Status { + MTC_Stopped = 0, + MTC_Forward, + MTC_Backward, + }; + +}; /* namespace MIDI */ + +#endif // __midi_types_h__ + + + + diff --git a/libs/midi++2/midi.cc b/libs/midi++2/midi.cc new file mode 100644 index 0000000000..7c2dc835fb --- /dev/null +++ b/libs/midi++2/midi.cc @@ -0,0 +1,170 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <string> +#include <cstdlib> +#include <midi++/types.h> + +const char *MIDI::controller_names[] = { + "bank (0)", + "mod (1)", + "breath (2)", + "ctrl 3", + "foot (4)", + "port tm (5)", + "data msb (6)", + "volume (7)", + "balance (8)", + "ctrl 9", + "pan (10)", + "express (11)", + "ctrl 12", + "ctrl 13", + "ctrl 14", + "ctrl 15", + "gpc 1", + "gpc 2", + "gpc 3", + "gpc 4", + "ctrl 20", + "ctrl 21", + "ctrl 22", + "ctrl 23", + "ctrl 24", + "ctrl 25", + "ctrl 26", + "ctrl 27", + "ctrl 28", + "ctrl 29", + "ctrl 30", + "ctrl 31", + "lsb 0 (32)", + "lsb 1 (33)", + "lsb 2 (34)", + "lsb 3 (35)", + "lsb 4 (36)", + "lsb 5 (37)", + "lsb 6 (38)", + "lsb 7 (39)", + "lsb 8 (40)", + "lsb 9 (41)", + "lsb 10 (42)", + "lsb 11 (43)", + "lsb 12 (44)", + "lsb 13 (45)", + "lsb 14 (46)", + "lsb 15 (47)", + "lsb 16 (48)", + "lsb 17 (49)", + "lsb 18 (50)", + "lsb 19 (51)", + "lsb 20 (52)", + "lsb 21 (53)", + "lsb 22 (54)", + "lsb 23 (55)", + "lsb 24 (56)", + "lsb 25 (57)", + "lsb 26 (58)", + "lsb 27 (59)", + "lsb 28 (60)", + "lsb 29 (61)", + "lsb 30 (62)", + "lsb 31 (63)", + "sustain (64)", + "portamento (65)", + "sostenuto (66)", + "soft ped (67)", + "ctrl 68", + "hold 2 (69)", + "ctrl 70", + "ctrl 71", + "ctrl 72", + "ctrl 73", + "ctrl 74", + "ctrl 75", + "ctrl 76", + "ctrl 77", + "ctrl 78", + "ctrl 79", + "gpc 5 (80)", + "gpc 6 (81)", + "gpc 7 (82)", + "gpc 8 (83)", + "ctrl 84", + "ctrl 85", + "ctrl 86", + "ctrl 87", + "ctrl 88", + "ctrl 89", + "ctrl 90", + "fx dpth (91)", + "tremolo (92)", + "chorus (93)", + "detune (94)", + "phaser (95)", + "data inc (96)", + "data dec (97)", + "nrpn lsb (98)", + "nrpn msg (99)", + "rpn lsb (100)", + "rpn msb (101)", + "ctrl 102", + "ctrl 103", + "ctrl 104", + "ctrl 105", + "ctrl 106", + "ctrl 107", + "ctrl 108", + "ctrl 109", + "ctrl 110", + "ctrl 111", + "ctrl 112", + "ctrl 113", + "ctrl 114", + "ctrl 115", + "ctrl 116", + "ctrl 117", + "ctrl 118", + "ctrl 119", + "snd off (120)", + "rst ctrl (121)", + "local (122)", + "notes off (123)", + "omni off (124)", + "omni on (125)", + "mono on (126)", + "poly on (127)", + 0 +}; + +MIDI::byte +MIDI::decode_controller_name (const char *name) + +{ + char *lparen; + size_t len; + + if ((lparen = strrchr (name, '(')) != 0) { + return atoi (lparen+1); + } else { + len = strcspn (name, "0123456789"); + return atoi (name+len); + } +} diff --git a/libs/midi++2/midichannel.cc b/libs/midi++2/midichannel.cc new file mode 100644 index 0000000000..a6759b7962 --- /dev/null +++ b/libs/midi++2/midichannel.cc @@ -0,0 +1,304 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <midi++/types.h> +#include <midi++/port.h> +#include <midi++/channel.h> + +using namespace sigc; +using namespace MIDI; + +Channel::Channel (byte channelnum, Port &p) : port (p) +{ + channel_number = channelnum; + + reset (false); +} + +void +Channel::connect_input_signals () + +{ + port.input()->channel_pressure[channel_number].connect + (mem_fun (*this, &Channel::process_chanpress)); + port.input()->channel_note_on[channel_number].connect + (mem_fun (*this, &Channel::process_note_on)); + port.input()->channel_note_off[channel_number].connect + (mem_fun (*this, &Channel::process_note_off)); + port.input()->channel_poly_pressure[channel_number].connect + (mem_fun (*this, &Channel::process_polypress)); + port.input()->channel_program_change[channel_number].connect + (mem_fun (*this, &Channel::process_program_change)); + port.input()->channel_controller[channel_number].connect + (mem_fun (*this, &Channel::process_controller)); + port.input()->channel_pitchbend[channel_number].connect + (mem_fun (*this, &Channel::process_pitchbend)); + port.input()->reset.connect (mem_fun (*this, &Channel::process_reset)); +} + +void +Channel::connect_output_signals () + +{ + port.output()->channel_pressure[channel_number].connect + (mem_fun (*this, &Channel::process_chanpress)); + port.output()->channel_note_on[channel_number].connect + (mem_fun (*this, &Channel::process_note_on)); + port.output()->channel_note_off[channel_number].connect + (mem_fun (*this, &Channel::process_note_off)); + port.output()->channel_poly_pressure[channel_number].connect + (mem_fun (*this, &Channel::process_polypress)); + port.output()->channel_program_change[channel_number].connect + (mem_fun (*this, &Channel::process_program_change)); + port.output()->channel_controller[channel_number].connect + (mem_fun (*this, &Channel::process_controller)); + port.output()->channel_pitchbend[channel_number].connect + (mem_fun (*this, &Channel::process_pitchbend)); + port.output()->reset.connect (mem_fun (*this, &Channel::process_reset)); +} + +void +Channel::reset (bool notes_off) +{ + program_number = channel_number; + bank_number = 0; + pitch_bend = 0; + + _last_note_on = 0; + _last_note_off = 0; + _last_on_velocity = 0; + _last_off_velocity = 0; + + if (notes_off) { + all_notes_off (); + } + + memset (polypress, 0, sizeof (polypress)); + memset (controller_msb, 0, sizeof (controller_msb)); + memset (controller_lsb, 0, sizeof (controller_lsb)); + + /* zero all controllers XXX not necessarily the right thing */ + + memset (controller_val, 0, sizeof (controller_val)); + + for (int n = 0; n < 128; n++) { + controller_14bit[n] = false; + } + + rpn_msb = 0; + rpn_lsb = 0; + nrpn_msb = 0; + nrpn_lsb = 0; + + _omni = true; + _poly = false; + _mono = true; + _notes_on = 0; +} + +void +Channel::process_note_off (Parser &parser, EventTwoBytes *tb) + +{ + _last_note_off = tb->note_number; + _last_off_velocity = tb->velocity; + + if (_notes_on) { + _notes_on--; + } +} + +void +Channel::process_note_on (Parser &parser, EventTwoBytes *tb) + +{ + _last_note_on = tb->note_number; + _last_on_velocity = tb->velocity; + _notes_on++; +} + +void +Channel::process_controller (Parser &parser, EventTwoBytes *tb) + +{ + unsigned short cv; + + /* XXX arguably need a lock here to protect non-atomic changes + to controller_val[...]. or rather, need to make sure that + all changes *are* atomic. + */ + + if (tb->controller_number <= 31) { /* unsigned: no test for >= 0 */ + + /* if this controller is already known to use 14 bits, + then treat this value as the MSB, and combine it + with the existing LSB. + + otherwise, just treat it as a 7 bit value, and set + it directly. + */ + + cv = (unsigned short) controller_val[tb->controller_number]; + + if (controller_14bit[tb->controller_number]) { + cv = ((tb->value << 7) | (cv & 0x7f)); + } else { + cv = tb->value; + } + + controller_val[tb->controller_number] = (controller_value_t)cv; + + } else if ((tb->controller_number >= 32 && + tb->controller_number <= 63)) { + + cv = (unsigned short) controller_val[tb->controller_number]; + + /* LSB for CC 0-31 arrived. + + If this is the first time (i.e. its currently + flagged as a 7 bit controller), mark the + controller as 14 bit, adjust the existing value + to be the MSB, and OR-in the new LSB value. + + otherwise, OR-in the new low 7bits with the old + high 7. + */ + + int cn = tb->controller_number - 32; + + if (controller_14bit[cn] == false) { + controller_14bit[cn] = true; + cv = (cv << 7) | (tb->value & 0x7f); + } else { + cv = (cv & 0x3f80) | (tb->value & 0x7f); + } + + controller_val[tb->controller_number] = + (controller_value_t) cv; + } else { + + /* controller can only take 7 bit values */ + + controller_val[tb->controller_number] = + (controller_value_t) tb->value; + } + + /* bank numbers are special, in that they have their own signal + */ + + if (tb->controller_number == 0) { + bank_number = (unsigned short) controller_val[0]; + if (port.input()) { + port.input()->bank_change (*port.input(), bank_number); + port.input()->channel_bank_change[channel_number] + (*port.input(), bank_number); + } + } + +} + +void +Channel::process_program_change (Parser &parser, byte val) + +{ + program_number = val; +} + +void +Channel::process_chanpress (Parser &parser, byte val) + +{ + chanpress = val; +} + +void +Channel::process_polypress (Parser &parser, EventTwoBytes *tb) + +{ + polypress[tb->note_number] = tb->value; +} + +void +Channel::process_pitchbend (Parser &parser, pitchbend_t val) + +{ + pitch_bend = val; +} + +void +Channel::process_reset (Parser &parser) + +{ + reset (); +} + +int +Channel::channel_msg (byte id, byte val1, byte val2) + +{ + unsigned char msg[3]; + int len = 0; + + msg[0] = id | (channel_number & 0xf); + + switch (id) { + case off: + msg[1] = val1 & 0x7F; + msg[2] = val2 & 0x7F; + len = 3; + break; + + case on: + msg[1] = val1 & 0x7F; + msg[2] = val2 & 0x7F; + len = 3; + break; + + case MIDI::polypress: + msg[1] = val1 & 0x7F; + msg[2] = val2 & 0x7F; + len = 3; + break; + + case controller: + msg[1] = val1 & 0x7F; + msg[2] = val2 & 0x7F; + len = 3; + break; + + case MIDI::program: + msg[1] = val1 & 0x7F; + len = 2; + break; + + case MIDI::chanpress: + msg[1] = val1 & 0x7F; + len = 2; + break; + + case MIDI::pitchbend: + msg[1] = val1 & 0x7F; + msg[2] = val2 & 0x7F; + len = 3; + break; + } + + return port.midimsg (msg, len); +} diff --git a/libs/midi++2/midicontrollable.cc b/libs/midi++2/midicontrollable.cc new file mode 100644 index 0000000000..f17782f3c4 --- /dev/null +++ b/libs/midi++2/midicontrollable.cc @@ -0,0 +1,325 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cstdio> /* for sprintf, sigh */ +#include <pbd/error.h> +#include <midi++/port.h> +#include <midi++/channel.h> +#include <midi++/controllable.h> + +using namespace sigc; +using namespace MIDI; + +Controllable::Controllable (Port *p, bool is_bistate) +{ + control_type = none; + _control_description = "MIDI Control: none"; + control_additional = (byte) -1; + bistate = is_bistate; + connections = 0; + feedback = true; // for now + + /* use channel 0 ("1") as the initial channel */ + + midi_rebind (p, 0); +} + +Controllable::~Controllable () +{ + drop_external_control (); +} + +void +Controllable::midi_forget () +{ + /* stop listening for incoming messages, but retain + our existing event + type information. + */ + + if (connections > 0) { + midi_sense_connection[0].disconnect (); + } + + if (connections > 1) { + midi_sense_connection[1].disconnect (); + } + + connections = 0; + midi_learn_connection.disconnect (); + +} + +void +Controllable::midi_rebind (Port *p, channel_t c) +{ + if ((port = p) == 0) { + midi_forget (); + } else { + if (c >= 0) { + bind_midi (c, control_type, control_additional); + } else { + midi_forget (); + } + } +} + +void +Controllable::learn_about_external_control () +{ + drop_external_control (); + + if (port) { + midi_learn_connection = port->input()->any.connect (mem_fun (*this, &Controllable::midi_receiver)); + learning_started (); + + } else { + info << "No MIDI port specified - external control disabled" << endmsg; + } +} + +void +Controllable::stop_learning () +{ + midi_learn_connection.disconnect (); +} + +void +Controllable::drop_external_control () +{ + if (connections > 0) { + midi_sense_connection[0].disconnect (); + } + if (connections > 1) { + midi_sense_connection[1].disconnect (); + } + + connections = 0; + midi_learn_connection.disconnect (); + + control_type = none; + control_additional = (byte) -1; +} + +void +Controllable::midi_sense_note_on (Parser &p, EventTwoBytes *tb) +{ + midi_sense_note (p, tb, true); +} + +void +Controllable::midi_sense_note_off (Parser &p, EventTwoBytes *tb) +{ + midi_sense_note (p, tb, false); +} + +void +Controllable::midi_sense_note (Parser &p, EventTwoBytes *msg, bool is_on) +{ + if (!bistate) { + set_value (msg->note_number/127.0); + } else { + + /* Note: parser handles the use of zero velocity to + mean note off. if we get called with is_on=true, then we + got a *real* note on. + */ + + if (msg->note_number == control_additional) { + set_value (is_on ? 1 : 0); + } + } +} + +void +Controllable::midi_sense_controller (Parser &, EventTwoBytes *msg) +{ + if (control_additional == msg->controller_number) { + if (!bistate) { + set_value (msg->value/127.0); + } else { + if (msg->value > 64.0) { + set_value (1); + } else { + set_value (0); + } + } + } +} + +void +Controllable::midi_sense_program_change (Parser &p, byte msg) +{ + /* XXX program change messages make no sense for bistates */ + + if (!bistate) { + set_value (msg/127.0); + } +} + +void +Controllable::midi_sense_pitchbend (Parser &p, pitchbend_t pb) +{ + /* pitchbend messages make no sense for bistates */ + + /* XXX gack - get rid of assumption about typeof pitchbend_t */ + + set_value ((pb/(float) SHRT_MAX)); +} + +void +Controllable::midi_receiver (Parser &p, byte *msg, size_t len) +{ + /* we only respond to channel messages */ + + if ((msg[0] & 0xF0) < 0x80 || (msg[0] & 0xF0) > 0xE0) { + return; + } + + /* if the our port doesn't do input anymore, forget it ... */ + + if (!port->input()) { + return; + } + + bind_midi ((channel_t) (msg[0] & 0xf), eventType (msg[0] & 0xF0), msg[1]); + + learning_stopped (); +} + +void +Controllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) +{ + char buf[64]; + + drop_external_control (); + + control_type = ev; + control_channel = chn; + control_additional = additional; + + if (port == 0 || port->input() == 0) { + return; + } + + Parser& p = *port->input(); + + int chn_i = chn; + switch (ev) { + case MIDI::off: + midi_sense_connection[0] = p.channel_note_off[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_note_off)); + + /* if this is a bistate, connect to noteOn as well, + and we'll toggle back and forth between the two. + */ + + if (bistate) { + midi_sense_connection[1] = p.channel_note_on[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_note_on)); + connections = 2; + } else { + connections = 1; + } + _control_description = "MIDI control: NoteOff"; + break; + + case MIDI::on: + midi_sense_connection[0] = p.channel_note_on[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_note_on)); + if (bistate) { + midi_sense_connection[1] = p.channel_note_off[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_note_off)); + connections = 2; + } else { + connections = 1; + } + _control_description = "MIDI control: NoteOn"; + break; + + case MIDI::controller: + midi_sense_connection[0] = p.channel_controller[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_controller)); + connections = 1; + snprintf (buf, sizeof (buf), "MIDI control: Controller %d", control_additional); + _control_description = buf; + break; + + case MIDI::program: + if (!bistate) { + midi_sense_connection[0] = p.channel_program_change[chn_i].connect + (mem_fun (*this, + &Controllable::midi_sense_program_change)); + connections = 1; + _control_description = "MIDI control: ProgramChange"; + } + break; + + case MIDI::pitchbend: + if (!bistate) { + midi_sense_connection[0] = p.channel_pitchbend[chn_i].connect + (mem_fun (*this, &Controllable::midi_sense_pitchbend)); + connections = 1; + _control_description = "MIDI control: Pitchbend"; + } + break; + + default: + break; + } +} + +void +Controllable::set_control_type (channel_t chn, eventType ev, MIDI::byte additional) +{ + bind_midi (chn, ev, additional); +} + +bool +Controllable::get_control_info (channel_t& chn, eventType& ev, byte& additional) +{ + if (control_type == none) { + chn = -1; + return false; + } + + ev = control_type; + chn = control_channel; + additional = control_additional; + + return true; +} + + +void +Controllable::send_midi_feedback (float val) +{ + byte msg[3]; + + if (port == 0 || control_type == none) { + return; + } + + msg[0] = (control_type & 0xF0) | (control_channel & 0xF); + msg[1] = control_additional; + msg[2] = (byte) (val * 127.0f); + + port->write (msg, 3); +} + diff --git a/libs/midi++2/midifactory.cc b/libs/midi++2/midifactory.cc new file mode 100644 index 0000000000..38baada204 --- /dev/null +++ b/libs/midi++2/midifactory.cc @@ -0,0 +1,95 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <midi++/types.h> +#include <midi++/factory.h> +#include <midi++/nullmidi.h> +#include <midi++/fifomidi.h> + +#ifdef WITH_ALSA +#include <midi++/alsa_sequencer.h> +#include <midi++/alsa_rawmidi.h> +#endif // WITH_ALSA + +#ifdef WITH_COREMIDI +#include <midi++/coremidi_midiport.h> +#endif // WITH_COREMIDI + + +using namespace std; +using namespace MIDI; + +Port * +PortFactory::create_port (PortRequest &req) + +{ + Port *port; + + switch (req.type) { +#ifdef WITH_ALSA + case Port::ALSA_RawMidi: + port = new ALSA_RawMidiPort (req); + break; + + case Port::ALSA_Sequencer: + port = new ALSA_SequencerMidiPort (req); + break; +#endif // WITH_ALSA + +#if WITH_COREMIDI + case Port::CoreMidi_MidiPort: + port = new CoreMidi_MidiPort (req); + break; +#endif // WITH_COREMIDI + + case Port::Null: + port = new Null_MidiPort (req); + break; + + case Port::FIFO: + port = new FIFO_MidiPort (req); + break; + + default: + req.status = PortRequest::TypeUnsupported; + return 0; + } + + req.status = PortRequest::OK; + + return port; +} + +void +PortFactory::add_port_request (vector<PortRequest *> &reqs, + const string &str) + +{ + PortRequest *req; + + req = new PortRequest; + req->devname = strdup (str.c_str()); + req->tagname = strdup (str.c_str()); + + req->mode = O_RDWR; + req->type = Port::ALSA_RawMidi; + + reqs.push_back (req); +} + diff --git a/libs/midi++2/midimanager.cc b/libs/midi++2/midimanager.cc new file mode 100644 index 0000000000..7b3ed7d336 --- /dev/null +++ b/libs/midi++2/midimanager.cc @@ -0,0 +1,374 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <fcntl.h> +#include <pbd/error.h> +#include <pbd/basename.h> + +#include <midi++/types.h> +#include <midi++/manager.h> +#include <midi++/factory.h> +#include <midi++/channel.h> +#include <midi++/port_request.h> + +using namespace std; +using namespace MIDI; + +Manager *Manager::theManager = 0; + +Manager::Manager () + +{ + inputPort = 0; + outputPort = 0; + inputChannelNumber = 0; + outputChannelNumber = 0; +} + +Manager::~Manager () + +{ + PortMap::iterator i; + + for (i = ports_by_device.begin(); i != ports_by_device.end(); i++) { + delete (*i).second; + } + + ports_by_device.erase (ports_by_device.begin(), ports_by_device.end()); + ports_by_tag.erase (ports_by_tag.begin(), ports_by_tag.end()); + + if (theManager == this) { + theManager = 0; + } +} + +Port * +Manager::add_port (PortRequest &req) + +{ + PortFactory factory; + Port *port; + PortMap::iterator existing; + pair<string, Port *> newpair; + + if ((existing = ports_by_device.find (req.devname)) != + ports_by_device.end()) { + + port = (*existing).second; + if (port->mode() == req.mode) { + + /* Same mode - reuse the port, and just + create a new tag entry. + */ + + newpair.first = req.tagname; + newpair.second = port; + + ports_by_tag.insert (newpair); + return port; + } + + /* If the existing is duplex, and this request + is not, then fail, because most drivers won't + allow opening twice with duplex and non-duplex + operation. + */ + + if ((req.mode == O_RDWR && port->mode() != O_RDWR) || + (req.mode != O_RDWR && port->mode() == O_RDWR)) { + error << "MIDIManager: port tagged \"" + << req.tagname + << "\" cannot be opened duplex and non-duplex" + << endmsg; + return 0; + } + + /* modes must be different or complementary */ + } + + + port = factory.create_port (req); + + if (port == 0) { + return 0; + } + + if (!port->ok()) { + delete port; + return 0; + } + + newpair.first = port->name(); + newpair.second = port; + ports_by_tag.insert (newpair); + + newpair.first = port->device(); + newpair.second = port; + ports_by_device.insert (newpair); + + /* first port added becomes the default input + port. + */ + + if (inputPort == 0) { + inputPort = port; + } + + if (outputPort == 0) { + outputPort = port; + } + + return port; +} + +int +Manager::remove_port (string name) +{ + PortMap::iterator res; + + if ((res = ports_by_device.find (name)) == ports_by_device.end()) { + return -1; + } + + ports_by_device.erase (res); + ports_by_device.erase ((*res).second->name()); + + delete (*res).second; + + return 0; +} + +int +Manager::set_input_port (string tag) +{ + PortMap::iterator res; + bool found = false; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if (tag == (*res).first) { + found = true; + break; + } + } + + if (!found) { + return -1; + } + + inputPort = (*res).second; + + return 0; +} + +int +Manager::set_input_port (size_t portnum) + +{ + PortMap::iterator res; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if ((*res).second->number() == portnum) { + inputPort = (*res).second; + return 0; + } + } + + return -1; +} + +int +Manager::set_output_port (string tag) + +{ + PortMap::iterator res; + bool found = false; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if (tag == (*res).first) { + found = true; + break; + } + } + + if (!found) { + return -1; + } + + // XXX send a signal to say we're about to change output ports + + if (outputPort) { + for (channel_t chan = 0; chan < 16; chan++) { + outputPort->channel (chan)->all_notes_off (); + } + } + outputPort = (*res).second; + + // XXX send a signal to say we've changed output ports + + return 0; +} + +int +Manager::set_output_port (size_t portnum) + +{ + PortMap::iterator res; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if ((*res).second->number() == portnum) { + outputPort = (*res).second; + return 0; + } + } + + return -1; +} + +Port * +Manager::port (string name) +{ + PortMap::iterator res; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if (name == (*res).first) { + return (*res).second; + } + } + + return 0; +} + +Port * +Manager::port (size_t portnum) + +{ + PortMap::iterator res; + + for (res = ports_by_tag.begin(); res != ports_by_tag.end(); res++) { + if ((*res).second->number() == portnum) { + return (*res).second; + } + } + + return 0; +} + +int +Manager::foreach_port (int (*func)(const Port &, size_t, void *), + void *arg) + +{ + PortMap::const_iterator i; + int retval; + int n; + + for (n = 0, i = ports_by_device.begin(); + i != ports_by_device.end(); i++, n++) { + + if ((retval = func (*((*i).second), n, arg)) != 0) { + return retval; + } + } + + return 0; +} + +int +Manager::parse_port_request (string str, Port::Type type) +{ + PortRequest *req; + string::size_type colon; + string tag; + + if (str.length() == 0) { + error << "MIDI: missing port specification" << endmsg; + return -1; + } + + /* Port specifications look like: + + devicename + devicename:tagname + devicename:tagname:mode + + where + + "devicename" is the full path to the requested file + + "tagname" (optional) is the name used to refer to the + port. If not given, PBD::basename (devicename) + will be used. + + "mode" (optional) is either "r" or "w" or something else. + if it is "r", the port will be opened + read-only, if "w", the port will be opened + write-only. Any other value, or no mode + specification at all, will cause the port to + be opened for reading and writing. + */ + + req = new PortRequest; + colon = str.find_first_of (':'); + + if (colon != string::npos) { + req->devname = strdup (str.substr (0, colon).c_str()); + } else { + req->devname = strdup (str.c_str()); + } + + if (colon < str.length()) { + + tag = str.substr (colon+1); + + /* see if there is a mode specification in the tag part */ + + colon = tag.find_first_of (':'); + + if (colon != string::npos) { + string modestr; + + req->tagname = strdup (tag.substr (0, colon).c_str()); + + modestr = tag.substr (colon+1); + if (modestr == "r") { + req->mode = O_RDONLY; + } else if (modestr == "w") { + req->mode = O_WRONLY; + } else { + req->mode = O_RDWR; + } + + } else { + req->tagname = strdup (tag.c_str()); + req->mode = O_RDWR; + } + + } else { + req->tagname = strdup (PBD::basename (req->devname)); + req->mode = O_RDWR; + } + + req->type = type; + + if (MIDI::Manager::instance()->add_port (*req) == 0) { + return -1; + } + + return 0; +} diff --git a/libs/midi++2/midiparser.cc b/libs/midi++2/midiparser.cc new file mode 100644 index 0000000000..04ac2728f1 --- /dev/null +++ b/libs/midi++2/midiparser.cc @@ -0,0 +1,800 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + This file was inspired by the MIDI parser for KeyKit by + Tim Thompson. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cstdlib> +#include <unistd.h> +#include <string> +#include <iostream> + +#include <midi++/types.h> +#include <midi++/parser.h> +#include <midi++/port.h> +#include <midi++/mmc.h> +#include <pbd/transmitter.h> + +using namespace std; +using namespace sigc; +using namespace MIDI; + +const char * +Parser::midi_event_type_name (eventType t) + +{ + switch (t) { + case none: + return "no midi messages"; + + case raw: + return "raw midi data"; + + case MIDI::any: + return "any midi message"; + + case off: + return "note off"; + + case on: + return "note on"; + + case polypress: + return "aftertouch"; + + case MIDI::controller: + return "controller"; + + case program: + return "program change"; + + case chanpress: + return "channel pressure"; + + case MIDI::pitchbend: + return "pitch bend"; + + case MIDI::sysex: + return "system exclusive"; + + case MIDI::song: + return "song position"; + + case MIDI::tune: + return "tune"; + + case MIDI::eox: + return "end of sysex"; + + case MIDI::timing: + return "timing"; + + case MIDI::start: + return "start"; + + case MIDI::stop: + return "continue"; + + case MIDI::contineu: + return "stop"; + + case active: + return "active sense"; + + default: + return "unknow MIDI event type"; + } +}; + +Parser::Parser (Port &p) + : _port (p) + +{ + trace_stream = 0; + trace_prefix = ""; + memset (message_counter, 0, sizeof (message_counter[0]) * 256); + msgindex = 0; + msgtype = none; + msglen = 256; + msgbuf = (unsigned char *) malloc (msglen); + msgbuf[msgindex++] = 0x90; + _mmc_forward = false; + reset_mtc_state (); + _offline = false; + + /* this hack deals with the possibility of our first MIDI + bytes being running status messages. + */ + + channel_msg (0x90); + state = NEEDSTATUS; + + pre_variable_state = NEEDSTATUS; + pre_variable_msgtype = none; +} + +Parser::~Parser () + +{ + delete msgbuf; +} + +void +Parser::trace_event (Parser &p, byte *msg, size_t len) + +{ + eventType type; + ostream *o; + + if ((o = trace_stream) == NULL) { /* can be asynchronously removed */ + return; + } + + type = (eventType) (msg[0]&0xF0); + + switch (type) { + case off: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " NoteOff NoteNum " + << (int) msg[1] + << " Vel " + << (int) msg[2] + << endmsg; + break; + + case on: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " NoteOn NoteNum " + << (int) msg[1] + << " Vel " + << (int) msg[2] + << endmsg; + break; + + case polypress: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " PolyPressure" + << (int) msg[1] + << endmsg; + break; + + case MIDI::controller: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " Controller " + << (int) msg[1] + << " Value " + << (int) msg[2] + << endmsg; + break; + + case program: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " Program Change ProgNum " + << (int) msg[1] + << endmsg; + break; + + case chanpress: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " Channel Pressure " + << (int) msg[1] + << endmsg; + break; + + case MIDI::pitchbend: + *o << trace_prefix + << "Channel " + << (msg[0]&0xF)+1 + << " Pitch Bend " + << ((msg[1]<<7)|msg[2]) + << endmsg; + break; + + case MIDI::sysex: + if (len == 1) { + switch (msg[0]) { + case 0xf8: + *o << trace_prefix + << "Clock" + << endmsg; + break; + case 0xfa: + *o << trace_prefix + << "Start" + << endmsg; + break; + case 0xfb: + *o << trace_prefix + << "Continue" + << endmsg; + break; + case 0xfc: + *o << trace_prefix + << "Stop" + << endmsg; + break; + case 0xfe: + *o << trace_prefix + << "Active Sense" + << endmsg; + break; + case 0xff: + *o << trace_prefix + << "System Reset" + << endmsg; + break; + default: + *o << trace_prefix + << "System Exclusive (1 byte : " << hex << (int) *msg << dec << ')' + << endmsg; + break; + } + } else { + *o << trace_prefix + << "System Exclusive (" << len << ") = [ " << hex; + for (unsigned int i = 0; i < len; ++i) { + *o << (int) msgbuf[i] << ' '; + } + *o << dec << ']' << endmsg; + + } + break; + + case MIDI::song: + *o << trace_prefix << "Song" << endmsg; + break; + + case MIDI::tune: + *o << trace_prefix << "Tune" << endmsg; + break; + + case MIDI::eox: + *o << trace_prefix << "End-of-System Exclusive" << endmsg; + break; + + case MIDI::timing: + *o << trace_prefix << "Timing" << endmsg; + break; + + case MIDI::start: + *o << trace_prefix << "Start" << endmsg; + break; + + case MIDI::stop: + *o << trace_prefix << "Stop" << endmsg; + break; + + case MIDI::contineu: + *o << trace_prefix << "Continue" << endmsg; + break; + + case active: + *o << trace_prefix << "Active Sense" << endmsg; + break; + + default: + *o << trace_prefix << "Unrecognized MIDI message" << endmsg; + break; + } +} + +void +Parser::trace (bool onoff, ostream *o, const string &prefix) + +{ + trace_connection.disconnect (); + + if (onoff) { + trace_stream = o; + trace_prefix = prefix; + trace_connection = any.connect + (mem_fun (*this, &Parser::trace_event)); + } else { + trace_prefix = ""; + trace_stream = 0; + } +} + +void +Parser::scanner (unsigned char inbyte) +{ + bool statusbit; + + // cerr << "parse: " << hex << (int) inbyte << dec << " state = " << state << " msgindex = " << msgindex << " runnable = " << runnable << endl; + + /* Check active sensing early, so it doesn't interrupt sysex. + + NOTE: active sense messages are not considered to fit under + "any" for the purposes of callbacks. If a caller wants + active sense messages handled, which is unlikely, then + they can just ask for it specifically. They are so unlike + every other MIDI message in terms of semantics that its + counter-productive to treat them similarly. + */ + + if (inbyte == 0xfe) { + message_counter[inbyte]++; + if (!_offline) { + active_sense (*this); + } + return; + } + + /* If necessary, allocate larger message buffer. */ + + if (msgindex >= msglen) { + msglen *= 2; + msgbuf = (unsigned char *) realloc (msgbuf, msglen); + } + + /* + Real time messages can occur ANYPLACE, + but do not interrupt running status. + */ + + bool rtmsg = false; + + switch (inbyte) { + case 0xf8: + rtmsg = true; + break; + case 0xfa: + rtmsg = true; + break; + case 0xfb: + rtmsg = true; + break; + case 0xfc: + rtmsg = true; + break; + case 0xfd: + rtmsg = true; + break; + case 0xfe: + rtmsg = true; + break; + case 0xff: + rtmsg = true; + break; + } + + if (rtmsg) { + if (edit (&inbyte, 1) >= 0 && !_offline) { + realtime_msg (inbyte); + } + + return; + } + + statusbit = (inbyte & 0x80); + + /* + * Variable length messages (ie. the 'system exclusive') + * can be terminated by the next status byte, not necessarily + * an EOX. Actually, since EOX is a status byte, this + * code ALWAYS handles the end of a VARIABLELENGTH message. + */ + + if (state == VARIABLELENGTH && statusbit) { + /* The message has ended, so process it */ + + /* add EOX to any sysex message */ + + if (inbyte == MIDI::eox) { + msgbuf[msgindex++] = inbyte; + } + +#if 0 + cerr << "SYSEX: " << hex; + for (unsigned int i = 0; i < msgindex; ++i) { + cerr << (int) msgbuf[i] << ' '; + } + cerr << dec << endl; +#endif + if (msgindex > 0 && edit (msgbuf, msgindex) >= 0) { + if (!possible_mmc (msgbuf, msgindex) || _mmc_forward) { + if (!possible_mtc (msgbuf, msgindex) || _mtc_forward) { + if (!_offline) { + sysex (*this, msgbuf, msgindex); + } + } + } + if (!_offline) { + any (*this, msgbuf, msgindex); + } + } + } + + /* + * Status bytes always start a new message, except EOX + */ + + if (statusbit) { + + msgindex = 0; + + if (inbyte == MIDI::eox) { + /* return to the state we had pre-sysex */ + + state = pre_variable_state; + runnable = was_runnable; + msgtype = pre_variable_msgtype; + + if (state != NEEDSTATUS && runnable) { + msgbuf[msgindex++] = last_status_byte; + } + } else { + msgbuf[msgindex++] = inbyte; + if ((inbyte & 0xf0) == 0xf0) { + system_msg (inbyte); + runnable = false; + } else { + channel_msg (inbyte); + } + } + + return; + } + + /* + * We've got a Data byte. + */ + + msgbuf[msgindex++] = inbyte; + + switch (state) { + case NEEDSTATUS: + /* + * We shouldn't get here, since in NEEDSTATUS mode + * we're expecting a new status byte, NOT any + * data bytes. On the other hand, some equipment + * with leaky modwheels and the like might be + * sending data bytes as part of running controller + * messages, so just handle it silently. + */ + break; + + case NEEDTWOBYTES: + /* wait for the second byte */ + if (msgindex < 3) + return; + /*FALLTHRU*/ + + case NEEDONEBYTE: + /* We've completed a 1 or 2 byte message. */ + + if (edit (msgbuf, msgindex) == 0) { + + /* message not cancelled by an editor */ + + message_counter[msgbuf[0] & 0xF0]++; + + if (!_offline) { + signal (msgbuf, msgindex); + } + } + + if (runnable) { + /* In Runnable mode, we reset the message + index, but keep the callbacks_pending and state the + same. This provides the "running status + byte" feature. + */ + msgindex = 1; + } else { + /* If not Runnable, reset to NEEDSTATUS mode */ + state = NEEDSTATUS; + } + break; + + case VARIABLELENGTH: + /* nothing to do */ + break; + } + return; +} + +/* + * realtime_msg(inbyte) + * + * Call the real-time function for the specified byte, immediately. + * These can occur anywhere, so they don't change the state. + */ + +void +Parser::realtime_msg(unsigned char inbyte) + +{ + message_counter[inbyte]++; + + if (_offline) { + return; + } + + switch (inbyte) { + case 0xf8: + timing (*this); + break; + case 0xfa: + start (*this); + break; + case 0xfb: + contineu (*this); + break; + case 0xfc: + stop (*this); + break; + case 0xfe: + /* !!! active sense message in realtime_msg: should not reach here + */ + break; + case 0xff: + reset (*this); + break; + } + + any (*this, &inbyte, 1); +} + +/* + * channel_msg(inbyte) + * + * Interpret a Channel (voice or mode) Message status byte. + */ + +void +Parser::channel_msg(unsigned char inbyte) +{ + last_status_byte = inbyte; + runnable = true; /* Channel messages can use running status */ + + /* The high 4 bits, which determine the type of channel message. */ + + switch (inbyte&0xF0) { + case 0x80: + msgtype = off; + state = NEEDTWOBYTES; + break; + case 0x90: + msgtype = on; + state = NEEDTWOBYTES; + break; + case 0xa0: + msgtype = polypress; + state = NEEDTWOBYTES; + break; + case 0xb0: + msgtype = MIDI::controller; + state = NEEDTWOBYTES; + break; + case 0xc0: + msgtype = program; + state = NEEDONEBYTE; + break; + case 0xd0: + msgtype = chanpress; + state = NEEDONEBYTE; + break; + case 0xe0: + msgtype = MIDI::pitchbend; + state = NEEDTWOBYTES; + break; + } +} + +/* + * system_msg(inbyte) + * + * Initialize (and possibly emit) the signals for the + * specified byte. Set the state that the state-machine + * should go into. If the signal is not emitted + * immediately, it will be when the state machine gets to + * the end of the MIDI message. + */ + +void +Parser::system_msg (unsigned char inbyte) +{ + message_counter[inbyte]++; + + switch (inbyte) { + case 0xf0: + pre_variable_msgtype = msgtype; + pre_variable_state = state; + was_runnable = runnable; + msgtype = MIDI::sysex; + state = VARIABLELENGTH; + break; + case 0xf1: + msgtype = MIDI::mtc_quarter; + state = NEEDONEBYTE; + break; + case 0xf2: + msgtype = MIDI::position; + state = NEEDTWOBYTES; + break; + case 0xf3: + msgtype = MIDI::song; + state = NEEDONEBYTE; + break; + case 0xf6: + if (!_offline) { + tune (*this); + } + state = NEEDSTATUS; + break; + case 0xf7: + break; + } + + // all these messages will be sent via any() + // when they are complete. + // any (*this, &inbyte, 1); +} + +void +Parser::signal (byte *msg, size_t len) +{ + channel_t chan = msg[0]&0xF; + int chan_i = chan; + + switch (msgtype) { + case none: + break; + + case off: + channel_active_preparse[chan_i] (*this); + note_off (*this, (EventTwoBytes *) &msg[1]); + channel_note_off[chan_i] + (*this, (EventTwoBytes *) &msg[1]); + channel_active_postparse[chan_i] (*this); + break; + + case on: + channel_active_preparse[chan_i] (*this); + + /* Hack to deal with MIDI sources that use velocity=0 + instead of noteOff. + */ + + if (msg[2] == 0) { + note_off (*this, (EventTwoBytes *) &msg[1]); + channel_note_off[chan_i] + (*this, (EventTwoBytes *) &msg[1]); + } else { + note_on (*this, (EventTwoBytes *) &msg[1]); + channel_note_on[chan_i] + (*this, (EventTwoBytes *) &msg[1]); + } + + channel_active_postparse[chan_i] (*this); + break; + + case MIDI::controller: + channel_active_preparse[chan_i] (*this); + controller (*this, (EventTwoBytes *) &msg[1]); + channel_controller[chan_i] + (*this, (EventTwoBytes *) &msg[1]); + channel_active_postparse[chan_i] (*this); + break; + + case program: + channel_active_preparse[chan_i] (*this); + program_change (*this, msg[1]); + channel_program_change[chan_i] (*this, msg[1]); + channel_active_postparse[chan_i] (*this); + break; + + case chanpress: + channel_active_preparse[chan_i] (*this); + pressure (*this, msg[1]); + channel_pressure[chan_i] (*this, msg[1]); + channel_active_postparse[chan_i] (*this); + break; + + case polypress: + channel_active_preparse[chan_i] (*this); + poly_pressure (*this, (EventTwoBytes *) &msg[1]); + channel_poly_pressure[chan_i] + (*this, (EventTwoBytes *) &msg[1]); + channel_active_postparse[chan_i] (*this); + break; + + case MIDI::pitchbend: + channel_active_preparse[chan_i] (*this); + pitchbend (*this, (msg[1]<<7)|msg[2]); + channel_pitchbend[chan_i] (*this, (msg[1]<<7)|msg[2]); + channel_active_postparse[chan_i] (*this); + break; + + case MIDI::sysex: + sysex (*this, msg, len); + break; + + case MIDI::mtc_quarter: + process_mtc_quarter_frame (msg); + mtc_quarter_frame (*this, *msg); + break; + + case MIDI::position: + position (*this, msg, len); + break; + + case MIDI::song: + song (*this, msg, len); + break; + + case MIDI::tune: + tune (*this); + + default: + /* XXX some kind of warning ? */ + break; + } + + any (*this, msg, len); +} + +bool +Parser::possible_mmc (byte *msg, size_t msglen) +{ + if (!MachineControl::is_mmc (msg, msglen)) { + return false; + } + + /* hand over the just the interior MMC part of + the sysex msg without the leading 0xF0 + */ + + if (!_offline) { + mmc (*this, &msg[1], msglen - 1); + } + + return true; +} + +void +Parser::set_offline (bool yn) +{ + if (_offline != yn) { + _offline = yn; + OfflineStatusChanged (); + + /* this hack deals with the possibility of our first MIDI + bytes being running status messages. + */ + + channel_msg (0x90); + state = NEEDSTATUS; + } +} + diff --git a/libs/midi++2/midiport.cc b/libs/midi++2/midiport.cc new file mode 100644 index 0000000000..6d374ed8c0 --- /dev/null +++ b/libs/midi++2/midiport.cc @@ -0,0 +1,131 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cstdio> +#include <fcntl.h> + +#include <midi++/types.h> +#include <midi++/port.h> +#include <midi++/channel.h> +#include <midi++/port_request.h> + +using namespace Select; +using namespace MIDI; + +size_t Port::nports = 0; + +Port::Port (PortRequest &req) + +{ + _ok = false; /* derived class must set to true if constructor + succeeds. + */ + + bytes_written = 0; + bytes_read = 0; + input_parser = 0; + output_parser = 0; + slowdown = 0; + + _devname = req.devname; + _tagname = req.tagname; + _mode = req.mode; + _number = nports++; + + if (_mode == O_RDONLY || _mode == O_RDWR) { + input_parser = new Parser (*this); + } else { + input_parser = 0; + } + + if (_mode == O_WRONLY || _mode == O_RDWR) { + output_parser = new Parser (*this); + } else { + output_parser = 0; + } + + for (int i = 0; i < 16; i++) { + _channel[i] = new Channel (i, *this); + + if (input_parser) { + _channel[i]->connect_input_signals (); + } + + if (output_parser) { + _channel[i]->connect_output_signals (); + } + } +} + + +Port::~Port () + +{ + for (int i = 0; i < 16; i++) { + delete _channel[i]; + } +} + +int +Port::clock () + +{ + static byte clockmsg = 0xf8; + + if (_mode != O_RDONLY) { + return midimsg (&clockmsg, 1); + } + + return 0; +} + +void +Port::selector_read_callback (Selectable *s, Select::Condition cond) + +{ + byte buf[64]; + read (buf, sizeof (buf)); +} + +void +Port::xforms_read_callback (int cond, int fd, void *ptr) + +{ + byte buf[64]; + + ((Port *)ptr)->read (buf, sizeof (buf)); +} + +void +Port::gtk_read_callback (void *ptr, int fd, int cond) + +{ + byte buf[64]; + + ((Port *)ptr)->read (buf, sizeof (buf)); +} + +void +Port::write_callback (byte *msg, unsigned int len, void *ptr) + +{ + ((Port *)ptr)->write (msg, len); +} + diff --git a/libs/midi++2/miditrace.cc b/libs/midi++2/miditrace.cc new file mode 100644 index 0000000000..d7c65d9f29 --- /dev/null +++ b/libs/midi++2/miditrace.cc @@ -0,0 +1,64 @@ +#include <cstdio> +#include <fcntl.h> + +#include <pbd/error.h> +#include <pbd/textreceiver.h> + +Transmitter error (Transmitter::Error); +Transmitter info (Transmitter::Info); +Transmitter warning (Transmitter::Warning); +Transmitter fatal (Transmitter::Fatal); +TextReceiver text_receiver ("mmctest"); + +#include "midi++/port.h" +#include "midi++/port_request.h" +#include "midi++/manager.h" + +using namespace MIDI; + +Port *port; +PortRequest midi_device; + +int +setup_midi () + +{ + midi_device.devname = "/dev/snd/midiC0D0"; + midi_device.tagname = "trident"; + midi_device.mode = O_RDWR; + midi_device.type = Port::ALSA_RawMidi; + + if ((port = MIDI::Manager::instance()->add_port (midi_device)) == 0) { + info << "MIDI port is not valid" << endmsg; + return -1; + } + + return 0; +} + +main (int argc, char *argv[]) + +{ + byte buf[1]; + + text_receiver.listen_to (error); + text_receiver.listen_to (info); + text_receiver.listen_to (fatal); + text_receiver.listen_to (warning); + + if (setup_midi ()) { + exit (1); + } + + port->input()->trace (true, &cout); + + while (1) { + if (port->read (buf, 1) < 0) { + error << "cannot read byte" + << endmsg; + break; + } + } +} + + diff --git a/libs/midi++2/mmc.cc b/libs/midi++2/mmc.cc new file mode 100644 index 0000000000..a0de774329 --- /dev/null +++ b/libs/midi++2/mmc.cc @@ -0,0 +1,577 @@ +/* + Copyright (C) 2000 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <map> + +#include <pbd/error.h> +#include <midi++/mmc.h> +#include <midi++/port.h> +#include <midi++/parser.h> + +using namespace std; +using namespace MIDI; + +static std::map<int,string> mmc_cmd_map; +static void build_mmc_cmd_map () +{ + pair<int,string> newpair; + + newpair.first = 0x1; + newpair.second = "Stop"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x2; + newpair.second = "Play"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x3; + newpair.second = "DeferredPlay"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4; + newpair.second = "FastForward"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x5; + newpair.second = "Rewind"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x6; + newpair.second = "RecordStrobe"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x7; + newpair.second = "RecordExit"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x8; + newpair.second = "RecordPause"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x9; + newpair.second = "Pause"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0xA; + newpair.second = "Eject"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0xB; + newpair.second = "Chase"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0xC; + newpair.second = "CommandErrorReset"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0xD; + newpair.second = "MmcReset"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x20; + newpair.second = "Illegal Mackie Jog Start"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x21; + newpair.second = "Illegal Mackie Jog Stop"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x40; + newpair.second = "Write"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x41; + newpair.second = "MaskedWrite"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x42; + newpair.second = "Read"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x43; + newpair.second = "Update"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x44; + newpair.second = "Locate"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x45; + newpair.second = "VariablePlay"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x46; + newpair.second = "Search"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x47; + newpair.second = "Shuttle"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x48; + newpair.second = "Step"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x49; + newpair.second = "AssignSystemMaster"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4A; + newpair.second = "GeneratorCommand"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4B; + newpair.second = "MtcCommand"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4C; + newpair.second = "Move"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4D; + newpair.second = "Add"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4E; + newpair.second = "Subtract"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x4F; + newpair.second = "DropFrameAdjust"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x50; + newpair.second = "Procedure"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x51; + newpair.second = "Event"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x52; + newpair.second = "Group"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x53; + newpair.second = "CommandSegment"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x54; + newpair.second = "DeferredVariablePlay"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x55; + newpair.second = "RecordStrobeVariable"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x7C; + newpair.second = "Wait"; + mmc_cmd_map.insert (newpair); + + newpair.first = 0x7F; + newpair.second = "Resume"; + mmc_cmd_map.insert (newpair); +} + + +MachineControl::MachineControl (Port &p, float version, + CommandSignature &csig, + ResponseSignature &rsig) + + : _port (p) +{ + Parser *parser; + + build_mmc_cmd_map (); + + _device_id = 1; + + if ((parser = _port.input()) != 0) { + parser->mmc.connect + (mem_fun (*this, &MachineControl::process_mmc_message)); + } else { + warning << "MMC connected to a non-input port: useless!" + << endmsg; + } +} + +void +MachineControl::set_device_id (byte id) + +{ + _device_id = id & 0x7f; +} + +bool +MachineControl::is_mmc (byte *sysex_buf, size_t len) + +{ + if (len < 4 || len > 48) { + return false; + } + + if (sysex_buf[1] != 0x7f) { + return false; + } + + if (sysex_buf[3] != 0x6 && /* MMC Command */ + sysex_buf[3] != 0x7) { /* MMC Response */ + return false; + } + + return true; +} + +void +MachineControl::process_mmc_message (Parser &p, byte *msg, size_t len) + +{ + size_t skiplen; + byte *mmc_msg; + bool single_byte; + + /* Reject if its not for us. 0x7f is the "all-call" device ID */ + + /* msg[0] = 0x7f (MMC sysex ID( + msg[1] = device ID + msg[2] = 0x6 (MMC command) or 0x7 (MMC response) + msg[3] = MMC command code + msg[4] = (typically) byte count for following part of command + */ + +#if 0 + cerr << "*** MMC message: len = " << len << "\n\t"; + for (size_t i = 0; i < len; i++) { + cerr << hex << (int) msg[i] << dec << ' '; + } + cerr << endl; +#endif + + if (msg[1] != 0x7f && msg[1] != _device_id) { + return; + } + + mmc_msg = &msg[3]; + len -= 3; + + do { + + single_byte = false; + + /* this works for all non-single-byte "counted" + commands. we set it to 1 for the exceptions. + */ + + std::map<int,string>::iterator x = mmc_cmd_map.find ((int)mmc_msg[0]); + string cmdname = "unknown"; + + if (x != mmc_cmd_map.end()) { + cmdname = (*x).second; + } + +#if 0 + cerr << "+++ MMC type " + << hex + << ((int) *mmc_msg) + << dec + << " \"" << cmdname << "\" " + << " len = " << len + << endl; +#endif + + switch (*mmc_msg) { + + /* SINGLE-BYTE, UNCOUNTED COMMANDS */ + + case cmdStop: + Stop (*this); + single_byte = true; + break; + + case cmdPlay: + Play (*this); + single_byte = true; + break; + + case cmdDeferredPlay: + DeferredPlay (*this); + single_byte = true; + break; + + case cmdFastForward: + FastForward (*this); + single_byte = true; + break; + + case cmdRewind: + Rewind (*this); + single_byte = true; + break; + + case cmdRecordStrobe: + RecordStrobe (*this); + single_byte = true; + break; + + case cmdRecordExit: + RecordExit (*this); + single_byte = true; + break; + + case cmdRecordPause: + RecordPause (*this); + single_byte = true; + break; + + case cmdPause: + Pause (*this); + single_byte = true; + break; + + case cmdEject: + Eject (*this); + single_byte = true; + break; + + case cmdChase: + Chase (*this); + single_byte = true; + break; + + case cmdCommandErrorReset: + CommandErrorReset (*this); + single_byte = true; + break; + + case cmdMmcReset: + MmcReset (*this); + single_byte = true; + break; + + case cmdIllegalMackieJogStart: + JogStart (*this); + single_byte = true; + break; + + case cmdIllegalMackieJogStop: + JogStop (*this); + single_byte = true; + break; + + /* END OF SINGLE-BYTE, UNCOUNTED COMMANDS */ + + case cmdMaskedWrite: + do_masked_write (mmc_msg, len); + break; + + case cmdLocate: + do_locate (mmc_msg, len); + break; + + case cmdShuttle: + do_shuttle (mmc_msg, len); + break; + + case cmdStep: + do_step (mmc_msg, len); + break; + + case cmdWrite: + case cmdRead: + case cmdUpdate: + case cmdVariablePlay: + case cmdSearch: + case cmdAssignSystemMaster: + case cmdGeneratorCommand: + case cmdMtcCommand: + case cmdMove: + case cmdAdd: + case cmdSubtract: + case cmdDropFrameAdjust: + case cmdProcedure: + case cmdEvent: + case cmdGroup: + case cmdCommandSegment: + case cmdDeferredVariablePlay: + case cmdRecordStrobeVariable: + case cmdWait: + case cmdResume: + error << "MIDI::MachineControl: unimplemented MMC command " + << hex << (int) *mmc_msg << dec + << endmsg; + + break; + + default: + error << "MIDI::MachineControl: unknown MMC command " + << hex << (int) *mmc_msg << dec + << endmsg; + + break; + } + + /* increase skiplen to cover the command byte and + count byte (if it existed). + */ + + if (!single_byte) { + skiplen = mmc_msg[1] + 2; + } else { + skiplen = 1; + } + + if (len <= skiplen) { + break; + } + + mmc_msg += skiplen; + len -= skiplen; + + } while (len > 1); /* skip terminating EOX byte */ +} + +int +MachineControl::do_masked_write (byte *msg, size_t len) + +{ + /* return the number of bytes "consumed" */ + + int retval = msg[1] + 2; /* bytes following + 2 */ + + switch (msg[2]) { + case 0x4f: /* Track Record Ready Status */ + write_track_record_ready (&msg[3], len - 3); + break; + + default: + warning << "MIDI::MachineControl: masked write to " + << hex << (int) msg[2] << dec + << " not implemented" + << endmsg; + } + + return retval; +} + +void +MachineControl::write_track_record_ready (byte *msg, size_t len) + +{ + size_t n; + size_t base_track; + + /* Bits 0-4 of the first byte are for special tracks: + + bit 0: video + bit 1: reserved + bit 2: time code + bit 3: aux track a + bit 4: aux track b + + */ + + /* XXX check needed to make sure we don't go outside the + support number of tracks. + */ + + base_track = (msg[0] * 7) - 5; + + for (n = 0; n < 7; n++) { + if (msg[1] & (1<<n)) { + + /* Only touch tracks that have the "mask" + bit set. + */ + + if (msg[2] & (1<<n)) { + trackRecordStatus[base_track+n] = true; + TrackRecordStatusChange (*this, base_track+n, + true); + } else { + trackRecordStatus[base_track+n] = false; + TrackRecordStatusChange (*this, base_track+n, + false); + } + } + + } +} + +int +MachineControl::do_locate (byte *msg, size_t msglen) + +{ + if (msg[2] == 0) { + warning << "MIDI::MMC: locate [I/F] command not supported" + << endmsg; + return 0; + } + + /* regular "target" locate command */ + + Locate (*this, &msg[3]); + return 0; +} + +int +MachineControl::do_step (byte *msg, size_t msglen) +{ + int steps = msg[2] & 0x3f; + + if (msg[2] & 0x40) { + steps = -steps; + } + + Step (*this, steps); + return 0; +} + +int +MachineControl::do_shuttle (byte *msg, size_t msglen) + +{ + size_t forward; + byte sh = msg[2]; + byte sm = msg[3]; + byte sl = msg[4]; + size_t left_shift; + size_t integral; + size_t fractional; + float shuttle_speed; + + if (sh & (1<<6)) { + forward = false; + } else { + forward = true; + } + + left_shift = (sh & 0x38); + + integral = ((sh & 0x7) << left_shift) | (sm >> (7 - left_shift)); + fractional = ((sm << left_shift) << 7) | sl; + + shuttle_speed = integral + + ((float)fractional / (1 << (14 - left_shift))); + + Shuttle (*this, shuttle_speed, forward); + + return 0; +} + diff --git a/libs/midi++2/mmctest.cc b/libs/midi++2/mmctest.cc new file mode 100644 index 0000000000..25ea964ded --- /dev/null +++ b/libs/midi++2/mmctest.cc @@ -0,0 +1,112 @@ +#include <cstdio> +#include <fcntl.h> + +#include <pbd/error.h> +#include <pbd/textreceiver.h> + +Transmitter error (Transmitter::Error); +Transmitter info (Transmitter::Info); +Transmitter warning (Transmitter::Warning); +Transmitter fatal (Transmitter::Fatal); +TextReceiver text_receiver ("mmctest"); + +#include "midi++/port.h" +#include "midi++/port_request.h" +#include "midi++/manager.h" +#include "midi++/mmc.h" + +using namespace MIDI; + +Port *port; +PortRequest midi_device; +Parser *parser; +MachineControl *mmc; +MachineControl::CommandSignature cs; +MachineControl::ResponseSignature rs; + +int +setup_midi () + +{ + midi_device.devname = "/dev/snd/midiC0D0"; + midi_device.tagname = "trident"; + midi_device.mode = O_RDWR; + midi_device.type = Port::ALSA_RawMidi; + + if ((port = MIDI::Manager::instance()->add_port (midi_device)) == 0) { + info << "MIDI port is not valid" << endmsg; + return -1; + } + + mmc = new MachineControl (*port, 0.0, cs, rs); + + return 0; +} + +void +do_deferred_play (MachineControl &mmc) + +{ + cout << "Deferred Play" << endl; +} + +void +do_stop (MachineControl &mmc) + +{ + cout << "Stop" << endl; +} + +void +do_ffwd (MachineControl &mmc) + +{ + cout << "Fast Forward" << endl; +} + +void +do_rewind (MachineControl &mmc) + +{ + cout << "Rewind" << endl; +} + +void +do_record_status (MachineControl &mmc, size_t track, bool enabled) + +{ + cout << "Track " << track + 1 << (enabled ? " enabled" : " disabled") + << endl; +} + +main (int argc, char *argv[]) + +{ + byte buf[1]; + + text_receiver.listen_to (error); + text_receiver.listen_to (info); + text_receiver.listen_to (fatal); + text_receiver.listen_to (warning); + + if (setup_midi ()) { + exit (1); + } + + + mmc->DeferredPlay.connect (mem_fun (do_deferred_play)); + mmc->FastForward.connect (mem_fun (do_ffwd)); + mmc->Rewind.connect (mem_fun (do_rewind)); + mmc->Stop.connect (mem_fun (do_stop)); + mmc->TrackRecordStatusChange.connect (mem_fun (do_record_status)); + + while (1) { + if (port->read (buf, 1) < 0) { + error << "cannot read byte" + << endmsg; + break; + } + } +} + + diff --git a/libs/midi++2/mtc.cc b/libs/midi++2/mtc.cc new file mode 100644 index 0000000000..19fdb1fabd --- /dev/null +++ b/libs/midi++2/mtc.cc @@ -0,0 +1,329 @@ +/* + Copyright (C) 2004 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cstdlib> +#include <unistd.h> +#include <string> +#include <iostream> + +#include <midi++/types.h> +#include <midi++/parser.h> +#include <midi++/port.h> +#include <midi++/mmc.h> +#include <pbd/transmitter.h> + +using namespace std; +using namespace sigc; +using namespace MIDI; + +bool +Parser::possible_mtc (byte *sysex_buf, size_t msglen) +{ + byte fake_mtc_time[4]; + + if (msglen != 10 || sysex_buf[0] != 0xf0 || sysex_buf[1] != 0x7f || sysex_buf[3] != 0x01 || sysex_buf[4] != 0x01) { + return false; + } + + /* full MTC */ + + fake_mtc_time[0] = sysex_buf[8]; // frames + fake_mtc_time[1] = sysex_buf[7]; // minutes + fake_mtc_time[2] = sysex_buf[6]; // seconds + fake_mtc_time[3] = (sysex_buf[5] & 0x1f); // hours + + _mtc_fps = MTC_FPS ((sysex_buf[5] & 0x60) >> 5); // fps + + /* wait for first quarter frame, which could indicate forwards + or backwards ... + */ + + reset_mtc_state (); + + /* emit signals */ + + mtc (*this, &sysex_buf[1], msglen - 1); + mtc_time (fake_mtc_time, true); + mtc_status (MTC_Stopped); + + return true; +} + +void +Parser::reset_mtc_state () +{ + _mtc_forward = false; + _mtc_running = MTC_Stopped; + _mtc_locked = false; + expected_mtc_quarter_frame_code = 0; + memset (_mtc_time, 0, sizeof (_mtc_time)); + memset (_qtr_mtc_time, 0, sizeof (_mtc_time)); + consecutive_qtr_frame_cnt = 0; + last_qtr_frame = 0; +} + +void +Parser::process_mtc_quarter_frame (byte *msg) +{ + int which_quarter_frame = (msg[1] & 0xf0) >> 4; + + /* Is it an expected frame? + Remember, the first can be frame 7 or frame 0, + depending on the direction of the MTC generator ... + */ + +#if 0 + cerr << "MTC: (state = " << _mtc_running << ") " + << which_quarter_frame << " vs. " << expected_mtc_quarter_frame_code + << " consecutive ? " << consecutive_qtr_frame_cnt + << endl; +#endif + + if (_mtc_running == MTC_Stopped) { + + /* we are stopped but are seeing qtr frame messages */ + + if (consecutive_qtr_frame_cnt == 0) { + + /* first quarter frame */ + + if (which_quarter_frame != 0 && which_quarter_frame != 7) { + + last_qtr_frame = which_quarter_frame; + consecutive_qtr_frame_cnt++; + } + + // cerr << "first seen qframe = " << (int) last_qtr_frame << endl; + + return; + + } else if (consecutive_qtr_frame_cnt == 1) { + + /* third quarter frame */ + + // cerr << "second seen qframe = " << (int) which_quarter_frame << endl; + + if (last_qtr_frame < which_quarter_frame) { + _mtc_running = MTC_Forward; + } else if (last_qtr_frame > which_quarter_frame) { + _mtc_running = MTC_Backward; + } + + mtc_status (_mtc_running); + } + + switch (_mtc_running) { + case MTC_Forward: + if (which_quarter_frame == 7) { + expected_mtc_quarter_frame_code = 0; + } else { + expected_mtc_quarter_frame_code = which_quarter_frame + 1; + } + break; + + case MTC_Backward: + if (which_quarter_frame == 0) { + expected_mtc_quarter_frame_code = 7; + + } else { + expected_mtc_quarter_frame_code = which_quarter_frame - 1; + } + break; + + case MTC_Stopped: + break; + } + + } else { + + /* already running */ + +// for testing bad MIDI connections etc. +// if ((random() % 500) < 10) { + + if (which_quarter_frame != expected_mtc_quarter_frame_code) { + + consecutive_qtr_frame_cnt = 0; + +#ifdef DEBUG_MTC + cerr << "MTC: (state = " << _mtc_running << ") " + << which_quarter_frame << " vs. " << expected_mtc_quarter_frame_code << endl; +#endif + + /* tell listener(s) that we skipped. if they return + true, just ignore this in terms of it being an error. + */ + + if (1) { /* mtc_skipped () */ + + /* no error, reset next expected frame */ + + switch (_mtc_running) { + case MTC_Forward: + if (which_quarter_frame == 7) { + expected_mtc_quarter_frame_code = 0; + } else { + expected_mtc_quarter_frame_code = which_quarter_frame + 1; + } + break; + + case MTC_Backward: + if (which_quarter_frame == 0) { + expected_mtc_quarter_frame_code = 7; + + } else { + expected_mtc_quarter_frame_code = which_quarter_frame - 1; + } + break; + + case MTC_Stopped: + break; + } + +#ifdef DEBUG_MTC + cerr << "SKIPPED, next expected = " << expected_mtc_quarter_frame_code << endl; +#endif + return; + } + + /* go back to waiting for the first frame */ + + expected_mtc_quarter_frame_code = 0; + memset (_qtr_mtc_time, 0, sizeof (_qtr_mtc_time)); + + _mtc_running = MTC_Stopped; + _mtc_locked = false; + mtc_status (MTC_Stopped); + + return; + + } else { + + /* received qtr frame matched expected */ + consecutive_qtr_frame_cnt++; + + } + } + + /* time code is looking good */ + + switch (which_quarter_frame) { + case 0: // frames LS nibble + _qtr_mtc_time[0] |= msg[1] & 0xf; + break; + + case 1: // frames MS nibble + _qtr_mtc_time[0] |= (msg[1] & 0xf)<<4; + break; + + case 2: // seconds LS nibble + _qtr_mtc_time[1] |= msg[1] & 0xf; + break; + + case 3: // seconds MS nibble + _qtr_mtc_time[1] |= (msg[1] & 0xf)<<4; + break; + + case 4: // minutes LS nibble + _qtr_mtc_time[2] |= msg[1] & 0xf; + break; + + case 5: // minutes MS nibble + _qtr_mtc_time[2] |= (msg[1] & 0xf)<<4; + break; + + case 6: // hours LS nibble + _qtr_mtc_time[3] |= msg[1] & 0xf; + break; + + case 7: + + /* last quarter frame msg has the MS bit of + the hour in bit 0, and the SMPTE FPS type + in bits 5 and 6 + */ + + _qtr_mtc_time[3] |= ((msg[1] & 0x1) << 4); + _mtc_fps = MTC_FPS ((msg[1] & 0x6) >> 1); + break; + + default: + /*NOTREACHED*/ + break; + + } + + mtc_qtr (*this); /* EMIT_SIGNAL */ + + // mtc (*this, &msg[1], msglen - 1); + + switch (_mtc_running) { + case MTC_Forward: + if ((which_quarter_frame == 7)) { + + /* we've reached the final of 8 quarter frame messages. + store the time, reset the pending time holder, + and signal anyone who wants to know the time. + */ + + if (consecutive_qtr_frame_cnt >= 8) { + memcpy (_mtc_time, _qtr_mtc_time, sizeof (_mtc_time)); + memset (_qtr_mtc_time, 0, sizeof (_qtr_mtc_time)); + if (!_mtc_locked) { + _mtc_locked = true; + } + mtc_time (_mtc_time, false); + } + expected_mtc_quarter_frame_code = 0; + + } else { + expected_mtc_quarter_frame_code = which_quarter_frame + 1; + } + break; + + case MTC_Backward: + if (which_quarter_frame == 0) { + + /* we've reached the final of 8 quarter frame messages. + store the time, reset the pending time holder, + and signal anyone who wants to know the time. + */ + + if (consecutive_qtr_frame_cnt >= 8) { + memcpy (_mtc_time, _qtr_mtc_time, sizeof (_mtc_time)); + memset (_qtr_mtc_time, 0, sizeof (_qtr_mtc_time)); + if (!_mtc_locked) { + _mtc_locked = true; + } + mtc_time (_mtc_time, false); + } + + expected_mtc_quarter_frame_code = 7; + + } else { + expected_mtc_quarter_frame_code = which_quarter_frame - 1; + } + break; + + default: + break; + } + +} diff --git a/libs/midi++2/port_request.cc b/libs/midi++2/port_request.cc new file mode 100644 index 0000000000..d081bdb570 --- /dev/null +++ b/libs/midi++2/port_request.cc @@ -0,0 +1,80 @@ +/* + Copyright (C) 2000 Paul Barton-Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <fcntl.h> +#include <string.h> +#include <midi++/port.h> +#include <midi++/port_request.h> + +using namespace std; +using namespace MIDI; + +PortRequest::PortRequest (const string &xdev, + const string &xtag, + const string &xmode, + const string &xtype) + +{ + status = OK; + + devname = strdup (xdev.c_str()); + tagname = strdup (xtag.c_str()); + + if (xmode == "output" || + xmode == "out" || + xmode == "OUTPUT" || + xmode == "OUT") { + mode = O_WRONLY; + + } else if (xmode == "input" || + xmode == "in" || + xmode == "INPUT" || + xmode == "IN") { + mode = O_RDONLY; + + } else if (xmode == "duplex" || + xmode == "DUPLEX" || + xmode == "inout" || + xmode == "INOUT") { + mode = O_RDWR; + } else { + status = Unknown; + } + + if (xtype == "ALSA/RAW" || + xtype == "alsa/raw") { + type = Port::ALSA_RawMidi; + } else if (xtype == "ALSA/SEQUENCER" || + xtype == "alsa/sequencer") { + type = Port::ALSA_Sequencer; + } else if (xtype == "COREMIDI" || + xtype == "coremidi") { + type = Port::CoreMidi_MidiPort; + } else if (xtype == "NULL" || + xtype == "null") { + type = Port::Null; + } else if (xtype == "FIFO" || + xtype == "fifo") { + type = Port::FIFO; + } else { + status = Unknown; + } +} + |