Logo Search packages:      
Sourcecode: ardour version File versions

session_midi.cc

/*
    Copyright (C) 1999-2002 Paul Davis 

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: session_midi.cc,v 1.47 2005/03/09 02:24:53 pauld Exp $
*/

#include <string>
#include <cmath>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>

#include <midi++/mmc.h>
#include <midi++/port.h>
#include <midi++/manager.h>
#include <pbd/error.h>
#include <pbd/lockmonitor.h>
#include <pbd/pthread_utils.h>

#include <ardour/configuration.h>
#include <ardour/audioengine.h>
#include <ardour/session.h>
#include <ardour/audio_track.h>
#include <ardour/diskstream.h>
#include <ardour/slave.h>
#include <ardour/cycles.h>

#include "i18n.h"

using namespace std;
using namespace ARDOUR;
using namespace SigC;

MIDI::MachineControl::CommandSignature MMC_CommandSignature;
MIDI::MachineControl::ResponseSignature MMC_ResponseSignature;

MultiAllocSingleReleasePool Session::MIDIRequest::pool ("midi", sizeof (Session::MIDIRequest), 256);

/***********************************************************************
 MTC, MMC, etc.
 **********************************************************************/

void
Session::set_mmc_control (bool yn)
{
      if (_mmc_port == 0 || mmc_control == yn) {
            return;
      }

      mmc_control = yn;
      set_dirty();
      poke_midi_thread ();

      ControlChanged (MMCControl); /* EMIT SIGNAL */
}

void
Session::set_midi_control (bool yn)
{
      if (_midi_port == 0 || midi_control == yn) {
            return;
      }

      midi_control = yn;
      set_dirty();
      poke_midi_thread ();

      { 
            LockMonitor lm (route_lock, __LINE__, __FILE__);
            for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
                  (*i)->reset_midi_control (_midi_port, midi_control);
            }
      }

      ControlChanged (MidiControl); /* EMIT SIGNAL */
}

void
Session::set_send_mtc (bool yn)
{
      /* set the persistent option value regardless */

      send_midi_timecode = yn;
      set_dirty();

      /* only set the internal flag if we have
         a port.
      */

      if (_mtc_port == 0 || send_mtc == yn) {
            return;
      }

      send_mtc = yn;
      ControlChanged (SendMTC); /* EMIT SIGNAL */
}

void
Session::set_send_mmc (bool yn)
{
      if (_mmc_port == 0) {
            return;
      }

      if (send_midi_machine_control == yn) {
            return;
      }

      /* only set the internal flag if we have
         a port.
      */

      if (_mmc_port) {
            send_mmc = yn;
      }

      /* set the persistent option value regardless */

      send_midi_machine_control = yn;
      set_dirty();

       ControlChanged (SendMMC); /* EMIT SIGNAL */
}

bool
Session::get_send_mtc () const
{
      return send_mtc;
}

bool
Session::get_send_mmc () const
{
      return send_mmc;
}

int
Session::set_mtc_port (string port_tag)
{
      if (port_tag.length() == 0) {
            if (_mtc_port) {
                  MIDI::Parser* parser;
                  if ((parser = _mtc_port->input()) != 0) {
                        parser->trace (false, 0);
                  }
                  if ((parser = _mtc_port->output()) != 0) {
                        parser->trace (false, 0);
                  }
            }
            _mtc_port = 0;
             MTC_PortChanged(); /* EMIT SIGNAL */
            return 0;
      }

      MIDI::Port* port;

      if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
            error << compose (_("unknown port %1 requested for MTC"), port_tag) << endl;
            return -1;
      }

//    if (_mtc_port == port) {
//          return 0;
//    }

      _mtc_port = port;

      set_trace_midi_input (Config->get_trace_midi_input(), _mtc_port);
      set_trace_midi_output (Config->get_trace_midi_output(), _mtc_port);

       MTC_PortChanged(); /* EMIT SIGNAL */

      Config->set_mtc_port_name (port_tag);
      change_midi_ports ();
      set_dirty();
      return 0;
}

int
Session::set_mmc_port (string port_tag)
{
      if (port_tag.length() == 0) {
            if (_mmc_port) {
                  MIDI::Parser* parser;
                  if ((parser = _mmc_port->input()) != 0) {
                        parser->trace (false, 0);
                  }
                  if ((parser = _mmc_port->output()) != 0) {
                        parser->trace (false, 0);
                  }
                  _mmc_port = 0;
                   MMC_PortChanged(); /* EMIT SIGNAL */
            }
            return 0;
      }

      MIDI::Port* port;

      if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
            return -1;
      }

