f7c47f2abac39bfa93c10633f96ed2b4eaad90a5
[ardour.git] / libs / surfaces / mackie / mackie_midi_builder.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 #include "mackie_midi_builder.h"
19
20 #include <typeinfo>
21 #include <sstream>
22 #include <iomanip>
23 #include <algorithm>
24 #include <cmath>
25
26 #include "pbd/compose.h"
27
28 #include "ardour/debug.h"
29 #include "controls.h"
30 #include "control_group.h"
31
32 #include "strip.h"
33 #include "button.h"
34 #include "led.h"
35 #include "ledring.h"
36 #include "pot.h"
37 #include "fader.h"
38 #include "jog.h"
39 #include "meter.h"
40 #include "midi_byte_array.h"
41 #include "surface.h"
42 #include "surface_port.h"
43
44 using namespace PBD;
45 using namespace Mackie;
46 using namespace std;
47
48 #define NUCLEUS_DEBUG 1
49
50 MIDI::byte MackieMidiBuilder::calculate_pot_value (midi_pot_mode mode, const ControlState & state)
51 {
52         // TODO do an exact calc for 0.50? To allow manually re-centering the port.
53         
54         // center on or off
55         MIDI::byte retval =  (state.pos > 0.45 && state.pos < 0.55 ? 1 : 0) << 6;
56         
57         // mode
58         retval |=  (mode << 4);
59         
60         // value, but only if off hasn't explicitly been set
61         if  (state.led_state != off)
62                 retval +=  (int(state.pos * 10.0) + 1) & 0x0f; // 0b00001111
63         
64         return retval;
65 }
66
67 MidiByteArray MackieMidiBuilder::build_led_ring (const Pot & pot, const ControlState & state, midi_pot_mode mode )
68 {
69         return build_led_ring (pot.led_ring(), state, mode);
70 }
71
72 MidiByteArray MackieMidiBuilder::build_led_ring (const LedRing & led_ring, const ControlState & state, midi_pot_mode mode)
73 {
74         // The other way of doing this:
75         // 0x30 + pot/ring number (0-7)
76         //, 0x30 + led_ring.ordinal() - 1
77         return MidiByteArray  (3
78                                // the control type
79                                , midi_pot_id
80                                // the id
81                                , 0x20 + led_ring.raw_id()
82                                // the value
83                                , calculate_pot_value (mode, state)
84                 );
85 }
86
87 MidiByteArray MackieMidiBuilder::build_led (const Button & button, LedState ls)
88 {
89         return build_led (button.led(), ls);
90 }
91
92 MidiByteArray MackieMidiBuilder::build_led (const Led & led, LedState ls)
93 {
94         MIDI::byte state = 0;
95         switch  (ls.state())
96         {
97                 case LedState::on:                      state = 0x7f; break;
98                 case LedState::off:                     state = 0x00; break;
99                 case LedState::none:                    state = 0x00; break; // actually, this should never happen.
100                 case LedState::flashing:        state = 0x01; break;
101         }
102         
103         return MidiByteArray  (3
104                 , midi_button_id
105                 , led.raw_id()
106                 , state
107         );
108 }
109
110 MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos)
111 {
112         int posi = int (0x3fff * pos);
113         
114         return MidiByteArray  (3
115                                , midi_fader_id | fader.raw_id()
116                                // lower-order bits
117                                , posi & 0x7f
118                                // higher-order bits
119                                ,  (posi >> 7)
120                 );
121 }
122
123 MidiByteArray MackieMidiBuilder::zero_strip (Surface& surface, const Strip & strip)
124 {
125         Group::Controls::const_iterator it = strip.controls().begin();
126         MidiByteArray retval;
127
128         for (; it != strip.controls().end(); ++it) {
129                 Control & control = **it;
130                 if  (control.accepts_feedback())
131                         retval << zero_control (control);
132         }
133         
134         // These must have sysex headers
135
136         /* XXX: not sure about this check to only display stuff for strips of index < 8 */
137         if (strip.index() < 8) {
138                 retval << strip_display_blank (surface, strip, 0);
139                 retval << strip_display_blank (surface, strip, 1);
140         }
141         
142         return retval;
143 }
144
145 MidiByteArray MackieMidiBuilder::zero_control (const Control & control)
146 {
147         switch (control.type()) {
148         case Control::type_button:
149                 return build_led ((Button&)control, off);
150                 
151         case Control::type_led:
152                 return build_led ((Led&)control, off);
153                 
154         case Control::type_fader:
155                 return build_fader ((Fader&)control, 0.0);
156                 
157         case Control::type_pot:
158                 return build_led_ring (dynamic_cast<const Pot&> (control), off);
159                 
160         case Control::type_led_ring:
161                 return build_led_ring (dynamic_cast<const LedRing&> (control), off);
162                 
163         case Control::type_meter:
164                 return const_cast<Meter&>(dynamic_cast<const Meter&>(control)).update_message (0.0);
165                 
166         default:
167                 ostringstream os;
168                 os << "Unknown control type " << control << " in Strip::zero_control";
169                 throw MackieControlException (os.str());
170         }
171 }
172
173 char translate_seven_segment (char achar)
174 {
175         achar = toupper (achar);
176         if  (achar >= 0x40 && achar <= 0x60)
177                 return achar - 0x40;
178         else if  (achar >= 0x21 && achar <= 0x3f)
179       return achar;
180         else
181       return 0x00;
182 }
183
184 MidiByteArray MackieMidiBuilder::two_char_display (const std::string & msg, const std::string & dots)
185 {
186         if  (msg.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: msg must be exactly 2 characters");
187         if  (dots.length() != 2) throw MackieControlException ("MackieMidiBuilder::two_char_display: dots must be exactly 2 characters");
188         
189         MidiByteArray bytes (5, 0xb0, 0x4a, 0x00, 0x4b, 0x00);
190         
191         // chars are understood by the surface in right-to-left order
192         // could also exchange the 0x4a and 0x4b, above
193         bytes[4] = translate_seven_segment (msg[0]) +  (dots[0] == '.' ? 0x40 : 0x00);
194         bytes[2] = translate_seven_segment (msg[1]) +  (dots[1] == '.' ? 0x40 : 0x00);
195         
196         return bytes;
197 }
198
199 MidiByteArray MackieMidiBuilder::two_char_display (unsigned int value, const std::string & /*dots*/)
200 {
201         ostringstream os;
202         os << setfill('0') << setw(2) << value % 100;
203         return two_char_display (os.str());
204 }
205
206 MidiByteArray MackieMidiBuilder::strip_display_blank (Surface& surface, const Strip & strip, unsigned int line_number)
207 {
208         // 6 spaces, not 7 because strip_display adds a space where appropriate
209         return strip_display (surface, strip, line_number, "      ");
210 }
211
212 MidiByteArray MackieMidiBuilder::strip_display (Surface& surface, const Strip & strip, unsigned int line_number, const std::string & line)
213 {
214         assert (line_number <= 1);
215
216         MidiByteArray retval;
217         uint32_t index = strip.index() % 8;
218
219         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display index: %1, line %2 = %3\n", strip.index(), line_number, line));
220
221         // sysex header
222         retval << surface.sysex_hdr();
223         
224         // code for display
225         retval << 0x12;
226         // offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
227         retval << (index * 7 + (line_number * 0x38));
228         
229         // ascii data to display
230         retval << line;
231         // pad with " " out to 6 chars
232         for (int i = line.length(); i < 6; ++i) {
233                 retval << ' ';
234         }
235         
236         // column spacer, unless it's the right-hand column
237         if (strip.index() < 7) {
238                 retval << ' ';
239         }
240
241         // sysex trailer
242         retval << MIDI::eox;
243         
244         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display midi: %1\n", retval));
245
246         return retval;
247 }
248         
249 MidiByteArray MackieMidiBuilder::all_strips_display (SurfacePort & /*port*/, std::vector<std::string> & /*lines1*/, std::vector<std::string> & /*lines2*/)
250 {
251         MidiByteArray retval;
252         retval << 0x12 << 0;
253         // NOTE remember max 112 bytes per message, including sysex headers
254         retval << "Not working yet";
255         return retval;
256 }
257
258 MidiByteArray 
259 MackieMidiBuilder::timecode_display (Surface& surface, const std::string & timecode, const std::string & last_timecode)
260 {
261         // if there's no change, send nothing, not even sysex header
262         if  (timecode == last_timecode) return MidiByteArray();
263         
264         // length sanity checking
265         string local_timecode = timecode;
266
267         // truncate to 10 characters
268         if  (local_timecode.length() > 10) {
269                 local_timecode = local_timecode.substr (0, 10);
270         }
271
272         // pad to 10 characters
273         while  (local_timecode.length() < 10) { 
274                 local_timecode += " ";
275         }
276                 
277         // find the suffix of local_timecode that differs from last_timecode
278         std::pair<string::const_iterator,string::iterator> pp = mismatch (last_timecode.begin(), last_timecode.end(), local_timecode.begin());
279         
280         MidiByteArray retval;
281         
282         // sysex header
283         retval << surface.sysex_hdr();
284         
285         // code for timecode display
286         retval << 0x10;
287         
288         // translate characters. These are sent in reverse order of display
289         // hence the reverse iterators
290         string::reverse_iterator rend = reverse_iterator<string::iterator> (pp.second);
291         for  (string::reverse_iterator it = local_timecode.rbegin(); it != rend; ++it) {
292                 retval << translate_seven_segment (*it);
293         }
294         
295         // sysex trailer
296         retval << MIDI::eox;
297         
298         return retval;
299 }