tag:blogger.com,1999:blog-54425402701517106982024-03-17T23:04:30.301-04:00_FrameworkAn Introduction to the Ableton Framework ClassesHanz Petrovhttp://www.blogger.com/profile/11357582251433610792noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-5442540270151710698.post-84626237920247664102010-09-07T22:00:00.004-04:002010-09-08T00:19:03.841-04:00Introduction to the Framework Classes Part 4 - the Final Chapter<div class="separator" style="clear: both; text-align: center;"></div><br />
<b>Introduction</b><br />
<br />
In <a href="http://remotescripts.blogspot.com/2010/03/introduction-to-framework-classes.html">Part 1</a> of this series, I mentioned that my journey into the world of MIDI remote scripts began with a search for a better way of integrating my FCB1010 foot controller into my Live setup. Well, it’s been a fun trip - with lots of interesting side investigations along the way - but now we’ve come full circle and it’s time to finish up what I set out to do in the beginning. In this article, we’ll have a look a couple of new _Framework methods introduced in version 8.1.3, which will allow us to operate scripts in <i>Combination Mode</i>, and we’ll create a generic script which will allow the FCB1010 to work in concert with the APC40 – in fact, we’ll set it up to <i>emulate</i> the APC40. And then we’re pretty much done. Let’s start with combination mode. <br />
<br />
<b>Combination Mode</b> – red and yellow and pink and green…<br />
<br />
As we saw in Part 1, <i>set_show_highlight</i>, a SessionComponent method, can be used to display the famous “red box”, which represents the portion of a Session View which a control surface is controlling. First seen with the APC40 and Launchpad, the red box is a must have for any clip launcher. New since 8.1.4, however, is a functional Combination Mode – specifically announced for the APC40 and APC20 – which “glues” two or more session highlights together. From the 8.1.4 changelog:<br />
<br />
<i>“Combination Mode is now active when multiple Akai APC40/20s are in use. This means that the topmost controller selected in your preferences will control tracks 1-8, the second controller selected will control tracks 9-16, and so on.”</i><br />
<br />
Sounds like fun – let’s see how it’s done.<br />
<br />
By looking through the APC sources, we can work our way back to the essential change at work here: the SessionComponent class now has new methods called <i>_link</i> and <i>_unlink</i> (together with a new attribute <i>_is_linked</i>, and a new list object known as <i>_linked_session_instances</i>). Linking sessions turns out to be no more difficult than calling the SessionComponent’s _link method, as follows:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">session</span> <span class="o">=</span> <span class="n">SessionComponent</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">,</span> <span class="n">num_scenes</span><span class="p">)</span>
<span class="n">session</span><span class="o">.</span><span class="n">_link</span><span class="p">()</span>
</pre></div></div><br />
Now, although multiple session boxes can be linked in this way, we need to manage our session offsets, if we want our sessions to sit beside each other, and not one on top of the other. In other words, we will need to sequentially assign non-zero offsets to our linked sessions, based on the adjacent session’s width. We'll add the required code to the ProjectX script from Part 1, as an illustration.<br />
<br />
First, we’ll need a list object to hold the active instances of our ProjectX ControlSurface class, and a static method which will be called at the end of initialisation:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">_active_instances</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">_combine_active_instances</span><span class="p">():</span>
<span class="n">track_offset</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">ProjectX</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">:</span>
<span class="n">instance</span><span class="o">.</span><span class="n">_activate_combination_mode</span><span class="p">(</span><span class="n">track_offset</span><span class="p">)</span>
<span class="n">track_offset</span> <span class="o">+=</span> <span class="n">session</span><span class="o">.</span><span class="n">width</span><span class="p">()</span>
<span class="n">_combine_active_instances</span> <span class="o">=</span> <span class="nb">staticmethod</span><span class="p">(</span><span class="n">_combine_active_instances</span><span class="p">)</span>
</pre></div></div><br />
We add a <i> _do_combine</i> call at the end of our init sequence, which in turn calls the <i>_combine_active_instances</i> static method – and <i>_combine_active_instances</i> calls <i>_activate_combination_mode</i> on each instance.<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_do_combine</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">ProjectX</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">:</span>
<span class="n">ProjectX</span><span class="o">.</span><span class="n">_active_instances</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">ProjectX</span><span class="o">.</span><span class="n">_combine_active_instances</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">_activate_combination_mode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">track_offset</span><span class="p">):</span>
<span class="k">if</span> <span class="n">session</span><span class="o">.</span><span class="n">_is_linked</span><span class="p">():</span>
<span class="n">session</span><span class="o">.</span><span class="n">_unlink</span><span class="p">()</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="n">track_offset</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">session</span><span class="o">.</span><span class="n">_link</span><span class="p">()</span>
</pre></div></div><br />
Now that the linking is all set up, we’ll define a <i>_do_uncombine</i> method, to clean things up when we disconnect; we’ll unlink our SessionComponent and remove ourselves from the list of active instances here.<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_do_uncombine</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="p">((</span><span class="bp">self</span> <span class="ow">in</span> <span class="n">ProjectX</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">)</span> <span class="ow">and</span> <span class="n">ProjectX</span><span class="o">.</span><span class="n">_active_instances</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
<span class="n">ProjectX</span><span class="o">.</span><span class="n">_combine_active_instances</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_do_uncombine</span><span class="p">()</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</pre></div></div><br />
So here’s what we get when we load several instances of our new ProjectX script via Live's MIDI preferences dialog:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hNCLWrEmihze3e3BN7XwW-l9fYYpOSgMYaaS7-krCX42_dljF9APXw3iLSdeRYij-7Po1rhJWdh6qRxHKyy1EBekAxf3WOYfiS4crIc9vB71BIBiVdfk9ovXgZE8NgSr9HVlmn4sDpMN/s1600/ProjectX_combo_mode.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7hNCLWrEmihze3e3BN7XwW-l9fYYpOSgMYaaS7-krCX42_dljF9APXw3iLSdeRYij-7Po1rhJWdh6qRxHKyy1EBekAxf3WOYfiS4crIc9vB71BIBiVdfk9ovXgZE8NgSr9HVlmn4sDpMN/s320/ProjectX_combo_mode.jpg" /></a></div><br />
<br />
The session highlights are all linked (with an automatic track offset for each instance, which matches the adjacent session highlight width), and when we move any one of them, the others move along together. By changing the offsets in <i>_activate_combination_mode</i>, we can get them to stack side-by-side, or one above the other, or if we wanted to, we could indeed stack them <i>one on top of the other</i>. By stacking them one on top of the other, we can control a single session highlight zone with multiple controllers – which is exactly what we want to do with the APC40 and FCB1010 in combination mode.<br />
<br />
As of version 8.1.3, the Framework scripts have included support for session linking, but so far, the only official scripts which implement combination mode are the APC scripts (as of 8.1.4). As shown above, however, support for combination mode can be extended to pretty much any Framework-based script. What we want to create then, is a script which will allow the FCB1010 to operate in combination mode together with the APC40. Let’s build one.<br />
<br />
<b>FCB1020</b> - a new script for the FCB1010<br />
<br />
The first thing to do when setting out to create a new script is to decide on a functional layout. If we know what we’re trying to achieve in terms of functionality and operation - before we touch a line of code - we can save ourselves a good deal of time down the road. In this case, we’re looking for an arrangement which will allow the FCB1010 to mirror the operation of the APC40, in so far as possible, and allow for optimized hands-free operation.<br />
<br />
Although the options for designing a control script are almost unlimited, generally, the resulting method of operation needs to be intuitive. The FCB1010 has some built-in constraints, but also offers a great deal of flexibility. We have 10 banks of 10 switches and 2 pedals to work with – equivalent to 100 “button” controls and 20 “sliders”. Interestingly, the APC40 has a similar number of buttons and knobs.<br />
<br />
If we look at the two controllers side-by-side, a pattern emerges. Each column of the APC40’s grid consists of 5 clip launch buttons (one per scene) and 5 track control buttons (clip stop, track select, activate, solo, & record). Each of the FCB1010’s 10 banks has 5 switches on the top row, and 5 switches on the bottom row. Based on this parallel, if we assign one FCB1010 bank to each of the APC40’s track columns, the resulting operation will indeed be intuitive, and will closely follow the APC’s layout. We only have 2 pedals per bank, however, so we’ll map them to Track Volume, and Send A – at least for now. Here’s how a typical FCB1010 bank will lay out, together with the APC40 layout for comparison.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvs4ZmjjuljRcVfA06-gmV5t2IxwBiRMqMEQrYHCxY0_YHLJXTBUokioq2wgOelYMAf4qDs6dAa2jv1BklOg2YVI34tFLs9HkAs3dzmz1C4max0Fq2kkyAC8_Qh0i1evqjorvv7zl9QFOI/s1600/FCB1020_Bank_01.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvs4ZmjjuljRcVfA06-gmV5t2IxwBiRMqMEQrYHCxY0_YHLJXTBUokioq2wgOelYMAf4qDs6dAa2jv1BklOg2YVI34tFLs9HkAs3dzmz1C4max0Fq2kkyAC8_Qh0i1evqjorvv7zl9QFOI/s400/FCB1020_Bank_01.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Bank 01</td></tr>
</tbody></table><br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSuP1WpbgclxQ7YcJ257gBgX57OoolNbnO1AlXify-qBf7XmgBpqzg_kqz7YM2xjFadyfcdUYb8p5Ks5b8fVScZKc92VDrCXfP-u0JgvomkPH1Ey7I5G0N_FDfzGw5pRBrzVuL2JpHUKRh/s1600/APC40_layout.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="291" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSuP1WpbgclxQ7YcJ257gBgX57OoolNbnO1AlXify-qBf7XmgBpqzg_kqz7YM2xjFadyfcdUYb8p5Ks5b8fVScZKc92VDrCXfP-u0JgvomkPH1Ey7I5G0N_FDfzGw5pRBrzVuL2JpHUKRh/s400/APC40_layout.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">APC40</td></tr>
</tbody></table><br />
We’ll use this layout for banks 1 through 8 (since the APC40 has 8 track control columns), but because the FCB1010 has 10 banks in total, we have 2 banks left over. Let’s use bank 00 for the Master Track controls, and the scene launch controls, in a similar arrangement to banks 1 through 8. There are no activate, solo or record buttons for the master track, so instead, we’ll map global play, stop and record here:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXXgYpEA7seUvCj08fasVseNlGKyQRZbXu_spz2OhgYVWi_JJT6fnsv3QIyJnoAGE4-JqhyRi3Z0Se2il0YJSCqD97A724p7HaMlEyYZwesQ75VxiwA3GUnxf1lZX3Xcg1x3FJpYntAI1g/s1600/FCB1020_Bank_00.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXXgYpEA7seUvCj08fasVseNlGKyQRZbXu_spz2OhgYVWi_JJT6fnsv3QIyJnoAGE4-JqhyRi3Z0Se2il0YJSCqD97A724p7HaMlEyYZwesQ75VxiwA3GUnxf1lZX3Xcg1x3FJpYntAI1g/s400/FCB1020_Bank_00.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Bank 00</td></tr>
</tbody></table><br />
<br />
Now we only have one bank left - bank 09. We’ll use bank 09 for session and track navigation, and for device control. Here’s how it will look:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioWIMCUjSPVOfpgocOyXaDuTaxoYwGfkwBKPkEjqmMAEPBkM30kn7tvtGcWL2eDD_eMjfeh58Q50bECFQLoJE4AB_mmQaexEv08OH68NfusDqVUIjlpVkXW_KaD8htI3rDpO3CuuXWgiac/s1600/FCB1020_Bank_09.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioWIMCUjSPVOfpgocOyXaDuTaxoYwGfkwBKPkEjqmMAEPBkM30kn7tvtGcWL2eDD_eMjfeh58Q50bECFQLoJE4AB_mmQaexEv08OH68NfusDqVUIjlpVkXW_KaD8htI3rDpO3CuuXWgiac/s400/FCB1020_Bank_09.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Bank 09</td></tr>
</tbody></table><br />
Although fairly intuitive, the layout described above might not suit everyone’s preferences. Wouldn’t it be nice if the end user could decide on his or her own preferred layout? Live’s User Remote Scripts allow for this kind of thing, so we’ll take a similar approach. Rather than hard-coding the note and controller mappings deep within our new script, we’ll pull all of the assignments out into a separate file, for easy access and editing. It will remain a python .py file (not a .txt file) - in the tradition of consts.py, of Mackie emulation fame - but since .py files are simple text files, they can be edited using any text editor. We’ll call our file <i>MIDI_map.py</i>. Here’s a sample of what it will contain:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="c"># General</span>
<span class="n">PLAY</span> <span class="o">=</span> <span class="mi">7</span> <span class="c">#Global play</span>
<span class="n">STOP</span> <span class="o">=</span> <span class="mi">8</span> <span class="c">#Global stop</span>
<span class="n">REC</span> <span class="o">=</span> <span class="mi">9</span> <span class="c">#Global record</span>
<span class="n">TAPTEMPO</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Tap tempo</span>
<span class="n">NUDGEUP</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Tempo Nudge Up</span>
<span class="n">NUDGEDOWN</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Tempo Nudge Down</span>
<span class="n">UNDO</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Undo</span>
<span class="n">REDO</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Redo</span>
<span class="n">LOOP</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Loop on/off</span>
<span class="n">PUNCHIN</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Punch in</span>
<span class="n">PUNCHOUT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Punch out</span>
<span class="n">OVERDUB</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Overdub on/off</span>
<span class="n">METRONOME</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Metronome on/off</span>
<span class="n">RECQUANT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Record quantization on/off</span>
<span class="n">DETAILVIEW</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Detail view switch</span>
<span class="n">CLIPTRACKVIEW</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Clip/Track view switch</span>
<span class="c"># Device Control</span>
<span class="n">DEVICELOCK</span> <span class="o">=</span> <span class="mi">99</span> <span class="c">#Device Lock (lock "blue hand")</span>
<span class="n">DEVICEONOFF</span> <span class="o">=</span> <span class="mi">94</span> <span class="c">#Device on/off</span>
<span class="n">DEVICENAVLEFT</span> <span class="o">=</span> <span class="mi">92</span> <span class="c">#Device nav left</span>
<span class="n">DEVICENAVRIGHT</span> <span class="o">=</span> <span class="mi">93</span> <span class="c">#Device nav right</span>
<span class="n">DEVICEBANKNAVLEFT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Device bank nav left</span>
<span class="n">DEVICEBANKNAVRIGHT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Device bank nav right</span>
<span class="c"># Arrangement View Controls</span>
<span class="n">SEEKFWD</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Seek forward</span>
<span class="n">SEEKRWD</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Seek rewind</span>
<span class="c"># Session Navigation (aka "red box")</span>
<span class="n">SESSIONLEFT</span> <span class="o">=</span> <span class="mi">95</span> <span class="c">#Session left</span>
<span class="n">SESSIONRIGHT</span> <span class="o">=</span> <span class="mi">96</span> <span class="c">#Session right</span>
<span class="n">SESSIONUP</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Session up</span>
<span class="n">SESSIONDOWN</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Session down</span>
<span class="n">ZOOMUP</span> <span class="o">=</span> <span class="mi">97</span> <span class="c">#Session Zoom up</span>
<span class="n">ZOOMDOWN</span> <span class="o">=</span> <span class="mi">98</span> <span class="c">#Session Zoom down</span>
<span class="n">ZOOMLEFT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Session Zoom left</span>
<span class="n">ZOOMRIGHT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Session Zoom right</span>
<span class="c"># Track Navigation</span>
<span class="n">TRACKLEFT</span> <span class="o">=</span> <span class="mi">90</span> <span class="c">#Track left</span>
<span class="n">TRACKRIGHT</span> <span class="o">=</span> <span class="mi">91</span> <span class="c">#Track right</span>
<span class="c"># Scene Navigation</span>
<span class="n">SCENEUP</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Scene down</span>
<span class="n">SCENEDN</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Scene up</span>
<span class="c"># Scene Launch</span>
<span class="n">SELSCENELAUNCH</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span> <span class="c">#Selected scene launch</span>
</pre></div></div><br />
Now we can easily change the layout of any of our banks, by editing this one file. In fact, pretty much anything goes – if we wanted to, we could have different layouts for each of the 10 banks, or leave some banks unassigned, for use with guitar effects, etc. To help with layout planning, an editable PDF template for the FCB1010 is included with the source code on the <a href="http://remotescripts.blogspot.com/p/support-files.html">Support Files</a> page.<br />
<br />
Okay, so now it’s time to assemble the code. Since we’re essentially emulating the APC40 here (yes, I admit that I was wrong in Part 2; APC40 emulation is not so crazy after all), we have a choice between starting with the APC scripts and customizing, or building a new set of scripts which have similar functionality. Since we won’t be supporting shifted operations in our script (for the FCB1010 this would require operation with two feet – difficult to do in an upright position), we will need to make significant changes to the APC scripts. Starting from scratch is definitely an option worth considering. On the other hand, the APC40 script will make for a good roadmap, and while we’re at it, we can include some of the special features of the APC40_22 script from Part 3 here as well.<br />
<br />
The structure will be fairly simple. We’ll have an <i>__init__.py</i> module (to identify the directory as a python package), a main module (called <i>FCB1020.py</i>), a <i>MIDI_map.py</i> constants file, and several “special” Framework component override modules. Here’s the file list (compete source code is available on the Support Files page):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">__init__</span><span class="o">.</span><span class="n">py</span>
<span class="n">FCB1020</span><span class="o">.</span><span class="n">py</span>
<span class="n">MIDI_Map</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialChannelStripComponent</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialMixerComponent</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialSessionComponent</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialTransportComponent</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialViewControllerComponent</span><span class="o">.</span><span class="n">py</span>
<span class="n">SpecialZoomingComponent</span><span class="o">.</span><span class="n">py</span>
</pre></div></div><br />
We won’t go into detail on the Special components, since that topic was covered in <a href="http://remotescripts.blogspot.com/2010/05/introduction-to-framework-classes-3.html">Part 3</a>. The main module follows the structure outlined in Part 1, but here's a quick overview. We start with the imports, and then define the combination mode static method (as discussed above):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span>
<span class="kn">from</span> <span class="nn">_Framework.InputControlElement</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">_Framework.SliderElement</span> <span class="kn">import</span> <span class="n">SliderElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonMatrixElement</span> <span class="kn">import</span> <span class="n">ButtonMatrixElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelStripComponent</span> <span class="kn">import</span> <span class="n">ChannelStripComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.DeviceComponent</span> <span class="kn">import</span> <span class="n">DeviceComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurfaceComponent</span> <span class="kn">import</span> <span class="n">ControlSurfaceComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.SessionZoomingComponent</span> <span class="kn">import</span> <span class="n">SessionZoomingComponent</span>
<span class="kn">from</span> <span class="nn">SpecialMixerComponent</span> <span class="kn">import</span> <span class="n">SpecialMixerComponent</span>
<span class="kn">from</span> <span class="nn">SpecialTransportComponent</span> <span class="kn">import</span> <span class="n">SpecialTransportComponent</span>
<span class="kn">from</span> <span class="nn">SpecialSessionComponent</span> <span class="kn">import</span> <span class="n">SpecialSessionComponent</span>
<span class="kn">from</span> <span class="nn">SpecialZoomingComponent</span> <span class="kn">import</span> <span class="n">SpecialZoomingComponent</span>
<span class="kn">from</span> <span class="nn">SpecialViewControllerComponent</span> <span class="kn">import</span> <span class="n">DetailViewControllerComponent</span>
<span class="kn">from</span> <span class="nn">MIDI_Map</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">FCB1020</span><span class="p">(</span><span class="n">ControlSurface</span><span class="p">):</span>
<span class="n">__doc__</span> <span class="o">=</span> <span class="s">" Script for FCB1010 in APC emulation mode "</span>
<span class="n">_active_instances</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">_combine_active_instances</span><span class="p">():</span>
<span class="n">track_offset</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">scene_offset</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">instance</span> <span class="ow">in</span> <span class="n">FCB1020</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">:</span>
<span class="n">instance</span><span class="o">.</span><span class="n">_activate_combination_mode</span><span class="p">(</span><span class="n">track_offset</span><span class="p">,</span> <span class="n">scene_offset</span><span class="p">)</span>
<span class="n">track_offset</span> <span class="o">+=</span> <span class="n">instance</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">width</span><span class="p">()</span>
<span class="n">_combine_active_instances</span> <span class="o">=</span> <span class="nb">staticmethod</span><span class="p">(</span><span class="n">_combine_active_instances</span><span class="p">)</span>
</pre></div></div><br />
Next we have our init method, where we instantiate our ControlSurface component and call the various setup methods. We setup the session, then setup the mixer, then assign the mixer to the session, to keep them in sync. The disconnect method follows, where we provide some cleanup for when the control surface is disconnected:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span> <span class="o">=</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span> <span class="o">=</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_load_MIDI_map</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session_zoom</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_session_control</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_mixer_control</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_mixer</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_device_and_transport_control</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_pads</span> <span class="o">=</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_load_pad_translations</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_do_combine</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_pads</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_do_uncombine</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session_zoom</span> <span class="o">=</span> <span class="bp">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span> <span class="o">=</span> <span class="bp">None</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</pre></div></div><br />
The balance of the combination mode methods are next:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_do_combine</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">FCB1020</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">:</span>
<span class="n">FCB1020</span><span class="o">.</span><span class="n">_active_instances</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">FCB1020</span><span class="o">.</span><span class="n">_combine_active_instances</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">_do_uncombine</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="p">((</span><span class="bp">self</span> <span class="ow">in</span> <span class="n">FCB1020</span><span class="o">.</span><span class="n">_active_instances</span><span class="p">)</span> <span class="ow">and</span> <span class="n">FCB1020</span><span class="o">.</span><span class="n">_active_instances</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
<span class="n">FCB1020</span><span class="o">.</span><span class="n">_combine_active_instances</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">_activate_combination_mode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">track_offset</span><span class="p">,</span> <span class="n">scene_offset</span><span class="p">):</span>
<span class="k">if</span> <span class="n">TRACK_OFFSET</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
<span class="n">track_offset</span> <span class="o">=</span> <span class="n">TRACK_OFFSET</span>
<span class="k">if</span> <span class="n">SCENE_OFFSET</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
<span class="n">scene_offset</span> <span class="o">=</span> <span class="n">SCENE_OFFSET</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">link_with_track_offset</span><span class="p">(</span><span class="n">track_offset</span><span class="p">,</span> <span class="n">scene_offset</span><span class="p">)</span>
</pre></div></div><br />
The session setup is based on Framework SessionComponent methods, with SessionZoomingComponent navigation thrown in for good measure:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_setup_session_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span> <span class="o">=</span> <span class="n">SpecialSessionComponent</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Session_Control'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_track_bank_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SESSIONRIGHT</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SESSIONLEFT</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_scene_bank_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SESSIONDOWN</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SESSIONUP</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_select_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SCENEDN</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SCENEUP</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_scene_launch_buttons</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SCENELAUNCH</span><span class="p">[</span><span class="n">index</span><span class="p">]]</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_track_stop_buttons</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKSTOP</span><span class="p">[</span><span class="n">index</span><span class="p">]]</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_stop_all_clips_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">STOPALLCLIPS</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_stop_track_clip_buttons</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_track_stop_buttons</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_stop_track_clip_value</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">selected_scene</span><span class="p">()</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Selected_Scene'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">selected_scene</span><span class="p">()</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SELSCENELAUNCH</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">set_slot_launch_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SELCLIPLAUNCH</span><span class="p">])</span>
<span class="k">for</span> <span class="n">scene_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
<span class="n">scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="n">scene</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Scene_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="n">button_row</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">scene</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_scene_launch_buttons</span><span class="p">[</span><span class="n">scene_index</span><span class="p">])</span>
<span class="n">scene</span><span class="o">.</span><span class="n">set_triggered_value</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">button</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">CLIPNOTEMAP</span><span class="p">[</span><span class="n">scene_index</span><span class="p">][</span><span class="n">track_index</span><span class="p">]]</span>
<span class="n">button_row</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="n">clip_slot</span> <span class="o">=</span> <span class="n">scene</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Clip_Slot_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session_zoom</span> <span class="o">=</span> <span class="n">SpecialZoomingComponent</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session_zoom</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Session_Overview'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_session_zoom</span><span class="o">.</span><span class="n">set_nav_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">ZOOMUP</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">ZOOMDOWN</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">ZOOMLEFT</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">ZOOMRIGHT</span><span class="p">])</span>
</pre></div></div><br />
Mixer, device, and transport setup methods are similar.<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_setup_mixer_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span> <span class="o">=</span> <span class="n">SpecialMixerComponent</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Mixer'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Master_Channel_Strip'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">MASTERSEL</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Selected_Channel_Strip'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">set_select_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKRIGHT</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKLEFT</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">set_crossfader_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">CROSSFADER</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">set_prehear_volume_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">CUELEVEL</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">MASTERVOLUME</span><span class="p">])</span>
<span class="k">for</span> <span class="n">track</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">strip</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="n">track</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Channel_Strip_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_arm_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKREC</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_solo_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKSOLO</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_mute_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKMUTE</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TRACKSEL</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TRACKVOL</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_pan_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TRACKPAN</span><span class="p">[</span><span class="n">track</span><span class="p">]])</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_send_controls</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TRACKSENDA</span><span class="p">[</span><span class="n">track</span><span class="p">]],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TRACKSENDB</span><span class="p">[</span><span class="n">track</span><span class="p">]],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TRACKSENDC</span><span class="p">[</span><span class="n">track</span><span class="p">]]))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_invert_mute_feedback</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
</pre></div></div><br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_setup_device_and_transport_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span> <span class="o">=</span> <span class="n">DeviceComponent</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Device_Component'</span>
<span class="n">device_bank_buttons</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">device_param_controls</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">device_param_controls</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">PARAMCONTROL</span><span class="p">[</span><span class="n">index</span><span class="p">]])</span>
<span class="n">device_bank_buttons</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICEBANK</span><span class="p">[</span><span class="n">index</span><span class="p">]])</span>
<span class="k">if</span> <span class="bp">None</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">device_bank_buttons</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">set_bank_buttons</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">set_parameter_controls</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">device_param_controls</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">set_on_off_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICEONOFF</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">set_bank_nav_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICEBANKNAVLEFT</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICEBANKNAVRIGHT</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="o">.</span><span class="n">set_lock_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICELOCK</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_device_component</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_device</span><span class="p">)</span>
<span class="n">detail_view_toggler</span> <span class="o">=</span> <span class="n">DetailViewControllerComponent</span><span class="p">()</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Detail_View_Control'</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_device_clip_toggle_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">CLIPTRACKVIEW</span><span class="p">])</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_detail_toggle_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DETAILVIEW</span><span class="p">])</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_device_nav_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICENAVLEFT</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">DEVICENAVRIGHT</span><span class="p">]</span> <span class="p">)</span>
<span class="n">transport</span> <span class="o">=</span> <span class="n">SpecialTransportComponent</span><span class="p">()</span>
<span class="n">transport</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Transport'</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_play_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">PLAY</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_stop_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">STOP</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_record_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">REC</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_nudge_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">NUDGEUP</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">NUDGEDOWN</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_undo_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">UNDO</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_redo_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">REDO</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_tap_tempo_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">TAPTEMPO</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_quant_toggle_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">RECQUANT</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_overdub_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">OVERDUB</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_metronome_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">METRONOME</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_tempo_control</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="p">[</span><span class="n">TEMPOCONTROL</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_loop_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">LOOP</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_seek_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SEEKFWD</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">SEEKRWD</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_punch_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">PUNCHIN</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="p">[</span><span class="n">PUNCHOUT</span><span class="p">])</span>
</pre></div></div><br />
We’ve also included a <i>DetailViewComponent</i> above, which communicates session view changes via the Live API. Next is <i>_on_selected_track_changed</i>, a ControlSurface class method override, which keeps the selected track’s device in focus. And for drum rack note mapping, we’ve included a <i>_load_pad_translations</i> method, which adds x and y offsets to the Drum Rack note and channel assignments, which are set in the <i>MIDI_map.py</i> file. This allows us to pass the translations array as an argument to the ControlSurface <i>set_pad_translations</i> method in the expected format.<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_on_selected_track_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">_on_selected_track_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">track</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_track</span>
<span class="n">device_to_select</span> <span class="o">=</span> <span class="n">track</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_device</span>
<span class="k">if</span> <span class="n">device_to_select</span> <span class="o">==</span> <span class="bp">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">track</span><span class="o">.</span><span class="n">devices</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span>
<span class="n">device_to_select</span> <span class="o">=</span> <span class="n">track</span><span class="o">.</span><span class="n">devices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">if</span> <span class="n">device_to_select</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">select_device</span><span class="p">(</span><span class="n">device_to_select</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device_component</span><span class="o">.</span><span class="n">set_device</span><span class="p">(</span><span class="n">device_to_select</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_load_pad_translations</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="o">-</span><span class="mi">1</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">DRUM_PADS</span><span class="p">:</span>
<span class="n">pad</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">col</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">pad</span> <span class="o">=</span> <span class="p">(</span><span class="n">col</span><span class="p">,</span> <span class="n">row</span><span class="p">,</span> <span class="n">DRUM_PADS</span><span class="p">[</span><span class="n">row</span><span class="o">*</span><span class="mi">4</span> <span class="o">+</span> <span class="n">col</span><span class="p">],</span> <span class="n">PADCHANNEL</span><span class="p">,)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_pads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pad</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_pad_translations</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_pads</span><span class="p">))</span>
</pre></div></div><br />
Finally, we have <i>_load_MIDI_map</i>. Here, we create a list of <i>ButtonElements</i> and a list of <i>SliderElements</i>. When we make mapping assignments in our MIDI_map.py file, we are actually indexing objects from these lists. By instantiating the ButtonElements and SliderElements as independent objects, we limit the risk of duplicate MIDI assignments, which would prevent our script from loading. Any particular MIDI note/channel message from a control surface can only be assigned to a single InputControlElement (such as a button or slider), however, an InputControlElement can be used more than once, with different components. This setup also allows us to append <i>None</i> to the end of each list, so that null assignments can be specified in the MIDI_map file, by using -1 in place of a note number (in python, [-1] corresponds to the last element of a list).<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_load_MIDI_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">for</span> <span class="n">note</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="n">button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">NOTECHANNEL</span><span class="p">,</span> <span class="n">note</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Note_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">note</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_note_map</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span> <span class="c">#add None to the end of the list, selectable with [-1]</span>
<span class="k">for</span> <span class="n">ctrl</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="n">control</span> <span class="o">=</span> <span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">CTRLCHANNEL</span><span class="p">,</span> <span class="n">ctrl</span><span class="p">)</span>
<span class="n">control</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Ctrl_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">ctrl</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">control</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_ctrl_map</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>
</pre></div></div><br />
Now, speaking of MIDI assignments, since all of our mappings are editable, and grouped in a separate file, couldn’t we use our script with just about <i>any</i> control surface, and not only the FCB1010? Yes, indeed we could.<br />
<br />
<b>Generic APC Emulation</b><br />
<br />
Our new FCB10120 script can be used as a generic APC emulator, since it merely maps MIDI Note and CC input to specific _Framework component functions, mimicking the APC script setup. In fact, none of this is very different from the User script mechanism provided by Ableton - although our script has a few extra features that the User script does not support, including Session Highlighting (aka “red box”), and the ability to work in combination mode, with an APC40, or with another instance of itself, or with any other controller which supports combination mode.<br />
<br />
Some setup is required, however, to accommodate alternate controller configurations. These configurations could be alternate configurations for the same control surface, or alternate configurations for different control surfaces.<br />
<br />
Probably the simplest way of setting up an alternate configuration, is to create one or more copies of the FCB1020 script folder, modify the assignments in the MIDI_map.py file as required, and then re-name the directory to suit. The new folder will be selectable by name from Live’s control surface drop-down list, the next time Live is started. This way, one could have, say, <i>FCB1020_device_mode</i> and <i>FCB1020_transport_mode</i> as separate configurations, listed one above the other in the control surface drop-down. Note however, that one should avoid leading underscores in folder name, unless a folder is intended to be hidden.<br />
<br />
Another way to accommodate alternate setups would be to reprogram the control surface itself – where this is possible - to match the note and CC mappings found in the MIDI_map.py file. Depending on the control surface, this could be done manually, with stand-alone software, or by using Live’s “dump” button from the preferences dialog (in fact, a sysex file for the FCB1010 is included with the support files package which accompanies this article, for this purpose).<br />
<br />
Of course, our script can also be used as a generic APC emulator for <i>multiple controllers at the same time</i>. This can be done a number of ways, including:<br />
1) Daisy-chain several control surfaces using MIDI Thru ports;<br />
2) Load the script multiple times, using multiple slots; <br />
3) Create multiple copies of the script folder, rename to suit, and load into different slots.<br />
<br />
For multiple control surfaces which use different MIDI channels, separate instances of the script would need to be loaded, from separate folders, with the channel assignments in the MIDI_maps.py modified to suit in each folder.<br />
<br />
And getting back to our original design setup, we can see that the FCB1010 and the APC40 now work well together in Combination Mode, and the FCB1010 is able to control of most of the APC’s functions – within one session highlight area, and without loss of focus. We have included a good deal of flexibility in our script too, so we can easily modify the various bank layouts to suit our needs, as they develop and change.<br />
<b><br />
Conclusion</b><br />
<br />
In this new era of multi-touch controllers, it’s nice to know that a sturdy old workhorse like the FCB1010 still has a place in our arsenal of control surfaces - and that it works well in combination with the soon-to-be-a-classic and not-yet-obsolete APC40. As for MIDI remote scripts, they’re still at the heart of all control surface communications with the Live API - and the _Framework classes have been holding their own for quite a while now, with interesting new methods being added from time to time. Hopefully, this series of articles has been useful, and will encourage others to share their findings with the Live community. Happy scripting.<br />
<br />
Hanz Petrov<br />
September 7, 2010<br />
<br />
hanz.petrov<br />
at gmail.comHanz Petrovhttp://www.blogger.com/profile/11357582251433610792noreply@blogger.com8tag:blogger.com,1999:blog-5442540270151710698.post-5259475338444039532010-05-13T22:00:00.145-04:002010-05-22T21:35:47.736-04:00Introduction to the Framework Classes Part 3<b>Introduction</b><br />
<br />
As we’ve demonstrated previously, the _Framework classes provide a very useful <i>framework</i> for coding MIDI control surface scripts in Python. We’ve looked at basic script setup, worked with simple Control Surface components and objects, and over-ridden high-level setup methods to achieve certain customization goals. Here in this post, we’ll have a look at the relationship between the _Framework classes and the “special” subclass modules which are used with many of the Framework-based MIDI remote scripts for newer controllers. <br />
<br />
As a working example, we’ll be adding APC20 functionality to the APC40 by modifying the APC40’s MIDI remote scripts. Our modifications will include Note Mode for the matrix, and User Mode for the sliders. We’ll also have another look at Device Lock, create mappings for Undo and Redo, and re-map the APC40’s endless encoder to control Tempo. For those who wish to follow along, the complete Python source files which accompany this article can be downloaded <a href="http://remotescripts.blogspot.com/p/support-files.html">here</a>.<br />
<br />
<b>Sysex and Subclasses</b><br />
<br />
As we’ve seen previously, the APC20 can be put into Note Mode by sending an appropriate Sysex string to the controller. In this mode, the 8x5 matrix can be used to send MIDI notes, trigger samples, control drum racks, etc. - similar to the Launchpad’s <i>User</i> modes. For the APC40, however, a firmware upgrade would be required in order enable Note Mode via Sysex. Fortunately, Sysex is not the only way. The Launchpad’s User Modes do not rely on Sysex, and we can use the same approach to add a Note Mode to the APC40. Before we do, however, let’s collect the scripts we need in order to combine the useful new features of the APC20 with those of the APC40.<br />
<br />
In Live 8.1.3, the APC20 and APC40 scripts are both subclasses of the APC “super-class” script, although the APC module actually resides in the APC40 folder, while the APC20 scripts are found in a separate folder. Here, we’ll copy all of the required scripts over into a new folder, which we’ll call APC40_20. Our modified controller script will then be selectable in Live’s MIDI preferences drop-down list as APC40_20. <br />
<br />
Since any “shifted” controls generally require a new subclass (as they inherit from and override Framework base class methods), and since many of the controls on both the APC40 and on the APC20 work in a shifted mode, we’ll need to include a fair number of “special” subclass modules. Here is a list of the modules we’ll be needing (in addition to the Framework modules, which are not listed):<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">__init__.py</div><div style="font-family: "Courier New",Courier,monospace;">APC.py (APC40 and APC20)</div><div style="font-family: "Courier New",Courier,monospace;">APC40plus20.py (based on APC40 script)</div><div style="font-family: "Courier New",Courier,monospace;">APCSessionComponent.py (APC20)</div><div style="font-family: "Courier New",Courier,monospace;">ConfigurableButtonElement.py (Launchpad)</div><div style="font-family: "Courier New",Courier,monospace;">DetailViewControllerComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">EncoderMixerModeSelectorComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">PedaledSessionComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">RingedEncoderElement.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableDeviceComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableSelectorComponent.py (APC20)</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableTranslatorComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableTransportComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableZoomingComponent.py (APC20)</div><div style="font-family: "Courier New",Courier,monospace;">SliderModesComponent.py (APC20)</div><div style="font-family: "Courier New",Courier,monospace;">SpecialChannelStripComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">SpecialMixerComponent.py (APC40)</div><div style="font-family: "Courier New",Courier,monospace;">SpecialTransportComponent.py (APC40)</div><br />
Note that we’ve included one non-APC module here, <i>ConfigurableButtonElement</i>, which is from the Launchpad scripts. One advantage of using the Framework classes for scripting is that special subclass modules are generally re-usable and interchangeable, especially for similar control surfaces.<br />
<br />
Now, with all of the subclass scripts gathered together in one place (for ease of editing and transportability), we’ll merge the APC20 and APC40 scripts. Both of these scripts are Framework-based, so there will be some import duplicates, which we can eliminate. There are also some APC20 special subclass modules which we will be using instead of the APC40’s plain vanilla Framework imports. Here is the merged import list:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span>
<span class="kn">from</span> <span class="nn">APC</span> <span class="kn">import</span> <span class="n">APC</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span>
<span class="kn">from</span> <span class="nn">_Framework.InputControlElement</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">_Framework.SliderElement</span> <span class="kn">import</span> <span class="n">SliderElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span>
<span class="kn">from</span> <span class="nn">_Framework.EncoderElement</span> <span class="kn">import</span> <span class="n">EncoderElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonMatrixElement</span> <span class="kn">import</span> <span class="n">ButtonMatrixElement</span>
<span class="kn">from</span> <span class="nn">_Framework.MixerComponent</span> <span class="kn">import</span> <span class="n">MixerComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ClipSlotComponent</span> <span class="kn">import</span> <span class="n">ClipSlotComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelStripComponent</span> <span class="kn">import</span> <span class="n">ChannelStripComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.SceneComponent</span> <span class="kn">import</span> <span class="n">SceneComponent</span>
<span class="c">#from _Framework.SessionZoomingComponent import SessionZoomingComponent #use ShiftableZoomingComponent from APC20 scripts instead</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelTranslationSelector</span> <span class="kn">import</span> <span class="n">ChannelTranslationSelector</span>
<span class="kn">from</span> <span class="nn">EncoderMixerModeSelectorComponent</span> <span class="kn">import</span> <span class="n">EncoderMixerModeSelectorComponent</span>
<span class="kn">from</span> <span class="nn">RingedEncoderElement</span> <span class="kn">import</span> <span class="n">RingedEncoderElement</span>
<span class="kn">from</span> <span class="nn">DetailViewControllerComponent</span> <span class="kn">import</span> <span class="n">DetailViewControllerComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableDeviceComponent</span> <span class="kn">import</span> <span class="n">ShiftableDeviceComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableTransportComponent</span> <span class="kn">import</span> <span class="n">ShiftableTransportComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableTranslatorComponent</span> <span class="kn">import</span> <span class="n">ShiftableTranslatorComponent</span>
<span class="kn">from</span> <span class="nn">PedaledSessionComponent</span> <span class="kn">import</span> <span class="n">PedaledSessionComponent</span>
<span class="kn">from</span> <span class="nn">SpecialMixerComponent</span> <span class="kn">import</span> <span class="n">SpecialMixerComponent</span>
<span class="c"># Additional imports from APC20.py:</span>
<span class="kn">from</span> <span class="nn">ShiftableZoomingComponent</span> <span class="kn">import</span> <span class="n">ShiftableZoomingComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableSelectorComponent</span> <span class="kn">import</span> <span class="n">ShiftableSelectorComponent</span>
<span class="kn">from</span> <span class="nn">SliderModesComponent</span> <span class="kn">import</span> <span class="n">SliderModesComponent</span>
<span class="c"># Import added from Launchpad scripts - needed for Note Mode:</span>
<span class="kn">from</span> <span class="nn">ConfigurableButtonElement</span> <span class="kn">import</span> <span class="n">ConfigurableButtonElement</span>
</pre></div></div><br />
Next we compare the APC20 and APC40 scripts for differences, retaining the most useful parts of each. Since most of the setting-up in the APC20 script setup is identical to that of APC40, and since the APC40 has more controls than the APC20, we’ll base our modified script on APC40 script, adding relevant code from the APC20 script as needed. The basic set-up of our script includes the following components:<br />
<br />
<i>SessionComponent</i> (includes session matrix, “red box” navigation controls, scene and clip launch buttons, and stop buttons)<br />
<i>SessionZoomingComponent</i> (includes “zoomed out” controls: matrix, selection, navigation, etc., for “banks of scenes”)<br />
<i>MixerComponent</i> (includes solo, mute, arm, and volume controls for tracks)<br />
<i>TransportComponent</i> (includes play, stop, record, tempo, metronome, quantize, etc., for song)<br />
<i>DeviceComponent</i> (includes device control, device on-off, device navigation, etc.)<br />
<br />
These are basic Framework components, however, with a complex controller like the APC40, most are actually instantiated as special subclass objects. The special subclass modules are typically used for over-riding existing Framework class methods, and for adding new methods - and often include direct calls to the LiveAPI. However, since they generally provide easily understood features, their usage is straight-forward and we can include most of them as-is, without modification, and without further investigation. We do need to sort out the conflicts between the APC40 and APC20 scripts, however - and add new code where we want to do new things or do things in different ways. It turns out that the only special modules which we’ll need to modify here are the following:<br />
<br />
<i>SpecialMixerComponent.py</i> – we will map Cue Level to Tempo here<br />
<i>ShiftableDeviceComponent.py </i>– we will check the Device Lock state here<br />
<i>ShiftableSelectorComponent.py</i> – we will implement Note Mode here<br />
<i>ShiftableTransportComponent.py</i> – we will add Undo and Redo, and handle the true endless encoder here<br />
<i>ShiftableZoomingComponent.py</i> – we will modify the scope of Note Mode here (keep scene launch active)<br />
<br />
Mapping Cue Level to Tempo provides a good example of subclass modification. It turns out that there is only one true endless encoder on the APC40/20, which is the Cue Level control. This is the control which we want to use as a Tempo control, due to its unique placement on the board. Unfortunately, however, the simple <i>set_tempo_control</i> Framework <i>TransportComponent</i> method, which we would normally use, throws an assertion error when mapped to a true endless encoder:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">set_tempo_control</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">control</span><span class="p">,</span> <span class="n">fine_control</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
<span class="k">assert</span> <span class="p">((</span><span class="n">control</span> <span class="o">==</span> <span class="bp">None</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">control</span><span class="p">,</span> <span class="n">EncoderElement</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">control</span><span class="o">.</span><span class="n">message_map_mode</span><span class="p">()</span> <span class="ow">is</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">absolute</span><span class="p">)))</span>
<span class="k">assert</span> <span class="p">((</span><span class="n">fine_control</span> <span class="o">==</span> <span class="bp">None</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">fine_control</span><span class="p">,</span> <span class="n">EncoderElement</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">fine_control</span><span class="o">.</span><span class="n">message_map_mode</span><span class="p">()</span> <span class="ow">is</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">absolute</span><span class="p">)))</span>
</pre></div></div><br />
Our true endless encoder is an EncoderElement with a map_mode value of <i>Live.MidiMap.MapMode.relative_two_compliment</i>, not <i>Live.MidiMap.MapMode.absolute</i>, so, we’ll need to dig deeper. Luckily, there’s an “old-fashioned” script which shows us the way - we can adapt code from the Mackie Control script to suit our purpose here (the Mackie jog wheel is also a true endless encoder). In the <i>ShiftableTransportComponent</i> module, we add a method for assigning the control, which now will throw an assertion error if the mapped control is <i>not</i> an endless encoder:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">set_tempo_encoder</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">control</span><span class="p">):</span>
<span class="k">assert</span> <span class="p">((</span><span class="n">control</span> <span class="o">==</span> <span class="bp">None</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">control</span><span class="p">,</span> <span class="n">EncoderElement</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">control</span><span class="o">.</span><span class="n">message_map_mode</span><span class="p">()</span> <span class="ow">is</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">relative_two_compliment</span><span class="p">)))</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span><span class="o">.</span><span class="n">remove_value_listener</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_value</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span> <span class="o">=</span> <span class="n">control</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span><span class="o">.</span><span class="n">add_value_listener</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_value</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">()</span>
</pre></div></div><br />
And we need to add the callback (i.e., the “listener value”). This is the method which is called whenever the controller (encoder) is adjusted:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_tempo_encoder_value</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shift_pressed</span><span class="p">:</span>
<span class="k">assert</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tempo_encoder_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">value</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">))</span>
<span class="n">backwards</span> <span class="o">=</span> <span class="p">(</span><span class="n">value</span> <span class="o">>=</span> <span class="mi">64</span><span class="p">)</span>
<span class="n">step</span> <span class="o">=</span> <span class="mf">0.1</span> <span class="c">#step = 1.0 #reduce this for finer control; 1.0 is 1 bpm</span>
<span class="k">if</span> <span class="n">backwards</span><span class="p">:</span>
<span class="n">amount</span> <span class="o">=</span> <span class="p">(</span><span class="n">value</span> <span class="o">-</span> <span class="mi">128</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">amount</span> <span class="o">=</span> <span class="n">value</span>
<span class="n">tempo</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="mi">999</span><span class="p">,</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">tempo</span> <span class="o">+</span> <span class="p">(</span><span class="n">amount</span> <span class="o">*</span> <span class="n">step</span><span class="p">))))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">tempo</span> <span class="o">=</span> <span class="n">tempo</span>
</pre></div></div><br />
Note that we’ve changed the step increment to a reasonably smooth value of 0.1 bpm, since we’re only using one encoder for tempo control here (the Framework basic <i>transport.set_tempo_control</i> method, which we’re not using, is actually set up to accept two controller assignments - one for coarse tuning and one for fine tempo tuning). If we wanted to, we could also adjust the Tempo range min and max values here as well. <br />
<br />
We can see that the <i>_tempo_encoder_value</i> method ends with a call to the LiveAPI; <i>self.song().tempo = tempo</i>. The LiveAPI is well documented, so these types of calls are easy to look up, when they're not easliy understood at first glance. We can also refer to the many MIDI remote scripts, and the _Framework scripts themselves, for examples of how to make the calls and what arguments to use.<br />
<br />
In the <i>_tempo_encoder_value </i>method above, we check to see if shift is pressed before doing anything else, because we want to revert to the original Cue Volume mapping when in shifted mode. We’ll also need to add some code to the <i>SpecialMixerComponent</i> module to do this. The <i>_shift_value method</i> is called when the shift button is pressed, which in turn calls the <i>update()</i> method; this is where we’ll connect our “prehear” (Cue Volume) control - right before the Crossfader control connection is made:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_shift_value</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="c">#added</span>
<span class="k">assert</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">value</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_shift_pressed</span> <span class="o">=</span> <span class="p">(</span><span class="n">value</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">()</span>
</pre></div></div><br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="c">#added override</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_allow_updates</span><span class="p">:</span>
<span class="n">master_track</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">master_track</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_enabled</span><span class="p">():</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_prehear_volume_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_shift_pressed</span><span class="p">:</span> <span class="c">#added </span>
<span class="bp">self</span><span class="o">.</span><span class="n">_prehear_volume_control</span><span class="o">.</span><span class="n">connect_to</span><span class="p">(</span><span class="n">master_track</span><span class="o">.</span><span class="n">mixer_device</span><span class="o">.</span><span class="n">cue_volume</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_prehear_volume_control</span><span class="o">.</span><span class="n">release_parameter</span><span class="p">()</span> <span class="c">#added </span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_crossfader_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_crossfader_control</span><span class="o">.</span><span class="n">connect_to</span><span class="p">(</span><span class="n">master_track</span><span class="o">.</span><span class="n">mixer_device</span><span class="o">.</span><span class="n">crossfader</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_prehear_volume_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_prehear_volume_control</span><span class="o">.</span><span class="n">release_parameter</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_crossfader_control</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_crossfader_control</span><span class="o">.</span><span class="n">release_parameter</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_bank_up_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_bank_up_button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_bank_down_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_bank_down_button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_next_track_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_next_track_button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_prev_track_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_prev_track_button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_rebuild_callback</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_update_requests</span> <span class="o">+=</span> <span class="mi">1</span>
</pre></div></div><br />
So far, so good. With some higher-level remote script adjustments and some lower-level subclass code modifications, we’ve successfully mapped Cue Level to Tempo. Now, on to Note Mode.<br />
<br />
<b>Note Mode</b><br />
<br />
In order to enable Note Mode, first we need to disable the clip slots, so that we can re-assign the matrix buttons to the MIDI notes of our chosing. We don’t want to totally disconnect the <i>ButtonElements</i>, we only want to change the mappings. The original APC20 script actually disables the <i>SessionComponent</i> altogether, however, this also puts the Scene Launch and Track Stop buttons out of action. To take out only the matrix, we need to disable the clip slots, and nothing else. We’ll do this in the <i>set_ignore_buttons </i>method of the<i> ShiftableZoomingComponent </i>class:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">set_ignore_buttons</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ignore</span><span class="p">):</span>
<span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">ignore</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">False</span><span class="p">))</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_ignore_buttons</span> <span class="o">!=</span> <span class="n">ignore</span><span class="p">):</span> <span class="c">#if ignore state changes..</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_ignore_buttons</span> <span class="o">=</span> <span class="n">ignore</span> <span class="c">#set new state</span>
<span class="k">if</span> <span class="p">(</span><span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_is_zoomed_out</span><span class="p">):</span> <span class="c">#if in session/clip view..</span>
<span class="k">if</span> <span class="n">ignore</span><span class="p">:</span> <span class="c">#disable clip slots on ignore</span>
<span class="k">for</span> <span class="n">scene_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
<span class="n">scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">clip_slot</span> <span class="o">=</span> <span class="n">scene</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span> <span class="c">#re-enable clip slots on ignore</span>
<span class="k">for</span> <span class="n">scene_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
<span class="n">scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">clip_slot</span> <span class="o">=</span> <span class="n">scene</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="c">#self._session.set_enabled((not ignore)) </span>
<span class="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">()</span>
</pre></div></div><br />
<br />
Now that the clip slots are disabled, we can re-map the matrix buttons to MIDI notes. We’ll do this in the <i>_on_note_mode_changed</i> method of the <i>ShiftableSelectorComponent</i> class. We’ll use a note layout which is similar to the APC20’s Note Mode layout, and to the Launchpad’s User 1 mode layout – both of which are based on split rows. Each half row will ascend in sets of 4 notes, which works well with the typical Live Rack (also 4 notes wide). Here's a map of our new Note Mode layout (together with the other mods in our script):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuzIRxeizW7Ng1q2w5hfFCecws1RmA9N00DfjSqjV-jhOqiM9OLQjIJRyuxmtWrugfaB19aR1EpNynB3AuOadbXR9BOBiyO_RJgqY8xJqTU0ce9Rwd0u7aHTbga6nxvCVOOtMkLRyN-exm/s1600/apc40_20.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuzIRxeizW7Ng1q2w5hfFCecws1RmA9N00DfjSqjV-jhOqiM9OLQjIJRyuxmtWrugfaB19aR1EpNynB3AuOadbXR9BOBiyO_RJgqY8xJqTU0ce9Rwd0u7aHTbga6nxvCVOOtMkLRyN-exm/s400/apc40_20.jpg" width="400" /></a></div><br />
In order to avoid conflict with the APC40 assignments, we’ll send all of our Note Mode notes out on channel 10 (the APC40 uses channels 1 through 8 - and possibly 9, according to the original note map). To map the notes and to change the channels, we’ll use the Framework <i>set_channel</i> and <i>set_identifier</i> methods. We’ll also use the Framework <i>button.send_value</i> method to create different colour patterns for the left and right sides of the grid – so that our note mapping is visually obvious. Here’s the code:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_on_note_mode_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_master_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">AssertionError</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_enabled</span><span class="p">()</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_invert_assignment</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">_toggle_pressed</span><span class="p">:</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_mode_active</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_master_button</span><span class="o">.</span><span class="n">turn_on</span><span class="p">()</span>
<span class="k">for</span> <span class="n">scene_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
<span class="c">#TODO: re-map scene_launch buttons to note velocity...</span>
<span class="n">scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">clip_slot</span> <span class="o">=</span> <span class="n">scene</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span>
<span class="n">button</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_matrix</span><span class="o">.</span><span class="n">get_button</span><span class="p">(</span><span class="n">track_index</span><span class="p">,</span> <span class="n">scene_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="bp">None</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">set_channel</span><span class="p">(</span><span class="mi">9</span><span class="p">)</span> <span class="c">#remap all Note Mode notes to channel 10</span>
<span class="k">if</span> <span class="n">track_index</span> <span class="o"><</span> <span class="mi">4</span><span class="p">:</span>
<span class="n">button</span><span class="o">.</span><span class="n">set_identifier</span><span class="p">(</span><span class="mi">52</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span> <span class="o">*</span> <span class="n">scene_index</span><span class="p">)</span> <span class="o">+</span> <span class="n">track_index</span><span class="p">)</span> <span class="c">#top row of left group (first 4 columns) notes 52 to 55</span>
<span class="k">if</span> <span class="p">(</span><span class="n">track_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">scene_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">track_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">!=</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">scene_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">):</span>
<span class="n">button</span><span class="o">.</span><span class="n">send_value</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c">#0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">button</span><span class="o">.</span><span class="n">send_value</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">button</span><span class="o">.</span><span class="n">set_identifier</span><span class="p">(</span><span class="mi">72</span> <span class="o">-</span> <span class="p">(</span><span class="mi">4</span> <span class="o">*</span> <span class="n">scene_index</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="n">track_index</span> <span class="o">-</span><span class="mi">4</span><span class="p">))</span> <span class="c">#top row of right group (next 4 columns) notes 72 to 75</span>
<span class="k">if</span> <span class="p">(</span><span class="n">track_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">scene_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">track_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">!=</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">scene_index</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">):</span>
<span class="n">button</span><span class="o">.</span><span class="n">send_value</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c">#0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">button</span><span class="o">.</span><span class="n">send_value</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_rebuild_callback</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_master_button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div></div><br />
Of course, we need to turn the lights off and re-enable the clip slots when Note Mode is switched back off. We’ll do that in the <i>_master_value</i> method (which is also where the APC20 Sysex Note Mode string normally gets queued-up):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_master_value</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="c">#this is the master_button value_listener, i.e. called when the master_button is pressed</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_master_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">AssertionError</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">AssertionError</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_enabled</span><span class="p">()</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_invert_assignment</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">_toggle_pressed</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_master_button</span><span class="o">.</span><span class="n">is_momentary</span><span class="p">()</span> <span class="ow">or</span> <span class="n">value</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span> <span class="c">#if the master button is pressed:</span>
<span class="c">#for button in self._select_buttons: #turn off track select buttons (only needed for APC20)</span>
<span class="c">#button.turn_off()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_matrix</span><span class="o">.</span><span class="n">reset</span><span class="p">()</span> <span class="c">#turn off the clip launch grid LEDs</span>
<span class="c">#mode_byte = NOTE_MODE #= 67 for APC20 Note Mode, send as part of sysex string to enable Note Mode</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_mode_active</span><span class="p">:</span> <span class="c">#if note mode is already on, turn it off:</span>
<span class="c">#mode_byte = ABLETON_MODE #= 65 for APC40 Ableton Mode 1</span>
<span class="k">for</span> <span class="n">scene_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
<span class="n">scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">scene_index</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track_index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">clip_slot</span> <span class="o">=</span> <span class="n">scene</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="n">track_index</span><span class="p">)</span>
<span class="n">button</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_matrix</span><span class="o">.</span><span class="n">get_button</span><span class="p">(</span><span class="n">track_index</span><span class="p">,</span> <span class="n">scene_index</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">turn_off</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_rebuild_callback</span><span class="p">()</span>
<span class="c">#self._mode_callback(mode_byte) #send sysex to set Mode (NOTE_MODE or ABLETON_MODE)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_note_mode_active</span> <span class="o">=</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_note_mode_active</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_zooming</span><span class="o">.</span><span class="n">set_ignore_buttons</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_note_mode_active</span><span class="p">)</span> <span class="c">#turn off matrix, scene launch, and clip stop buttons when in Note Mode</span>
<span class="c">#self._transport.update() #only needed for APC20</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_on_note_mode_changed</span><span class="p">()</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div></div><br />
So now we have a working Note Mode on the APC40 (don’t forget that we do have to enable the MIDI Input Track switch in Live's MIDI preferences dialog, so that our notes get passed along to the track). Not too difficult, was it? Of course, I wouldn’t expect to see official support for Note Mode on the APC40 from Ableton or Akai anytime soon, if only because thousands of units are already out there, without any red stenciling to tell users which button to press to enable Note Mode – and they might get confused. Well, that, and it might hurt sales of the APC20.<br />
<br />
<b>Undo/Redo</b><br />
<br />
Now, before we sign off, we’ll take a minute and re-map the Tempo Nudge buttons to Undo and Redo. It just so happens that the OpenLabs scripts have a nice <i>SpecialTransportComponent</i> module, which provides methods for setting Undo, Redo, and Back to Start (BTS) buttons; we’ll copy the required code over to our equivalent <i>ShiftableTransportComponent</i> module. Here is the Undo code (Redo is almost identical):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">set_undo_button</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">undo_button</span><span class="p">):</span>
<span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">undo_button</span><span class="p">,</span> <span class="p">(</span><span class="n">ButtonElement</span><span class="p">,</span>
<span class="nb">type</span><span class="p">(</span><span class="bp">None</span><span class="p">)))</span>
<span class="k">if</span> <span class="p">(</span><span class="n">undo_button</span> <span class="o">!=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span><span class="p">):</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span><span class="o">.</span><span class="n">remove_value_listener</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_undo_value</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span> <span class="o">=</span> <span class="n">undo_button</span>
<span class="k">if</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span><span class="o">.</span><span class="n">add_value_listener</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_undo_value</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">()</span>
</pre></div></div><br />
And the callback (featuring some more LiveAPI calls):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_undo_value</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">assert</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">value</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_enabled</span><span class="p">():</span>
<span class="k">if</span> <span class="p">((</span><span class="n">value</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_undo_button</span><span class="o">.</span><span class="n">is_momentary</span><span class="p">())):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">can_undo</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">undo</span><span class="p">()</span>
</pre></div></div><br />
<b>Conclusion</b><br />
<br />
Well, that’s it for now. We’ve shown that implementing Note Mode on the APC40 is relatively straight-forward, and does not in fact require a firmware update. We’ve also demonstrated that much can be accomplished simply by extending Framework-based scripts and subclasses. Our custom tweaks and mappings do not require templates or add-ons, and best part is that they are free – free as in free beer! Cheers.<br />
<br />
Hanz Petrov<br />
May 13, 2010<br />
<br />
hanz.petrov<br />
at gmail.com<br />
<br />
PostScript: It would seem that many (if not all) of the Live MIDI remote scripts, for all of the various control surfaces, were coded by Jan B. over at Ableton. His code is a pleasure to work with and fun to learn from - my appreciation goes out to Jan and to the entire Ableton team.Hanz Petrovhttp://www.blogger.com/profile/11357582251433610792noreply@blogger.com25tag:blogger.com,1999:blog-5442540270151710698.post-36526286731523153752010-04-12T18:00:00.016-04:002010-04-13T01:12:06.591-04:00Introduction to the Framework Classes Part 2<b>Background</b><br />
<br />
In this post, we’ll have a look at the differences between Live 7 and Live 8 remote scripts, get the <i>Max for Live</i> point-of-view on control surfaces, take a detailed look at the newest APC40 (and APC20) scripts – and demonstrate a few APC script hacks along the way. If you’re new to MIDI remote scripts, you might want to have a look at <a href="http://remotescripts.blogspot.com/2010/03/introduction-to-framework-classes.html">Part 1</a> of the Introduction to the Framework Classes before coming back here for Part 2. <br />
<br />
<b>Keeping up with recent changes</b><br />
<br />
As discussed previously, most of what we know about MIDI remote scripting has been based on exploring decompiled Python pyc files. The Live 7 remote scripts are Python 2.2 files, and have proven to be relatively easy to decompile. Live 8’s integration of Python 2.5, on the other hand, presents a new challenge. At present, there is no reliable, freely accessible method for decompiling 2.5 pyc files. It is possible, however, to get a good sense of the latest changes made to the MIDI remote scripts using the tools at hand.<br />
<br />
<a href="http://sourceforge.net/projects/unpyc/">Unpyc</a> is one such tool, and unpyc can decompile python 2.5 files - but only up to a point. In most cases, it will only produce partial source code, but at least it lets us know where it is having trouble. It can, however, disassemble 2.5 files without fail. When we’re armed with a partial decompile, and a complete disassembly, it is possible to reconstruct working script source code - although the process is tedious at the best of times. But even without reconstructing complete sources, Unpyc allows us to take a peek behind the scenes and understand the nature of the changes implemented with the most recent MIDI remote scripts.<br />
<br />
As an example, here is the mixer setup method from the APC40.py script - Live 8.1.1 version:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_setup_mixer_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">mixer</span> <span class="o">=</span> <span class="n">SpecialMixerComponent</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Mixer'</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Master_Channel_Strip'</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Selected_Channel_Strip'</span>
<span class="k">for</span> <span class="n">track</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">strip</span> <span class="o">=</span> <span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="n">track</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Channel_Strip_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span>
<span class="n">volume_control</span> <span class="o">=</span> <span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
<span class="n">arm_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">48</span><span class="p">)</span>
<span class="n">solo_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">49</span><span class="p">)</span>
<span class="n">mute_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">50</span><span class="p">)</span>
<span class="n">select_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">51</span><span class="p">)</span>
<span class="n">volume_control</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Volume_Control'</span>
<span class="n">arm_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Arm_Button'</span>
<span class="n">solo_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Solo_Button'</span>
<span class="n">mute_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Mute_Button'</span>
<span class="n">select_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">track</span><span class="p">)</span> <span class="o">+</span> <span class="s">'_Select_Button'</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="n">volume_control</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_arm_button</span><span class="p">(</span><span class="n">arm_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_solo_button</span><span class="p">(</span><span class="n">solo_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_mute_button</span><span class="p">(</span><span class="n">mute_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">select_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_invert_mute_feedback</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">crossfader</span> <span class="o">=</span> <span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">15</span><span class="p">)</span>
<span class="n">master_volume_control</span> <span class="o">=</span> <span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">14</span><span class="p">)</span>
<span class="n">master_select_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">80</span><span class="p">)</span>
<span class="n">prehear_control</span> <span class="o">=</span> <span class="n">EncoderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">relative_two_compliment</span><span class="p">)</span>
<span class="n">crossfader</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Crossfader'</span>
<span class="n">master_volume_control</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Master_Volume_Control'</span>
<span class="n">master_select_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Master_Select_Button'</span>
<span class="n">prehear_control</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Prehear_Volume_Control'</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_crossfader_control</span><span class="p">(</span><span class="n">crossfader</span><span class="p">)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_prehear_volume_control</span><span class="p">(</span><span class="n">prehear_control</span><span class="p">)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="n">master_volume_control</span><span class="p">)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">master_select_button</span><span class="p">)</span>
<span class="k">return</span> <span class="n">mixer</span>
</pre></div></div><br />
Compare the above with the equivalent code from the 7.0.18 version:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_setup_mixer_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">mixer</span> <span class="o">=</span> <span class="n">MixerComponent</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>
<span class="k">for</span> <span class="n">track</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">strip</span> <span class="o">=</span> <span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="n">track</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_arm_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">48</span><span class="p">))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_solo_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">49</span><span class="p">))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_mute_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">50</span><span class="p">))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="mi">51</span><span class="p">))</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
<span class="n">strip</span><span class="o">.</span><span class="n">set_invert_mute_feedback</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_crossfader_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">15</span><span class="p">))</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_prehear_volume_control</span><span class="p">(</span><span class="n">EncoderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">relative_two_compliment</span><span class="p">))</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">14</span><span class="p">))</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">80</span><span class="p">))</span>
<span class="k">return</span> <span class="n">mixer</span>
</pre></div></div><br />
As we can see, the main difference between the Live 7.0.18 code and the Live 8.1.1 code is that in the new script, <i>name</i> attributes have been assigned to most of the components and elements. So who needs names? Max for Live needs names.<br />
<br />
<b>Max for Live – keeping up with Cycling ‘74</b><br />
<br />
Max for Live requires Live 8.1 or higher, and, accordingly, new Live 8 versions include Max-compatible MIDI remote scripts. The <i>name</i> attributes assigned to the python objects and methods allow Max for Live to see and access python methods and objects more easily (objects including control surfaces, components, and control elements). This can be verified with the help of Max for Live’s LiveAPI resource patches (<i>M4L.api.SelectComponent</i>, <i>M4L.api.SelectControlSurface</i>, <i>M4L.api.SelectControl</i>, etc.). Before we demonstrate, however, let’s have a look at how Control Surfaces fit into the world of Max’s <a href="http://www.cycling74.com/docs/max5/refpages/m4l-ref/m4l_live_object_model.html">Live Object Model</a> (LOM).<br />
<br />
The three main root objects in the Live Object Model are known as <i>live_app</i> (Application), <i>live_set</i> (Song), and <i>control_surfaces</i> (Control Surface). Cycling ‘74 provides somewhat detailed documentation for the first two, however, the last one seems to have been left out - intentionally, no doubt. Based on the LOM diagram, we can see that the Control Surfaces root object includes classes for components and controls. These map directly to python Framework modules (modules with names like <i>ControlSurface</i>, <i>TransportComponent</i>, <i>SessionComponent</i>, <i>ButtonElement</i>, etc.). Now, although documentation which explains this is almost non-existent, it turns out that the python methods and objects of the Framework classes are both visible and accessible from Max for Live. The remote script methods appear to Max as functions – functions which can be called using the syntax and arguments defined in the Python scripts.<br />
<br />
It is now clear that in order to gain a proper understanding of how to manipulate more complicated Control Surfaces, a study of the python remote scripts is essential - because that's where the Control Surface functions originate. We'll be demonstrating this link between Max for Live and the python scripts, but before we do, let’s take a peek under the hood of what has proven to be a very popular Live controller - the <a href="http://www.akaipro.com/apc40">APC40</a> (now almost a year old).<br />
<br />
<b>The APC40 – under the hood</b><br />
<br />
The latest Live 7 versions include MIDI remote scripts for the APC40, and, of course, Live 8.1 and higher also support the APC40. As mentioned above, the Live 8.1 versions of the APC40 scripts show slight differences - generally, name attributes have been added to most of the methods and objects. We’ll base our investigation here on the 8.1.1 scripts, in an effort to stay somewhat current.<br />
<br />
There are 11 files in the APC40 MIDI remote scripts directory:<br />
<div class="separator" style="clear: both; text-align: center;"></div><br />
<div style="font-family: "Courier New",Courier,monospace;">__init__.pyc</div><div style="font-family: "Courier New",Courier,monospace;">APC40.pyc</div><div style="font-family: "Courier New",Courier,monospace;">DetailViewControllerComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">EncoderMixerModeSelectorComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">PedaledSessionComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableDeviceComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableTranslatorComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">ShiftableTransportComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">SpecialChannelStripComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">SpecialMixerComponent.pyc</div><div style="font-family: "Courier New",Courier,monospace;">RingedEncoderElement.pyc</div><br />
The <i><span style="font-family: "";">__init__.py</span></i> script is rather uninteresting, as it is with most scripts:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span>
<span class="kn">from</span> <span class="nn">APC40</span> <span class="kn">import</span> <span class="n">APC40</span>
<span class="k">def</span> <span class="nf">create_instance</span><span class="p">(</span><span class="n">c_instance</span><span class="p">):</span>
<span class="sd">""" Creates and returns the APC40 script """</span>
<span class="k">return</span> <span class="n">APC40</span><span class="p">(</span><span class="n">c_instance</span><span class="p">)</span>
</pre></div></div><br />
This is a standard init, as shown in Part 1 of the Introduction to Remote Scripts. Now, the script files here with the longish names are special classes, which inherit from Framework modules, but add custom functionality. What they do (and the name of the Framework classes they inherit from) is described in the Docstrings of the scripts themselves:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">class</span> <span class="nc">DetailViewControllerComponent</span><span class="p">(</span><span class="n">ControlSurfaceComponent</span><span class="p">):</span>
<span class="s">' Component that can toggle the device chain- and clip view of the selected track '</span>
<span class="k">class</span> <span class="nc">EncoderMixerModeSelectorComponent</span><span class="p">(</span><span class="n">ModeSelectorComponent</span><span class="p">):</span>
<span class="s">' Class that reassigns encoders on the AxiomPro to different mixer functions '</span>
<span class="k">class</span> <span class="nc">PedaledSessionComponent</span><span class="p">(</span><span class="n">SessionComponent</span><span class="p">):</span>
<span class="s">' Special SessionComponent with a button (pedal) to fire the selected clip slot '</span>
<span class="k">class</span> <span class="nc">RingedEncoderElement</span><span class="p">(</span><span class="n">EncoderElement</span><span class="p">):</span>
<span class="s">' Class representing a continuous control on the controller enclosed with an LED ring '</span>
<span class="k">class</span> <span class="nc">ShiftableDeviceComponent</span><span class="p">(</span><span class="n">DeviceComponent</span><span class="p">):</span>
<span class="s">' DeviceComponent that only uses bank buttons if a shift button is pressed '</span>
<span class="k">class</span> <span class="nc">ShiftableTranslatorComponent</span><span class="p">(</span><span class="n">ChannelTranslationSelector</span><span class="p">):</span>
<span class="s">' Class that translates the channel of some buttons as long as a shift button is held '</span>
<span class="k">class</span> <span class="nc">ShiftableTransportComponent</span><span class="p">(</span><span class="n">TransportComponent</span><span class="p">):</span>
<span class="s">' TransportComponent that only uses certain buttons if a shift button is pressed '</span>
</pre></div></div><br />
It is interesting to note that the <i>EncoderMixerModeSelectorComponent</i> module appears to have been recycled from the AxiomPro script. And, for anyone interested, the rest of the python code can be examined <a href="http://hanzoffsystems.tech.officelive.com/APC40_813.rar">here</a>.<br />
<br />
The most interesting module of the lot by far, is <span style="font-family: "";">APC40.py</span> - which is where most of the action is. This file begins with the imports:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span>
<span class="kn">from</span> <span class="nn">_Framework.InputControlElement</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">_Framework.SliderElement</span> <span class="kn">import</span> <span class="n">SliderElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span>
<span class="kn">from</span> <span class="nn">_Framework.EncoderElement</span> <span class="kn">import</span> <span class="n">EncoderElement</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonMatrixElement</span> <span class="kn">import</span> <span class="n">ButtonMatrixElement</span>
<span class="kn">from</span> <span class="nn">_Framework.MixerComponent</span> <span class="kn">import</span> <span class="n">MixerComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ClipSlotComponent</span> <span class="kn">import</span> <span class="n">ClipSlotComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelStripComponent</span> <span class="kn">import</span> <span class="n">ChannelStripComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.SceneComponent</span> <span class="kn">import</span> <span class="n">SceneComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.SessionZoomingComponent</span> <span class="kn">import</span> <span class="n">SessionZoomingComponent</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelTranslationSelector</span> <span class="kn">import</span> <span class="n">ChannelTranslationSelector</span>
<span class="kn">from</span> <span class="nn">EncoderMixerModeSelectorComponent</span> <span class="kn">import</span> <span class="n">EncoderMixerModeSelectorComponent</span>
<span class="kn">from</span> <span class="nn">RingedEncoderElement</span> <span class="kn">import</span> <span class="n">RingedEncoderElement</span>
<span class="kn">from</span> <span class="nn">DetailViewControllerComponent</span> <span class="kn">import</span> <span class="n">DetailViewControllerComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableDeviceComponent</span> <span class="kn">import</span> <span class="n">ShiftableDeviceComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableTransportComponent</span> <span class="kn">import</span> <span class="n">ShiftableTransportComponent</span>
<span class="kn">from</span> <span class="nn">ShiftableTranslatorComponent</span> <span class="kn">import</span> <span class="n">ShiftableTranslatorComponent</span>
<span class="kn">from</span> <span class="nn">PedaledSessionComponent</span> <span class="kn">import</span> <span class="n">PedaledSessionComponent</span>
<span class="kn">from</span> <span class="nn">SpecialMixerComponent</span> <span class="kn">import</span> <span class="n">SpecialMixerComponent</span>
</pre></div></div><br />
As expected, in addition to the <i>Live</i> import (which provides direct access to the Live API), and the special modules listed previously, all of the other imports are _Framework modules. Again, see Part 1 for more detail.<br />
<br />
Next are the constants, which are used in the APC40 sysex exchange (more on this later):<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">SYSEX_INQUIRY</span> <span class="o">=</span> <span class="p">(</span><span class="mi">240</span><span class="p">,</span> <span class="mi">126</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">247</span><span class="p">)</span>
<span class="n">MANUFACTURER_ID</span> <span class="o">=</span> <span class="mi">71</span>
<span class="n">PRODUCT_MODEL_ID</span> <span class="o">=</span> <span class="mi">115</span>
<span class="n">APPLICTION_ID</span> <span class="o">=</span> <span class="mi">65</span>
<span class="n">CONFIGURATION_ID</span> <span class="o">=</span> <span class="mi">1</span>
</pre></div></div><br />
Then, after the name and docstring, we have the obligatory __init__ method. The APC40 __init__ looks like this:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_suppress_session_highlight</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">98</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Shift_Button'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_suggested_input_port</span> <span class="o">=</span> <span class="s">'Akai APC40'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_suggested_output_port</span> <span class="o">=</span> <span class="s">'Akai APC40'</span>
<span class="n">session</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_setup_session_control</span><span class="p">()</span>
<span class="n">mixer</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_setup_mixer_control</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_device_and_transport_control</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_global_control</span><span class="p">(</span><span class="n">mixer</span><span class="p">)</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_mixer</span><span class="p">(</span><span class="n">mixer</span><span class="p">)</span>
<span class="k">for</span> <span class="n">component</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">components</span><span class="p">:</span>
<span class="n">component</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device_id</span> <span class="o">=</span> <span class="mi">0</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_common_channel</span> <span class="o">=</span> <span class="mi">0</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dongle_challenge</span> <span class="o">=</span> <span class="p">(</span><span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">get_random_int</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2000000</span><span class="p">),</span> <span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">get_random_int</span><span class="p">(</span><span class="mi">2000001</span><span class="p">,</span> <span class="mi">4000000</span><span class="p">))</span>
</pre></div></div><br />
As we can see, standard Framework-based scripting is used to create a session object, a mixer, device and transport components, and global controls, and then to assign the mixer to the session. There’s nothing very mysterious here – except perhaps <i>_dongle_challenge</i>. And - you may be wondering - where does the infamous “secret handshake” live? Why, in <i>handle_sysex</i> of course.<br />
<br />
<b>The Secret Handshake – not so secret anymore</b><br />
<br />
Within days of the APC40 hitting retail shelves, it was discovered that the secret handshake is based on a sysex exchange. But how does it work exactly, and what is it hiding? It’s not hiding anything that can’t be done with basic Framework scripting (as we saw in Part 1), and it works by sending a “dongle challenge” sysex string, then looking for a correct response from the controller. If the response from the controller matches the expected response (i.e. the handshake succeeds), then all of the controls on the controller are enabled and the session highlight (aka “red box”) is turned on. Here’s part of the handle_sysex method, in native python:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">handle_sysex</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">midi_bytes</span><span class="p">):</span>
<span class="k">if</span> <span class="p">((</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">==</span> <span class="mi">6</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)):</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">==</span> <span class="n">MANUFACTURER_ID</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">==</span> <span class="n">PRODUCT_MODEL_ID</span><span class="p">)</span>
<span class="n">version_bytes</span> <span class="o">=</span> <span class="n">midi_bytes</span><span class="p">[</span><span class="mi">9</span><span class="p">:</span><span class="mi">13</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device_id</span> <span class="o">=</span> <span class="n">midi_bytes</span><span class="p">[</span><span class="mi">13</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_send_midi</span><span class="p">((</span><span class="mi">240</span><span class="p">,</span>
<span class="n">MANUFACTURER_ID</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device_id</span><span class="p">,</span>
<span class="n">PRODUCT_MODEL_ID</span><span class="p">,</span>
<span class="mi">96</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span>
<span class="mi">4</span><span class="p">,</span>
<span class="n">APPLICTION_ID</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">application</span><span class="p">()</span><span class="o">.</span><span class="n">get_major_version</span><span class="p">(),</span>
<span class="bp">self</span><span class="o">.</span><span class="n">application</span><span class="p">()</span><span class="o">.</span><span class="n">get_minor_version</span><span class="p">(),</span>
<span class="bp">self</span><span class="o">.</span><span class="n">application</span><span class="p">()</span><span class="o">.</span><span class="n">get_bugfix_version</span><span class="p">(),</span>
<span class="mi">247</span><span class="p">))</span>
<span class="n">challenge1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">]</span>
<span class="n">challenge2</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">]</span>
<span class="c">#...</span>
<span class="n">dongle_message</span> <span class="o">=</span> <span class="p">((((</span><span class="mi">240</span><span class="p">,</span>
<span class="n">MANUFACTURER_ID</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_device_id</span><span class="p">,</span>
<span class="n">PRODUCT_MODEL_ID</span><span class="p">,</span>
<span class="mi">80</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span>
<span class="mi">16</span><span class="p">)</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">challenge1</span><span class="p">))</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">challenge2</span><span class="p">))</span> <span class="o">+</span> <span class="p">(</span><span class="mi">247</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_send_midi</span><span class="p">(</span><span class="n">dongle_message</span><span class="p">)</span>
<span class="n">message</span> <span class="o">=</span> <span class="p">(((</span><span class="s">'APC40: Got response from controller, version '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(((</span><span class="n">version_bytes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="n">version_bytes</span><span class="p">[</span><span class="mi">1</span><span class="p">])))</span> <span class="o">+</span> <span class="s">'.'</span><span class="p">)</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(((</span><span class="n">version_bytes</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="n">version_bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">])))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">elif</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">==</span> <span class="mi">81</span><span class="p">):</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="n">MANUFACTURER_ID</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">_device_id</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">==</span> <span class="n">PRODUCT_MODEL_ID</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">assert</span> <span class="p">(</span><span class="n">midi_bytes</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">==</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="p">[</span><span class="nb">long</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
<span class="nb">long</span><span class="p">(</span><span class="mi">0</span><span class="p">)]</span>
<span class="c">#...</span>
<span class="n">expected_response</span> <span class="o">=</span> <span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">encrypt_challenge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dongle_challenge</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dongle_challenge</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="k">if</span> <span class="p">((</span><span class="nb">long</span><span class="p">(</span><span class="n">expected_response</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">==</span> <span class="n">response</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="ow">and</span> <span class="p">(</span><span class="nb">long</span><span class="p">(</span><span class="n">expected_response</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">==</span> <span class="n">response</span><span class="p">[</span><span class="mi">1</span><span class="p">])):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_suppress_session_highlight</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">for</span> <span class="n">component</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">components</span><span class="p">:</span>
<span class="n">component</span><span class="o">.</span><span class="n">set_enabled</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_on_selected_track_changed</span><span class="p">()</span>
</pre></div></div><br />
The above sysex usage matches that described in the <a href="http://www.akaipro.com/extras/product/apc40/APC40_Communications_Protocol_rev_1.pdf">Akai APC40 Communications Protocol document</a>. Well, the standard MMC Device Enquiry and response parts do anyway – the dongle part is not documented. As we can see, some of this exchange involves setting the APC40 into “Ableton Mode” and identifying the host application to the APC40 as Live – complete with major, minor, and bugfix version info.<br />
<br />
Although the APC40 handshake is effective at discouraging casual emulation, it is not clear what exactly is being protected here, since any script can make of use the Framework SessionComponent module (or the Live API) to get the infamous “red box” to display. On the other hand, who in their right mind would want to emulate a hard-wired non-programmable MIDI controller with an 8x5 grid of LED buttons, 16 rotary controllers, a cross-fader, and 9 sliders – unless they’re someone who already owns an APC40?! I suspect that the majority of Monome owners probably wouldn’t be interested in using their high-end boutique hardware to emulate a mass-market controller like the APC40, and besides, they’ve already got a wealth of open-source software applications and scripts to play with. So the real mystery is: why the dongle?<br />
<br />
In any event, bypassing the handshake is simply a matter of overriding the handle_sysex method. Other than for Mode initialization, sysex is not an important part of the APC40 scripts.<br />
<br />
Now, for a change of pace, let’s have a quick look at how we can manipulate the APC40’s LEDs using a remote script. We’ll set up a little light show with some python code, and then call it up from Max.<br />
<br />
<b>APC40 Lights & Magic</b><br />
<br />
As described in the APC40 Communications Protocol document, the various LEDs on the APC40 (around 380 of them according to Akai - and hence the need for a separate power supply) can be manipulated via MIDI messages sent to the controller. No sysex voodoo here – simple note on and note off commands is all it takes.<br />
<br />
The LEDs are turned on with MIDI note-on messages. The first byte of the message is the note-on part, with the MIDI channel bits used to identify the track number (for those LEDs which are associated with tracks). Individual LEDs are identified using the second byte (the note number), and the third byte is used to set the colour and state of the LED. The <a href="http://www.akaipro.com/extras/product/apc40/APC40map.jpg">APC40 note map</a> is a useful reference here.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrXaClcEOuyRYYH7jyBb6_aiKnTGq2MbXGbIJA7PFma_W7qqWV8l-oi4ekWDZzxVJqbgfHItLX6DhUn1UYfNk-nVviPrQDbC0BKGcxt0OWZzZi_Cr_PWFTVjUJJxd40_uyWn9oByg4crrL/s1600/APC40map.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrXaClcEOuyRYYH7jyBb6_aiKnTGq2MbXGbIJA7PFma_W7qqWV8l-oi4ekWDZzxVJqbgfHItLX6DhUn1UYfNk-nVviPrQDbC0BKGcxt0OWZzZi_Cr_PWFTVjUJJxd40_uyWn9oByg4crrL/s400/APC40map.jpg" width="400" /></a></div><br />
The Communications Protocol document lists the following possible colours and states for the clip launch LEDs:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green</div><br />
In the _setup_session_control method of the APC40 python script, these are assigned as values:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">clip_slot</span><span class="o">.</span><span class="n">set_started_value</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_triggered_to_play_value</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_recording_value</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_triggered_to_record_value</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
<span class="n">clip_slot</span><span class="o">.</span><span class="n">set_stopped_value</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</pre></div></div><br />
The values in the python script match the colours and states listed in the Communications Protocol document, although only 5 are assigned above. If we wanted to change the 8x5 grid colour assignments (if we had colour vision deficiency, for example), we’d need to override this part of the script.<br />
<br />
Now, based on this information, we’ll set up a random pattern of colours - using the Live API to generate the random values – and then get the colours to scroll down the rows of the matrix. Before we can call our new functions, however, we’ll need a MIDI remote script to put them in. Although we could add the new code straight into the APC40 script (by modifying the source code), instead, let’s create a new script which inherits from the APC40 class. We’ll call our new control surface script <i>APC40plus1</i>.<br />
<br />
Here’s the new _init__.py code for our new script:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="kn">from</span> <span class="nn">APC40plus1</span> <span class="kn">import</span> <span class="n">APC40plus1</span>
<span class="k">def</span> <span class="nf">create_instance</span><span class="p">(</span><span class="n">c_instance</span><span class="p">):</span>
<span class="k">return</span> <span class="n">APC40plus1</span><span class="p">(</span><span class="n">c_instance</span><span class="p">)</span>
</pre></div></div><br />
And here’s the code for our new APC40plus1 class, complete with lightshow methods:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="c"># http://remotescripts.blogspot.com</span>
<span class="kn">from</span> <span class="nn">APC40.APC40</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">APC40plus1</span><span class="p">(</span><span class="n">APC40</span><span class="p">):</span>
<span class="n">__module__</span> <span class="o">=</span> <span class="n">__name__</span>
<span class="n">__doc__</span> <span class="o">=</span> <span class="s">' Main class derived from APC40 '</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">APC40</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">show_message</span><span class="p">(</span><span class="s">"APC40plus1 script loaded"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">light_loop</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">light_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c">#self.name = 'light_loop'</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">4</span><span class="p">):</span> <span class="c">#start lights in 4 ticks; end after 104; step in 4 tick increments</span>
<span class="bp">self</span><span class="o">.</span><span class="n">schedule_message</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">lights_on</span><span class="p">)</span> <span class="c">#turn lights on</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">108</span><span class="p">,</span> <span class="mi">157</span><span class="p">,</span> <span class="mi">4</span><span class="p">):</span> <span class="c">#start turning off after 108 ticks; end after 156; step in 4 tick increments </span>
<span class="bp">self</span><span class="o">.</span><span class="n">schedule_message</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">lights_off</span><span class="p">)</span> <span class="c">#turn lights off</span>
<span class="bp">self</span><span class="o">.</span><span class="n">schedule_message</span><span class="p">(</span><span class="mi">156</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">refresh_state</span><span class="p">)</span> <span class="c">#refresh the controller to turn clip lights back on</span>
<span class="k">def</span> <span class="nf">lights_on</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">col_num</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">8</span><span class="p">):</span> <span class="c">#load random colour numbers into the buffer row (row 0)</span>
<span class="n">colour</span> <span class="o">=</span> <span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">get_random_int</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="c">#0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green</span>
<span class="k">if</span> <span class="n">colour</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">colour</span> <span class="o">></span> <span class="mi">6</span><span class="p">:</span> <span class="c">#filter out the blinking lights (even numbers) and skew towards having more "off" lights</span>
<span class="n">colour</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">list_of_rows</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">col_num</span><span class="p">]</span> <span class="o">=</span> <span class="n">colour</span> <span class="c">#load the buffer row</span>
<span class="bp">self</span><span class="o">.</span><span class="n">load_leds</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">lights_off</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">col_num</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">8</span><span class="p">):</span> <span class="c">#step through 8 columns/tracks/channels</span>
<span class="n">list_of_rows</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">col_num</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span> <span class="c">#set to zero (lights off)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">load_leds</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">load_leds</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">row_num</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span> <span class="c">#the zero row is a buffer, which gets loaded with a random sequence of colours</span>
<span class="n">note</span> <span class="o">=</span> <span class="mi">52</span> <span class="o">+</span> <span class="n">row_num</span> <span class="c">#set MIDI notes to send to APC, starting at 53, which is the first scene_launch button </span>
<span class="k">if</span> <span class="n">row_num</span> <span class="o">==</span> <span class="mi">6</span><span class="p">:</span> <span class="c">#the clip_stop row is out of sequence with the grid</span>
<span class="n">note</span> <span class="o">=</span> <span class="mi">52</span> <span class="c">#note number for clip_stop row </span>
<span class="k">for</span> <span class="n">col_num</span> <span class="ow">in</span> <span class="nb">range</span> <span class="p">(</span><span class="mi">8</span><span class="p">):</span> <span class="c">#8 columns/tracks/channels</span>
<span class="n">list_of_rows</span><span class="p">[</span><span class="n">row_num</span><span class="p">][</span><span class="n">col_num</span><span class="p">]</span> <span class="o">=</span> <span class="n">list_of_rows</span><span class="p">[</span><span class="n">row_num</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">col_num</span><span class="p">]</span> <span class="c">#load each slot from the preceding row</span>
<span class="n">status_byte</span> <span class="o">=</span> <span class="n">col_num</span> <span class="c">#set channel part of status_byte</span>
<span class="n">status_byte</span> <span class="o">+=</span> <span class="mi">144</span> <span class="c">#add note_on to channel number</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_send_midi</span><span class="p">((</span><span class="n">status_byte</span><span class="p">,</span> <span class="n">note</span><span class="p">,</span> <span class="n">list_of_rows</span><span class="p">[</span><span class="n">row_num</span><span class="p">][</span><span class="n">col_num</span><span class="p">]))</span> <span class="c">#for APC LEDs, send (status_byte, note, colour)</span>
<span class="n">list_of_rows</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="mi">8</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">)]</span> <span class="c">#create an 8 x 7 array of zeros; 8 tracks x (1 buffer row + 5 scene rows + 1 clip stop row)</span>
</pre></div></div><br />
With these two files saved to a new MIDI remote scripts folder, we can select our new controller script in the MIDI preferences drop-down. This will force Live to compile and run the new script. If the “red box” doesn’t show up, we know that something is wrong, and we can check the Live log file for errors, and trouble-shoot from there. We’ve set up our light show to run on initialization, so it should run immediately, but what we really wanted to demonstrate is that the our functions can be called from Max for Live.<br />
<br />
For this, we’ll create a simple Max patch which uses the <i>M4L.api.SelectControlSurface</i> resource patch to get a path to our new function, so that we can bang it with the function call. We send a <i>call light_loop</i> message to the control script, which in turn calls <i>lightshow_on</i> to turns the lights on, and <i>lightshow_off </i>to turn the lights back off - and resets the control surface, so that the LEDs reflect session view state.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlJ_g1aFTmQAyYodY6wbQV1APAldWiV0-IkK38E6piiyZJnR85fQtgZYiuoSyJcu8zKMM8UxcT8BcL29KGMSqaLHyQjEef1FXGvaJPxg6mv_cJbwj0D5pj8DqU4Xa5OKCSODixh7LhReV9/s1600/Max_call_light_loop.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlJ_g1aFTmQAyYodY6wbQV1APAldWiV0-IkK38E6piiyZJnR85fQtgZYiuoSyJcu8zKMM8UxcT8BcL29KGMSqaLHyQjEef1FXGvaJPxg6mv_cJbwj0D5pj8DqU4Xa5OKCSODixh7LhReV9/s320/Max_call_light_loop.jpg" /></a></div><br />
And here's the result:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwZQdI1TyiQ95FXkBZdMDZrPyF7Ep5hEmWgkLTC3DG6rB7Qxas4fPnYRU47WOdn_GU605sUZq2Z3zbf5HCjow' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><br />
Well, although that was a neat diversion, let’s try to do something more useful here – we’ll re-assign the Metronome button to act as a Device Lock button. We’ll do this using our same APC40plus1 script, together with some method overrides (and <i>without</i> the help of Max for Live this time).<br />
<br />
<b>Device Lock – why isn’t this feature standard?</b><br />
<br />
The Framework <i>TransportComponent</i> class contains both of the methods which interest us here:<br />
<i>set_metronome_button</i> and <i>set_device_lock_button</i>. These are inherited by the APC40 class, and can be used to change the button assignments. In order to change one for the other, we need to override the APC40 script method where they are normally assigned. This happens in the <i>_setup_device_and_transport_control</i> section of code. Before we override the method, however, we’ll need to do some basic setup.<br />
<br />
First, we instantiate an APC40 ControlSurface object, then we initialize using the APC40’s __init__ method, and finally we override the _setup_device_and_transport_control method, which is where we assign the physical metronome button to act as a device lock button:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="c"># http://remotescripts.blogspot.com</span>
<span class="kn">from</span> <span class="nn">APC40.APC40</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">APC40plus1</span><span class="p">(</span><span class="n">APC40</span><span class="p">):</span>
<span class="n">__module__</span> <span class="o">=</span> <span class="n">__name__</span>
<span class="n">__doc__</span> <span class="o">=</span> <span class="s">' Main class derived from APC40 '</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">APC40</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">show_message</span><span class="p">(</span><span class="s">"APC40plus1 script loaded"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_setup_device_and_transport_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="c">#overriden so that we can to reassign the metronome button to device lock</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">device_bank_buttons</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">device_param_controls</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">bank_button_labels</span> <span class="o">=</span> <span class="p">(</span><span class="s">'Clip_Track_Button'</span><span class="p">,</span> <span class="s">'Device_On_Off_Button'</span><span class="p">,</span> <span class="s">'Previous_Device_Button'</span><span class="p">,</span> <span class="s">'Next_Device_Button'</span><span class="p">,</span> <span class="s">'Detail_View_Button'</span><span class="p">,</span> <span class="s">'Rec_Quantization_Button'</span><span class="p">,</span> <span class="s">'Midi_Overdub_Button'</span><span class="p">,</span> <span class="s">'Device_Lock_Button'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">device_bank_buttons</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">58</span> <span class="o">+</span> <span class="n">index</span><span class="p">))</span>
<span class="n">device_bank_buttons</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">bank_button_labels</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
<span class="n">ring_mode_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="ow">not</span> <span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">24</span> <span class="o">+</span> <span class="n">index</span><span class="p">)</span>
<span class="n">ringed_encoder</span> <span class="o">=</span> <span class="n">RingedEncoderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">16</span> <span class="o">+</span> <span class="n">index</span><span class="p">,</span> <span class="n">Live</span><span class="o">.</span><span class="n">MidiMap</span><span class="o">.</span><span class="n">MapMode</span><span class="o">.</span><span class="n">absolute</span><span class="p">)</span>
<span class="n">ringed_encoder</span><span class="o">.</span><span class="n">set_ring_mode_button</span><span class="p">(</span><span class="n">ring_mode_button</span><span class="p">)</span>
<span class="n">ringed_encoder</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Device_Control_'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="n">ring_mode_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">ringed_encoder</span><span class="o">.</span><span class="n">name</span> <span class="o">+</span> <span class="s">'_Ring_Mode_Button'</span>
<span class="n">device_param_controls</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ringed_encoder</span><span class="p">)</span>
<span class="n">device</span> <span class="o">=</span> <span class="n">ShiftableDeviceComponent</span><span class="p">()</span>
<span class="n">device</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Device_Component'</span>
<span class="n">device</span><span class="o">.</span><span class="n">set_bank_buttons</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">))</span>
<span class="n">device</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
<span class="n">device</span><span class="o">.</span><span class="n">set_parameter_controls</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">device_param_controls</span><span class="p">))</span>
<span class="n">device</span><span class="o">.</span><span class="n">set_on_off_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">device</span><span class="o">.</span><span class="n">set_lock_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">7</span><span class="p">])</span> <span class="c">#assign device lock to bank_button 8 (in place of metronome)...</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_device_component</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="n">detail_view_toggler</span> <span class="o">=</span> <span class="n">DetailViewControllerComponent</span><span class="p">()</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Detail_View_Control'</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_device_clip_toggle_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_detail_toggle_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span>
<span class="n">detail_view_toggler</span><span class="o">.</span><span class="n">set_device_nav_buttons</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span>
<span class="n">transport</span> <span class="o">=</span> <span class="n">ShiftableTransportComponent</span><span class="p">()</span>
<span class="n">transport</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Transport'</span>
<span class="n">play_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">91</span><span class="p">)</span>
<span class="n">stop_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">92</span><span class="p">)</span>
<span class="n">record_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">93</span><span class="p">)</span>
<span class="n">nudge_up_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>
<span class="n">nudge_down_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">101</span><span class="p">)</span>
<span class="n">tap_tempo_button</span> <span class="o">=</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">99</span><span class="p">)</span>
<span class="n">play_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Play_Button'</span>
<span class="n">stop_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Stop_Button'</span>
<span class="n">record_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Record_Button'</span>
<span class="n">nudge_up_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Nudge_Up_Button'</span>
<span class="n">nudge_down_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Nudge_Down_Button'</span>
<span class="n">tap_tempo_button</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">'Tap_Tempo_Button'</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_play_button</span><span class="p">(</span><span class="n">play_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_stop_button</span><span class="p">(</span><span class="n">stop_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_record_button</span><span class="p">(</span><span class="n">record_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_nudge_buttons</span><span class="p">(</span><span class="n">nudge_up_button</span><span class="p">,</span> <span class="n">nudge_down_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_tap_tempo_button</span><span class="p">(</span><span class="n">tap_tempo_button</span><span class="p">)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_quant_toggle_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">5</span><span class="p">])</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_overdub_button</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">[</span><span class="mi">6</span><span class="p">])</span>
<span class="c">#transport.set_metronome_button(device_bank_buttons[7]) #using this button for lock to device instead...</span>
<span class="n">bank_button_translator</span> <span class="o">=</span> <span class="n">ShiftableTranslatorComponent</span><span class="p">()</span>
<span class="n">bank_button_translator</span><span class="o">.</span><span class="n">set_controls_to_translate</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">device_bank_buttons</span><span class="p">))</span>
<span class="n">bank_button_translator</span><span class="o">.</span><span class="n">set_shift_button</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_shift_button</span><span class="p">)</span>
</pre></div></div><br />
Now, we save our new APC40plus1 script into its MIDI Remote Scripts directory, fire up Live, and select APC40plus1 from the MIDI Control Surfaces drop-down.<br />
<br />
Voilà, the Metronome button is now a Device Lock button (sorry, no video - but trust me - it does work). The python code for this mod (together with the lightshow) can be downloaded <a href="http://hanzoffsystems.tech.officelive.com/APC40plus1.rar">here</a>. Now, although we’ve made functional changes without modifying the original APC40 scripts, we’re still dependent on those original scripts, because we inherit methods from them. This means that we’ll need to watch out for MIDI remote script changes in new versions of Live, since they might break our new script. So why don’t we get a head start by checking out compatibility with 8.1.3 RC-1?<br />
<br />
<b>Live 8.1.3 – subtle changes</b><br />
<br />
As Live 8.1.3 enters the Release Candidate phase, we can take a sneak peek at the close-to-final MIDI remote scripts included with the beta release. It turns out that not much has changed here. The Framework classes are essentially the same, and support for one or two new control surfaces has been added – most notably the APC20 and Serato controllers. We’ll leave the Serato scripts for a future investigation (they look fascinating by the way), and take a peek under the hood of the APC20 scripts instead.<br />
<br />
<b>APC20 – the APC40’s little brother</b><br />
<br />
The APC20 is basically an APC 40 minus the cross-fader, rotary controllers, and right hand buttons. To maintain basic functionality with fewer buttons, some of the buttons on the left hand side are now dual-purpose, as described in the <a href="http://www.akaipro.com/stuff/contentmgr/files/21/677104637a7cd95a009e5f4da489d80a/file/apc20___quickstart_guide___reva.pdf">APC20 Quickstart Guide</a>. For example, the APC40’s Stop All Clips button has become the Shift button on the APC20. By holding down this button and selecting one of the Record buttons, the APC20 sliders can be assigned to Volume, Pan, Send A , Send B, Send C, User 1, User 2, or User 3. <br />
<br />
One new APC20 feature, which is not shared with the APC40, is Note Mode. The APC20 can be put into Note Mode using the Note Mode button (which was the Master Select button on the APC40). This allows the 8x5 grid to be used for sending MIDI notes - to control a drum rack, for example. So, is there a Note Mode on the APC40?<br />
<br />
Sysex is used to set the APC modes, and while APC40 has 3 documented modes, the APC20 has 4. As shown in the APC40 Communications Protocol document, a “Type 0” sysex message is used to set the APC40’s Modes. Byte 8 is the Mode identifier:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">0x40 (decimal 64) Generic Mode</div><div style="font-family: "Courier New",Courier,monospace;">0x41 (decimal 65) Ableton Live Mode</div><div style="font-family: "Courier New",Courier,monospace;">0x42 (decimal 66) Alternate Ableton Live Mode</div><br />
The 8.1.3 MIDI remote scripts show that a new Mode byte value has been added for the APC20:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">0x43 (decimal 67) APC20 Note Mode</div><br />
Unfortunately, this new Mode value (0x43) is meaningless to the APC40 – the hardware does not respond to this value. This is not a remote script limitation, but rather a firmware update would be required in order to for the APC40 to have a Note Mode. So far, none is available. On the other hand, it might well be possible to create a script which emulates Note Mode by translating MIDI Note and Channel data on the fly (there is already a Framework method for channel translation, which could be a good place to start). A future scripting project, perhaps, if Akai decides not to provide firmware updates for the APC40.<br />
<br />
Further investigation of the 8.1.3 scripts shows that the structure of the APC40 remote scripts has changed somewhat, in order to accommodate the APC20. Both the APC20 and the APC40 classes are now sub-classes of an APC ‘super’ class. The APC class now handles the handshake, and other methods common to both models. There is also a new APCSessionComponent module, which is used for linking multiple APCs (this module appears to handle “red box” offsets, so that multiple session boxes for multiple APCs will sit side-by-side). Here is the docstring for the class:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">class</span> <span class="nc">APCSessionComponent</span><span class="p">(</span><span class="n">SessionComponent</span><span class="p">):</span>
<span class="s">" Special SessionComponent for the APC controllers' combination mode "</span>
</pre></div></div><br />
Otherwise, little seems to have changed between 8.1.1 and 8.1.3. In fact, our APC40plus1 script runs without error on 8.1.3, even though it was based on 8.1.1 code. Which is good news.<br />
<br />
Now, just for fun, we can try to emulate an APC20 using an APC40, by overriding the sysex bytes for Product ID and Mode. The product_model_id bytes for the APC20 would be 123 - for the APC40 it’s 115 (or hex 0x73, as listed in the APC40 Communications Protocol document). So, we need to modify the APC20 script as follows:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="k">def</span> <span class="nf">_product_model_id_byte</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">115</span> <span class="c">#was originally 123</span>
</pre></div></div><br />
And, in the APC20 consts.py file, we need to change the Note Mode value from 67 to 65:<br />
<br />
<div class="hlcode"><div class="syntax"><pre><span class="n">MANUFACTURER_ID</span> <span class="o">=</span> <span class="mi">71</span>
<span class="n">ABLETON_MODE</span> <span class="o">=</span> <span class="mi">65</span>
<span class="n">NOTE_MODE</span> <span class="o">=</span> <span class="mi">65</span> <span class="c">#Was: 67 (= APC20 Note Mode); 65 = APC40 Ableton Mode 1</span>
</pre></div></div><br />
Well, the emulation does work, but of course, an APC40 is only half alive in APC20 mode – the right hand side is completely inactive – and Note Mode does nothing (yet). On the other hand, the ability to use the shift + record buttons to re-assign sliders is a nice feature. It would of course be possible to build a hybrid script which combines features from both the APC20 and the APC40 scripts, if anyone felt so inclined. Simply a question of finding the time - and we all know that there's never enough of that...<br />
<br />
<b>Conclusion</b><br />
<br />
The MIDI remote scripts, and particularly the Framework classes, provide a convenient and relatively straight-forward mechanism for tailoring the operation of control surfaces - with or without Max for Live. The Live 8 scripts do cater to Max for Live, but maintain the same basic functionality they’ve always had. The APC40 scripts provide a good example of this, as do the new APC20 scripts - both of which provide a good platform for customization.<br />
<br />
Well, happy scripting - and remember to share your discoveries and custom scripts with the rest of the Live community!<br />
<br />
Hanz Petrov<br />
April 13, 2010Hanz Petrovhttp://www.blogger.com/profile/11357582251433610792noreply@blogger.com17tag:blogger.com,1999:blog-5442540270151710698.post-5842946082526742272010-03-12T22:00:00.002-05:002010-03-17T19:35:02.370-04:00Introduction to the Framework Classes<b>Background</b><br />
<br />
My journey into the world of Ableton MIDI remote scripts began with a search for a better way to set up my FCB1010 as a Live controller. It didn’t take long before I realized that in order to fully customize my setup, I’d need to explore emulation and learn something about scripting in Python. The results of my explorations are documented here, in hopes that they may be useful to others.<br />
<br />
If you’ve found your way here, then you probably already know a thing or two about control surfaces, and you’re probably aware that Live has built-in support for many <a href="http://www.ableton.com/pages/controllers/all_controllers">controllers</a>. (If you’re unfamiliar with basic controller setup procedures, have a look at the MIDI and Key Remote Control section of the Live help file, or check out the <i>Control Surface Reference Lessons</i> in Live’s <i>Help View</i>.)<br />
<br />
Live provides its “instant mapping” support for controllers through the use of MIDI Remote Scripts. MIDI remote scripts are written in the <a href="http://en.wikipedia.org/wiki/Python_%28programming_language%29">Python</a> programming language, and essentially serve to translate MIDI data into instructions for controlling various aspects of the Live application. Each of the controllers which Live supports has a dedicated script folder and its own dedicated scripts. We’ll get into the details later – but first, a bit of history.<br />
<b><br />
Emulation</b><br />
<br />
It has been quite some time since ever-curious and inventive Live users discovered that it is possible to take advantage of Live’s “instant mapping” capabilities by emulating a supported controller (typically using one which is not). The most well-known emulation model is “Mackie emulation”.<br />
<br />
Emulation essentially involves telling Live that you have a certain piece of MIDI hardware hooked up, when in fact you do not. The caveat is that your hardware must be capable of supplying Live with the MIDI messages it expects to see coming from the controller which you are emulating. This can be done either by reconfiguring your controller, or by filtering through an intermediate application (such as <a href="http://www.midiox.com/">MIDI-OX</a>). This type of emulation is basically “black box emulation”, since the MIDI remote scripts are not modified (and their inner workings do not need to be understood in order for it to work). Black box emulation is somewhat limiting - the next step was to investigate the scripts themselves. <br />
<br />
<b>Script Files</b><br />
<br />
The remote scripts are installed with the Live application, and if your OS is Windows, you should be able to find them here (or in a similar location):<br />
<span style="font-family: "Courier New",Courier,monospace;">C:\Program Files\Ableton\Live 8.x.x\Resources\MIDI Remote Scripts\</span><br />
<br />
A typical MIDI Remote Scripts directory will contain a series of folders with names similar to the following:<br />
<span style="font-family: "Courier New",Courier,monospace;">_Axiom</span><br />
<span style="font-family: "Courier New",Courier,monospace;">_Framework</span><br />
<span style="font-family: "Courier New",Courier,monospace;">_Generic</span><br />
<span style="font-family: "Courier New",Courier,monospace;">_MxDCore</span><br />
<span style="font-family: "Courier New",Courier,monospace;">_Tools</span><br />
<span style="font-family: "Courier New",Courier,monospace;">_UserScript</span><br />
<span style="font-family: "Courier New",Courier,monospace;">APC40</span><br />
<span style="font-family: "Courier New",Courier,monospace;">Axiom</span><br />
<span style="font-family: "Courier New",Courier,monospace;">AxiomPro</span><br />
<span style="font-family: "Courier New",Courier,monospace;">Axiom_25_Classic</span><br />
<span style="font-family: "Courier New",Courier,monospace;">Axiom_49_61_Classic</span><br />
etc...<br />
<br />
The first few directories, which are named with a leading underscore, will not appear in the Live MIDI preferences control surfaces drop-down list (they are mostly “private” helper scripts). The other folders contain the python compiled (.PYC) script files for each of the supported controllers. The folder names are used to populate the control surfaces drop-down list in Live (changes in folder names will not be visible in the drop-down until Live is re-started).<br />
<br />
Within each folder, there is generally an __init__.pyc file, a .pyc file named after the controller, and one or more additional .pyc files. As an example, for the Vestax VCM600, the following files are found in the VCM600 directory:<br />
<span style="font-family: "Courier New",Courier,monospace;">__init__.pyc</span><br />
<span style="font-family: "Courier New",Courier,monospace;">VCM600.pyc</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ViewTogglerComponent.pyc</span><br />
<br />
PYC files are not readable by humans, however, it was soon discovered that by decompiling the controller script files, the source code could be analyzed - providing a convenient map to the default MIDI mappings, and insight into how MIDI remote scripts actually work.<br />
<br />
<b>Sources</b><br />
<br />
Python PYC files are relatively easy to decompile, and the resulting PY files are quite readable - in fact, they are practically identical to the original source files.<br />
<br />
Python files can be decompiled in a variety of ways. The <a href="http://sourceforge.net/projects/decompyle/">Decompyle</a> project at Sourceforge (among others) works well for python files up to version 2.3. There are also online “<a href="http://www.depython.com/">depyhton</a>” services which work for more recent python files, however, there is no non-commercial service which can handle version 2.5 files.<br />
<br />
The version of a PYC file can be determined by examining the first four bytes of the file in a hex editor. The “magic numbers” are as follows:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">99 4e 0d 0a python 1.5</span><br />
<span style="font-family: "Courier New",Courier,monospace;">Fc c4 0d 0a python 1.6</span><br />
<span style="font-family: "Courier New",Courier,monospace;">87 c6 0d 0a python 2.0</span><br />
<span style="font-family: "Courier New",Courier,monospace;">2a eb 0d 0a python 2.1</span><br />
<span style="font-family: "Courier New",Courier,monospace;">2d ed 0d 0a python 2.2</span><br />
<span style="font-family: "Courier New",Courier,monospace;">3b f2 0d 0a python 2.3</span><br />
<span style="font-family: "Courier New",Courier,monospace;">6d f2 0d 0a python 2.4</span><br />
<span style="font-family: "Courier New",Courier,monospace;">B3 f2 0d 0a python 2.5</span><br />
<br />
The Live 7.x.x scripts have generally be found to be python version 2.2 files, while the 8.x.x scripts are generally python ver. 2.5 (unfortunately). The Live 7.0.13 scripts in decompyled .PY format have been available <a href="http://www.assembla.com/spaces/live-api/documents/">here</a> for some time. These files have proven to be extremely useful as a reference for understanding remote scripting.<br />
<br />
Early explorations of the decompiled scripts focused on the commonly used consts.py file. This file is used to define constants in many early-generation scripts – including MIDI note mappings for control surfaces. No longer any need to pore through MIDI implementation charts, or manually map out MIDI note assignments – it’s all there in the files. Modifying the consts.py file was an easy way to tailor an emulation, and many went on to create new custom scripts from scratch – some very elaborate.<br />
<br />
Many of the links at right point to sites with valuable source code, documentation, and insights into scripting – all well worth exploring. There has also been much investigation into the workings of the LiveAPI, which is equally important (the “dark side” of scripting). Until now, however, there has not been much exploration into a key part of the puzzle - the Framework Classes - recently developed by Ableton.<br />
<br />
<b>_Framework Scripts</b><br />
<br />
In the past, scripting seems to have been an “every man for himself” affair. OEMs who wanted native “instant mapping” support presumably had to code their own python scripts, with much redundancy and not much sharing. For simple scripts, this was not a huge problem, however, advanced scripts often consist of many files and hundreds of lines of code. As the control surface market grows and matures, the need for a unified set of helper scripts seems obvious. It appears that Ableton’s solution to this issue has come in the form of the Framework scripts.<br />
<br />
Newer controllers now make extensive (sometimes exclusive) use of the Framework classes. The list includes the Akai APC40, the Novation Launchpad, the M-Audio Axiom Pro, the Open Labs products, and the Vestax VCM600, with more sure to follow. The Framework scripts are essentially a set of utility classes - a modular library of classes and objects - which handles most of the heavy lifting, and reduces the need to for direct calls to the LiveAPI.<br />
<br />
The Framework classes represent the “other half” of the <a href="http://www.cycling74.com/docs/max5/refpages/m4l-ref/m4l_live_object_model.html">Live Object Model</a> (LOM) – as illustrated by the Max for Live reference documents. The max for Live documents describe the Live API half in some detail, and include indirect reference to the Framework classes (control_surfaces).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkQsC-EgdCAlDImFYlSv8lk9XkUAztIVU__qW9sKVIfupZxeba9dvLGJ-DmjeT69U5kV-dakwXGj7IRNg9-uQ43CS72iJBew2pVkuBr1HM7k-pGqAKnh5eNSdIbenyGVyAg6HFSZGn6Y_P/s1600-h/ObjectModel.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkQsC-EgdCAlDImFYlSv8lk9XkUAztIVU__qW9sKVIfupZxeba9dvLGJ-DmjeT69U5kV-dakwXGj7IRNg9-uQ43CS72iJBew2pVkuBr1HM7k-pGqAKnh5eNSdIbenyGVyAg6HFSZGn6Y_P/s400/ObjectModel.png" width="400" /></a></div><br />
The <i>Component</i> and <i>Control (</i><i>element)</i> names exposed in the Max for Live documents closely mirror the Framework module names. Compare with the script file names in the _Framework directory of the MIDI Remote Scripts folder (sorted here according to type):<br />
<br />
<b>Central Base Class</b>:<br />
<span style="font-family: "Courier New",Courier,monospace;">ControlSurface.</span><br />
<br />
<b>Control Surface Components:</b><br />
<span style="font-family: "Courier New",Courier,monospace;">ControlSurfaceComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">TransportComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">SessionComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ClipSlotComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ChannelStripComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">MixerComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">DeviceComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">CompoundComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ModeSelectorComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">SceneComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">SessionZoomingComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">TrackEQComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">TrackFilterComponent</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ChannelTranslationSelector</span><br />
<br />
<b>Control Elements:</b><br />
<span style="font-family: "Courier New",Courier,monospace;">ControlElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ButtonElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ButtonMatrixElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">ButtonSliderElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">EncoderElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">InputControlElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">NotifyingControlElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">PhysicalDisplayElement</span><br />
<span style="font-family: "Courier New",Courier,monospace;">SliderElement</span><br />
<br />
<b>Other Classes:</b><br />
<span style="font-family: "Courier New",Courier,monospace;">DisplayDataSource</span><br />
<span style="font-family: "Courier New",Courier,monospace;">LogicalDisplaySegment</span><br />
<br />
And now it’s time to explore the inner workings of the Framework scripts. We’ll begin by setting up a suitable editing environment.<br />
<br />
<b>Editing Scripts</b><br />
<br />
I’ve found that it’s generally best to use a dedicated source code editor for any non-trivial scripting work (if for no other reason than to control the use of whitespace, which Python uses for indentation). In a pinch, however, pretty much any text editor can be used to open and edit a .PY file (but be careful not to mix tabs and spaces if you edit python files in a text editor). An Integrated Development Environment (IDE) is best, and most of my scripting work has been done with Wing IDE. A free version is available <a href="http://www.wingware.com/wingide-101/index">here</a>. Stani’s Python Editor (<a href="http://sourceforge.net/projects/spe/">SPE</a>) is another python IDE which is worth looking at, although there are many <a href="http://wiki.python.org/moin/IntegratedDevelopmentEnvironments">alternatives</a> – suited to many different Operating Systems.<br />
<br />
Installing Python itself is an essential part of setting up an Integrated Development Environment. Python is installed as part of the setup routine of some IDE software, or it can be installed separately. Python also comes pre-installed with some Operating Systems (but not Windows). <br />
<br />
Strictly speaking, python does not need to be installed for basic remote scripting work, since Live has a built-in python compiler (if Live finds a .PY file in a MIDI remote scripts directory, it will attempt to compile the file on start-up). Nonetheless, the benefits of using an IDE only come with python installed (including joys of auto code-completion).<br />
<br />
Typical python script code looks like this (in the Wing IDE editor environment):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHHTzpeJzqkji1Fntsnm24v8-01JX84qjZQvu_UVHoPb-GWntsBpX52CWN93w7msJU7B9eEcpR-x4SuqAS826uCwtixxrUc-Q5d8IltVr20cemxx1RVTBOXGyeG7zMpf-UKca5B6Gkav5W/s1600-h/Wing+IDE.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHHTzpeJzqkji1Fntsnm24v8-01JX84qjZQvu_UVHoPb-GWntsBpX52CWN93w7msJU7B9eEcpR-x4SuqAS826uCwtixxrUc-Q5d8IltVr20cemxx1RVTBOXGyeG7zMpf-UKca5B6Gkav5W/s400/Wing+IDE.png" width="400" /></a></div><br />
Although experience with coding (in any language) is a big advantage, a quick way to get started with python scripting is to just play around. Start with some sample code (copy of a simple script in a new folder, for example), and experiment with cut and paste, and trial and error. Typically, a script with errors will simply not compile, and nothing more will happen. It might be possible to break your Live installation with a bad script, but until you know enough to be dangerous, it’s highly unlikely.<br />
<br />
<b>Debugging</b><br />
<br />
Live provides several built-in mechanisms which can simplify debugging. I’ve found that the first key to debugging remote scripts is to make use of the Log file. The log file is a simple text file, which can be found in the Ableton Live Preferences directory:<br />
<span style="font-family: "Courier New",Courier,monospace;">C:\Documents and Settings\username\Application Data\Ableton\Live 8.x.x\Preferences\Log.txt</span><br />
<br />
Whenever Live encounters an error, it will be written to this file. This includes python compile and execution errors. If something unexpected happens after you’ve edited a script (or if the script doesn’t run at all), have a look through the Log file – the problem can often be pinpointed in this way.<br />
<br />
Here’s an example of some bad code:<br />
<div class="syntax"><pre><span class="n">transport</span><span class="o">.</span><span class="n">set_foo_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">89</span><span class="p">))</span>
</pre></div><br />
And here’s what will show up in the Log file:<br />
<div class="syntax"><pre>4812 ms. RemoteScriptError: Traceback (most recent call last):
4813 ms. RemoteScriptError: File "C:\Program Files\Ableton\Live 8.1\Resources\MIDI Remote Scripts\ProjectX\__init__.py", line 7, in create_instance
4813 ms. RemoteScriptError:
4814 ms. RemoteScriptError: return ProjectX(c_instance)
4814 ms. RemoteScriptError: File "C:\Program Files\Ableton\Live 8.1\Resources\MIDI Remote Scripts\ProjectX\ProjectX.py", line 48, in __init__
4815 ms. RemoteScriptError:
4816 ms. RemoteScriptError: self._setup_transport_control() # Run the transport setup part of the script
4817 ms. RemoteScriptError: File "C:\Program Files\Ableton\Live 8.1\Resources\MIDI Remote Scripts\ProjectX\ProjectX.py", line 78, in _setup_transport_control
4818 ms. RemoteScriptError:
4819 ms. RemoteScriptError: transport.set_foo_button(ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 89))
4819 ms. RemoteScriptError: AttributeError
4820 ms. RemoteScriptError: :
4820 ms. RemoteScriptError: 'TransportComponent' object has no attribute 'set_foo_button'
4821 ms. RemoteScriptError:
</pre></div><br />
The same Log file can also be used for tracing (via the Framework log_message method). Pretty much anything can be traced. Here’s an example:<br />
<div class="syntax"><pre><span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="s">"Captain's log stardate "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">get_random_int</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20000</span><span class="p">)))</span>
</pre></div><br />
And here’s what will show up in the log file:<br />
<div class="syntax"><pre>255483 ms. RemoteScriptMessage: Captain's log stardate 5399
</pre></div><br />
As I make modifications to a python script, I will frequently recompile to check functionality (i.e. to make sure I haven’t broken anything). I do this by setting the controller to “none” in the MIDI preferences pull-down, then immediately re-selecting my custom controller script by name. This will cause Live to recompile the modified script(s) – no need to re-load the application each time.<br />
<br />
Some of the links at right detail other working methods, but I’ve found the above to be sufficient to my needs.<br />
<br />
Now, let’s use the Framework classes to build a simple Transport script, as an example.<br />
<br />
<b>Example Script</b><br />
<br />
We’ll need to create a new folder in the MIDI Remote Scripts directory, which we can name with anything we want (although be aware that if the name starts with an underscore, it won’t show up in the Preferences drop down). We’ll call ours AAA, so that it appears at the top of the drop-down list. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd8tx-zQtLpFfOslUIrr-9LSDsw-1vVmVPCP_c8o5S8GOT_9PcJQkDVS00lFxzjcSzfDIhjzu-SzJE8XUuu1mpH317CleamzCS6kqtEOZxEfgq2BzLxHfZgXhyphenhyphenSNaJY5KdgK77XAAwFvz5/s1600-h/Preferences.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd8tx-zQtLpFfOslUIrr-9LSDsw-1vVmVPCP_c8o5S8GOT_9PcJQkDVS00lFxzjcSzfDIhjzu-SzJE8XUuu1mpH317CleamzCS6kqtEOZxEfgq2BzLxHfZgXhyphenhyphenSNaJY5KdgK77XAAwFvz5/s400/Preferences.png" width="327" /></a></div><br />
Next, we’ll need to create two files to put into this folder. The first will be nameed __init__.py file. This file marks our directory as a Python package (for the compiler), and contains only a few lines:<br />
<div class="syntax"><pre><span class="c">#__init__.py</span>
<span class="kn">from</span> <span class="nn">Transport</span> <span class="kn">import</span> <span class="n">Transport</span>
<span class="k">def</span> <span class="nf">create_instance</span><span class="p">(</span><span class="n">c_instance</span><span class="p">):</span>
<span class="k">return</span> <span class="n">Transport</span><span class="p">(</span><span class="n">c_instance</span><span class="p">)</span>
</pre></div><br />
Next, we’ll create a Transport.py file, which is our main script file (note that the file name won’t appear in the drop-down – only the folder name is important). This file contains a few more lines.<br />
<div class="syntax"><pre><span class="c">#Transport.py </span>
<span class="c">#This is a stripped-down script, which uses the Framework classes to assign MIDI notes to play, stop and record.</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span> <span class="c"># Central base class for scripts based on the new Framework</span>
<span class="kn">from</span> <span class="nn">_Framework.TransportComponent</span> <span class="kn">import</span> <span class="n">TransportComponent</span> <span class="c"># Class encapsulating all functions in Live's transport section</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span> <span class="c"># Class representing a button a the controller</span>
<span class="k">class</span> <span class="nc">Transport</span><span class="p">(</span><span class="n">ControlSurface</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="n">transport</span> <span class="o">=</span> <span class="n">TransportComponent</span><span class="p">()</span> <span class="c">#Instantiate a Transport Component</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_play_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">61</span><span class="p">))</span> <span class="c">#ButtonElement(is_momentary, msg_type, channel, identifier)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_stop_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">63</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_record_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">66</span><span class="p">))</span>
</pre></div><br />
Now, if we open up Live and select AAA from the MIDI preferences pull-down, Live will compile our .PY files, create corresponding .PYC files in our script folder, and run the scripts.<br />
<br />
MIDI notes 60, 61 and 63 on Channel 1 should now be automatically mapped to Play, Stop, and Record respectively. The python sources for this simple script can be found <a href="http://hanzoffsystems.tech.officelive.com/AAA.rar">here</a>. Of course, there are many other simple mappings we could make, following the same basic structure. For example, if we wanted to map Tap Tempo to a key, we’d simply add the following line to our script:<br />
<div class="syntax"><pre><span class="n">transport</span><span class="o">.</span><span class="n">set_tap_tempo_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">68</span><span class="p">))</span>
</pre></div><br />
The script above uses one of the most basic Framework modules - the TransportComponent module. In addition to the three methods in the script, other public TransportComponent methods include the following:<br />
<span style="font-family: "Courier New",Courier,monospace;"> set_stop_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_play_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_seek_buttons(ffwd_button, rwd_button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_nudge_buttons(up_button, down_button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_record_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_tap_tempo_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_loop_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_punch_buttons(in_button, out_button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_metronom_button(button)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_overdub_button(sbutton)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_tempo_control(control, fine_control)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> set_song_position_control(control) </span><br />
<br />
<div style="font-family: inherit;">(Note that <span style="font-family: "Courier New",Courier,monospace;">set_metronom_button</span> is actually mis-spelled in the 7.x.x Framework, but is corrected to <span style="font-family: "Courier New",Courier,monospace;">set_metronome_button</span> in 8.x.x. This means that scripts using this method will only run on one version or the other, depending on the spelling used..!)</div><div style="font-family: inherit;"><br />
</div><span style="font-family: inherit;">Mo</span>st of the Framework files and functions (actually classes and methods) are self-explanatory, and it is sufficient to browse the names of the classes and methods in order to understand what they do. Others are more complicated, and are best understood by referring to sample code. The VCM600, the Launchpad, and the Axiom Pro all make use of the Framework classes, and so does the APC40 (naturally). These are great scripts to use as references (secret link for attentive readers <a href="http://hanzoffsystems.tech.officelive.com/MIDI%20Remote%20Scripts%20PY%207.0.18.rar">here</a>). Documentation for the Framework classes, generated from the decompiled sources, can be consulted online <a href="http://hanzoffsystems.tech.officelive.com/">here</a>. Again, for the most part, all of the functions work as one would expect them to.<br />
<br />
When working with decompiled sources, it is worth noting that many of the scripts are “old school”, pre-dating the development of the Framework classes. While they certainly work, they tend to be much more complicated than newer scripts. The complexity is now handled by the Framework classes, which makes most scripting tasks much simpler. <br />
<br />
On the other hand, even scripts based on the Framework can be complicated, especially when additional classes need to be developed to handle special functionality which the Framework does not provide. The APC40 scripts are a good example of complex scripting.<br />
<br />
Now, let’s try building a set of scripts that can do some of the fancier things which new generation controllers can do, using the Framework classes (I want a “red box” too!).<br />
<br />
<b>ProjectX</b> <br />
<br />
We won’t exactly be emulating the APC40 or Launchpad here, since their functionality is so tightly tied to their hardware layouts - although admittedly APC40 emulation could be fun to explore (it probably wouldn’t be of any great use to anyone, however, except possibly an APC40 owner wanting to customize). Instead, we’ll turn a bog standard MIDI keyboard into a two-dimension grid controller, using the Framework classes.<br />
<br />
A MIDI keyboard is generally one-dimensional, and we want to do some of the things that a two-dimensional grid controller can do. To get around this limitation, we’ll use two sets of keys - one for the vertical (scenes) and one for the horizontal (tracks) – a moveable X-Y grid of keys. We’ll call our “controller” ProjectX.<br />
<br />
The ProjectX script is made up of two sets of keyboard mappings, which can be used together or independently. Part X is a vertical session component (“red box”), and Part Y is a horizontal session component (“yellow box”). We’ll keep it relatively simple (it is intended to be used with a standard MIDI keyboard, after all), but we will demonstrate the use of several of the Framework classes and methods along the way - primarily the Session, Mixer and Transport components. <br />
<br />
Here is a keyboard map, which shows the note assignments of the mappings we’ll be making.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3ckXi_CT9ofoNeqSHoaE1WPvEMs36Iq21OUzWTqpl3OutFA5IOipxjr8GKteLxGRMjWA9wF6ntN70-DlgBLgF4eHIfj26rlaPLUC7IxYyRMYLAVmj0vBMldVvucr45ZnlFlr7fW6tNsJk/s1600-h/ProjectX+keys.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3ckXi_CT9ofoNeqSHoaE1WPvEMs36Iq21OUzWTqpl3OutFA5IOipxjr8GKteLxGRMjWA9wF6ntN70-DlgBLgF4eHIfj26rlaPLUC7IxYyRMYLAVmj0vBMldVvucr45ZnlFlr7fW6tNsJk/s400/ProjectX+keys.png" width="400" /></a></div> The APC40, Launchpad and Monome all have grids of buttons; we’ve split the two grid dimensions into two session boxes here. The “red box” will be 1 track wide by 7 scenes high, and the “yellow box” will be 7 tracks wide by 1 scene high. The red box represents a set of 7 scenes (or clip slots), and the yellow box represents a set of 7 tracks. Used together, they form a virtual grid of 7 tracks by 7 scenes, each of which is controlled by a separate set of seven “white notes”. Here’s what they look like in the session view (sorry, no video):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTnFyphzIQCIZiMRobL51X49uP3BceQi6Eg-Ee1oyLhy8QBDp6jUWYrrGtvpm5L3VgwbRAN_To08lK50Pytqc3LCNXgR_5suPfTBqjokLZ93AeDObdXgK2zq8w5ZbMNMtF1d0T6yPBlzRj/s1600-h/ProjectX+Demo+1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="335" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTnFyphzIQCIZiMRobL51X49uP3BceQi6Eg-Ee1oyLhy8QBDp6jUWYrrGtvpm5L3VgwbRAN_To08lK50Pytqc3LCNXgR_5suPfTBqjokLZ93AeDObdXgK2zq8w5ZbMNMtF1d0T6yPBlzRj/s400/ProjectX+Demo+1.png" width="400" /></a></div><br />
And here is the ProjectX script (red box), that uses Framework magic:<br />
<div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span> <span class="c"># This allows us (and the Framework methods) to use the Live API on occasion</span>
<span class="kn">import</span> <span class="nn">time</span> <span class="c"># We will be using time functions for time-stamping our log file outputs</span>
<span class="sd">""" All of the Framework files are listed below, but we are only using using some of them in this script (the rest are commented out) """</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span> <span class="c"># Class representing a button a the controller</span>
<span class="c">#from _Framework.ButtonMatrixElement import ButtonMatrixElement # Class representing a 2-dimensional set of buttons</span>
<span class="c">#from _Framework.ButtonSliderElement import ButtonSliderElement # Class representing a set of buttons used as a slider</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelStripComponent</span> <span class="kn">import</span> <span class="n">ChannelStripComponent</span> <span class="c"># Class attaching to the mixer of a given track</span>
<span class="c">#from _Framework.ChannelTranslationSelector import ChannelTranslationSelector # Class switches modes by translating the given controls' message channel</span>
<span class="kn">from</span> <span class="nn">_Framework.ClipSlotComponent</span> <span class="kn">import</span> <span class="n">ClipSlotComponent</span> <span class="c"># Class representing a ClipSlot within Live</span>
<span class="kn">from</span> <span class="nn">_Framework.CompoundComponent</span> <span class="kn">import</span> <span class="n">CompoundComponent</span> <span class="c"># Base class for classes encompasing other components to form complex components</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlElement</span> <span class="kn">import</span> <span class="n">ControlElement</span> <span class="c"># Base class for all classes representing control elements on a controller</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span> <span class="c"># Central base class for scripts based on the new Framework</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurfaceComponent</span> <span class="kn">import</span> <span class="n">ControlSurfaceComponent</span> <span class="c"># Base class for all classes encapsulating functions in Live</span>
<span class="c">#from _Framework.DeviceComponent import DeviceComponent # Class representing a device in Live</span>
<span class="c">#from _Framework.DisplayDataSource import DisplayDataSource # Data object that is fed with a specific string and notifies its observers</span>
<span class="c">#from _Framework.EncoderElement import EncoderElement # Class representing a continuous control on the controller</span>
<span class="kn">from</span> <span class="nn">_Framework.InputControlElement</span> <span class="kn">import</span> <span class="o">*</span> <span class="c"># Base class for all classes representing control elements on a controller</span>
<span class="c">#from _Framework.LogicalDisplaySegment import LogicalDisplaySegment # Class representing a specific segment of a display on the controller</span>
<span class="kn">from</span> <span class="nn">_Framework.MixerComponent</span> <span class="kn">import</span> <span class="n">MixerComponent</span> <span class="c"># Class encompassing several channel strips to form a mixer</span>
<span class="c">#from _Framework.ModeSelectorComponent import ModeSelectorComponent # Class for switching between modes, handle several functions with few controls</span>
<span class="c">#from _Framework.NotifyingControlElement import NotifyingControlElement # Class representing control elements that can send values</span>
<span class="c">#from _Framework.PhysicalDisplayElement import PhysicalDisplayElement # Class representing a display on the controller</span>
<span class="kn">from</span> <span class="nn">_Framework.SceneComponent</span> <span class="kn">import</span> <span class="n">SceneComponent</span> <span class="c"># Class representing a scene in Live</span>
<span class="kn">from</span> <span class="nn">_Framework.SessionComponent</span> <span class="kn">import</span> <span class="n">SessionComponent</span> <span class="c"># Class encompassing several scene to cover a defined section of Live's session</span>
<span class="kn">from</span> <span class="nn">_Framework.SessionZoomingComponent</span> <span class="kn">import</span> <span class="n">SessionZoomingComponent</span> <span class="c"># Class using a matrix of buttons to choose blocks of clips in the session</span>
<span class="kn">from</span> <span class="nn">_Framework.SliderElement</span> <span class="kn">import</span> <span class="n">SliderElement</span> <span class="c"># Class representing a slider on the controller</span>
<span class="c">#from _Framework.TrackEQComponent import TrackEQComponent # Class representing a track's EQ, it attaches to the last EQ device in the track</span>
<span class="c">#from _Framework.TrackFilterComponent import TrackFilterComponent # Class representing a track's filter, attaches to the last filter in the track</span>
<span class="kn">from</span> <span class="nn">_Framework.TransportComponent</span> <span class="kn">import</span> <span class="n">TransportComponent</span> <span class="c"># Class encapsulating all functions in Live's transport section</span>
<span class="sd">""" Here we define some global variables """</span>
<span class="n">CHANNEL</span> <span class="o">=</span> <span class="mi">0</span> <span class="c"># Channels are numbered 0 through 15, this script only makes use of one MIDI Channel (Channel 1)</span>
<span class="n">session</span> <span class="o">=</span> <span class="bp">None</span> <span class="c">#Global session object - global so that we can manipulate the same session object from within any of our methods </span>
<span class="n">mixer</span> <span class="o">=</span> <span class="bp">None</span> <span class="c">#Global mixer object - global so that we can manipulate the same mixer object from within any of our methods</span>
<span class="k">class</span> <span class="nc">ProjectX</span><span class="p">(</span><span class="n">ControlSurface</span><span class="p">):</span>
<span class="n">__module__</span> <span class="o">=</span> <span class="n">__name__</span>
<span class="n">__doc__</span> <span class="o">=</span> <span class="s">" ProjectX keyboard controller script "</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="sd">"""everything except the '_on_selected_track_changed' override and 'disconnect' runs from here"""</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">"</span><span class="si">%d</span><span class="s">.%m.%Y %H:%M:%S"</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">())</span> <span class="o">+</span> <span class="s">"--------------= ProjectX log opened =--------------"</span><span class="p">)</span> <span class="c"># Writes message into Live's main log file. This is a ControlSurface method.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="c"># Turn off rebuild MIDI map until after we're done setting up</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_transport_control</span><span class="p">()</span> <span class="c"># Run the transport setup part of the script</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_mixer_control</span><span class="p">()</span> <span class="c"># Setup the mixer object</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_session_control</span><span class="p">()</span> <span class="c"># Setup the session object </span>
<span class="sd">""" Here is some Live API stuff just for fun """</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Live</span><span class="o">.</span><span class="n">Application</span><span class="o">.</span><span class="n">get_application</span><span class="p">()</span> <span class="c"># get a handle to the App</span>
<span class="n">maj</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">get_major_version</span><span class="p">()</span> <span class="c"># get the major version from the App</span>
<span class="nb">min</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">get_minor_version</span><span class="p">()</span> <span class="c"># get the minor version from the App</span>
<span class="n">bug</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">get_bugfix_version</span><span class="p">()</span> <span class="c"># get the bugfix version from the App</span>
<span class="bp">self</span><span class="o">.</span><span class="n">show_message</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">maj</span><span class="p">)</span> <span class="o">+</span> <span class="s">"."</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="nb">min</span><span class="p">)</span> <span class="o">+</span> <span class="s">"."</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">bug</span><span class="p">))</span> <span class="c">#put them together and use the ControlSurface show_message method to output version info to console</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span> <span class="c">#Turn rebuild back on, now that we're done setting up</span>
<span class="k">def</span> <span class="nf">_setup_transport_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span> <span class="c"># We'll only be using momentary buttons here</span>
<span class="n">transport</span> <span class="o">=</span> <span class="n">TransportComponent</span><span class="p">()</span> <span class="c">#Instantiate a Transport Component</span>
<span class="sd">"""set up the buttons"""</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_play_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">61</span><span class="p">))</span> <span class="c">#ButtonElement(is_momentary, msg_type, channel, identifier) Note that the MIDI_NOTE_TYPE constant is defined in the InputControlElement module</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_stop_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">63</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_record_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">66</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_overdub_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">68</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_nudge_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">75</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">73</span><span class="p">))</span> <span class="c">#(up_button, down_button)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_tap_tempo_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">78</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_metronome_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">80</span><span class="p">))</span> <span class="c">#For some reason, in Ver 7.x.x this method's name has no trailing "e" , and must be called as "set_metronom_button()"...</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_loop_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">82</span><span class="p">))</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_punch_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">85</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">87</span><span class="p">))</span> <span class="c">#(in_button, out_button)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_seek_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">90</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">92</span><span class="p">))</span> <span class="c"># (ffwd_button, rwd_button)</span>
<span class="sd">"""set up the sliders"""</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_tempo_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">26</span><span class="p">),</span> <span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">25</span><span class="p">))</span> <span class="c">#(control, fine_control)</span>
<span class="n">transport</span><span class="o">.</span><span class="n">set_song_position_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">24</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">_setup_mixer_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">num_tracks</span> <span class="o">=</span> <span class="mi">7</span> <span class="c">#A mixer is one-dimensional; here we define the width in tracks - seven columns, which we will map to seven "white" notes</span>
<span class="sd">"""Here we set up the global mixer"""</span> <span class="c">#Note that it is possible to have more than one mixer...</span>
<span class="k">global</span> <span class="n">mixer</span> <span class="c">#We want to instantiate the global mixer as a MixerComponent object (it was a global "None" type up until now...)</span>
<span class="n">mixer</span> <span class="o">=</span> <span class="n">MixerComponent</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">with_eqs</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">with_filters</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c">#(num_tracks, num_returns, with_eqs, with_filters)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_track_offset</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c">#Sets start point for mixer strip (offset from left)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_track</span> <span class="o">=</span> <span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">_track</span> <span class="c">#set the selected strip to the first track, so that we don't, for example, try to assign a button to arm the master track, which would cause an assertion error</span>
<span class="sd">"""set up the mixer buttons"""</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_select_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">56</span><span class="p">),</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">54</span><span class="p">))</span> <span class="c">#left, right track select </span>
<span class="n">mixer</span><span class="o">.</span><span class="n">master_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">94</span><span class="p">))</span> <span class="c">#jump to the master track</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_mute_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">42</span><span class="p">))</span> <span class="c">#sets the mute ("activate") button</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_solo_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">44</span><span class="p">))</span> <span class="c">#sets the solo button</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_arm_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">46</span><span class="p">))</span> <span class="c">#sets the record arm button</span>
<span class="sd">"""set up the mixer sliders"""</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">selected_strip</span><span class="p">()</span><span class="o">.</span><span class="n">set_volume_control</span><span class="p">(</span><span class="n">SliderElement</span><span class="p">(</span><span class="n">MIDI_CC_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">14</span><span class="p">))</span> <span class="c">#sets the continuous controller for volume</span>
<span class="sd">"""note that we have split the mixer functions across two scripts, in order to have two session highlight boxes (one red, one yellow), so there are a few things which we are not doing here..."""</span>
<span class="k">def</span> <span class="nf">_setup_session_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">num_tracks</span> <span class="o">=</span> <span class="mi">1</span> <span class="c">#single column</span>
<span class="n">num_scenes</span> <span class="o">=</span> <span class="mi">7</span> <span class="c">#seven rows, which will be mapped to seven "white" notes</span>
<span class="k">global</span> <span class="n">session</span> <span class="c">#We want to instantiate the global session as a SessionComponent object (it was a global "None" type up until now...)</span>
<span class="n">session</span> <span class="o">=</span> <span class="n">SessionComponent</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">,</span> <span class="n">num_scenes</span><span class="p">)</span> <span class="c">#(num_tracks, num_scenes) A session highlight ("red box") will appear with any two non-zero values</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c">#(track_offset, scene_offset) Sets the initial offset of the "red box" from top left</span>
<span class="sd">"""set up the session navigation buttons"""</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_select_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">27</span><span class="p">))</span> <span class="c"># (next_button, prev_button) Scene select buttons - up & down - we'll also use a second ControlComponent for this (yellow box)</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_scene_bank_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">51</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">49</span><span class="p">))</span> <span class="c"># (up_button, down_button) This is to move the "red box" up or down (increment track up or down, not screen up or down, so they are inversed)</span>
<span class="c">#session.set_track_bank_buttons(ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 56), ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 54)) # (right_button, left_button) This moves the "red box" selection set left & right. We'll put our track selection in Part B of the script, rather than here...</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_stop_all_clips_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">70</span><span class="p">))</span>
<span class="n">session</span><span class="o">.</span><span class="n">selected_scene</span><span class="p">()</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">30</span><span class="p">))</span>
<span class="sd">"""Here we set up the scene launch assignments for the session"""</span>
<span class="n">launch_notes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">60</span><span class="p">,</span> <span class="mi">62</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">71</span><span class="p">]</span> <span class="c">#this is our set of seven "white" notes, starting at C4</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_scenes</span><span class="p">):</span> <span class="c">#launch_button assignment must match number of scenes</span>
<span class="n">session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="n">launch_notes</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> <span class="c">#step through the scenes (in the session) and assign corresponding note from the launch_notes array</span>
<span class="sd">"""Here we set up the track stop launch assignment(s) for the session"""</span> <span class="c">#The following code is set up for a longer array (we only have one track, so it's over-complicated, but good for future adaptation)..</span>
<span class="n">stop_track_buttons</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">):</span>
<span class="n">stop_track_buttons</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">58</span> <span class="o">+</span> <span class="n">index</span><span class="p">))</span> <span class="c">#this would need to be adjusted for a longer array (because we've already used the next note numbers elsewhere)</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_stop_track_clip_buttons</span><span class="p">(</span><span class="nb">tuple</span><span class="p">(</span><span class="n">stop_track_buttons</span><span class="p">))</span> <span class="c">#array size needs to match num_tracks </span>
<span class="sd">"""Here we set up the clip launch assignments for the session"""</span>
<span class="n">clip_launch_notes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">48</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">52</span><span class="p">,</span> <span class="mi">53</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">57</span><span class="p">,</span> <span class="mi">59</span><span class="p">]</span> <span class="c">#this is a set of seven "white" notes, starting at C3</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_scenes</span><span class="p">):</span>
<span class="n">session</span><span class="o">.</span><span class="n">scene</span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="o">.</span><span class="n">clip_slot</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">set_launch_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="n">clip_launch_notes</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> <span class="c">#step through scenes and assign a note to first slot of each </span>
<span class="sd">"""Here we set up a mixer and channel strip(s) which move with the session"""</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_mixer</span><span class="p">(</span><span class="n">mixer</span><span class="p">)</span> <span class="c">#Bind the mixer to the session so that they move together</span>
<span class="k">def</span> <span class="nf">_on_selected_track_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""This is an override, to add special functionality (we want to move the session to the selected track, when it changes)</span>
<span class="sd"> Note that it is sometimes necessary to reload Live (not just the script) when making changes to this function"""</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">_on_selected_track_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="c"># This will run component.on_selected_track_changed() for all components</span>
<span class="sd">"""here we set the mixer and session to the selected track, when the selected track changes"""</span>
<span class="n">selected_track</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_track</span> <span class="c">#this is how to get the currently selected track, using the Live API</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">set_track</span><span class="p">(</span><span class="n">selected_track</span><span class="p">)</span>
<span class="n">all_tracks</span> <span class="o">=</span> <span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">tracks</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">return_tracks</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">master_track</span><span class="p">,))</span> <span class="c">#this is from the MixerComponent's _next_track_value method</span>
<span class="n">index</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">all_tracks</span><span class="p">)</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">selected_track</span><span class="p">)</span> <span class="c">#and so is this</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">session</span><span class="o">.</span><span class="n">_scene_offset</span><span class="p">)</span> <span class="c">#(track_offset, scene_offset); we leave scene_offset unchanged, but set track_offset to the selected track. This allows us to jump the red box to the selected track.</span>
<span class="k">def</span> <span class="nf">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""clean things up on disconnect"""</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">"</span><span class="si">%d</span><span class="s">.%m.%Y %H:%M:%S"</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">())</span> <span class="o">+</span> <span class="s">"--------------= ProjectX log closed =--------------"</span><span class="p">)</span> <span class="c">#Create entry in log file</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div><br />
And here is the counterpart ProjectY script (yellow box):<br />
<div class="syntax"><pre><span class="kn">import</span> <span class="nn">Live</span> <span class="c"># This allows us (and the Framework methods) to use the Live API on occasion</span>
<span class="kn">import</span> <span class="nn">time</span> <span class="c"># We will be using time functions for time-stamping our log file outputs</span>
<span class="sd">""" We are only using using some of the Framework classes them in this script (the rest are not listed here) """</span>
<span class="kn">from</span> <span class="nn">_Framework.ButtonElement</span> <span class="kn">import</span> <span class="n">ButtonElement</span> <span class="c"># Class representing a button a the controller</span>
<span class="kn">from</span> <span class="nn">_Framework.ChannelStripComponent</span> <span class="kn">import</span> <span class="n">ChannelStripComponent</span> <span class="c"># Class attaching to the mixer of a given track</span>
<span class="kn">from</span> <span class="nn">_Framework.ClipSlotComponent</span> <span class="kn">import</span> <span class="n">ClipSlotComponent</span> <span class="c"># Class representing a ClipSlot within Live</span>
<span class="kn">from</span> <span class="nn">_Framework.CompoundComponent</span> <span class="kn">import</span> <span class="n">CompoundComponent</span> <span class="c"># Base class for classes encompasing other components to form complex components</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlElement</span> <span class="kn">import</span> <span class="n">ControlElement</span> <span class="c"># Base class for all classes representing control elements on a controller</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurface</span> <span class="kn">import</span> <span class="n">ControlSurface</span> <span class="c"># Central base class for scripts based on the new Framework</span>
<span class="kn">from</span> <span class="nn">_Framework.ControlSurfaceComponent</span> <span class="kn">import</span> <span class="n">ControlSurfaceComponent</span> <span class="c"># Base class for all classes encapsulating functions in Live</span>
<span class="kn">from</span> <span class="nn">_Framework.InputControlElement</span> <span class="kn">import</span> <span class="o">*</span> <span class="c"># Base class for all classes representing control elements on a controller</span>
<span class="kn">from</span> <span class="nn">_Framework.MixerComponent</span> <span class="kn">import</span> <span class="n">MixerComponent</span> <span class="c"># Class encompassing several channel strips to form a mixer</span>
<span class="kn">from</span> <span class="nn">_Framework.SceneComponent</span> <span class="kn">import</span> <span class="n">SceneComponent</span> <span class="c"># Class representing a scene in Live</span>
<span class="kn">from</span> <span class="nn">_Framework.SessionComponent</span> <span class="kn">import</span> <span class="n">SessionComponent</span> <span class="c"># Class encompassing several scene to cover a defined section of Live's session</span>
<span class="kn">from</span> <span class="nn">_Framework.SliderElement</span> <span class="kn">import</span> <span class="n">SliderElement</span> <span class="c"># Class representing a slider on the controller</span>
<span class="kn">from</span> <span class="nn">_Framework.TransportComponent</span> <span class="kn">import</span> <span class="n">TransportComponent</span> <span class="c"># Class encapsulating all functions in Live's transport section</span>
<span class="sd">""" Here we define some global variables """</span>
<span class="n">CHANNEL</span> <span class="o">=</span> <span class="mi">0</span> <span class="c"># Channels are numbered 0 through 15, this script only makes use of one MIDI Channel (Channel 1)</span>
<span class="n">session</span> <span class="o">=</span> <span class="bp">None</span> <span class="c">#Global session object - global so that we can manipulate the same session object from within our methods </span>
<span class="n">mixer</span> <span class="o">=</span> <span class="bp">None</span> <span class="c">#Global mixer object - global so that we can manipulate the same mixer object from within our methods</span>
<span class="k">class</span> <span class="nc">ProjectY</span><span class="p">(</span><span class="n">ControlSurface</span><span class="p">):</span>
<span class="n">__module__</span> <span class="o">=</span> <span class="n">__name__</span>
<span class="n">__doc__</span> <span class="o">=</span> <span class="s">" ProjectY keyboard controller script "</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">):</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">c_instance</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">"</span><span class="si">%d</span><span class="s">.%m.%Y %H:%M:%S"</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">())</span> <span class="o">+</span> <span class="s">"--------------= ProjectY log opened =--------------"</span><span class="p">)</span> <span class="c"># Writes message into Live's main log file. This is a ControlSurface method.</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> <span class="c"># Turn off rebuild MIDI map until after we're done setting up</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_mixer_control</span><span class="p">()</span> <span class="c"># Setup the mixer object</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_setup_session_control</span><span class="p">()</span> <span class="c"># Setup the session object</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_suppress_rebuild_requests</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span> <span class="c"># Turn rebuild back on, once we're done setting up</span>
<span class="k">def</span> <span class="nf">_setup_mixer_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span> <span class="c"># We use non-latching buttons (keys) throughout, so we'll set this as a constant</span>
<span class="n">num_tracks</span> <span class="o">=</span> <span class="mi">7</span> <span class="c"># Here we define the mixer width in tracks (a mixer has only one dimension)</span>
<span class="k">global</span> <span class="n">mixer</span> <span class="c"># We want to instantiate the global mixer as a MixerComponent object (it was a global "None" type up until now...)</span>
<span class="n">mixer</span> <span class="o">=</span> <span class="n">MixerComponent</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">with_eqs</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">with_filters</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span> <span class="c">#(num_tracks, num_returns, with_eqs, with_filters)</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">set_track_offset</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c">#Sets start point for mixer strip (offset from left)</span>
<span class="sd">"""set up the mixer buttons"""</span>
<span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_track</span> <span class="o">=</span> <span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">_track</span>
<span class="c">#mixer.selected_strip().set_mute_button(ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 42))</span>
<span class="c">#mixer.selected_strip().set_solo_button(ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 44))</span>
<span class="c">#mixer.selected_strip().set_arm_button(ButtonElement(is_momentary, MIDI_NOTE_TYPE, CHANNEL, 46))</span>
<span class="n">track_select_notes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">36</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">41</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">47</span><span class="p">]</span> <span class="c">#more note numbers need to be added if num_scenes is increased</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">):</span>
<span class="n">mixer</span><span class="o">.</span><span class="n">channel_strip</span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="o">.</span><span class="n">set_select_button</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="n">track_select_notes</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span>
<span class="k">def</span> <span class="nf">_setup_session_control</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">is_momentary</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">num_tracks</span> <span class="o">=</span> <span class="mi">7</span>
<span class="n">num_scenes</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">global</span> <span class="n">session</span> <span class="c">#We want to instantiate the global session as a SessionComponent object (it was a global "None" type up until now...)</span>
<span class="n">session</span> <span class="o">=</span> <span class="n">SessionComponent</span><span class="p">(</span><span class="n">num_tracks</span><span class="p">,</span> <span class="n">num_scenes</span><span class="p">)</span> <span class="c">#(num_tracks, num_scenes)</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c">#(track_offset, scene_offset) Sets the initial offset of the red box from top left</span>
<span class="sd">"""set up the session buttons"""</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_track_bank_buttons</span><span class="p">(</span><span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">39</span><span class="p">),</span> <span class="n">ButtonElement</span><span class="p">(</span><span class="n">is_momentary</span><span class="p">,</span> <span class="n">MIDI_NOTE_TYPE</span><span class="p">,</span> <span class="n">CHANNEL</span><span class="p">,</span> <span class="mi">37</span><span class="p">))</span> <span class="c"># (right_button, left_button) This moves the "red box" selection set left & right. We'll use the mixer track selection instead...</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_mixer</span><span class="p">(</span><span class="n">mixer</span><span class="p">)</span> <span class="c">#Bind the mixer to the session so that they move together</span>
<span class="n">selected_scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_scene</span> <span class="c">#this is from the Live API</span>
<span class="n">all_scenes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">scenes</span>
<span class="n">index</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">all_scenes</span><span class="p">)</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">selected_scene</span><span class="p">)</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="c">#(track_offset, scene_offset)</span>
<span class="k">def</span> <span class="nf">_on_selected_scene_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""This is an override, to add special functionality (we want to move the session to the selected scene, when it changes)"""</span>
<span class="sd">"""When making changes to this function on the fly, it is sometimes necessary to reload Live (not just the script)..."""</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">_on_selected_scene_changed</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="c"># This will run component.on_selected_scene_changed() for all components</span>
<span class="sd">"""Here we set the mixer and session to the selected track, when the selected track changes"""</span>
<span class="n">selected_scene</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">selected_scene</span> <span class="c">#this is how we get the currently selected scene, using the Live API</span>
<span class="n">all_scenes</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">song</span><span class="p">()</span><span class="o">.</span><span class="n">scenes</span> <span class="c">#then get all of the scenes</span>
<span class="n">index</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">all_scenes</span><span class="p">)</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">selected_scene</span><span class="p">)</span> <span class="c">#then identify where the selected scene sits in relation to the full list</span>
<span class="n">session</span><span class="o">.</span><span class="n">set_offsets</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">_track_offset</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="c">#(track_offset, scene_offset) Set the session's scene offset to match the selected track (but make no change to the track offset)</span>
<span class="k">def</span> <span class="nf">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""clean things up on disconnect"""</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log_message</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">"</span><span class="si">%d</span><span class="s">.%m.%Y %H:%M:%S"</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">())</span> <span class="o">+</span> <span class="s">"--------------= ProjectY log closed =--------------"</span><span class="p">)</span> <span class="c">#Create entry in log file</span>
<span class="n">ControlSurface</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div><br />
Of course, each of these scripts also has a corresponding __init__.py, which looks some thing like this:<br />
<div class="syntax"><pre><span class="kn">from</span> <span class="nn">ProjectX</span> <span class="kn">import</span> <span class="n">ProjectX</span>
<span class="k">def</span> <span class="nf">create_instance</span><span class="p">(</span><span class="n">c_instance</span><span class="p">):</span>
<span class="sd">""" Creates and returns the ProjectX script """</span>
<span class="k">return</span> <span class="n">ProjectX</span><span class="p">(</span><span class="n">c_instance</span><span class="p">)</span>
</pre></div><br />
To use these scripts, two file folders need to be saved to the MIDI Remote Scripts directory (one for X, and one for Y), and the two controllers need to be loaded using the MIDI preferences drop-down. The .PY source files are <a href="http://hanzoffsystems.tech.officelive.com/ProjectX%20MIDI%20Remote%20Scripts.rar">here</a>. Unfortunately, I was not able to find a way to load two session highlight boxes from with one script, which explains the two-folder approach. Either script could be used independently, but then we would lose the X-Y interaction. In any event, the idea was to explore using the Framework classes in a novel way. Maybe not too useful in a real-world application, but hopefully these scripts show some of the Framework classes’ hidden potential.<br />
<br />
<b>Conclusion</b><br />
<br />
The Framework classes may evolve with newer versions of Live, and some functions may cease to work as expected. However, since all newer controller scripts seem to be based on the Framework classes, it is likely that change will be kept to a minimum (or at least, hopefully, new methods will not break old ones). There are risks involved in working with an undocumented function library, but on the other hand, the Framework classes certainly help to make remote scripting easy. <br />
<br />
Hopefully this exploration has been helpful to somebody out there. Go, be creative, have fun - and share your work with others!<br />
<br />
Hanz Petrov<br />
March 2010Hanz Petrovhttp://www.blogger.com/profile/11357582251433610792noreply@blogger.com52