//    if (_mmc_port == port) {
//          return 0;
//    }

      _mmc_port = port;

      if (mmc) {
            delete mmc;
      }

      mmc = new MIDI::MachineControl (*_mmc_port, 1.0, 
                              MMC_CommandSignature,
                              MMC_ResponseSignature);

      mmc->Play.connect 
            (slot (*this, &Session::mmc_deferred_play));
      mmc->DeferredPlay.connect 
            (slot (*this, &Session::mmc_deferred_play));
      mmc->Stop.connect 
            (slot (*this, &Session::mmc_stop));
      mmc->FastForward.connect 
            (slot (*this, &Session::mmc_fast_forward));
      mmc->Rewind.connect 
            (slot (*this, &Session::mmc_rewind));
      mmc->RecordPause.connect 
            (slot (*this, &Session::mmc_record_pause));
      mmc->RecordStrobe.connect 
            (slot (*this, &Session::mmc_record_strobe));
      mmc->RecordExit.connect 
            (slot (*this, &Session::mmc_record_exit));
      mmc->Locate.connect 
            (slot (*this, &Session::mmc_locate));
      mmc->Step.connect 
            (slot (*this, &Session::mmc_step));
      mmc->Shuttle.connect 
            (slot (*this, &Session::mmc_shuttle));
//    mmc->TrackRecordStatusChange.connect
//          (slot (*this, &Session::mmc_record_enable));
      
      set_trace_midi_input (Config->get_trace_midi_input(), _mmc_port);
      set_trace_midi_output (Config->get_trace_midi_output(), _mmc_port);

      MMC_PortChanged(); /* EMIT SIGNAL */

      Config->set_mmc_port_name (port_tag);
      change_midi_ports ();
      set_dirty();
      return 0;
}

int
Session::set_midi_port (string port_tag)
{
      if (port_tag.length() == 0) {
            if (_midi_port) {
                  MIDI::Parser* parser;
                  if ((parser = _midi_port->input()) != 0) {
                        parser->trace (false, 0);
                  }
                  if ((parser = _midi_port->output()) != 0) {
                        parser->trace (false, 0);
                  }
            }
            _midi_port = 0;
             MIDI_PortChanged(); /* EMIT SIGNAL */
            return 0;
      }

      MIDI::Port* port;

      if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
            return -1;
      }

      _midi_port = port;

      set_trace_midi_input (Config->get_trace_midi_input(), _midi_port);
      set_trace_midi_output (Config->get_trace_midi_output(), _midi_port);

       MIDI_PortChanged(); /* EMIT SIGNAL */

      Config->set_midi_port_name (port_tag);
      change_midi_ports ();
      set_dirty();
      return 0;
}

void
Session::set_trace_midi_input (bool yn, MIDI::Port* port)
{
      MIDI::Parser* input_parser;

      if (port) {
            if ((input_parser = port->input()) != 0) {
                  input_parser->trace (yn, &cout, "input: ");
            }
      } else {

            if (_mmc_port) {
                  if ((input_parser = _mmc_port->input()) != 0) {
                        input_parser->trace (yn, &cout, "input: ");
                  }
            }
            
            if (_mtc_port && _mtc_port != _mmc_port) {
                  if ((input_parser = _mtc_port->input()) != 0) {
                        input_parser->trace (yn, &cout, "input: ");
                  }
            }

            if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port  ) {
                  if ((input_parser = _midi_port->input()) != 0) {
                        input_parser->trace (yn, &cout, "input: ");
                  }
            }
      }

      Config->set_trace_midi_input (yn);
}

void
Session::set_trace_midi_output (bool yn, MIDI::Port* port)
{
      MIDI::Parser* output_parser;

      if (port) {
            if ((output_parser = port->output()) != 0) {
                  output_parser->trace (yn, &cout, "output: ");
            }
      } else {
            if (_mmc_port) {
                  if ((output_parser = _mmc_port->output()) != 0) {
                        output_parser->trace (yn, &cout, "output: ");
                  }
            }
            
            if (_mtc_port && _mtc_port != _mmc_port) {
                  if ((output_parser = _mtc_port->output()) != 0) {
                        output_parser->trace (yn, &cout, "output: ");
                  }
            }

            if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port  ) {
                  if ((output_parser = _midi_port->output()) != 0) {
                        output_parser->trace (yn, &cout, "output: ");
                  }
            }

      }

      Config->set_trace_midi_output (yn);
}

