drastic, fundamental redesign of MCP code
[ardour.git] / libs / surfaces / mackie / surface_port.cc
1 /*
2         Copyright (C) 2006,2007 John Anderson
3
4         This program is free software; you can redistribute it and/or modify
5         it under the terms of the GNU General Public License as published by
6         the Free Software Foundation; either version 2 of the License, or
7         (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12         GNU General Public License for more details.
13
14         You should have received a copy of the GNU General Public License
15         along with this program; if not, write to the Free Software
16         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <sstream>
20 #include <cstring>
21 #include <cerrno>
22
23 #include <sigc++/sigc++.h>
24 #include <boost/shared_array.hpp>
25
26 #include "midi++/types.h"
27 #include "midi++/port.h"
28 #include "midi++/manager.h"
29
30 #include "ardour/debug.h"
31 #include "ardour/rc_configuration.h"
32
33 #include "controls.h"
34 #include "mackie_control_exception.h"
35 #include "surface.h"
36 #include "surface_port.h"
37
38
39 #include "i18n.h"
40
41 using namespace std;
42 using namespace Mackie;
43 using namespace PBD;
44
45 /** @param input_port Input MIDI::Port; this object takes responsibility for removing it from
46  *  the MIDI::Manager and destroying it.
47  *  @param output_port Output MIDI::Port; responsibility similarly taken.
48  */
49 SurfacePort::SurfacePort (Surface& s, MIDI::Port & input_port, MIDI::Port & output_port)
50         : _surface (&s)
51         , _input_port (&input_port)
52         , _output_port (&output_port)
53         , _active (false)
54 {
55 }
56
57 SurfacePort::~SurfacePort()
58 {
59         close ();
60
61         MIDI::Manager* mm = MIDI::Manager::instance ();
62         
63         if (_input_port) {
64                 mm->remove_port (_input_port);
65                 delete _input_port;
66         }
67
68         if (_output_port) {
69                 mm->remove_port (_output_port);
70                 delete _output_port;
71         }
72 }
73
74 // wrapper for one day when strerror_r is working properly
75 string fetch_errmsg (int error_number)
76 {
77         char * msg = strerror (error_number);
78         return msg;
79 }
80         
81 MidiByteArray SurfacePort::read()
82 {
83         const int max_buf_size = 512;
84         MIDI::byte buf[max_buf_size];
85         MidiByteArray retval;
86
87         // check active. Mainly so that the destructor
88         // doesn't destroy the mutex while it's still locked
89         if  (!active()) {
90                 return retval;
91         }
92         
93         // return nothing read if the lock isn't acquired
94
95         // read port and copy to return value
96         int nread = input_port().read (buf, sizeof (buf));
97
98         if (nread >= 0) {
99                 retval.copy (nread, buf);
100                 if ((size_t) nread == sizeof (buf)) {
101 #ifdef PORT_DEBUG
102                         cout << "SurfacePort::read recursive" << endl;
103 #endif
104                         retval << read();
105                 }
106         } else {
107                 if  (errno != EAGAIN) {
108                         ostringstream os;
109                         os << "Surface: error reading from port: " << input_port().name();
110                         os << ": " << errno << fetch_errmsg (errno);
111
112                         cout << os.str() << endl;
113                         inactive_event();
114                         throw MackieControlException (os.str());
115                 }
116         }
117 #ifdef PORT_DEBUG
118         cout << "SurfacePort::read: " << retval << endl;
119 #endif
120         return retval;
121 }
122
123 void SurfacePort::write (const MidiByteArray & mba)
124 {
125         if (mba.empty()) {
126                 return;
127         }
128
129 #ifdef PORT_DEBUG
130         cout << "SurfacePort::write: " << mba << " to " << output_port().name() << endl;
131 #endif
132         
133         // check active before and after lock - to make sure
134         // that the destructor doesn't destroy the mutex while
135         // it's still in use
136         if (!active()) return;
137
138         int count = output_port().write (mba.bytes().get(), mba.size(), 0);
139         if  (count != (int)mba.size()) {
140                 if  (errno == 0) {
141                         cout << "port overflow on " << output_port().name() << ". Did not write all of " << mba << endl;
142                 } else if  (errno != EAGAIN) {
143                         ostringstream os;
144                         os << "Surface: couldn't write to port " << output_port().name();
145                         os << ", error: " << fetch_errmsg (errno) << "(" << errno << ")";
146                         
147                         cout << os.str() << endl;
148                         inactive_event();
149                 }
150         }
151 #ifdef PORT_DEBUG
152         cout << "SurfacePort::wrote " << count << endl;
153 #endif
154 }
155
156
157 void SurfacePort::open()
158 {
159         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::open %1\n", *this));
160         input_port().parser()->sysex.connect_same_thread (sysex_connection, boost::bind (&SurfacePort::handle_midi_sysex, this, _1, _2, _3));
161         _active = true;
162 }
163
164 void SurfacePort::close()
165 {
166         DEBUG_TRACE (DEBUG::MackieControl, "SurfacePort::close\n");
167         sysex_connection.disconnect();
168
169         if (_surface) {
170                 // faders to minimum
171                 _surface->write_sysex (0x61);
172                 // All LEDs off
173                 _surface->write_sysex (0x62);
174                 // Reset (reboot into offline mode)
175                 _surface->write_sysex (0x63);
176         }
177
178         _active = false;
179 }
180
181 void 
182 SurfacePort::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
183 {
184         MidiByteArray bytes (count, raw_bytes);
185
186         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
187
188         switch (bytes[5])
189         {
190                 case 0x01:
191                         _surface->write_sysex (host_connection_query (bytes));
192                         break;
193                 case 0x03:
194                         // not used right now
195                         _surface->write_sysex (host_connection_confirmation (bytes));
196                         break;
197                 case 0x04:
198                         inactive_event ();
199                         cout << "host connection error" << bytes << endl;
200                         break;
201                 case 0x14:
202                         // probe_emulation (bytes);
203                         break;
204                 default:
205                         cout << "unknown sysex: " << bytes << endl;
206         }
207 }
208
209 MidiByteArray calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
210 {
211         MidiByteArray l;
212         back_insert_iterator<MidiByteArray> back  (l);
213         copy (begin, end, back);
214         
215         MidiByteArray retval;
216         
217         // this is how to calculate the response to the challenge.
218         // from the Logic docs.
219         retval <<  (0x7f &  (l[0] +  (l[1] ^ 0xa) - l[3]));
220         retval <<  (0x7f &  ( (l[2] >> l[3]) ^  (l[0] + l[3])));
221         retval <<  (0x7f &  ((l[3] -  (l[2] << 2)) ^  (l[0] | l[1])));
222         retval <<  (0x7f &  (l[1] - l[2] +  (0xf0 ^  (l[3] << 4))));
223         
224         return retval;
225 }
226
227 // not used right now
228 MidiByteArray SurfacePort::host_connection_query (MidiByteArray & bytes)
229 {
230         MidiByteArray response;
231
232         // handle host connection query
233         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
234         
235         if  (bytes.size() != 18) {
236                 cerr << "expecting 18 bytes, read " << bytes << " from " << input_port().name() << endl;
237                 return response;
238         }
239
240         // build and send host connection reply
241         response << 0x02;
242         copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response));
243         response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4);
244         return response;
245 }
246
247 // not used right now
248 MidiByteArray SurfacePort::host_connection_confirmation (const MidiByteArray & bytes)
249 {
250         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
251         
252         // decode host connection confirmation
253         if  (bytes.size() != 14) {
254                 ostringstream os;
255                 os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
256                 throw MackieControlException (os.str());
257         }
258         
259         // send version request
260         return MidiByteArray (2, 0x13, 0x00);
261 }
262
263
264 ostream & Mackie::operator <<  (ostream & os, const SurfacePort & port)
265 {
266         os << "{ ";
267         os << "name: " << port.input_port().name() << " " << port.output_port().name();
268         os << "; ";
269         os << " }";
270         return os;
271 }
272