diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | e2de64d6f1beb9e492daf5b886e19933c1fa41dd (patch) | |
tree | 9047cf9e6b5c43878d5bf82660adae77ceee097a /arts/midi | |
download | tdemultimedia-e2de64d6f1beb9e492daf5b886e19933c1fa41dd.tar.gz tdemultimedia-e2de64d6f1beb9e492daf5b886e19933c1fa41dd.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdemultimedia@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'arts/midi')
33 files changed, 4325 insertions, 0 deletions
diff --git a/arts/midi/Makefile.am b/arts/midi/Makefile.am new file mode 100644 index 00000000..b5719649 --- /dev/null +++ b/arts/midi/Makefile.am @@ -0,0 +1,53 @@ +####### Various modules for artsmodules + +SUBDIRS = mcopclass +INCLUDES= -I$(top_builddir)/arts/runtime -I$(srcdir)/freeverb -I$(arts_includes) $(all_includes) + +lib_LTLIBRARIES = libartsmidi_idl.la libartsmidi.la + +bin_PROGRAMS = midisend +noinst_PROGRAMS = midisynctest + +midisend_SOURCES = midisend.cc midimsg.c +midisend_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIBPTHREAD) +midisend_LDADD = libartsmidi_idl.la +midisend_COMPILE_FIRST = artsmidi.h + +midisynctest_SOURCES = midisynctest.cc +midisynctest_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIBPTHREAD) +midisynctest_LDADD = libartsmidi.la -lsoundserver_idl +midisynctest_COMPILE_FIRST = artsmidi.h + +libartsmidi_idl_la_SOURCES = artsmidi.cc +libartsmidi_idl_la_LIBADD = -lmcop -lartsflow +libartsmidi_idl_la_LDFLAGS = $(all_libraries) -L$(arts_libraries) \ + -no-undefined + +libartsmidi_la_SOURCES = midimanager_impl.cc midiclient_impl.cc \ + miditest_impl.cc midimanagerport_impl.cc rawmidiport_impl.cc \ + systemmiditimer_impl.cc audiomiditimer_impl.cc miditimercommon.cc \ + audiosync_impl.cc audiotimer.cc alsamidigateway_impl.cc \ + alsamidiport_impl.cc midisyncgroup_impl.cc timestampmath.cc +libartsmidi_la_COMPILE_FIRST = artsmidi.h + +libartsmidi_la_LIBADD = libartsmidi_idl.la -lartsflow $(ARTS_LIBASOUND) +libartsmidi_la_LDFLAGS = $(all_libraries) -L$(arts_libraries) \ + -no-undefined + +artsmidi.mcopclass: artsmidi.h +artsmidi.mcoptype: artsmidi.h +artsmidi.cc artsmidi.h: $(srcdir)/artsmidi.idl $(MCOPIDL) + $(MCOPIDL) -t -I$(arts_includes) $(srcdir)/artsmidi.idl + +DISTCLEANFILES = artsmidi.cc artsmidi.h \ + artsmidi.mcoptype artsmidi.mcopclass + +####### install idl files + +artsincludedir = $(includedir)/arts +artsinclude_HEADERS = artsmidi.h artsmidi.idl + +mcoptypedir = $(libdir)/mcop +mcoptype_DATA = artsmidi.mcoptype artsmidi.mcopclass + +artsmidi.lo: artsmidi.h diff --git a/arts/midi/README.midi b/arts/midi/README.midi new file mode 100644 index 00000000..e341ca01 --- /dev/null +++ b/arts/midi/README.midi @@ -0,0 +1,261 @@ +Midi, Audio and Synchronization: +================================ + +1. Introduction +2. The midi manager +3. Midi synchronization +4. Audio timestamping and synchronization +5. Example code + + + 1. Introduction + --------------- + +Since aRts-1.0 (as shipped with KDE3.0), aRts provides a lot more +infrastructure to deal with midi, audio, and their synchronization. The main +goal is to provide a unified interface between sequencers (or other programs +that require notes or audio tracks to be played at certain given time stamps) +and underlying software/hardware that can play notes/audio tracks. + +Currently, there exist five distinct destinations that aRts supports, which +can all be used at the same time or individually, that is: + + * aRts synthetic midi instruments + * ALSA-0.5 + * ALSA-0.9 + * OSS + * other aRts modules (including but not limited to the playback/recording + of audio tracks) + + 2. The midi manager + ------------------- + +The midi manager is the basic component that connects between applications +that supply/record midi data, and devices that process midi data. Devices +might be both, virtual (as in software synthesis) or real (as in hardware +devices). + +From the view of the midi manager, all event streams correspond to one midi +client. So, a midi client might be an application (such as a sequencer) that +provides events, or an ALSA hardware device that consumes events. If there +are multiple event streams, they correspond to multiple clients. That is, +if an application wishes to play three different midi tracks, one over ALSA, +and two over two different synthetic instruments, it needs to register itself +three times, with three different clients. + +The midi managers job is to connect midi clients (as in event streams). It +maintains a list of connections that the user can modify with an application +like artscontrol. Applications could also, if they wish so, modify this +connection list. + +As a use case, we'll consider the following: you want to write a sequencer +application that plays back two different tracks to two different devices. +You want the user to be able to select these devices in a drop down box for +each track. + +1) getting a list of choices: + +First, you will want to obtain a list of choices which the user could possibly +connect your tracks to. You do so by reading the + +interface MidiManager { // SINGLETON: Arts_MidiManager + /** + * a list of clients + */ + readonly attribute sequence<MidiClientInfo> clients; + + //... +}; + +attribute. The three fields of each client that are interesting for you are + +struct MidiClientInfo { + long ID; + + //... + + MidiClientDirection direction; + MidiClientType type; + string title; +}; + +You would list those devices in the dropdown box that are of the appropriate +direction, which is mcdRecord, as you would want a client that receives midi +events (this might be confusing, but you look from the view of the client). + +Then, there is the type field, which tells you whether the client is a device- +like thing (like a synthetic instrument), or another application (like another +application currently recording a track). While it might not be an impossible +setup that you send events between two applications, usually users will choose +such clients that have mctDestination as type. + +Finally, you can list the titles in a drop down box, and keep the ID for making +a connection later. + +2) registering clients: + +You will need to register one client for each track. Use + + /** + * add a client + * + * this creates a new MidiManagerClient + */ + MidiClient addClient(MidiClientDirection direction, MidiClientType type, + string title, string autoRestoreID); + +to do so. + +3) connecting: + +As you probably don't want your sequencer user to use artscontrol to setup +connections between your tracks and the devices, you will need to connect +your clients to the hardware devices for playing something. + +You can connect clients to their appropriate destinations using + + /** + * connect two clients + */ + void connect(long clientID, long destinationID); + +and + + /** + * disconnect two clients + */ + void disconnect(long clientID, long destinationID); + +Keep in mind that a client might be connected to more than one destination +at the same time, so that you will need to disconnect the old destination +before connecting the new one. + +4) playing events: + +You can now play events to the tracks, using each client's + + MidiPort addOutputPort(); + +function for getting a port where you can send events to. However, you will +also need to ensure that the events will get synchronized as soon as you are +playing back events to different devices. Read the next section for details +on this. + + 3. Midi synchronization + ----------------------- + +As soon as you are writing a real sequencer, you might want to output to more +than one midi device at a time. For instance, you might want to let some of +your midi events be played by aRts synthesis, while others should be sent +over the external midi port. + +To support this setup, a new interface called MidiSyncGroup has been added. To +output midi events synchronized over more than one port, you proceed as follows: + +a) you obtain a reference to the midi manager object + + MidiManager midiManager = DynamicCast(Reference("global:Arts_MidiManager")); + if(midiManager.isNull()) arts_fatal("midimanager is null"); + +b) you create a midi synchronization group which will ensure that the + timestamps of your midi events will be synchronized + + MidiSyncGroup syncGroup = midiManager.addSyncGroup(); + +c) you add a client to the midi manager for each port you want to output + midi data over + + MidiClient client = midiManager.addClient(mcdPlay, mctApplication, "midisynctest", "midisynctest"); + MidiClient client2 = midiManager.addClient(mcdPlay, mctApplication, "midisynctest2", "midisynctest2"); + +d) you insert the clients in the synchronization group + + syncGroup.addClient(client); + syncGroup.addClient(client2); + +e) you create ports for each client as usual + + MidiPort port = client.addOutputPort(); + MidiPort port2 = client2.addOutputPort(); + +f) at this point, you will need to ensure that the midi clients you created + are connected, you can either leave the user with artscontrol for doing + this, or use the clients and connect methods of the midiManager object + yourself (see use case discussed in previous section) + +g) you output events over the ports as usual + + /* where t is a suitable TimeStamp */ + MidiEvent e = MidiEvent(t,MidiCommand(mcsNoteOn|0, notes[np], 100)); + port.processEvent(e); + port2.processEvent(e); + + 4. Audio timestamping and synchronization + ----------------------------------------- + +Audio in aRts is usually handled as structures consisting of small modules +that do something. While this model allows you to describe anything you want +to, from playing a sample to playing a synthetic sequence of notes with a +synthetic instruments, it doesn't give you any notion of time. More so, if +you build a large graph of objects, you might need quite some time for this, +and you will want to have them all started at the same time. + +To solve this issue, an AudioSync interface has been introduced, that allows +you to start() and stop() either synchronized at a specific point in time. + +Suppose you have two synthesis modules which together play back a sample. +What can you do to start them at the same time? + + Synth_PLAY_WAV wav = //... create on server + Synth_AMAN_PLAY sap //... create on server + AudioSync audioSync = //... create on server + + wav.filename("/opt/kde3/share/sounds/pop.wav"); + sap.title("midisynctest2"); + sap.autoRestoreID("midisynctest2"); + connect(wav,sap); + + // this queues back start() to be called atomically later + audioSync.queueStart(wav); + audioSync.queueStart(sap); + + // this line is a synchronized version of + // wav.start(); + // sap.start(); + audioSync.execute(); + +You could also play them back at a specific time in the future and query the +current time using the time and executeAt methods: + +interface AudioSync { + /** + * the current time + */ + readonly attribute TimeStamp time; + + //... + + /** + * atomically executes all queued modifications to the flow system + * at a given time + */ + void executeAt(TimeStamp timeStamp); +}; + +Finally, to get synchronized midi and audio, you can insert the AudioSync +object into a midi synchronization group, then their timestamps will be +synchronized to those of the midi channels. + + 5. Example code + --------------- + +An example that illustrates most things discussed in this document is +midisynctest.cc, which plays back two synchronized midi streams and samples. +Note that you might want to change the source code, as it hardcodes the +location of the .wav file. + + +Questions and comments are welcome. + +Stefan Westerfeld +stefan@space.twc.de diff --git a/arts/midi/alsamidigateway_impl.cc b/arts/midi/alsamidigateway_impl.cc new file mode 100644 index 00000000..91a0f118 --- /dev/null +++ b/arts/midi/alsamidigateway_impl.cc @@ -0,0 +1,243 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/** + * compile real version if we have ALSA support, dummy version otherwise + */ +#if defined(HAVE_ARTS_LIBASOUND2) || defined(HAVE_ARTS_LIBASOUND) + +#ifdef HAVE_ALSA_ASOUNDLIB_H +#include <alsa/asoundlib.h> +#elif defined(HAVE_SYS_ASOUNDLIB_H) +#include <sys/asoundlib.h> +#endif + +#include "alsamidiport_impl.h" +#include <arts/debug.h> +#include <stdio.h> + +using namespace Arts; +using namespace std; + +class AlsaMidiGateway_impl : virtual public AlsaMidiGateway_skel { +protected: + snd_seq_t *seq; + + struct PortEntry { + int alsaClient, alsaPort; + bool keep; + + AlsaMidiPort port; + MidiClient client; + }; + list<PortEntry> ports; + +#ifdef HAVE_ARTS_LIBASOUND2 +/* ALSA-0.9 specific code */ + int alsaOpen() { + return snd_seq_open(&seq, "hw", SND_SEQ_OPEN_DUPLEX, 0); + } + bool alsaScan(MidiManager midiManager) { + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + + snd_seq_client_info_alloca(&cinfo); + snd_seq_client_info_set_client(cinfo, -1); + + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + int client = snd_seq_client_info_get_client(cinfo); + + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_client(pinfo, client); + + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + unsigned int cap; + + cap = (SND_SEQ_PORT_CAP_SUBS_WRITE|SND_SEQ_PORT_CAP_WRITE); + if ((snd_seq_port_info_get_capability(pinfo) & cap) == cap) { + string name = snd_seq_port_info_get_name(pinfo); + int client = snd_seq_port_info_get_client(pinfo); + int port = snd_seq_port_info_get_port(pinfo); + + createPort(midiManager, name, client, port); + } + } + } + + return true; + } +#else +/* ALSA-0.5 specific code */ + int alsaOpen() { + return snd_seq_open(&seq, SND_SEQ_OPEN); + } + + bool alsaScan(MidiManager midiManager) { + snd_seq_system_info_t sysinfo; + + int err = snd_seq_system_info(seq, &sysinfo); + if (err < 0) + { + arts_warning("snd_seq_systeminfo failed: %s", snd_strerror(err)); + return false; + } + + for(int client = 0; client < sysinfo.clients; client++) + { + snd_seq_client_info_t cinfo; + if (snd_seq_get_any_client_info(seq, client, &cinfo) == 0) + { + for(int port = 0; port < sysinfo.ports; port++) + { + snd_seq_port_info_t pinfo; + if(snd_seq_get_any_port_info(seq, client, port, &pinfo) == 0) + { + unsigned int cap; + cap = (SND_SEQ_PORT_CAP_SUBS_WRITE|SND_SEQ_PORT_CAP_WRITE); + + if ((pinfo.capability & cap) == cap) + createPort(midiManager, pinfo.name, client, port); + } + } + } + } + + return true; + } +#endif + +public: + AlsaMidiGateway_impl() : seq(0) + { + } + + ~AlsaMidiGateway_impl() + { + if(seq) + snd_seq_close(seq); + } + + void createPort(MidiManager midiManager, string name, int client, int port) + { + if(name != "aRts") + { + char nr[1024]; + + sprintf(nr, " (%3d:%-3d)", client, port); + name += nr; + + list<PortEntry>::iterator pi = ports.begin(); + while(pi != ports.end() && (pi->alsaClient != client || pi->alsaPort != port)) + pi++; + + if(pi != ports.end()) /* we already have this port */ + pi->keep = true; + else /* we need to create it */ + { + PortEntry pe; + pe.port = AlsaMidiPort::_from_base( + new AlsaMidiPort_impl(seq, client, port)); + + if(pe.port.open()) + { + pe.client = midiManager.addClient(mcdRecord, + mctDestination, + name, name); + pe.client.addInputPort(pe.port); + pe.alsaClient = client; + pe.alsaPort = port; + pe.keep = true; + + ports.push_back(pe); + } + } + } + } + + bool rescan() + { + MidiManager midiManager = DynamicCast(Reference("global:Arts_MidiManager")); + if(midiManager.isNull()) + { + arts_warning("AlsaMidiGateway: can't find MidiManager"); + return false; + } + + if(!seq) + { + int err = alsaOpen(); + if (err < 0) + { + arts_warning("AlsaMidiGateway: could not open sequencer %s", + snd_strerror(err)); + seq = 0; + return false; + } + } + + list<PortEntry>::iterator pi; + for(pi = ports.begin(); pi != ports.end(); pi++) + pi->keep = false; + + if(!alsaScan(midiManager)) + return false; + + /* erase those ports that are no longer needed */ + pi = ports.begin(); + while(pi != ports.end()) + { + if(!pi->keep) + pi = ports.erase(pi); + else + pi++; + } + + return true; + } +}; + +#else + +using namespace Arts; +using namespace std; + +class AlsaMidiGateway_impl : virtual public AlsaMidiGateway_skel { +public: + bool rescan() + { + /* dummy version: no ALSA support compiled in */ + return false; + } +}; + +#endif + +namespace Arts { + REGISTER_IMPLEMENTATION(AlsaMidiGateway_impl); +} diff --git a/arts/midi/alsamidiport_impl.cc b/arts/midi/alsamidiport_impl.cc new file mode 100644 index 00000000..e53d7fe7 --- /dev/null +++ b/arts/midi/alsamidiport_impl.cc @@ -0,0 +1,232 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "alsamidiport_impl.h" + +#if defined(HAVE_ARTS_LIBASOUND2) || defined(HAVE_ARTS_LIBASOUND) +#include <arts/debug.h> + +#ifdef HAVE_ARTS_LIBASOUND +#define snd_seq_queue_status_alloca(x) \ + *x = (snd_seq_queue_status_t *)alloca(sizeof(snd_seq_queue_status_t)) +#define snd_seq_queue_status_get_tick_time(x) x->tick +#define snd_seq_queue_status_get_real_time(x) (&(x->time)) +#endif + +using namespace std; +using namespace Arts; + +AlsaMidiPort_impl::AlsaMidiPort_impl(snd_seq_t *seq, long client, long port) + : _client(client), _port(port), alsaSeq(seq) +{ + opened = false; +} + +/* interface MidiPort */ +Arts::TimeStamp AlsaMidiPort_impl::time() +{ + snd_seq_queue_status_t *status; + snd_seq_queue_status_alloca(&status); + + snd_seq_get_queue_status(alsaSeq, alsaQueue, status); + snd_seq_tick_time_t ttime = snd_seq_queue_status_get_tick_time(status); + const snd_seq_real_time_t *rtime = + snd_seq_queue_status_get_real_time(status); + + return Arts::TimeStamp(rtime->tv_sec, rtime->tv_nsec / 1000); +} + +Arts::TimeStamp AlsaMidiPort_impl::playTime() +{ + return time(); +} + +void AlsaMidiPort_impl::fillAlsaEvent(snd_seq_event_t *ev, + const MidiCommand& command) +{ + ev->source = alsaSourceAddr; + ev->dest = alsaDestAddr; + + mcopbyte channel = command.status & mcsChannelMask; + + switch(command.status & mcsCommandMask) + { + case mcsNoteOn: + snd_seq_ev_set_noteon(ev, channel, command.data1, command.data2); + break; + case mcsNoteOff: + snd_seq_ev_set_noteoff(ev, channel, command.data1, command.data2); + break; + case mcsProgram: + snd_seq_ev_set_pgmchange(ev, channel, command.data1); + break; + case mcsParameter: + snd_seq_ev_set_controller(ev, channel, command.data1, command.data2); + break; + default: + /* unhandled */ + return; + } +} + +void AlsaMidiPort_impl::sendAlsaEvent(snd_seq_event_t *ev) +{ + int ret = snd_seq_event_output(alsaSeq, ev); + if (ret < 0) { + arts_warning("AlsaMidiPort: error writing note %s\n", + snd_strerror(ret)); + return; + } + flushAlsa(); +} + +void AlsaMidiPort_impl::processCommand(const MidiCommand& command) +{ + snd_seq_event_t ev; + snd_seq_ev_clear(&ev); + + fillAlsaEvent(&ev, command); + sendAlsaEvent(&ev); +} + +void AlsaMidiPort_impl::processEvent(const MidiEvent& event) +{ + snd_seq_event_t ev; + snd_seq_real_time_t time; + + time.tv_sec = event.time.sec; + time.tv_nsec = event.time.usec * 1000; + + snd_seq_ev_clear(&ev); + snd_seq_ev_schedule_real(&ev, alsaQueue, 0, &time); + + fillAlsaEvent(&ev, event.command); + sendAlsaEvent(&ev); +} + +/* interface AlsaMidiPort */ +void AlsaMidiPort_impl::client(long newClient) +{ + if(newClient != _client) + { + _client = newClient; + + if(opened) + { + close(); + open(); + } + + client_changed(newClient); + } +} + +long AlsaMidiPort_impl::client() +{ + return _client; +} + +void AlsaMidiPort_impl::port(long newPort) +{ + if(newPort != _port) + { + _port = newPort; + + if(opened) + { + close(); + open(); + } + + port_changed(newPort); + } +} + +long AlsaMidiPort_impl::port() +{ + return _port; +} + +bool AlsaMidiPort_impl::open() +{ + arts_return_val_if_fail(opened == false, false); + + alsaQueue = snd_seq_alloc_queue(alsaSeq); + alsaClientId = snd_seq_client_id(alsaSeq); + + alsaPort = snd_seq_create_simple_port(alsaSeq, "aRts", + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE | + SND_SEQ_PORT_CAP_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC); + if (alsaPort < 0) { + arts_warning("AlsaMidiPort: can't creating port %s\n", + snd_strerror(alsaPort)); + return false; + } + + alsaSourceAddr.client = alsaClientId; + alsaSourceAddr.port = alsaPort; + + alsaDestAddr.client = _client; + alsaDestAddr.port = _port; + + int ret; + ret = snd_seq_connect_to(alsaSeq, alsaPort, + alsaDestAddr.client, + alsaDestAddr.port); + if (ret < 0) { + arts_warning("AlsaMidiPort: error connecting port %s\n", + snd_strerror(ret)); + /* FIXME: destroy port here */ + return false; + } + + snd_seq_start_queue(alsaSeq, alsaQueue, 0); + flushAlsa(); + + opened = true; + return true; +} + +void AlsaMidiPort_impl::close() +{ + if(!opened) + return; + + opened = false; +} + +void AlsaMidiPort_impl::flushAlsa() +{ +#ifdef HAVE_ARTS_LIBASOUND2 + snd_seq_drain_output(alsaSeq); +#else + int err; + while((err = snd_seq_flush_output(alsaSeq)) > 0) + { + arts_debug("alsa flush error %d\n",snd_strerror(err)); + usleep(2000); + } +#endif +} +#endif diff --git a/arts/midi/alsamidiport_impl.h b/arts/midi/alsamidiport_impl.h new file mode 100644 index 00000000..251f9d94 --- /dev/null +++ b/arts/midi/alsamidiport_impl.h @@ -0,0 +1,85 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_ALSAMIDIPORT_IMPL_H +#define ARTS_ALSAMIDIPORT_IMPL_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/** + * compile real version if we have ALSA support, dummy version otherwise + */ +#if defined(HAVE_ARTS_LIBASOUND2) || defined(HAVE_ARTS_LIBASOUND) + +#ifdef HAVE_ALSA_ASOUNDLIB_H +#include <alsa/asoundlib.h> +#elif defined(HAVE_SYS_ASOUNDLIB_H) +#include <sys/asoundlib.h> +#endif + +#include "artsmidi.h" + +namespace Arts { + +class AlsaMidiPort_impl : virtual public AlsaMidiPort_skel { +protected: + long _client, _port; + bool opened; + + snd_seq_t *alsaSeq; + + int alsaQueue; + int alsaClientId; + int alsaPort; + + snd_seq_addr_t alsaSourceAddr; + snd_seq_addr_t alsaDestAddr; + + void fillAlsaEvent(snd_seq_event_t *ev, const MidiCommand& command); + void sendAlsaEvent(snd_seq_event_t *ev); + void flushAlsa(); + +public: + AlsaMidiPort_impl(snd_seq_t *seq, long client, long port); + void close(); + + /* interface MidiPort */ + Arts::TimeStamp time(); + Arts::TimeStamp playTime(); + void processCommand(const MidiCommand& command); + void processEvent(const MidiEvent& event); + + /* interface AlsaMidiPort */ + void client(long newClient); + long client(); + + void port(long newPort); + long port(); + + bool open(); +}; + +} +#endif /* HAVE_ARTS_LIBASOUND2 */ +#endif /* ARTS_ALSAMIDIPORT_IMPL_H */ diff --git a/arts/midi/artsmidi.idl b/arts/midi/artsmidi.idl new file mode 100644 index 00000000..63bf868b --- /dev/null +++ b/arts/midi/artsmidi.idl @@ -0,0 +1,379 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +/* + * DISCLAIMER: The interfaces in artsmidi.idl (and the derived .cc/.h files) + * DO NOT GUARANTEE BINARY COMPATIBILITY YET. + * + * They are intended for developers. You shouldn't expect that applications in + * binary form will be fully compatibile with further releases of these + * interfaces. + */ + +#include <artsflow.idl> + +module Arts { + +/* This is modelled somewhat after + - the AudioManager concept + - the aRts-0.3.4.1 MidiPort concept + - libkmid + + It adds timing as new feature compared to older implementation, and also + tries to do the full set of midi operations. + + It's current state is "experimental", and "binary compatibility not kept". + */ + +/** + * an absolute timestamp + */ +struct TimeStamp { + long sec,usec; +}; + +/** + * different status of a midi command + */ +enum MidiCommandStatus { +// Masks: + mcsCommandMask = 0xf0, + mcsChannelMask = 0x0f, + +// Commands: + mcsNoteOff = 0x80, + mcsNoteOn = 0x90, + mcsKeyPressure = 0xa0, + mcsParameter = 0xb0, + mcsProgram = 0xc0, + mcsChannelPressure = 0xd0, + mcsPitchWheel = 0xe0 +}; + +/** + * the following are to be used once status is (mcsParameter|channel): + */ +enum MidiCommandParameter { + mcpSustain = 0x40, + mcpAllNotesOff = 0x7b +}; + +/** + * a midi command + */ +struct MidiCommand { + byte status; + byte data1; + byte data2; +}; + +/** + * a midi event + */ + +struct MidiEvent { + TimeStamp time; + MidiCommand command; +}; + +/** + * a midi port + */ +interface MidiPort { + /** + * the current absolute time (since the existence of the midi device) + */ + readonly attribute TimeStamp time; + + /** + * the current play time + * + * Some midi devices, for instance synthetic audio devices, have a certain + * amount of internal buffering. This causes a time difference between + * where events are currently being rendered, which is the timestamp + * obtained by "time", and the events that the listener is hearing right + * now, which is this timestamp, the "playTime". + */ + readonly attribute TimeStamp playTime; + + /** + * processes a midi command + */ + oneway void processCommand(MidiCommand command); + + /** + * processes a midi event + */ + oneway void processEvent(MidiEvent event); +}; + +enum MidiClientDirection { mcdPlay, mcdRecord }; +enum MidiClientType { mctDestination, mctApplication }; + +/** + * information about a midi client + */ +struct MidiClientInfo { + long ID; + sequence<long> connections; + + MidiClientDirection direction; + MidiClientType type; + string title, autoRestoreID; +}; + +/** + * a midi manager client + */ +interface MidiClient { + readonly attribute MidiClientInfo info; + + /** + * you can change the title of your client on the fly - everything else + * (besides the actual assignment) is static + */ + attribute string title; + + /** + * creates a new port through which the client can receive data from + * the midi manager + */ + void addInputPort(MidiPort port); + + /** + * creates a new port through which the client can send data to the + * midi manager + */ + MidiPort addOutputPort(); + + /** + * removes a port + */ + void removePort(MidiPort port); +}; + +interface AudioSync; + +/** + * this synchronizes multiple midi clients - it also allows synchronization + * with audio events + */ +interface MidiSyncGroup { + /** + * adds a midi client to the synchronization group + * + * hint: during adding the client, the timestamps related to that + * client will jump + */ + void addClient(MidiClient client); + + /** + * deletes a midi client from the synchronization group + */ + void removeClient(MidiClient client); + + /** + * adds an AudioSync object to the synchronization group + * + * hint: during adding the AudioSync object, the timestamps related to + * that object might jump + */ + void addAudioSync(AudioSync audioSync); + + /** + * deletes an AudioSync object from the synchronization group + */ + void removeAudioSync(AudioSync audioSync); +}; + +/** + * Some general notes to the understanding of the midi manager. The midi + * manager has the task to intelligently assign applications to destinations. + * + * It is important to understand what it actually does to understand the + * distinction first, which is expressed through the "MidiClientType" of + * each client. + * + * APPLICATIONS: An application is a user visible application, that produces + * or records midi data. It is important for the understanding of an + * application, that an application actually *wants* to be supplied with + * data, or wants to get its data played. Thus, adding an application to + * the midi manager is an implicit request: "go and find a place where to + * put the events to (or get the events from)". + * + * Examples for applications would be games or midi players. + * + * DESTINATIONS: A destination is a system service that plays or supplies + * midi data. The characteristic here is that a destination is something + * that is there if you need it. + * + * Examples for destinations might be might be a hardware device or an + * emulation of a hardware device (such as a virtual sampler). + * + * So the process is as follows: + * - destinations register themselves at the midi manager, and provide + * system services in that way + * + * - when the user starts an application (such as a midi player), the midi + * manager's task is to assign it to a suitable destination + * + * - the user can interact with the process by changing the way applications + * are assigned to destinations - the midi manager will try to learn + * what the user wants, and next time do a better job while assigning + * + * To actually record or play some data, you need to register a client first, + * and after that, you can add Input or Output "MidiPort"s to your client, + * so that you can actually send or receive events with them. + */ +interface MidiManager { // SINGLETON: Arts_MidiManager + /** + * a list of clients + */ + readonly attribute sequence<MidiClientInfo> clients; + + /** + * add a client + * + * this creates a new MidiManagerClient + */ + MidiClient addClient(MidiClientDirection direction, MidiClientType type, + string title, string autoRestoreID); + + /** + * connect two clients + */ + void connect(long clientID, long destinationID); + + /** + * disconnect two clients + */ + void disconnect(long clientID, long destinationID); + + /** + * add a synchronization group + * + * this creates a new MidiSyncGroup + */ + MidiSyncGroup addSyncGroup(); +}; + +interface MidiTest : MidiPort { +}; + +interface RawMidiPort : MidiPort { + attribute string device; + attribute boolean input, output; + attribute boolean running; + boolean open(); +}; + +interface AlsaMidiGateway { + boolean rescan(); +}; + +interface AlsaMidiPort : MidiPort { + attribute long client; + attribute long port; + boolean open(); +}; + +/** + * EXPERIMENTAL interface for audio synchronization - this allows multiple + * objects to be started and stopped at a precisely defined timestamp + */ +interface AudioSync { + /** + * the current time + */ + readonly attribute TimeStamp time; + + /** + * the current play time + * + * Since aRts has internal buffering, there is a time difference between + * where events are currently being rendered, which is the timestamp + * obtained by "time", and the events that the listener is hearing right + * now, which is this timestamp, the "playTime". + */ + readonly attribute TimeStamp playTime; + + /** + * queues calling synthModule.start() later + * + * (will keep a reference on the module until executed) + */ + void queueStart(SynthModule synthModule); + + /** + * queues calling synthModule.stop() later + * + * (will keep a reference on the module until executed) + */ + void queueStop(SynthModule synthModule); + + /** + * atomically executes all queued modification to the flow system + */ + void execute(); + + /** + * atomically executes all queued modifications to the flow system + * at a given time + */ + void executeAt(TimeStamp timeStamp); +}; + +/** + * Midi Timer - can be used to provide timing for midi ports that have + * no "native" timing. + */ +interface MidiTimer +{ + /** + * the current time + */ + readonly attribute TimeStamp time; + + /** + * this will put the event into an event queue and send it to the port + * once the time for the event has been reached + */ + oneway void queueEvent(MidiPort port, MidiEvent event); +}; + +/** + * Uses the system time (i.e. gettimeofday() and similar) to provide midi + * timing + */ +interface SystemMidiTimer : MidiTimer +{ +}; + +/** + * Uses the audio time (i.e. samples rendered to /dev/dsp) to provide midi + * timing + */ +interface AudioMidiTimer : MidiTimer +{ +}; + +}; diff --git a/arts/midi/audiomiditimer_impl.cc b/arts/midi/audiomiditimer_impl.cc new file mode 100644 index 00000000..1e39e4f8 --- /dev/null +++ b/arts/midi/audiomiditimer_impl.cc @@ -0,0 +1,116 @@ + /* + + Copyright (C) 2001 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" +#include "artsflow.h" +#include "stdsynthmodule.h" +#include "debug.h" +#include "miditimercommon.h" +#include "audiotimer.h" +#include "flowsystem.h" + +using namespace std; + +namespace Arts { + +class AudioMidiTimerCommon : public MidiTimerCommon, public AudioTimerCallback +{ +protected: + AudioMidiTimerCommon(); + virtual ~AudioMidiTimerCommon(); + + AudioTimer *audioTimer; + +public: + // allocation: share one AudioMidiTimerCommon for everbody who needs one + static AudioMidiTimerCommon *subscribe(); + + TimeStamp time(); + void updateTime(); +}; + +} + +using namespace Arts; + +static AudioMidiTimerCommon *AudioMidiTimerCommon_the = 0; + +AudioMidiTimerCommon::AudioMidiTimerCommon() +{ + AudioMidiTimerCommon_the = this; + + audioTimer = AudioTimer::subscribe(); + audioTimer->addCallback(this); +} + +AudioMidiTimerCommon::~AudioMidiTimerCommon() +{ + audioTimer->removeCallback(this); + audioTimer->unsubscribe(); + + AudioMidiTimerCommon_the = 0; +} + +TimeStamp AudioMidiTimerCommon::time() +{ + return audioTimer->time(); +} + +void AudioMidiTimerCommon::updateTime() +{ + processQueue(); +} + +AudioMidiTimerCommon *AudioMidiTimerCommon::subscribe() +{ + if(!AudioMidiTimerCommon_the) + AudioMidiTimerCommon_the = new AudioMidiTimerCommon(); + AudioMidiTimerCommon_the->refCount++; + return AudioMidiTimerCommon_the; +} + +namespace Arts { + +class AudioMidiTimer_impl : public AudioMidiTimer_skel { +protected: + AudioMidiTimerCommon *timer; +public: + AudioMidiTimer_impl() + { + timer = AudioMidiTimerCommon::subscribe(); + } + ~AudioMidiTimer_impl() + { + timer->unsubscribe(); + } + TimeStamp time() + { + return timer->time(); + } + void queueEvent(MidiPort port, const MidiEvent& event) + { + timer->queueEvent(port, event); + } +}; + +REGISTER_IMPLEMENTATION(AudioMidiTimer_impl); +} diff --git a/arts/midi/audiosync_impl.cc b/arts/midi/audiosync_impl.cc new file mode 100644 index 00000000..fdba8231 --- /dev/null +++ b/arts/midi/audiosync_impl.cc @@ -0,0 +1,203 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "audiosync_impl.h" +#include "midisyncgroup_impl.h" +#include "audiotimer.h" +#include "audiosubsys.h" +#include "timestampmath.h" + +#undef AUDIO_DEBUG_DRIFT + +using namespace std; +using namespace Arts; + +namespace Arts { + static list<AudioSync_impl *> audioSyncImplList; +} + +void AudioSync_impl::AudioSyncEvent::execute() +{ + list<SynthModule>::iterator i; + + for(i = startModules.begin(); i != startModules.end(); i++) + i->start(); + + for(i = stopModules.begin(); i != stopModules.end(); i++) + i->stop(); +} + +AudioSync_impl::AudioSync_impl() + : newEvent(new AudioSyncEvent), syncGroup(0) +{ + syncOffset = TimeStamp(0,0); + + timer = AudioTimer::subscribe(); + timer->addCallback(this); + + audioSyncImplList.push_back(this); +} + +AudioSync_impl::~AudioSync_impl() +{ + delete newEvent; + + while(!events.empty()) + { + delete events.front(); + events.pop_front(); + } + + if(syncGroup) + { + syncGroup->audioSyncDied(this); + syncGroup = 0; + } + audioSyncImplList.remove(this); + timer->removeCallback(this); + timer->unsubscribe(); +} + +TimeStamp AudioSync_impl::time() +{ + if(syncGroup) + return syncGroup->time(); + else + return audioTime(); +} + +TimeStamp AudioSync_impl::playTime() +{ + if(syncGroup) + return syncGroup->playTime(); + else + return audioPlayTime(); +} + +TimeStamp AudioSync_impl::audioTime() +{ + return timer->time(); +} + +TimeStamp AudioSync_impl::audioPlayTime() +{ + double delay = AudioSubSystem::the()->outputDelay(); + + TimeStamp time = audioTime(); + timeStampDec(time,timeStampFromDouble(delay)); + return time; +} + +TimeStamp AudioSync_impl::clientTime() +{ + TimeStamp time = audioTime(); + timeStampDec(time, syncOffset); + return time; +} + +void AudioSync_impl::queueStart(SynthModule synthModule) +{ + newEvent->startModules.push_back(synthModule); +} + +void AudioSync_impl::queueStop(SynthModule synthModule) +{ + newEvent->stopModules.push_back(synthModule); +} + +void AudioSync_impl::execute() +{ + newEvent->execute(); + newEvent->startModules.clear(); + newEvent->stopModules.clear(); +} + +void AudioSync_impl::executeAt(const TimeStamp& timeStamp) +{ + newEvent->time = timeStamp; + if(syncGroup) + timeStampInc(newEvent->time, syncOffset); + + events.push_back(newEvent); + + newEvent = new AudioSyncEvent; +} + +void AudioSync_impl::updateTime() +{ + TimeStamp now = audioTime(); + list<AudioSyncEvent*>::iterator i; + + i = events.begin(); + while(i != events.end()) + { + AudioSyncEvent *event = *i; + TimeStamp& eventTime = event->time; + + if( now.sec > eventTime.sec + || ((now.sec == eventTime.sec) && (now.usec > eventTime.usec))) + { + event->execute(); + delete event; + i = events.erase(i); + } + else + { + i++; + } + } +} + +void AudioSync_impl::setSyncGroup(MidiSyncGroup_impl *newSyncGroup) +{ + syncGroup = newSyncGroup; +} + +void AudioSync_impl::synchronizeTo(const TimeStamp& time) +{ +#ifdef AUDIO_DEBUG_DRIFT + TimeStamp drift = syncOffset; // debug drift +#endif + + syncOffset = audioPlayTime(); + timeStampDec(syncOffset, time); + +#ifdef AUDIO_DEBUG_DRIFT + timeStampDec(drift, syncOffset); // debug drift + printf("SYNC DRIFT %30s %30s: %f\n", + "AudioSync", "AudioSync", timeStampToDouble(drift)); +#endif +} + +AudioSync_impl *AudioSync_impl::find(AudioSync audioSync) +{ + list<AudioSync_impl *>::iterator i; + + for(i = audioSyncImplList.begin(); i != audioSyncImplList.end(); i++) + { + if((*i)->_isEqual(audioSync._base())) + return (*i); + } + return 0; +} + +namespace Arts { REGISTER_IMPLEMENTATION(AudioSync_impl); } diff --git a/arts/midi/audiosync_impl.h b/arts/midi/audiosync_impl.h new file mode 100644 index 00000000..835b8942 --- /dev/null +++ b/arts/midi/audiosync_impl.h @@ -0,0 +1,79 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef AUDIOSYNC_IMPL_H +#define AUDIOSYNC_IMPL_H + +#include "artsmidi.h" +#include "audiotimer.h" + +namespace Arts { + +class MidiSyncGroup_impl; +class AudioSync_impl : virtual public AudioSync_skel, + virtual public AudioTimerCallback +{ + AudioTimer *timer; + + struct AudioSyncEvent + { + TimeStamp time; + std::list<SynthModule> startModules; + std::list<SynthModule> stopModules; + + void execute(); + }; + std::list<AudioSyncEvent *> events; + AudioSyncEvent *newEvent; + MidiSyncGroup_impl *syncGroup; + TimeStamp syncOffset; + + TimeStamp audioTime(); + TimeStamp audioPlayTime(); + +public: + AudioSync_impl(); + ~AudioSync_impl(); + + // public interface + TimeStamp time(); + TimeStamp playTime(); + + void queueStart(SynthModule synthModule); + void queueStop(SynthModule synthModule); + void execute(); + void executeAt(const TimeStamp& timeStamp); + + // interface to AudioTimer + void updateTime(); + + // interface to MidiSyncGroup + static AudioSync_impl *find(AudioSync audioSync); + + void synchronizeTo(const TimeStamp& time); + void setSyncGroup(MidiSyncGroup_impl *syncGroup); + TimeStamp clientTime(); +}; + +} + +#endif /* AUDIOSYNC_IMPL_H */ diff --git a/arts/midi/audiotimer.cc b/arts/midi/audiotimer.cc new file mode 100644 index 00000000..354f153b --- /dev/null +++ b/arts/midi/audiotimer.cc @@ -0,0 +1,98 @@ + /* + + Copyright (C) 2001-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" +#include "artsflow.h" +#include "stdsynthmodule.h" +#include "debug.h" +#include "miditimercommon.h" +#include "audiotimer.h" +#include "flowsystem.h" + +using namespace std; +using namespace Arts; + +static AudioTimer *AudioTimer_the = 0; + +AudioTimer::AudioTimer() +{ + AudioTimer_the = this; + samples = seconds = 0; +} + +AudioTimer::~AudioTimer() +{ + AudioTimer_the = 0; +} + +TimeStamp AudioTimer::time() +{ + return TimeStamp(seconds, + (long)((float)samples / samplingRateFloat * 1000000.0)); +} + +void AudioTimer::notify(const Notification &) +{ + list<AudioTimerCallback *>::iterator i; + for(i = callbacks.begin(); i != callbacks.end(); i++) + (*i)->updateTime(); +} + +void AudioTimer::calculateBlock(unsigned long s) +{ + samples += s; + while(samples > samplingRate) + { + samples -= samplingRate; + seconds++; + } + Notification n; + n.receiver = this; + n.ID = 0; + n.data = 0; + n.internal = 0; + NotificationManager::the()->send(n); +} + +AudioTimer *AudioTimer::subscribe() +{ + if(!AudioTimer_the) + { + new AudioTimer(); + AudioTimer_the->_node()->start(); + } + else + { + AudioTimer_the->_copy(); + } + return AudioTimer_the; +} + +void AudioTimer::addCallback(AudioTimerCallback *callback) +{ + callbacks.push_back(callback); +} + +void AudioTimer::removeCallback(AudioTimerCallback *callback) +{ + callbacks.remove(callback); +} diff --git a/arts/midi/audiotimer.h b/arts/midi/audiotimer.h new file mode 100644 index 00000000..f0299cf3 --- /dev/null +++ b/arts/midi/audiotimer.h @@ -0,0 +1,73 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ +#ifndef AUDIOTIMER_H +#define AUDIOTIMER_H + +#include "artsmidi.h" +#include "artsflow.h" +#include "stdsynthmodule.h" +#include "miditimercommon.h" +#include "flowsystem.h" + +namespace Arts { + +/** + * EXPERIMENTAL (this interface will probably not stay this way) way to handle + * audio based time stamps. Callback interface. + */ +class AudioTimerCallback { +public: + virtual void updateTime() = 0; +}; + +/** + * EXPERIMENTAL (this interface will probably not stay this way) way to handle + * audio based time stamps. Timer class for audio timestamps. + */ +class AudioTimer : public SynthModule_skel, public StdSynthModule + /* we are a SynthModule to get the timing */ +{ +protected: + AudioTimer(); + virtual ~AudioTimer(); + + std::list<AudioTimerCallback *> callbacks; + + long samples; /* at most samplingRate, overflow goes in seconds */ + long seconds; + +public: + // allocation: share one AudioTimer for everbody who needs one + static AudioTimer *subscribe(); + void unsubscribe() { _release(); } + + void notify(const Notification& n); + TimeStamp time(); + void calculateBlock(unsigned long samples); + + void addCallback(AudioTimerCallback *callback); + void removeCallback(AudioTimerCallback *callback); +}; + +} + +#endif diff --git a/arts/midi/mcopclass/Makefile.am b/arts/midi/mcopclass/Makefile.am new file mode 100644 index 00000000..5fbeafe8 --- /dev/null +++ b/arts/midi/mcopclass/Makefile.am @@ -0,0 +1,2 @@ +mcopclassdir = $(libdir)/mcop/Arts +mcopclass_DATA = MidiManager.mcopclass diff --git a/arts/midi/mcopclass/MidiManager.mcopclass b/arts/midi/mcopclass/MidiManager.mcopclass new file mode 100644 index 00000000..a86af4a4 --- /dev/null +++ b/arts/midi/mcopclass/MidiManager.mcopclass @@ -0,0 +1,3 @@ +Interface=Arts::MidiManager,Arts::Object +Language=C++ +Library=libartsmidi.la diff --git a/arts/midi/midiclient_impl.cc b/arts/midi/midiclient_impl.cc new file mode 100644 index 00000000..d57e7fd7 --- /dev/null +++ b/arts/midi/midiclient_impl.cc @@ -0,0 +1,274 @@ + /* + + Copyright (C) 2000-2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "midiclient_impl.h" +#include "midimanager_impl.h" +#include "midimanagerport_impl.h" +#include "midisyncgroup_impl.h" +#include "timestampmath.h" + +#undef DEBUG_SYNC_DRIFT + +using namespace Arts; +using namespace std; + +MidiClient_impl::MidiClient_impl(const MidiClientInfo& info, + MidiManager_impl *manager) :_info(info), manager(manager), syncGroup(0) +{ +} + +MidiClient_impl::~MidiClient_impl() +{ + while(!_info.connections.empty()) + disconnect(manager->findClient(_info.connections[0])); + + if(syncGroup) + { + syncGroup->clientDied(this); + syncGroup = 0; + } + manager->removeClient(this); +} + +MidiClientInfo MidiClient_impl::info() +{ + return _info; +} + +void MidiClient_impl::title(const string &newvalue) +{ + _info.title = newvalue; +} + +string MidiClient_impl::title() +{ + return _info.title; +} + +void MidiClient_impl::addInputPort(MidiPort port) +{ + assert(_info.direction == mcdRecord); + + ports.push_back(port); + + // FIXME: should we synchronize inputPorts at all + rebuildConnections(); +} + +MidiPort MidiClient_impl::addOutputPort() +{ + assert(_info.direction == mcdPlay); + + MidiPort port = MidiPort::_from_base(new MidiManagerPort_impl(this)); + ports.push_back(port); + + rebuildConnections(); + return port; +} + +void MidiClient_impl::removePort(MidiPort port) +{ + list<MidiPort>::iterator i = ports.begin(); + while(i != ports.end()) + { + if (i->_isEqual(port)) + i = ports.erase(i); + else + i++; + } + + rebuildConnections(); +} + +void MidiClient_impl::rebuildConnections() +{ + _connections.clear(); + + vector<long>::iterator li; + for(li = _info.connections.begin(); li != _info.connections.end(); li++) + { + MidiClient_impl *other = manager->findClient(*li); + assert(other); + + list<MidiPort>::iterator pi; + for(pi = other->ports.begin(); pi != other->ports.end(); pi++) + { + MidiClientConnection mcc; + mcc.offset = TimeStamp(0,0); + mcc.port = *pi; + _connections.push_back(mcc); + } + } + adjustSync(); +} + +list<MidiClientConnection> *MidiClient_impl::connections() +{ + return &_connections; +} + +static void removeElement(vector<long>& vec, long el) +{ + vector<long> tmp; + vec.swap(tmp); + vector<long>::iterator i; + for(i = tmp.begin(); i != tmp.end(); i++) + if(*i != el) vec.push_back(*i); +} + +void MidiClient_impl::connect(MidiClient_impl *dest) +{ + assert(_info.direction != dest->_info.direction); + + disconnect(dest); + + _info.connections.push_back(dest->ID()); + dest->_info.connections.push_back(ID()); + + list<MidiPort>::iterator pi; + + /* add the other clients ports to our connection list */ + for(pi = dest->ports.begin(); pi != dest->ports.end(); pi++) + { + MidiClientConnection mcc; + mcc.offset = TimeStamp(0,0); + mcc.port = *pi; + _connections.push_back(mcc); + } + + /* add our ports to the other clients connection list */ + for(pi = ports.begin(); pi != ports.end(); pi++) + { + MidiClientConnection mcc; + mcc.offset = TimeStamp(0,0); + mcc.port = *pi; + dest->_connections.push_back(mcc); + } + adjustSync(); +} + +void MidiClient_impl::disconnect(MidiClient_impl *dest) +{ + assert(_info.direction != dest->_info.direction); + + removeElement(_info.connections,dest->ID()); + removeElement(dest->_info.connections,ID()); + + list<MidiPort>::iterator pi; + + /* remove the other clients ports from our connection list */ + for(pi = dest->ports.begin(); pi != dest->ports.end(); pi++) + { + list<MidiClientConnection>::iterator ci = _connections.begin(); + + while(ci != _connections.end()) + { + if(ci->port._isEqual(*pi)) + ci = _connections.erase(ci); + else + ci++; + } + } + + /* remove our ports from the other clients connection list */ + for(pi = ports.begin(); pi != ports.end(); pi++) + { + list<MidiClientConnection>::iterator ci = dest->_connections.begin(); + + while(ci != dest->_connections.end()) + { + if(ci->port._isEqual(*pi)) + ci = dest->_connections.erase(ci); + else + ci++; + } + } + adjustSync(); +} + +void MidiClient_impl::synchronizeTo(const TimeStamp& time) +{ + list<MidiClientConnection>::iterator i; + + for(i = _connections.begin(); i != _connections.end(); i++) + { + MidiClientConnection& mcc = *i; + +#ifdef DEBUG_SYNC_DRIFT + TimeStamp drift = mcc.offset; // debug drift +#endif + + mcc.offset = mcc.port.playTime(); + timeStampDec(mcc.offset, time); + +#ifdef DEBUG_SYNC_DRIFT + timeStampDec(drift,mcc.offset); // debug drift + printf("SYNC DRIFT %30s %30s: %f\n", + mcc.port._interfaceName().c_str(), _info.title.c_str(), + timeStampToDouble(drift)); +#endif + } +} + +void MidiClient_impl::setSyncGroup(MidiSyncGroup_impl *newSyncGroup) +{ + syncGroup = newSyncGroup; +} + +void MidiClient_impl::adjustSync() +{ + if(syncGroup) + syncGroup->clientChanged(this); + else + synchronizeTo(systemMidiTimer.time()); +} + +TimeStamp MidiClient_impl::time() +{ + if(syncGroup) + return syncGroup->time(); + else + return clientTime(); +} + +TimeStamp MidiClient_impl::playTime() +{ + if(syncGroup) + return syncGroup->playTime(); + else + return systemMidiTimer.time(); +} + +TimeStamp MidiClient_impl::clientTime() +{ + TimeStamp result = playTime(); + + list<MidiClientConnection>::iterator i; + for(i = _connections.begin(); i != _connections.end(); i++) + { + TimeStamp time = i->port.time(); + timeStampDec(time, i->offset); + result = timeStampMax(result, time); + } + + return result; +} diff --git a/arts/midi/midiclient_impl.h b/arts/midi/midiclient_impl.h new file mode 100644 index 00000000..b1aeb204 --- /dev/null +++ b/arts/midi/midiclient_impl.h @@ -0,0 +1,80 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_MIDICLIENT_IMPL_H +#define ARTS_MIDICLIENT_IMPL_H + +#include "artsmidi.h" +#include "midimanager_impl.h" +#include "midimanagerport_impl.h" + +namespace Arts { + +struct MidiClientConnection +{ + TimeStamp offset; + MidiPort port; +}; + +class MidiManager_impl; +class MidiSyncGroup_impl; +class MidiClient_impl : virtual public MidiClient_skel +{ +protected: + SystemMidiTimer systemMidiTimer; + MidiClientInfo _info; + MidiManager_impl *manager; + MidiSyncGroup_impl *syncGroup; + std::list<MidiPort> ports; + std::list<MidiClientConnection> _connections; + +public: + MidiClient_impl(const MidiClientInfo& info, MidiManager_impl *manager); + ~MidiClient_impl(); + + // MCOP interface + MidiClientInfo info(); + void title(const std::string &newvalue); + std::string title(); + void addInputPort(MidiPort port); + MidiPort addOutputPort(); + void removePort(MidiPort port); + + // interface to MidiManager/Port + inline long ID() { return _info.ID; } + std::list<MidiClientConnection> *connections(); + void connect(MidiClient_impl *dest); + void disconnect(MidiClient_impl *dest); + void rebuildConnections(); + void adjustSync(); + + TimeStamp time(); + TimeStamp playTime(); + + // interface to MidiSyncGroup + void synchronizeTo(const TimeStamp& time); + void setSyncGroup(MidiSyncGroup_impl *syncGroup); + TimeStamp clientTime(); +}; + +} +#endif /* ARTS_MIDICLIENT_IMPL_H */ diff --git a/arts/midi/midimanager_impl.cc b/arts/midi/midimanager_impl.cc new file mode 100644 index 00000000..1ba07962 --- /dev/null +++ b/arts/midi/midimanager_impl.cc @@ -0,0 +1,156 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "midimanager_impl.h" +#include "midiclient_impl.h" +#include "midisyncgroup_impl.h" +#include "debug.h" + +using namespace Arts; +using namespace std; + +static int cleanReference(const string& reference) +{ + Object test = Reference("global:"+reference); + if(test.isNull()) + { + Dispatcher::the()->globalComm().erase(reference); + return 1; + } + else + return 0; +} + +MidiManager_impl::MidiManager_impl() : nextID(1) +{ + cleanReference("Arts_MidiManager"); + if(!ObjectManager::the()->addGlobalReference(Object::_from_base(_copy()), + "Arts_MidiManager")) + { + arts_warning("can't register Arts::MidiManager"); + } + else + { + arts_debug("Arts::MidiManager registered successfully."); + } + Dispatcher::the()->ioManager()->addTimer(1000, this); +} + +MidiManager_impl::~MidiManager_impl() +{ + Dispatcher::the()->ioManager()->removeTimer(this); +} + +vector<MidiClientInfo> *MidiManager_impl::clients() +{ + if(!alsaMidiGateway.isNull()) + { + if(!alsaMidiGateway.rescan()) + alsaMidiGateway = AlsaMidiGateway::null(); + } + + vector<MidiClientInfo> *result = new vector<MidiClientInfo>; + + list<MidiClient_impl *>::iterator i; + for(i = _clients.begin(); i != _clients.end(); i++) + result->push_back((*i)->info()); + + return result; +} + +MidiClient MidiManager_impl::addClient(MidiClientDirection direction, + MidiClientType type, const string& title, const string& autoRestoreID) +{ + MidiClientInfo info; + + info.ID = nextID++; + info.direction = direction; + info.type = type; + info.title = title; + info.autoRestoreID = autoRestoreID; + + MidiClient_impl *impl = new MidiClient_impl(info, this); + _clients.push_back(impl); + return MidiClient::_from_base(impl); +} + +void MidiManager_impl::removeClient(MidiClient_impl *client) +{ + _clients.remove(client); +} + +MidiClient_impl *MidiManager_impl::findClient(long clientID) +{ + list<MidiClient_impl *>::iterator i; + + for(i = _clients.begin(); i != _clients.end(); i++) + { + if((*i)->ID() == clientID) + return (*i); + } + return 0; +} + +void MidiManager_impl::connect(long clientID, long destinationID) +{ + MidiClient_impl *src = findClient(clientID); + MidiClient_impl *dest = findClient(destinationID); + + arts_return_if_fail(src); + arts_return_if_fail(dest); + src->connect(dest); +} + +void MidiManager_impl::disconnect(long clientID, long destinationID) +{ + MidiClient_impl *src = findClient(clientID); + MidiClient_impl *dest = findClient(destinationID); + + arts_return_if_fail(src); + arts_return_if_fail(dest); + src->disconnect(dest); +} + +MidiSyncGroup MidiManager_impl::addSyncGroup() +{ + MidiSyncGroup_impl *impl = new MidiSyncGroup_impl(this); + syncGroups.push_back(impl); + return MidiSyncGroup::_from_base(impl); +} + +void MidiManager_impl::removeSyncGroup(MidiSyncGroup_impl *group) +{ + syncGroups.remove(group); +} + +void MidiManager_impl::notifyTime() +{ + list<MidiClient_impl *>::iterator i; + for(i = _clients.begin(); i != _clients.end(); i++) + (*i)->adjustSync(); + + list<MidiSyncGroup_impl *>::iterator gi; + for(gi = syncGroups.begin(); gi != syncGroups.end(); gi++) + (*gi)->adjustSync(); +} + +namespace Arts { REGISTER_IMPLEMENTATION(MidiManager_impl); } diff --git a/arts/midi/midimanager_impl.h b/arts/midi/midimanager_impl.h new file mode 100644 index 00000000..0c9ec5df --- /dev/null +++ b/arts/midi/midimanager_impl.h @@ -0,0 +1,67 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_MIDIMANAGER_IMPL_H +#define ARTS_MIDIMANAGER_IMPL_H + +#include "artsmidi.h" + +namespace Arts { + +class MidiClient_impl; +class MidiSyncGroup_impl; +class MidiManager_impl : virtual public MidiManager_skel, + virtual public TimeNotify +{ +protected: + std::list<MidiClient_impl *> _clients; + std::list<MidiSyncGroup_impl *> syncGroups; + + long nextID; + AlsaMidiGateway alsaMidiGateway; + + void notifyTime(); + +public: + MidiManager_impl(); + ~MidiManager_impl(); + + // public interface + std::vector<MidiClientInfo> *clients(); + + MidiClient addClient(MidiClientDirection direction, MidiClientType type, + const std::string& title, const std::string& autoRestoreID); + + void connect(long clientID, long destinationID); + void disconnect(long clientID, long destinationID); + MidiSyncGroup addSyncGroup(); + + // interface to MidiClient_impl + void removeClient(MidiClient_impl *client); + MidiClient_impl *findClient(long clientID); + + // interface to MidiSyncGroup_impl + void removeSyncGroup(MidiSyncGroup_impl *group); +}; + +} +#endif /* ARTS_MIDIMANAGER_IMPL_H */ diff --git a/arts/midi/midimanagerport_impl.cc b/arts/midi/midimanagerport_impl.cc new file mode 100644 index 00000000..a6973a51 --- /dev/null +++ b/arts/midi/midimanagerport_impl.cc @@ -0,0 +1,71 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "midimanagerport_impl.h" +#include "midimanager_impl.h" +#include "midiclient_impl.h" +#include "timestampmath.h" + +#include <stdio.h> + +using namespace Arts; +using namespace std; + +MidiManagerPort_impl::MidiManagerPort_impl(MidiClient_impl *client) + : client(client) +{ +} + +TimeStamp MidiManagerPort_impl::time() +{ + return client->time(); +} + +TimeStamp MidiManagerPort_impl::playTime() +{ + return client->playTime(); +} + + +void MidiManagerPort_impl::processCommand(const MidiCommand& command) +{ + list<MidiClientConnection> *connections = client->connections(); + list<MidiClientConnection>::iterator i; + + for(i = connections->begin(); i != connections->end(); i++) + i->port.processCommand(command); +} + +void MidiManagerPort_impl::processEvent(const MidiEvent& event) +{ + list<MidiClientConnection> *connections = client->connections(); + list<MidiClientConnection>::iterator i; + + for(i = connections->begin(); i != connections->end(); i++) + { + /* relocate the event to the synchronized time */ + TimeStamp time = event.time; + timeStampInc(time, i->offset); + + i->port.processEvent(MidiEvent(time, event.command)); + } +} diff --git a/arts/midi/midimanagerport_impl.h b/arts/midi/midimanagerport_impl.h new file mode 100644 index 00000000..56998546 --- /dev/null +++ b/arts/midi/midimanagerport_impl.h @@ -0,0 +1,46 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef MIDIMANAGERPORT_IMPL_H +#define MIDIMANAGERPORT_IMPL_H + +#include "artsmidi.h" + +namespace Arts { + +class MidiClient_impl; + +class MidiManagerPort_impl : public MidiPort_skel +{ + MidiClient_impl *client; + SystemMidiTimer timer; +public: + MidiManagerPort_impl(MidiClient_impl *client); + TimeStamp time(); + TimeStamp playTime(); + void processCommand(const MidiCommand& command); + void processEvent(const MidiEvent& event); +}; + +} + +#endif /* MIDIMANAGERPORT_IMPL_H */ diff --git a/arts/midi/midimsg.c b/arts/midi/midimsg.c new file mode 100644 index 00000000..7032fba8 --- /dev/null +++ b/arts/midi/midimsg.c @@ -0,0 +1,177 @@ + +#include <stdio.h> +#include <stdlib.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#include "midimsg.h" + +#define STATUS_MASK 0x80 +#define MESSAGE_TYPE_MASK 0xf0 +#define CHANNEL_MASK 0x0f + +Byte midiReadByte(int fd) +{ + Byte current_byte; + /* + ** for now ignore all realtime-messages (0xF8 .. 0xFF), which may get + ** embedded *inside* every other message + */ + do { + /* + ** read() is wrapped in a while() in order to handle interruption + ** by a signal. In the normal case, read() is only called once. + */ + while (read(/* fd = */ fd, /* buf = */ (void *)(¤t_byte), + /* count = */ 1) < 1) {} + + } while(current_byte >= 0xf8); + + return current_byte; +} + +int midimsgGetMessageType(Byte *message) +{ + return (message[0] & MESSAGE_TYPE_MASK); +} + +void midimsgSetMessageType(Byte *message_inout, int message_type) +{ + message_inout[0] = (message_inout[0] & ~MESSAGE_TYPE_MASK) | message_type; +} + +int midimsgGetChannel(Byte *message) +{ + return (message[0] & CHANNEL_MASK); +} + +void midimsgSetChannel(Byte *message_inout, int channel) +{ + message_inout[0] = (message_inout[0] & ~CHANNEL_MASK) | channel; +} + +int midimsgGetPitch(Byte *message) +{ + return (message[1]); +} + +void midimsgSetPitch(Byte *message_inout, int pitch) +{ + message_inout[1] = pitch; +} + +int midimsgGetVelocity(Byte *message) +{ + return (message[2]); +} + +void midimsgSetVelocity(Byte *message_inout, int velocity) +{ + message_inout[2] = velocity; +} + +int midimsgGetParameterNumber(Byte *message) +{ + return (message[1]); +} + +void midimsgSetParameterNumber(Byte *message_inout, int number) +{ + message_inout[1] = number; +} + +int midimsgGetParameterValue(Byte *message) +{ + return (message[2]); +} + +void midimsgSetParameterValue(Byte *message_inout, int value) +{ + message_inout[2] = value; +} + +int midimsgGetPitchWheelValue(Byte *message) +{ + return (((int)(message[2]) << 7) + message[1]); +} + +void midimsgRead(int fd, Byte *message_return) +{ + Byte current_byte; + static Byte last_status_byte; + + current_byte = midiReadByte(fd); + + if ((current_byte & STATUS_MASK) == 0) + { + /* + ** must be a running status, unless we're picking up mid-message + ** (which would be an unhandled error) + */ + + message_return[0] = last_status_byte; + } + else + { + message_return[0] = current_byte; + last_status_byte = current_byte; + + current_byte = midiReadByte(fd); + } + + switch (midimsgGetMessageType(/* message = */ message_return)) + { + case MIDIMSG_NOTE_ON: + case MIDIMSG_NOTE_OFF: + case MIDIMSG_KEY_PRESSURE: + case MIDIMSG_PARAMETER: + case MIDIMSG_PITCH_WHEEL: + + message_return[1] = current_byte; + + current_byte = midiReadByte(fd); + message_return[2] = current_byte; + + if ((midimsgGetMessageType(/* message = */ message_return) == + MIDIMSG_NOTE_ON) && (midimsgGetVelocity(/* message = */ + message_return) == 0)) + { + /* note-on with velocity of zero is equivalent to note-off */ + + midimsgSetMessageType(/* message_inout = */ message_return, + /* message_type = */ MIDIMSG_NOTE_OFF); + } + + return; + + case MIDIMSG_PROGRAM: + case MIDIMSG_CHANNEL_PRESSURE: + + message_return[1] = current_byte; + + return; + } +} + +void midimsgWrite(int fd, Byte *message) +{ + switch (midimsgGetMessageType(/* message = */ message)) + { + case MIDIMSG_NOTE_ON: + case MIDIMSG_NOTE_OFF: + case MIDIMSG_KEY_PRESSURE: + case MIDIMSG_PARAMETER: + case MIDIMSG_PITCH_WHEEL: + write(fd, message, 3); + break; + case MIDIMSG_PROGRAM: + case MIDIMSG_CHANNEL_PRESSURE: + write(fd, message, 2); + break; + } +} + diff --git a/arts/midi/midimsg.h b/arts/midi/midimsg.h new file mode 100644 index 00000000..3bba90d7 --- /dev/null +++ b/arts/midi/midimsg.h @@ -0,0 +1,44 @@ + +#ifndef MIDIMSG_H +#define MIDIMSG_H + +typedef unsigned char Byte; + +#define MIDIMSG_NOTE_OFF 0x80 +#define MIDIMSG_NOTE_ON 0x90 +#define MIDIMSG_KEY_PRESSURE 0xa0 +#define MIDIMSG_PARAMETER 0xb0 +#define MIDIMSG_PROGRAM 0xc0 +#define MIDIMSG_CHANNEL_PRESSURE 0xd0 +#define MIDIMSG_PITCH_WHEEL 0xe0 + +#ifndef True +#define True 1 +#define False 0 +#endif + +int midimsgGetMessageType(Byte *message); +void midimsgSetMessageType(Byte *message_inout, int message_type); + +int midimsgGetChannel(Byte *message); +void midimsgSetChannel(Byte *message_inout, int channel); + +int midimsgGetPitch(Byte *message); +void midimsgSetPitch(Byte *message_inout, int pitch); + +int midimsgGetVelocity(Byte *message); +void midimsgSetVelocity(Byte *message_inout, int velocity); + +int midimsgGetParameterNumber(Byte *message); +void midimsgSetParameterNumber(Byte *message_inout, int number); + +int midimsgGetParameterValue(Byte *message); +void midimsgSetParameterValue(Byte *message_inout, int value); + +int midimsgGetPitchWheelValue(Byte *message); + +void midimsgRead(int fd, Byte *message_return); +void midimsgWrite(int fd, Byte *message_return); + +#endif + diff --git a/arts/midi/midisend.cc b/arts/midi/midisend.cc new file mode 100644 index 00000000..2c1878ef --- /dev/null +++ b/arts/midi/midisend.cc @@ -0,0 +1,375 @@ +/* + +Copyright (C) 1999 Emmeran Seehuber + the_emmy@gmx.de + +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. + +*/ + +/* + Changes: + 16.09.1999 Emmeran "Emmy" Seehuber <the_emmy@gmx.de> + - Implementeted mapping of channels and pitches. + - Reworked option parsing, now using getopt(). + Note: The parameters of the programms have changed ! +*/ + +/* +** This program was in original by David G. Slomin. +** It was released to the Public Domain on 1/25/99. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "midisend.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +using namespace std; + +int input_fd = -1, test = 0, verbose = 0; +char cFileName[1025]; +int optch; +CMidiMap Map; + +void usage(char *prog) +{ + fprintf(stderr,"\n"); + fprintf(stderr,"Usage: %s [ -f <mididevice> ] [ -m <mapfile> ] [ -v ] [ -t <loop> ]\n",prog); + fprintf(stderr," -f the mididevice to read the input from.\n"); + fprintf(stderr," Default is /dev/midi. If you specify a dash, it is stdin\n"); + fprintf(stderr," -m the mapfile to load.\n"); + fprintf(stderr," -v verbose output.\n"); + fprintf(stderr," -t test mode. Generates a testoutput on the midibus\n"); + fprintf(stderr," -l long test mode. Generates a testoutput on the midibus\n"); + exit(1); +} + +void parseArgs(int argc, char** argv) +{ + // Setup default + strcpy(cFileName,"/dev/midi"); + + while((optch = getopt(argc,argv,"m:f:vtl")) > 0) + { + switch(optch) + { + case 'm': if( !Map.readMap(optarg) ) + fprintf(stderr,"%s: can't read file %s!\n",argv[0],optarg); + break; + case 't': test = 1; + break; + case 'l': test = 2; + break; +#ifdef VERSION + case 'v': verbose = 1; printf("MidiSend %s\n", VERSION ); + break; +#endif + case 'f': strncpy(cFileName,optarg,1024); + break; + default: usage(argv[0]); + break; + } + } +} + + +#ifdef COMMON_BINARY +int midisend_main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + Arts::Dispatcher dispatcher; + Arts::MidiManager manager = Arts::Reference("global:Arts_MidiManager"); + + if (manager.isNull()) + { + fprintf(stderr, "%s trouble: No midimanager object found; please start " + "artsd.\n",argv[0]); + exit(EXIT_FAILURE); + } + + /* + ** MIDI input initialization. + */ + + parseArgs(argc,argv); + + string title = string("midisend (") + cFileName +")"; + Arts::MidiClient client + = manager.addClient(Arts::mcdPlay,Arts::mctApplication,title,"midisend"); + Arts::MidiPort port = client.addOutputPort(); + + if(test) + { + if( verbose ) + printf("performing test ...\n"); + unsigned long i,max=5000; + if(test==2) max = 20000; + for(i=0;i<max;i++) + { + port.processCommand( + Arts::MidiCommand(Arts::mcsNoteOn, 60+(i%12), 100)); + port.processCommand( + Arts::MidiCommand(Arts::mcsNoteOff,60+(i%12), 0)); + } + exit(0); + } + + if( verbose ) + printf("trying to open %s ...", cFileName ); + + input_fd = open(cFileName,O_RDONLY); + + if(input_fd == -1) + { + fprintf(stderr,"\n%s trouble: can't open input device!\n",argv[0]); + exit(EXIT_FAILURE); + } + else if( verbose ) + printf(" ok!\n"); + + + /* + ** Main loop. + */ + + if(verbose) + printf("beginning loop ...\n"); + + unsigned char msg[3]; + + while (1) + { + midimsgRead(input_fd, msg); + switch (midimsgGetMessageType(msg)) + { + case MIDIMSG_NOTE_OFF: + Map.mapMsg(msg); + port.processCommand( + Arts::MidiCommand(Arts::mcsNoteOff|midimsgGetChannel(msg), + midimsgGetPitch(msg), midimsgGetVelocity(msg))); + if( verbose ) + printf("NoteOff: Channel %d, Pitch %3d\n", midimsgGetChannel(msg),midimsgGetPitch(msg)); + break; + case MIDIMSG_NOTE_ON: + Map.mapMsg(msg); + port.processCommand( + Arts::MidiCommand(Arts::mcsNoteOn|midimsgGetChannel(msg), + midimsgGetPitch(msg), midimsgGetVelocity(msg))); + if( verbose ) + printf("NoteOn : Channel %d, Pitch %3d, Velocity %2d\n", midimsgGetChannel(msg), + midimsgGetPitch(msg),midimsgGetVelocity(msg)); + break; + case MIDIMSG_PITCH_WHEEL: + Map.mapMsg(msg); + port.processCommand( + Arts::MidiCommand(Arts::mcsPitchWheel|midimsgGetChannel(msg), + midimsgGetPitch(msg), midimsgGetVelocity(msg))); + if( verbose ) + printf("PitchWheel : Channel %d, LSB %3d MSB %3d\n", midimsgGetChannel(msg), + midimsgGetPitch(msg),midimsgGetVelocity(msg)); + break; + } + } +} + +//-------------------------------------------- +// The mapping stuff +//-------------------------------------------- + +bool CMidiMap::readMap(const char* pszFileName) +{ + if( verbose ) + printf("reading mapfile %s ...\n", pszFileName); + + FILE *file = fopen(pszFileName,"r"); + if( !file ) + return false; + + char cBuffer[1024+1]; + char* pszLine; + int nLine = 0; + while( (pszLine = fgets(cBuffer,1024,file)) ) { + nLine++; + parseLine(pszLine,pszFileName,nLine); + } + fclose(file); + + return true; +} + +bool CMidiMap::getNextWord(char*& pszLine, char*& pszWord) +{ + // First skip all leading blanks, etc. + bool bCont = true; + while(bCont) { + char cHelp = *pszLine; + switch( cHelp ) + { + case 0: return false; // Out of line + case ' ': + case '\n': + case '\r': + case '\t': pszLine++; break; // Goto next char + default: bCont = false; break; // NonSpace character -> Word begins here. + } + } + + // The word starts here + pszWord = pszLine; + + // And now, goto the end of the word. + bCont = true; + while(bCont) { + char cHelp = *pszLine; + switch( cHelp ) + { + case 0: + case ',': + case ';': + case ' ': + case '\n': + case '\r': + case '\t': *pszLine++ = 0; bCont = false; break; // Goto next char + default: pszLine++; break; + } + } + + return true; +} + +void CMidiMap::parseLine(char* pszLine, const char* pszConfigFile, int nConfigLine ) +{ + char* pszWord = 0; + bool bOk = true; + + // Get first word of the line + bOk = bOk && getNextWord(pszLine,pszWord); + if( !bOk ) + return; + + // Skip comments + if( *pszWord == '#' ) + return; + + if( strcmp(pszWord,"PRC") == 0 ) { + bOk = bOk && getNextWord(pszLine,pszWord); + int nOrigChannel = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nStart = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nEnd = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nChannel = atol(pszWord); + if( bOk ) { + channelMaps[nOrigChannel].nChannel = nOrigChannel; + for( int i = nStart; i <= nEnd; i++ ) { + channelMaps[nOrigChannel].channelRemaps[i].nPitch = i; + channelMaps[nOrigChannel].channelRemaps[i].nChannel = nChannel; + } + } + else { + printf("midisend: (PRC) missing parameters at %s:%d\n", pszConfigFile,nConfigLine); + } + return; + } + + if( strcmp(pszWord,"PRD") == 0 ) { + bOk = bOk && getNextWord(pszLine,pszWord); + int nOrigChannel = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nStart = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nEnd = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nPitchDiff = atol(pszWord); + if( bOk ) { + channelMaps[nOrigChannel].nChannel = nOrigChannel; + for( int i = nStart; i <= nEnd; i++ ) { + channelMaps[nOrigChannel].pitchRemaps[i].nPitch = i; + channelMaps[nOrigChannel].pitchRemaps[i].nToPitch = i + nPitchDiff; + } + } + else { + printf("midisend: (PRD) missing parameters at %s:%d\n", pszConfigFile,nConfigLine); + } + return; + } + + if( strcmp(pszWord,"PTC") == 0 ) { + bOk = bOk && getNextWord(pszLine,pszWord); + int nOrigChannel = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nPitch = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nToChannel = atol(pszWord); + if( bOk ) { + channelMaps[nOrigChannel].nChannel = nOrigChannel; + channelMaps[nOrigChannel].channelRemaps[nPitch].nPitch = nPitch; + channelMaps[nOrigChannel].channelRemaps[nPitch].nChannel = nToChannel; + } + else { + printf("midisend: (PTC) missing parameters at %s:%d\n", pszConfigFile,nConfigLine); + } + return; + } + + if( strcmp(pszWord,"PTP") == 0 ) { + bOk = bOk && getNextWord(pszLine,pszWord); + int nOrigChannel = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nPitch = atol(pszWord); + bOk = bOk && getNextWord(pszLine,pszWord); + int nToPitch = atol(pszWord); + if( bOk ) { + channelMaps[nOrigChannel].nChannel = nOrigChannel; + channelMaps[nOrigChannel].pitchRemaps[nPitch].nPitch = nPitch; + channelMaps[nOrigChannel].pitchRemaps[nPitch].nToPitch = nToPitch; + } + else { + printf("midisend: (PTP) missing parameters at %s:%d\n", pszConfigFile,nConfigLine); + } + return; + } + + printf("midisend: Unknown command at %s:%d\n", pszConfigFile,nConfigLine); +} + +void CMidiMap::mapMsg(Byte* msg) +{ + // Get out the data for mapping + int nChannel = midimsgGetChannel(msg); + int nPitch = midimsgGetPitch(msg); + + // Is there something to map for this channel ? + if( channelMaps.find(nChannel) != channelMaps.end() ) { + // => Yes, than do it. + + // C/P => C + if( channelMaps[nChannel].channelRemaps.find(nPitch) != channelMaps[nChannel].channelRemaps.end() ) + midimsgSetChannel(msg,channelMaps[nChannel].channelRemaps[nPitch].nChannel); + + // C/P => P + if( channelMaps[nChannel].pitchRemaps.find(nPitch) != channelMaps[nChannel].pitchRemaps.end() ) { + midimsgSetPitch(msg,channelMaps[nChannel].pitchRemaps[nPitch].nToPitch); + } + } +} diff --git a/arts/midi/midisend.h b/arts/midi/midisend.h new file mode 100644 index 00000000..817ebfa2 --- /dev/null +++ b/arts/midi/midisend.h @@ -0,0 +1,106 @@ +/* + +Copyright (C) 1999 Emmeran Seehuber + the_emmy@gmx.de + +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. + +*/ +#ifndef MIDISEND_H +#define MIDISEND_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include "artsmidi.h" +#include <vector> +#include <iostream> +#include <string> + +extern "C" +{ +#include "midimsg.h" +} + +/* + This class does the mapping of the + channels and pitches +*/ +class CMidiMap { +public: + /* + Reads in the mapfile pszFileName. + Returns TRUE, if successful. + */ + bool readMap(const char* pszFileName); + /* + Maps the given message according to + the actual read configuration. + */ + void mapMsg(Byte* msg); + +private: + /* + Parses a configuration line. + */ + void parseLine(char* pszLine, const char* pszConfigFile, int nConfigLine ); + + /* + Gets the next word out of the line. throws + the exception CEOutOfLine, if there is no + more word in the line. + + A word consists of all but \0, spacecharacter, ';' and + ',' + + pszLine is the pointer to the start of the + line. This function modifies the pointer. + pszWord is the pointer to the found word. + */ + bool getNextWord(char*& pszLine, char*& pszWord); +private: + + /* + For each channel one instance of this structure + exists in the channelMaps. It holds the mapping information + for nChannel. + */ + struct ChannelMaps { + int nChannel; + + struct ChannelRemap { + int nPitch; + int nChannel; + }; + + struct PitchRemap { + int nPitch; + int nToPitch; + }; + + typedef std::map<int,ChannelRemap> ChannelRemapMap; + ChannelRemapMap channelRemaps; + typedef std::map<int,PitchRemap> PitchRemapMap; + PitchRemapMap pitchRemaps; + }; + typedef std::map<int,ChannelMaps> ChannelMapsMap; + ChannelMapsMap channelMaps; +}; + + +#endif diff --git a/arts/midi/midisyncgroup_impl.cc b/arts/midi/midisyncgroup_impl.cc new file mode 100644 index 00000000..57ebca2c --- /dev/null +++ b/arts/midi/midisyncgroup_impl.cc @@ -0,0 +1,125 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "midisyncgroup_impl.h" +#include "audiosync_impl.h" +#include "midiclient_impl.h" +#include "midimanager_impl.h" +#include "timestampmath.h" + +using namespace Arts; +using namespace std; + +MidiSyncGroup_impl::MidiSyncGroup_impl(MidiManager_impl *manager) + : manager(manager) +{ +} + +MidiSyncGroup_impl::~MidiSyncGroup_impl() +{ + /* tell clients we're dead */ + list<MidiClient_impl *>::iterator i; + for(i = clients.begin(); i != clients.end(); i++) + (*i)->setSyncGroup(0); + + list<AudioSync_impl *>::iterator ai; + for(ai = audioSyncs.begin(); ai != audioSyncs.end(); ai++) + (*ai)->setSyncGroup(0); + + manager->removeSyncGroup(this); +} + +void MidiSyncGroup_impl::addClient(MidiClient client) +{ + /* add client to list */ + MidiClient_impl *impl = manager->findClient(client.info().ID); + impl->setSyncGroup(this); + clients.push_back(impl); + + impl->synchronizeTo(masterTimer.time()); +} + +void MidiSyncGroup_impl::removeClient(MidiClient client) +{ + /* remove client from the list */ + MidiClient_impl *impl = manager->findClient(client.info().ID); + impl->setSyncGroup(0); + clients.remove(impl); +} + +void MidiSyncGroup_impl::addAudioSync(AudioSync audioSync) +{ + AudioSync_impl *impl = AudioSync_impl::find(audioSync); + impl->setSyncGroup(this); + audioSyncs.push_back(impl); + + impl->synchronizeTo(masterTimer.time()); +} + +void MidiSyncGroup_impl::removeAudioSync(AudioSync audioSync) +{ + AudioSync_impl *impl = AudioSync_impl::find(audioSync); + impl->setSyncGroup(0); + audioSyncs.remove(impl); +} + +void MidiSyncGroup_impl::clientChanged(MidiClient_impl *client) +{ + client->synchronizeTo(masterTimer.time()); +} + +void MidiSyncGroup_impl::clientDied(MidiClient_impl *client) +{ + clients.remove(client); +} + +void MidiSyncGroup_impl::audioSyncDied(AudioSync_impl *audioSync) +{ + audioSyncs.remove(audioSync); +} + +TimeStamp MidiSyncGroup_impl::time() +{ + TimeStamp result = masterTimer.time(); + + list<MidiClient_impl *>::iterator i; + for(i = clients.begin(); i != clients.end(); i++) + result = timeStampMax(result, (*i)->clientTime()); + + list<AudioSync_impl *>::iterator ai; + for(ai = audioSyncs.begin(); ai != audioSyncs.end(); ai++) + result = timeStampMax(result, (*ai)->clientTime()); + + return result; +} + +TimeStamp MidiSyncGroup_impl::playTime() +{ + return masterTimer.time(); +} + +void MidiSyncGroup_impl::adjustSync() +{ + list<AudioSync_impl *>::iterator ai; + for(ai = audioSyncs.begin(); ai != audioSyncs.end(); ai++) + (*ai)->synchronizeTo(masterTimer.time()); +} diff --git a/arts/midi/midisyncgroup_impl.h b/arts/midi/midisyncgroup_impl.h new file mode 100644 index 00000000..2c2191ac --- /dev/null +++ b/arts/midi/midisyncgroup_impl.h @@ -0,0 +1,67 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_MIDISYNCGROUP_IMPL_H +#define ARTS_MIDISYNCGROUP_IMPL_H + +#include "artsmidi.h" + +namespace Arts { + +class MidiClient_impl; +class MidiManager_impl; +class AudioSync_impl; + +class MidiSyncGroup_impl : virtual public MidiSyncGroup_skel { +protected: + SystemMidiTimer masterTimer; + + MidiManager_impl *manager; + std::list<MidiClient_impl *> clients; + std::list<AudioSync_impl *> audioSyncs; + +public: + MidiSyncGroup_impl(MidiManager_impl *manager); + ~MidiSyncGroup_impl(); + + // public interface + void addClient(MidiClient client); + void removeClient(MidiClient client); + + void addAudioSync(AudioSync audioSync); + void removeAudioSync(AudioSync audioSync); + + // interface to MidiClient (AudioSync) + void clientChanged(MidiClient_impl *client); + void clientDied(MidiClient_impl *client); + void audioSyncDied(AudioSync_impl *audioSync); + + TimeStamp time(); + TimeStamp playTime(); + + // interface to MidiManager + void adjustSync(); +}; + +} + +#endif /* ARTS_MIDISYNCGROUP_IMPL_H */ diff --git a/arts/midi/midisynctest.cc b/arts/midi/midisynctest.cc new file mode 100644 index 00000000..4faa5279 --- /dev/null +++ b/arts/midi/midisynctest.cc @@ -0,0 +1,137 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "config.h" +#include "artsmidi.h" +#include "soundserver.h" +#include "timestampmath.h" +#include "debug.h" +#include <stdio.h> +#include <math.h> + +using namespace Arts; +using namespace std; + +int main() +{ + Dispatcher dispatcher; + + MidiManager midiManager = DynamicCast(Reference("global:Arts_MidiManager")); + if(midiManager.isNull()) + arts_fatal("midimanager is null"); + + SoundServer soundServer = DynamicCast(Reference("global:Arts_SoundServer")); + if(soundServer.isNull()) + arts_fatal("soundServer is null"); + + MidiSyncGroup syncGroup = midiManager.addSyncGroup(); + MidiClient client = midiManager.addClient(mcdPlay, mctApplication, "midisynctest", "midisynctest"); + syncGroup.addClient(client); + + MidiPort port = client.addOutputPort(); + + MidiClient client2 = midiManager.addClient(mcdPlay, mctApplication, "midisynctest2", "midisynctest2"); + + syncGroup.addClient(client2); + + MidiPort port2 = client2.addOutputPort(); + + /* setup audio synchronization */ + AudioSync audioSync; + audioSync = DynamicCast(soundServer.createObject("Arts::AudioSync")); + if(audioSync.isNull()) + arts_fatal("audioSync is null"); + + syncGroup.addAudioSync(audioSync); + + const int C = 60; + const int D = 62; + const int E = 64; + const int F = 65, f = F - 12; + const int G = 67, g = G - 12; + const int A = 69, a = A - 12; + const int H = 71, h = H - 12; + int np = 0; + int notes[] = { C,E,G,E,a,C,E,C,f,a,C,a,g,h,D,h,0 }; + + printf("connect port1 and port2 to two different ports in the artscontrol midi manager,\n" + "hit return"); + getchar(); + + TimeStamp t = port.time(); + timeStampInc(t,TimeStamp(0,100000)); + for(;;) + { + Synth_PLAY_WAV wav; + Synth_AMAN_PLAY sap; + + MidiEvent e; + + e = MidiEvent(t,MidiCommand(mcsNoteOn|0, notes[np], 100)); + + port.processEvent(e); + port2.processEvent(e); + + if((np & 1) == 0) + { + /* setup wave player */ + wav = DynamicCast(soundServer.createObject("Arts::Synth_PLAY_WAV")); + if(wav.isNull()) + arts_fatal("can't create Arts::Synth_PLAY_WAV"); + + sap = DynamicCast(soundServer.createObject("Arts::Synth_AMAN_PLAY")); + if(sap.isNull()) + arts_fatal("can't create Arts::Synth_AMAN_PLAY"); + + wav.filename("/opt/kde3/share/sounds/pop.wav"); + sap.title("midisynctest2"); + sap.autoRestoreID("midisynctest2"); + connect(wav,sap); + + audioSync.queueStart(wav); + audioSync.queueStart(sap); + audioSync.executeAt(t); + } + + timeStampInc(t,TimeStamp(0,100000)); + + e = MidiEvent(t,MidiCommand(mcsNoteOff|0, notes[np], 100)); + + port.processEvent(e); + port2.processEvent(e); + + if((np & 1) == 0) + { + audioSync.queueStop(wav); + audioSync.queueStop(sap); + audioSync.executeAt(t); + } + + timeStampInc(t,TimeStamp(0,400000)); + + while(port.time().sec < (t.sec - 2)) + usleep(100000); + + np++; + if(notes[np] == 0) np = 0; + } +} diff --git a/arts/midi/miditest_impl.cc b/arts/midi/miditest_impl.cc new file mode 100644 index 00000000..fe61e4c7 --- /dev/null +++ b/arts/midi/miditest_impl.cc @@ -0,0 +1,57 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "midimanager_impl.h" +#include <stdio.h> + +namespace Arts { + +class MidiTest_impl : virtual public MidiTest_skel { +public: + Arts::TimeStamp time() + { + return TimeStamp(0,0); + } + Arts::TimeStamp playTime() + { + return time(); + } + void processCommand(const MidiCommand& command) + { + if((command.status & mcsCommandMask) == mcsNoteOn) + { + mcopbyte ch = command.status & mcsChannelMask; + + printf("noteon ch = %d, note = %d, vel = %d\n", + ch,command.data1,command.data2); + } + } + void processEvent(const MidiEvent& event) + { + printf("At %ld.%06ld: ",event.time.sec,event.time.usec); + processCommand(event.command); + } +}; + +REGISTER_IMPLEMENTATION(MidiTest_impl); +} + diff --git a/arts/midi/miditimercommon.cc b/arts/midi/miditimercommon.cc new file mode 100644 index 00000000..205b72e5 --- /dev/null +++ b/arts/midi/miditimercommon.cc @@ -0,0 +1,72 @@ + /* + + Copyright (C) 2001 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" +#include "debug.h" +#include "miditimercommon.h" +#include "math.h" + +#undef DEBUG_JITTER + +using namespace std; +using namespace Arts; + +MidiTimerCommon::MidiTimerCommon() :refCount(0) +{ + refCount = 0; +} + +MidiTimerCommon::~MidiTimerCommon() +{ + arts_assert(refCount == 0); +} + +void MidiTimerCommon::processQueue() +{ + TimeStamp now = time(); + + list<TSNote>::iterator n = noteQueue.begin(); + while(n != noteQueue.end()) + { + TSNote& note = *n; + TimeStamp& noteTime = note.event.time; + + if( now.sec > noteTime.sec + || ((now.sec == noteTime.sec) && (now.usec > noteTime.usec))) + { +#ifdef DEBUG_JITTER + float jitter = (now.sec-noteTime.sec) * 1000.0; + jitter += (float)(now.usec-noteTime.usec) / 1000.0; + arts_debug("midi jitter: %f",jitter); +#endif + + note.port.processCommand(note.event.command); + n = noteQueue.erase(n); + } + else n++; + } +} + +void MidiTimerCommon::queueEvent(MidiPort port,const MidiEvent& event) +{ + noteQueue.push_back(TSNote(port, event)); +} diff --git a/arts/midi/miditimercommon.h b/arts/midi/miditimercommon.h new file mode 100644 index 00000000..5f4bb250 --- /dev/null +++ b/arts/midi/miditimercommon.h @@ -0,0 +1,59 @@ + /* + + Copyright (C) 2001 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_MIDI_MIDITIMERCOMMON_H +#define ARTS_MIDI_MIDITIMERCOMMON_H + +#include "artsmidi.h" + +namespace Arts { + +class MidiTimerCommon { +protected: + struct TSNote { + MidiPort port; + MidiEvent event; + + TSNote() { /* for some stl impls */ } + + TSNote(MidiPort port, const MidiEvent& event) : + port(port), event(event) + { + } + }; + std::list<TSNote> noteQueue; + + int refCount; + void processQueue(); + + MidiTimerCommon(); + virtual ~MidiTimerCommon(); + +public: + void unsubscribe() { if(--refCount == 0) delete this; } + + void queueEvent(MidiPort port, const MidiEvent& event); + virtual TimeStamp time() = 0; +}; + +} +#endif /* ARTS_MIDI_MIDITIMERCOMMON_H */ diff --git a/arts/midi/rawmidiport_impl.cc b/arts/midi/rawmidiport_impl.cc new file mode 100644 index 00000000..0c9fd340 --- /dev/null +++ b/arts/midi/rawmidiport_impl.cc @@ -0,0 +1,306 @@ + /* + + Copyright (C) 2000 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" +#include <iomanager.h> +#include <debug.h> +#include <fcntl.h> + +using namespace std; + +namespace Arts { + +class RawMidiPort_impl : virtual public RawMidiPort_skel, + virtual public IONotify +{ +protected: + int fd; + + string _device; + bool _input, _output; + bool _running; + mcopbyte laststatus; + queue<mcopbyte> inq; + MidiClient clientRecord, clientPlay; + MidiPort outputPort; + MidiManager manager; + + RawMidiPort self() { + return RawMidiPort::_from_base(_copy()); + } + SystemMidiTimer timer; + +public: + RawMidiPort_impl() + :_device("/dev/midi"), _input(true), _output(true), _running(false), + clientRecord(MidiClient::null()), clientPlay(MidiClient::null()), + outputPort(MidiPort::null()), + manager(Reference("global:Arts_MidiManager")) + { + } + Arts::TimeStamp time() + { + return timer.time(); + } + Arts::TimeStamp playTime() + { + return timer.time(); + } + + // attribute string running; + bool running() { return _running; } + void running(bool newrunning) { + if(_running == newrunning) return; + + if(newrunning) + open(); + else + close(); + + running_changed(_running); + } + + // attribute string device; + void device(const string& newdevice) + { + if(newdevice == _device) return; + + if(_running) + { + close(); + _device = newdevice; + open(); + } + else _device = newdevice; + + device_changed(newdevice); + } + string device() { return _device; } + + // attribute boolean input; + void input(bool newinput) + { + if(newinput == _input) return; + + if(_running) + { + close(); + _input = newinput; + open(); + } + else _input = newinput; + + input_changed(newinput); + } + bool input() { return _input; } + + // attribute boolean output; + void output(bool newoutput) + { + if(newoutput == _output) return; + + if(_running) + { + close(); + _output = newoutput; + open(); + } + else _output = newoutput; + + output_changed(newoutput); + } + bool output() { return _output; } + + bool open() { + arts_return_val_if_fail(_running == false, true); + arts_return_val_if_fail(_output || _input, false); + arts_return_val_if_fail(manager.isNull() == false, false); + laststatus = 0; + + int mode = O_NDELAY; + if(_input) + { + if(_output) + mode |= O_RDWR; + else + mode |= O_RDONLY; + } + else mode |= O_WRONLY; + + fd = ::open(_device.c_str(),mode); + if(fd != -1) + { + IOManager *iom = Dispatcher::the()->ioManager(); + if(_output) + iom->watchFD(fd,IOType::read,this); + + string name = "OSS Midi Port ("+_device+")"; + if(_input) + { + clientRecord = + manager.addClient(mcdRecord,mctDestination,name,name); + clientRecord.addInputPort(self()); + } + if(_output) + { + clientPlay = + manager.addClient(mcdPlay,mctDestination,name,name); + outputPort = clientPlay.addOutputPort(); + } + + _running = true; + running_changed(true); + } + return _running; + } + + void close() + { + arts_return_if_fail(_running == true); + + if(_input) + { + clientRecord.removePort(self()); + clientRecord = MidiClient::null(); + } + if(_output) + { + clientPlay.removePort(outputPort); + clientPlay = MidiClient::null(); + } + + Dispatcher::the()->ioManager()->remove(this,IOType::all); + ::close(fd); + } + + int midiMsgLen(mcopbyte status) + { + switch(status & mcsCommandMask) + { + case mcsNoteOn: + case mcsNoteOff: + case mcsKeyPressure: + case mcsParameter: + case mcsPitchWheel: + return 3; + break; + case mcsProgram: + case mcsChannelPressure: + return 2; + break; + } + return 0; + } + void notifyIO(int fd, int type) + { + arts_return_if_fail(_running); + assert(fd == this->fd); + + // convert iomanager notification types to audiosubsys notification + if(type & IOType::read) + { + mcopbyte buffer[1024]; + int count = read(fd,buffer,1024); + for(int i=0; i<count; i++) + { + /* + * for now ignore all realtime-messages (0xF8 .. 0xFF), + * which may get * embedded *inside* every other message + */ + if(buffer[i] < 0xf8) + inq.push(buffer[i]); + } + } + processMidi(); + } + + void processMidi() + { + for(;;) + { + // if we get a status byte, this is our new status + if(!inq.empty()) + { + if(inq.front() & 0x80) + { + laststatus = inq.front(); + inq.pop(); + } + } + + // try to read a midi message with our current status + // (this supports running status as well as normal messages) + int len = midiMsgLen(laststatus); + if(len) + { + if(len == 2) + { + if(inq.empty()) return; // need more input + + MidiCommand command; + command.status = laststatus; + command.data1 = inq.front(); inq.pop(); + command.data2 = 0; + outputPort.processCommand(command); + } + else if(len == 3) + { + if(inq.size() < 2) return; // need more input + + MidiCommand command; + command.status = laststatus; + command.data1 = inq.front(); inq.pop(); + command.data2 = inq.front(); inq.pop(); + outputPort.processCommand(command); + } + else + { + arts_assert(false); + } + } + else + { + if(inq.empty()) return; // need more input + + /* we are somewhat out of sync it seems -> read something + * away and hope we'll find a status byte */ + inq.pop(); + } + } + } + void processCommand(const MidiCommand& command) + { + char message[3] = { command.status, command.data1, command.data2 }; + + int len = midiMsgLen(command.status); + if(midiMsgLen(command.status)) + write(fd, message, len); + } + void processEvent(const MidiEvent& event) + { + timer.queueEvent(self(), event); + } +}; + +REGISTER_IMPLEMENTATION(RawMidiPort_impl); +} + diff --git a/arts/midi/systemmiditimer_impl.cc b/arts/midi/systemmiditimer_impl.cc new file mode 100644 index 00000000..bda27a8c --- /dev/null +++ b/arts/midi/systemmiditimer_impl.cc @@ -0,0 +1,105 @@ + /* + + Copyright (C) 2001 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "artsmidi.h" +#include "debug.h" +#include "miditimercommon.h" + +using namespace std; +using namespace Arts; + +namespace Arts { + +class SystemMidiTimerCommon : public MidiTimerCommon, + public TimeNotify { +protected: + SystemMidiTimerCommon(); + virtual ~SystemMidiTimerCommon(); + +public: + // allocation: share one SystemMidiTimerCommon for everbody who needs one + static SystemMidiTimerCommon *subscribe(); + + void notifyTime(); + TimeStamp time(); +}; + +} + +static SystemMidiTimerCommon *SystemMidiTimerCommon_the = 0; + +SystemMidiTimerCommon::SystemMidiTimerCommon() +{ + SystemMidiTimerCommon_the = this; + Dispatcher::the()->ioManager()->addTimer(10, this); +} + +SystemMidiTimerCommon::~SystemMidiTimerCommon() +{ + Dispatcher::the()->ioManager()->removeTimer(this); + SystemMidiTimerCommon_the = 0; +} + +TimeStamp SystemMidiTimerCommon::time() +{ + timeval tv; + gettimeofday(&tv,0); + return TimeStamp(tv.tv_sec, tv.tv_usec); +} + +void SystemMidiTimerCommon::notifyTime() +{ + processQueue(); +} + +SystemMidiTimerCommon *SystemMidiTimerCommon::subscribe() +{ + if(!SystemMidiTimerCommon_the) new SystemMidiTimerCommon(); + SystemMidiTimerCommon_the->refCount++; + return SystemMidiTimerCommon_the; +} + +class SystemMidiTimer_impl : public SystemMidiTimer_skel { +protected: + SystemMidiTimerCommon *timer; +public: + SystemMidiTimer_impl() + { + timer = SystemMidiTimerCommon::subscribe(); + } + ~SystemMidiTimer_impl() + { + timer->unsubscribe(); + } + TimeStamp time() + { + return timer->time(); + } + void queueEvent(MidiPort port, const MidiEvent& event) + { + timer->queueEvent(port, event); + } +}; + +namespace Arts { + REGISTER_IMPLEMENTATION(SystemMidiTimer_impl); +} diff --git a/arts/midi/timestampmath.cc b/arts/midi/timestampmath.cc new file mode 100644 index 00000000..ff33c280 --- /dev/null +++ b/arts/midi/timestampmath.cc @@ -0,0 +1,112 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#include "timestampmath.h" +#include <arts/debug.h> +#include <stdio.h> + +using namespace std; + +namespace Arts { +void timeStampInc(TimeStamp& t, const TimeStamp& delta) +{ + /* expect a normalized t, delta */ + arts_return_if_fail(t.usec >= 0 && t.usec < 1000000); + arts_return_if_fail(delta.usec >= 0 && delta.usec < 1000000); + + t.sec += delta.sec; + t.usec += delta.usec; + + if (t.usec >= 1000000) + { + t.usec -= 1000000; + t.sec += 1; + } + + arts_assert (t.usec >= 0 && t.usec < 1000000); +} + +void timeStampDec(TimeStamp& t, const TimeStamp& delta) +{ + /* expect a normalized t, delta */ + arts_return_if_fail(t.usec >= 0 && t.usec < 1000000); + arts_return_if_fail(delta.usec >= 0 && delta.usec < 1000000); + + t.sec -= delta.sec; + t.usec -= delta.usec; + + if(t.usec < 0) + { + t.usec += 1000000; + t.sec -= 1; + } + + arts_assert(t.usec >= 0 && t.usec < 1000000); +} + +string timeStampToString(const TimeStamp& t) +{ + arts_return_val_if_fail(t.usec >= 0 && t.usec < 1000000, ""); + + char buffer[1024]; + if(t.sec < 0 && t.usec != 0) + { + sprintf(buffer, "-%d.%06d", -t.sec-1, 1000000-t.usec); + } + else + { + sprintf(buffer, "%d.%06d", t.sec, t.usec); + } + return buffer; +} + +double timeStampToDouble(const TimeStamp& t) +{ + arts_return_val_if_fail(t.usec >= 0 && t.usec < 1000000, 0.0); + + return double(t.sec) + double(t.usec)/1000000.0; +} + +TimeStamp timeStampFromDouble(double d) +{ + TimeStamp t; + + arts_return_val_if_fail(d >= 0, t); + + t.sec = int(d); + d -= t.sec; + t.usec = int(d * 1000000.0); + + return t; +} + +TimeStamp timeStampMax(const TimeStamp& t1, const TimeStamp& t2) +{ + if(t1.sec > t2.sec) + return t1; + else if((t1.sec == t2.sec) && (t1.usec > t2.usec)) + return t1; + else + return t2; +} + +} diff --git a/arts/midi/timestampmath.h b/arts/midi/timestampmath.h new file mode 100644 index 00000000..934df716 --- /dev/null +++ b/arts/midi/timestampmath.h @@ -0,0 +1,62 @@ + /* + + Copyright (C) 2002 Stefan Westerfeld + stefan@space.twc.de + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + */ + +#ifndef ARTS_TIMESTAMPMATH_H +#define ARTS_TIMESTAMPMATH_H + +#include "artsmidi.h" +#include <kdelibs_export.h> +namespace Arts { + +/** + * increments the timestamp by delta + */ +KDE_EXPORT void timeStampInc(TimeStamp& t, const TimeStamp& delta); + +/** + * decrements the timestamp by delta + */ +void timeStampDec(TimeStamp& t, const TimeStamp& delta); + +/** + * stringifies a timestamp + */ +std::string timeStampToString(const TimeStamp& t); + +/** + * converts a timestamp to a double of seconds + */ +double timeStampToDouble(const TimeStamp& t); + +/** + * converts a double of seconds to a timestamp + */ +TimeStamp timeStampFromDouble(double d); + +/** + * returns the maximum of two timestamps + */ +TimeStamp timeStampMax(const TimeStamp& t1, const TimeStamp& t2); + +} + +#endif /* ARTS_TIMESTAMPMATH_H */ |