bool
Session::get_trace_midi_input(MIDI::Port *port)
{
      MIDI::Parser* input_parser;
      if (port) {
            if ((input_parser = port->input()) != 0) {
                  return input_parser->tracing();
            }
      }
      else {
            if (_mmc_port) {
                  if ((input_parser = _mmc_port->input()) != 0) {
                        return input_parser->tracing();
                  }
            }
            
            if (_mtc_port) {
                  if ((input_parser = _mtc_port->input()) != 0) {
                        return input_parser->tracing();
                  }
            }

            if (_midi_port) {
                  if ((input_parser = _midi_port->input()) != 0) {
                        return input_parser->tracing();
                  }
            }
      }

      return false;
}

bool
Session::get_trace_midi_output(MIDI::Port *port)
{
      MIDI::Parser* output_parser;
      if (port) {
            if ((output_parser = port->output()) != 0) {
                  return output_parser->tracing();
            }
      }
      else {
            if (_mmc_port) {
                  if ((output_parser = _mmc_port->output()) != 0) {
                        return output_parser->tracing();
                  }
            }
            
            if (_mtc_port) {
                  if ((output_parser = _mtc_port->output()) != 0) {
                        return output_parser->tracing();
                  }
            }

            if (_midi_port) {
                  if ((output_parser = _midi_port->output()) != 0) {
                        return output_parser->tracing();
                  }
            }
      }

      return false;

}


void
Session::set_midi_feedback (bool yn)
{
      if (_midi_port == 0) {
            return;
      }
      
      midi_feedback = yn;
      set_dirty();

      if (yn) {
            /* make sure the feedback thread is alive */
            start_feedback ();
      } else {
            /* maybe put the feedback thread to sleep */
            stop_feedback ();
      }

      ControlChanged (MidiFeedback); /* EMIT SIGNAL */

      send_all_midi_feedback ();
}

void
Session::send_all_midi_feedback ()
{
      if (midi_feedback) {
            // send out current state of all routes
            LockMonitor lm (route_lock, __LINE__, __FILE__);
            for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
                  (*i)->send_all_midi_feedback ();
            }
      }
}

void
Session::setup_midi_control ()
{
      last_outbound_mtc_frame = 0;

      /* setup the MMC buffer */
      
      mmc_buffer[0] = 0xf0; // SysEx
      mmc_buffer[1] = 0x7f; // Real Time SysEx ID for MMC
      mmc_buffer[2] = 0x7f; // "broadcast" device ID
      mmc_buffer[3] = 0x6;  // MCC

      /* Set up the qtr frame message */
      
      mtc_msg[0] = 0xf1;
      mtc_msg[2] = 0xf1;
      mtc_msg[4] = 0xf1;
      mtc_msg[6] = 0xf1;
      mtc_msg[8] = 0xf1;
      mtc_msg[10] = 0xf1;
      mtc_msg[12] = 0xf1;
      mtc_msg[14] = 0xf1;

      if (_mmc_port != 0) {

            mmc_control = mmc_control;
            send_mmc = send_midi_machine_control;

      } else {

            mmc = 0;
            send_mmc = false;
            mmc_control = false;
      }

      if (_mtc_port != 0) {

            send_mtc = send_midi_timecode;

      } else {

            send_mtc = false;
      }
}

int
Session::midi_read (MIDI::Port* port)
{
      MIDI::byte buf[512];
      
      /* reading from the MIDI port activates the Parser
         that in turn generates signals that we care
         about. the port is already set to NONBLOCK so that
         can read freely here.
      */
      
      while (1) {
            
            // cerr << "+++ READ ON " << port->name() << endl;
            
            int nread = port->read (buf, sizeof (buf));

            // cerr << "-- READ (" << nread << " ON " << port->name() << endl;
            
            if (nread > 0) {
                  if ((size_t) nread < sizeof (buf)) {
                        break;
                  } else {
                        continue;
                  }
            } else if (nread == 0) {
                  break;
            } else if (errno == EAGAIN) {
                  break;
            } else {
                  fatal << compose(_("Error reading from MIDI port %1"), port->name()) << endmsg;
                  /*NOTREACHED*/
            }
      }

      return 0;
}

void
Session::mmc_deferred_play (MIDI::MachineControl &mmc)
{
      if (mmc_control && (_slave_type != MTC)) {
            request_transport_speed (1.0);
      }
}

void
Session::mmc_record_pause (MIDI::MachineControl &mmc)
{
      if (mmc_control) {
            maybe_enable_record();
      }
}

