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