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