void
Session::mmc_record_strobe (MIDI::MachineControl &mmc)
{
      if (!mmc_control) 
            return;

      if (!punch_in) {
            enable_record ();
      } else {
            atomic_set (&_record_status, Enabled);
            RecordEnabled (); /* EMIT SIGNAL */
            save_state ("", true);
      }

      /* record strobe does an implicit "Play" command */

      request_transport_speed (1.0);
}

void
Session::mmc_record_exit (MIDI::MachineControl &mmc)
{
      if (mmc_control) {
            disable_record ();
      }
}

void
Session::mmc_stop (MIDI::MachineControl &mmc)
{
      if (mmc_control) {
            request_stop ();
      }
}

static bool step_queued = false;

void

Session::mmc_step (MIDI::MachineControl &mmc, int steps)
{
      if (!mmc_control) {
            return;
      }

      struct timeval now;
      struct timeval diff = { 0, 0 };

      gettimeofday (&now, 0);
      
      timersub (&now, &last_mmc_step, &diff);

      gettimeofday (&now, 0);
      timersub (&now, &last_mmc_step, &diff);

      if (last_mmc_step.tv_sec != 0 && (diff.tv_usec + (diff.tv_sec * 1000000)) < _engine.usecs_per_cycle()) {
            return;
      }
      
      double diff_secs = diff.tv_sec + (diff.tv_usec / 1000000.0);
      double cur_speed = (((steps * 0.5) * smpte_frames_per_second) / diff_secs) / smpte_frames_per_second;
      
      if (_transport_speed == 0 || cur_speed * _transport_speed < 0) {
            /* change direction */
            step_speed = cur_speed;
      } else {
            step_speed = (0.6 * step_speed) + (0.4 * cur_speed);
      }

      step_speed *= 0.25;

#if 0
      cerr << "delta = " << diff_secs 
           << " ct = " << _transport_speed
           << " steps = " << steps
           << " new speed = " << cur_speed 
           << " speed = " << step_speed
           << endl;
#endif      

      request_transport_speed (step_speed);
      last_mmc_step = now;

      if (!step_queued) {
            midi_timeouts.push_back (slot (*this, &Session::mmc_step_timeout));
            step_queued = true;
      }
}

void
Session::mmc_rewind (MIDI::MachineControl &mmc)
{
      if (mmc_control) {
            request_transport_speed(-8.0f);
      }
}

void
Session::mmc_fast_forward (MIDI::MachineControl &mmc)
{
      if (mmc_control) {
            request_transport_speed(8.0f);
      }
}

void
Session::mmc_locate (MIDI::MachineControl &mmc, const MIDI::byte* mmc_tc)
{
      if (!mmc_control) {
            return;
      }

      jack_nframes_t sr = _current_frame_rate;
      jack_nframes_t target_frame;
      jack_nframes_t fps;

      fps = mmc_tc[0] & 0x30;

      target_frame = ((mmc_tc[0] & 0xf)* 60 * 60 * sr) +
            (mmc_tc[1] * 60 * sr) +
            (mmc_tc[2] * sr) +
            (mmc_tc[3] * _frames_per_smpte_frame);
      
      if (target_frame > max_frames) {
            target_frame = max_frames;
      }

      /* Some (all?) MTC/MMC devices do not send a full MTC frame
         at the end of a locate, instead sending only an MMC
         locate command. This causes the current position
         of an MTC slave to become out of date. Catch this.
      */

      MTC_Slave* mtcs = dynamic_cast<MTC_Slave*> (_slave);

      if (mtcs != 0) {
            // cerr << "Locate *with* MTC slave\n";
            mtcs->handle_locate (mmc_tc);
      } else {
            // cerr << "Locate without MTC slave\n";
            request_locate (target_frame, false);
      }
}

void
Session::mmc_shuttle (MIDI::MachineControl &mmc, float speed, bool forw)
{
      if (!mmc_control) {
            return;
      }

      if (shuttle_speed_threshold >= 0 && speed > shuttle_speed_threshold) {
            speed *= shuttle_speed_factor;
      }
      
      if (forw) {
            request_transport_speed (speed);
      } else {
            request_transport_speed (-1.0f * speed);
      }
}

