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