void
Session::mmc_record_enable (MIDI::MachineControl &mmc, int32_t trk, bool enabled)
{
      if (mmc_control) {

            /* don't take route or diskstream lock: if using dynamic punch,
               this could cause a dropout. XXX is that really OK?
               or should we queue a rec-enable request?
            */

            int32_t n;
            RouteList::iterator i;

            for (n = 0, i = routes.begin(); i != routes.end(); ++i) {
                  AudioTrack *at;

                  if ((at = dynamic_cast<AudioTrack*>(*i)) != 0) {
                        if (n++ == trk) {
                              at->set_record_enable (enabled, &mmc);
                              break;
                        }
                  }
            }
      }
}

void
Session::send_full_time_code_in_another_thread ()
{
      send_time_code_in_another_thread (true);
}

void
Session::send_midi_time_code_in_another_thread ()
{
      send_time_code_in_another_thread (false);
}

void
Session::send_time_code_in_another_thread (bool full)
{
      jack_nframes_t delta;

      if (_transport_frame > last_outbound_mtc_frame) {
            delta = _transport_frame - last_outbound_mtc_frame;
      } else {
            delta = last_outbound_mtc_frame - _transport_frame;
      }

      if (delta < _frames_per_smpte_frame) {

            /* There is no work to do, since we're still
               at the same SMPTE frame as last time we did this.
               We throttle this here so that we don't overload
               the transport thread with requests.
            */

            return;
      }

      MIDIRequest* request = new MIDIRequest;
      
      if (full) {
            request->type = MIDIRequest::SendFullMTC;
      } else {
            request->type = MIDIRequest::SendMTC;
      }
      
      midi_requests.write (&request, 1);
      poke_midi_thread ();
}

void
Session::change_midi_ports ()
{
      MIDIRequest* request = new MIDIRequest;
      request->type = MIDIRequest::PortChange;
      midi_requests.write (&request, 1);
      poke_midi_thread ();
}

int
Session::send_full_time_code ()

{
      MIDI::byte msg[10];
      SMPTE_Time smpte;

      if (_mtc_port == 0 || !send_mtc) {
            return 0;
      }

      msg[0] = 0xf0;
      msg[1] = 0x7f;
      msg[2] = 0x7f;
      msg[3] = 0x1;
      msg[4] = 0x1;
      msg[9] = 0xf7;

      last_outbound_mtc_frame = _transport_frame;

      smpte_time (_transport_frame, smpte);

      msg[5] = mtc_smpte_bits | smpte.hours;
      msg[6] = smpte.minutes;
      msg[7] = smpte.seconds;
      msg[8] = smpte.frames;

      {
            LockMonitor lm (midi_lock, __LINE__, __FILE__);

            if (_mtc_port->midimsg (msg, sizeof (msg))) {
                  error << _("Session: could not send full MIDI time code") << endmsg;
                  
                  return -1;
            }
      }

      last_mtc_smpte_frame = 
            (jack_nframes_t) floor ((double) _transport_frame /
                        ((1.0/smpte_frames_per_second)/
                         (1.0/_current_frame_rate)));

      return 0;
}

int
Session::send_midi_time_code ()
{
      if (_mtc_port == 0 || !send_mtc)  {
            return 0;
      }

      MIDI::byte hb;
      jack_nframes_t elapsed_smpte_frames;
      jack_nframes_t current_smpte_frame;
      uint32_t n;
      SMPTE_Time smpte;

      current_smpte_frame = (jack_nframes_t) floor ((double) _transport_frame /
                                     ((1.0/smpte_frames_per_second)/
                                      (1.0/_current_frame_rate)));

      if (current_smpte_frame < last_mtc_smpte_frame) {
            /* XXX backwards motion ... help ! */
            return 0;
      }

      elapsed_smpte_frames = current_smpte_frame - last_mtc_smpte_frame;

      /* Assumption: elapsed frames < smpte_frames_per_second

         i.e. there is never more than 1 second between the
         last_mtc_smpte_frame and right now. This means that we only
         have to compute the clock parts once, then we just
         increment the frames part each time.
      */

      smpte_time (_transport_frame, smpte);

      { 
            LockMonitor lm (midi_lock, __LINE__, __FILE__);

            /* Now, this is a little wierd. We can't send MTC Quarter Frame
               messages the way they are really intended because our audio
               is block-processed, and we have no way to deliver 1/4-frame
               messages at "the right time". So instead, we make a bursty
               transmission here, sending 8 qtr-frame messages per pair
               of elapsed frames.

               XXX This is NOT RIGHT! Some better way to handle this
               is needed.
            */

            for (n = 0; n < elapsed_smpte_frames; n += 2) {
                  
                  hb = mtc_smpte_bits|smpte.hours;
                  
                  mtc_msg[1] =  0x00 | (smpte.frames & 0xf);
                  mtc_msg[3] =  0x10 | ((smpte.frames & 0xf0) >> 4);
                  mtc_msg[5] =  0x20 | (smpte.seconds & 0xf);
                  mtc_msg[7] =  0x30 | ((smpte.seconds & 0xf0) >> 4);
                  mtc_msg[9] =  0x40 | (smpte.minutes & 0xf);
                  mtc_msg[11] = 0x50 | ((smpte.minutes & 0xf0) >> 4);
                  mtc_msg[13] = 0x60 | (hb & 0xf);
                  mtc_msg[15] = 0x70 | ((hb & 0xf0) >> 4);
                  
                  if (_mtc_port->midimsg (mtc_msg, sizeof (mtc_msg))) {
                        error << compose(_("Session: cannot send quarter-frame MTC message (%1)"), strerror (errno)) 
                              << endmsg;
                        
                        /* set this anyway to prevent lots of
                           messages being sent.
                        */

                        last_outbound_mtc_frame = _transport_frame;
                        return -1;
                  }
                  
                  smpte.frames += 2;
                  last_mtc_smpte_frame += 2;
            }
      }

      last_outbound_mtc_frame = _transport_frame;
      return 0;
}

/***********************************************************************
 OUTBOUND MMC STUFF
 **********************************************************************/

void
Session::send_mmc_in_another_thread (MIDI::MachineControl::Command cmd, jack_nframes_t target_frame)
{
      MIDIRequest* request = new MIDIRequest;

      request->type = MIDIRequest::SendMMC;
      request->mmc_cmd = cmd;
      request->locate_frame = target_frame;

      midi_requests.write (&request, 1);
      poke_midi_thread ();
}

void
Session::deliver_mmc (MIDI::MachineControl::Command cmd, jack_nframes_t where)
{
      using namespace MIDI;
      int nbytes = 4;
      SMPTE_Time smpte;

      if (_mmc_port == 0 || !send_mmc) {
            return;
      }

      mmc_buffer[nbytes++] = cmd;

      // cerr << "delivering MMC, cmd = " << hex << (int) cmd << dec << endl;
      
      switch (cmd) {
      case MachineControl::cmdLocate:
            smpte_time_subframes (where, smpte);

            mmc_buffer[nbytes++] = 0x6; // byte count
            mmc_buffer[nbytes++] = 0x1; // "TARGET" subcommand
            mmc_buffer[nbytes++] = smpte.hours;
            mmc_buffer[nbytes++] = smpte.minutes;
            mmc_buffer[nbytes++] = smpte.seconds;
            mmc_buffer[nbytes++] = smpte.frames;
            mmc_buffer[nbytes++] = smpte.subframes;
            break;

      case MachineControl::cmdStop:
            break;

      case MachineControl::cmdPlay:
            /* always convert Play into Deferred Play */
            mmc_buffer[4] = MachineControl::cmdDeferredPlay;
            break;

      case MachineControl::cmdRecordStrobe:
            break;

      case MachineControl::cmdRecordExit:
            break;

      case MachineControl::cmdRecordPause:
            break;

      default:
            nbytes = 0;
      };

      if (nbytes) {

            mmc_buffer[nbytes++] = 0xf7; // terminate SysEx/MMC message

            LockMonitor lm (midi_lock, __LINE__, __FILE__);

            if (_mmc_port->write (mmc_buffer, nbytes) != nbytes) {
                  error << compose(_("MMC: cannot send command %1%2%3"), &hex, cmd, &dec) << endmsg;
            }
      }
}

bool
Session::mmc_step_timeout ()
{
      struct timeval now;
      struct timeval diff;
      double diff_usecs;
      gettimeofday (&now, 0);

      timersub (&now, &last_mmc_step, &diff);
      diff_usecs = diff.tv_sec * 1000000 + diff.tv_usec;

      if (diff_usecs > 1000000.0 || fabs (_transport_speed) < 0.0000001) {
            /* too long or too slow, stop transport */
            request_transport_speed (0.0);
            step_queued = false;
            return false;
      }

      if (diff_usecs < 250000.0) {
            /* too short, just keep going */
            return true;
      }

      /* slow it down */

      request_transport_speed (_transport_speed * 0.75);
      return true;
}


void
Session::send_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t ch, MIDI::EventTwoBytes data)
{
      // in another thread, really
      
      MIDIRequest* request = new MIDIRequest;

      request->type = MIDIRequest::SendMessage;
      request->port = port;
      request->ev = ev;
      request->chan = ch;
      request->data = data;
      
      midi_requests.write (&request, 1);
      poke_midi_thread ();
}

void
Session::deliver_midi (MIDI::Port * port, MIDI::byte* buf, int32_t bufsize)
{
      // in another thread, really
      
      MIDIRequest* request = new MIDIRequest;

      request->type = MIDIRequest::Deliver;
      request->port = port;
      request->buf = buf;
      request->size = bufsize;
      
      midi_requests.write (&request, 1);
      poke_midi_thread ();
}

void
Session::deliver_midi_message (MIDI::Port * port, MIDI::eventType ev, MIDI::channel_t ch, MIDI::EventTwoBytes data)
{
      if (port == 0 || ev == MIDI::none) {
            return;
      }

      midi_msg[0] = (ev & 0xF0) | (ch & 0xF); 
      midi_msg[1] = data.controller_number;
      midi_msg[2] = data.value;

      port->write (midi_msg, 3);
}

void
Session::deliver_data (MIDI::Port * port, MIDI::byte* buf, int32_t size)
{
      if (port) {
            port->write (buf, size);
      }

      /* this is part of the semantics of the Deliver request */

      delete [] buf;
}

/*---------------------------------------------------------------------------
 MIDI THREAD 
 ---------------------------------------------------------------------------*/

int
Session::start_midi_thread ()
{
      if (pipe (midi_request_pipe)) {
            error << compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
            return -1;
      }

      if (fcntl (midi_request_pipe[0], F_SETFL, O_NONBLOCK)) {
            error << compose(_("UI: cannot set O_NONBLOCK on "    "signal read pipe (%1)"), strerror (errno)) << endmsg;
            return -1;
      }

      if (fcntl (midi_request_pipe[1], F_SETFL, O_NONBLOCK)) {
            error << compose(_("UI: cannot set O_NONBLOCK on "    "signal write pipe (%1)"), strerror (errno)) << endmsg;
            return -1;
      }

      if (pthread_create_and_store ("transport", &midi_thread, 0, _midi_thread_work, this)) {
            error << _("Session: could not create transport thread") << endmsg;
            return -1;
      }

      // pthread_detach (midi_thread);

      return 0;
}

void
Session::terminate_midi_thread ()
{
      MIDIRequest* request = new MIDIRequest;
      void* status;

      request->type = MIDIRequest::Quit;

      midi_requests.write (&request, 1);
      poke_midi_thread ();

      pthread_join (midi_thread, &status);
}

void
Session::poke_midi_thread ()
{
      char c;

      if (write (midi_request_pipe[1], &c, 1) != 1) {
            error << compose(_("cannot send signal to midi thread! (%1)"), strerror (errno)) << endmsg;
      }
}

void *
Session::_midi_thread_work (void* arg)
{
      pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0);
      pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);

      ((Session *) arg)->midi_thread_work ();
      return 0;
}

void
Session::midi_thread_work ()
{
      MIDIRequest* request;
      struct pollfd pfd[4];
      int nfds = 0;
      int timeout;
      int fds_ready;
      struct sched_param rtparam;
      int x;
      bool restart;

      PBD::ThreadCreated (pthread_self(), X_("MIDI"));

      memset (&rtparam, 0, sizeof (rtparam));
      rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
      
      if ((x = pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam)) != 0) {
            // do we care? not particularly.
      } 

      while (1) {

            nfds = 0;

            pfd[nfds].fd = midi_request_pipe[0];
            pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
            nfds++;

            /* if we are using MMC control, we obviously have to listen
               on the appropriate port.
            */

            if (mmc_control && _mmc_port && _mmc_port->selectable() >= 0) {
                  pfd[nfds].fd = _mmc_port->selectable();
                  pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
                  nfds++;
            }

            /* if MTC is being handled on a different port from MMC
               or we are not handling MMC at all, poll
               the relevant port.
            */

            if (_mtc_port && (_mtc_port != _mmc_port || !mmc_control) && _mtc_port->selectable() >= 0) {
                  pfd[nfds].fd = _mtc_port->selectable();
                  pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
                  nfds++;
            }

            if (_midi_port && (_midi_port != _mmc_port || !mmc_control) && (_midi_port != _mtc_port) && _midi_port->selectable() >= 0) {
                  pfd[nfds].fd = _midi_port->selectable();
                  pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
                  nfds++;
            }
            
            if (!midi_timeouts.empty()) {
                  timeout = 100; /* 10msecs */
            } else {
                  timeout = -1; /* if there is no data, we don't care */
            }

        again:
            // cerr << "poll on " << nfds << " for " << timeout << endl;
            if (poll (pfd, nfds, timeout) < 0) {
                  if (errno == EINTR) {
                        /* gdb at work, perhaps */
                        goto again;
                  }

                  error << compose(_("MIDI thread poll failed (%1)"), strerror (errno)) << endmsg;

                  break;
            }
            // cerr << "MIDI thread wakes at " << get_cycles () << endl;

            fds_ready = 0;
            restart = false;

            /* check the transport request pipe */

            if (pfd[0].revents & ~POLLIN) {
                  error << _("Error on transport thread request pipe") << endmsg;
                  break;
            }

            if (pfd[0].revents & POLLIN) {

                  char foo[16];
                  
                  // cerr << "MIDI request FIFO ready\n";
                  fds_ready++;

                  /* empty the pipe of all current requests */

                  while (1) {
                        size_t nread = read (midi_request_pipe[0], &foo, sizeof (foo));

                        if (nread > 0) {
                              if ((size_t) nread < sizeof (foo)) {
                                    break;
                              } else {
                                    continue;
                              }
                        } else if (nread == 0) {
                              break;
                        } else if (errno == EAGAIN) {
                              break;
                        } else {
                              fatal << _("Error reading from transport request pipe") << endmsg;
                              /*NOTREACHED*/
                        }
                  }

                  while (midi_requests.read (&request, 1) == 1) {

                        switch (request->type) {
                              
                        case MIDIRequest::SendFullMTC:
                              // cerr << "send full MTC\n";
                              send_full_time_code ();
                              // cerr << "... done\n";
                              break;
                              
                        case MIDIRequest::SendMTC:
                              // cerr << "send qtr MTC\n";
                              send_midi_time_code ();
                              // cerr << "... done\n";
                              break;
                              
                        case MIDIRequest::SendMMC:
                              // cerr << "send MMC\n";
                              deliver_mmc (request->mmc_cmd, request->locate_frame);
                              // cerr << "... done\n";
                              break;

                        case MIDIRequest::SendMessage:
                              // cerr << "send Message\n";
                              deliver_midi_message (request->port, request->ev, request->chan, request->data);
                              // cerr << "... done\n";
                              break;
                              
                        case MIDIRequest::Deliver:
                              // cerr << "deliver\n";
                              deliver_data (_midi_port, request->buf, request->size);
                              // cerr << "... done\n";
                              break;
                                    
                        case MIDIRequest::PortChange:
                              /* restart poll with new ports */
                              // cerr << "rebind\n";
                              restart = true;
                              break;
                                    
                        case MIDIRequest::Quit:
                              delete request;
                              pthread_exit_pbd (0);
                              /*NOTREACHED*/
                              break;
                              
                        default:
                              break;
                        }


                        delete request;
                  }

            } 

            if (restart) {
                  continue;
            }

            if (nfds > 1) {

                  /* Check the MMC port */
                  
                  if ((pfd[1].revents & ~POLLIN)) {
                        error << compose(_("Transport: error polling extra MIDI port #1 (revents =%1%2%3"), &hex, pfd[1].revents, &dec) << endmsg;
                        break;
                  }
                  
                  if (pfd[1].revents & POLLIN) {
                        // cerr << "MMC port ready\n";
                        fds_ready++;
                        midi_read (_mmc_port);
                  }
            }

            if (nfds > 2) {
                  /* Check the MTC port (its not the same as the MMC port) */
                  
                  if ((pfd[2].revents & ~POLLIN)) {
                        error << compose(_("Transport: error polling extra MIDI port #2 (revents =%1%2%3"), &hex, pfd[1].revents, &dec) << endmsg;
                        break;
                  }
                  
                  if (pfd[2].revents & POLLIN) {
                        // cerr << "MTC port ready\n";
                        fds_ready++;
                        midi_read (_mtc_port);
                  }
            }

            /* timeout driven */
            
            if (fds_ready < 2 && timeout != -1) {

                  for (MidiTimeoutList::iterator i = midi_timeouts.begin(); i != midi_timeouts.end(); ) {
                        
                        MidiTimeoutList::iterator tmp;
                        tmp = i;
                        ++tmp;
                        
                        if (!(*i).call ()) {
                              midi_timeouts.erase (i);
                        }
                        
                        i = tmp;
                  }
            }
      }
}


Generated by  Doxygen 1.6.0   Back to index