diff --git a/gtk2_ardour/icons/cc121.png b/gtk2_ardour/icons/cc121.png
new file mode 100644
index 0000000000000000000000000000000000000000..098ed8eb11f86891b084c1575c47043b8bf8bb6a
GIT binary patch
literal 26938
zcmV)lK%c*fP)<h;3K|Lk000e1NJLTq00651003nO0ssI2y=a7r00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmYE+YT{E+YYWr9XB6000McNliru;0Y8F02~1xhLZpQAOJ~3
zK~#9!e7$L`W!ZHewuZg;Ip@yzO;xX|tE#)2-5i?pAc?fd7A4AzZAA_vDN-QI(GSUi
zWyC=O8BvTNfAlLqA|Me0#8v>waAbx`5Wr$YF_dMQG6#{2ICOJrcC)*>x@&&pozAfL
zS}Q-!y|-??D)OQ5>eYSso^$rzYp?ySZ+&YOe)hAU{qmQ;?7c^10009(06+jkL;ye}
z05fR6+Sh=H5Wvh3$xPeNY<wwztv|55g4zAgKlrtN(QJ8BA~G{(HZwDG&Ko2|YoE@n
zz3hV@1VltpU0&2Y5g`&l1hn=&?Ned<;nmICzX-{|5U_nSw08kCFa)<TmiGNdbpP2A
z5fJVV8G@=3ArYzSdRz~W{mhH)+surJ)<3kb6OkDZ5#rga*?MtS*Hl&7c)s=x>sQ)|
z6%l6s;0HhG#^dcbzxf$6WAe+@TTNm+uy#spIlTK80BWkr%<KEp`k5fE))Ha4G`49I
zn@{$zDYW0)VG!ZBzWMEg*&z|NMtR?o5QunrS#5`BNN#Da)-E1m`&bAD3ZSitBOw|B
z0GI=`_IlPD7^dY;0A4MytxaV)enUW7zXq)V;BwxW93ZOdd*1WT5W<=D-W>RPyzLL|
zN}pN3)v_%&#`@Dlk1(lrbyG^tx%J1*j0o4mX|JQIh~}JgF{PLi5t+u#t>12fc6x2;
z2>=mPtz8~OG%%C2HVtb(aeu3U8MM!-poq5lpqoapzGFM`P3vx&_&dMzKi5seh^D~I
zs%j5xn+JB%>Y50qt?6kyc?huDzF+{hntdWLLqr8@N4m5r1E2@|1R{d9mq7yqTskTs
zT=|dX7Xv1+dS3o7|Lgy>zrWu;(%QGR|6IRvW_OB6`>Xby8~%egHzxp?-8YoYv8R;U
z>uhc(5!*00Fq7p9nz{C;<zT=b_9|=h-Mo#ewpO-ju&tjULV)$s8lblKeArp7zn>Y&
zrW1-WHchPx?M?+#15?pQJ>>Eu?O4|KcHi2-Y`qa}^$e=EVF3W3wqBL{`wtP<J`9$&
zv|25uGow%iA&8R7vRu3IO+UXr9X9vC%GFzI<(u<^hyc1gn2<J?;|Dp>^{zN`-mPCJ
zA~OL1w;ccGLYkfNRH}An*D|xI!Ny6v+#;*{u2&P62c-S*^Y4rWFmwAJA~HkoSWHAz
z)M#UPYg<`8Jeuipa#sVkr8fgyVVC7t0N1xR1A}&*+jqf(8x8?z{oE_Zzl1indfNNT
zjA(>Fta{c`+s$*v`ZrhW3?zEU0t|p22`M&h;EZ9drfB)(rt53keG7QtWHKcrKrZqO
zkgNK%$Oi0eu_%d<$*XFXXJA-WWzp$W%|b*>l^Is3(3VHXrhRMxy)%=tHve`ZTL8G)
zkp^PPRB-9F6oBqOEU#AUK5ktuYI_8<?tHbXD>P$>R8_SPUPTPZxqu9)KuBr=i0vt0
zpa2MF2#QD>Xa)_I;B!5M`wN3;h6JeE+Mc$faqj+XZ-SxCpEr-ic3ZA|^ZI<m2XEIR
zn6;&v8JaCmyhlv9wcZqv5Dd)BodJriv7{8svi`$A{MRYkPk!VB-}%<HSFXMA!4H1$
z>eVOz-%tOW4}9PQpZe6NRP~pB>6bq7iBG)aozJ}OZEtIu_>etX3tu0`{Oggo-`gdB
zq+Ofd|C?I-e{eLLUSu_2xLR#90BNz$a-G`!hl{d&_Sp{|pB#^d!xvwA`OcktjsmD;
zo>T$BVCgRamw&@q<ce@6wAt8pOC%{GtH6Opq&=GvF@zu@?bEjJX}@nyOMA@?Sn#k{
zU;EnoClnE{9!Abg_(MM|Kkm-N4DB|`GWYNQ@(XwG-jxKu|NFmhDO`Ty{J;E{|KgW_
z`R{$^&;D#So4xYND{p=4TYvBOe(%K>U);a24e*RvLUd>BZWGyUTKDGnl^?d%`aN(Q
zSnF$%4Yf62)D6&e-)iZRezA4;dOuPFGXo<tyl`=UGCevzIy`@F@A%}nQFM&SlCGlR
z)s{2@-1OdSFLDOh-yaKrp_ze-tQ^Ghq&PV_na^i2rqO70a&l7F_1oY6_88-O5+1o`
zYgG6{{r>~!=@}fbvdahNd^vUPz?Op{1O!uvjX39n&wl0a|MD;V{C`@<`ak&S&-{~r
z^4~oD^qa0+y8Mxk{M3hk@+WTIyz!6!%l~3)dklcguAPzf7?^qe+?xpY3@kc>{Lj1%
z5lzhy5P*$Yuwi5a0ZU*87**6%OiVyU>R6$)I9CBo!N3$vJrWsHyDVa+>KuRj+uu1o
znU2Ogw{PEF9-W|3%mb5=87_m4m1owaa{w~~H8e00RTZQ}44|u^8P${&Of@AFNg~=B
zP)aGLW;UCZWwlsTEiy^ctgpX`8_uldCb)YD^Z}wFt^xPzoHA(t0BoXK+q}j7Q(%P@
z+xU$U%{%wnYp?yz@BEKfu3QmG|NNi*-gs;H>%ac%WmSFt3t#xeCqDj>k9=f4pG~I|
z5f#x8vZiU)xF)6Ky<d|LtxbsB4?@laMGw;h?J20Lwj_K6&A8DB48D`dB3hPZ&8$LI
z7aJv2G!;<*BLD(a5~h&nb4YdWP+TFIolXiSQGNcoZ>zwyYd4&8h?qk%^i{W8Lqm-e
zN|Ow<iCtx&<r&Zp#E}Ekg*3%rEQZ1aDSFQ9s$@vi%z=7HXkaNxN)Z5?CWa7Hb^rW+
zk$0|LyZ+gK_m|_XtxK0K5OUI(QVJoYlr~ZMqul-@5Y=iQ;Scx-!Um(a{@`l1&7Fmk
z07)9>+`HcMj`J7JiKI)HE*%~oO(xSPp14dzzxHdtRutJg-}&~UD2V7cfAcqo!+z5=
zts8gFHBA#jXn6_m{rbRL+eB;Wo2KwcZ`RJZsx1Ghs>~#Aw$tT#lUI#5p*q%{CS;ui
zl*Fl|7QrE92^8?&FzZ$^uhY&Xc^8fyoQ!(&axtu3onn?{s?xG!GZ7pOWHyD^<XIL8
zx|C@*ujeI6BE;4QFuNF|5{0d;li7k7;z5dzZ-|TW==k_x<onnxy+ukXr38Q?7UMjG
z@TpII`qQ8O%xl+QPsx~hIPCxGum1fXd;fa@u&(PBQG=T?+atX_5p58TfWXTwO!`A&
z#WUwi`>!Q~hHJjfz<bv;jrW}AnfJZLV!=$?TZ7}{BQqV3$1&F9@u;q=rm3Cds;aB1
zMx>M)=iF#Cx_9qhi+b9H&$7%p*XeX_-MSS**xK51&aJV}nb2i@BDUXS6hu-LHmeMq
zUY0FV-ZUoBaxX;Z@?J5SE{e=1$)XAh6NBi43_CuY&U!}Z=pdv}xK7^PI-E;BE@wyN
zEJ@>(1VKUSZnrr--Lb@&sTg<~ce=`SOC`=N#F)?!7%Y)<O5x<rq1B6iw~I_k<6zhU
zl+o6?)7u9_R1q_=x~{9bGBrf5>*h;;^_5S4@=sp9{u&`MJ7(vc|I<JH%(-)WS02B#
zCR$o({6kLq)jM0x(+~*26PhIfWI$u~NgA*-2DIL6)`FZRBeWztmRv*oqdLcv$>jL>
z*n3Yzi^a@2>U26WrYy@34i3z$)9Kv3dj}B#D5W$SjR5ra?b|6O07xkvA0KaTZ`XCb
zSS-BvWm(Q<vk<~?IBYMwCcxM9$it%*Ng^qwsDRNISvMaaPb-9w7qv-1pJf^9sZ*|V
zA3;b=B>^IGR>r!QQtWk{K;N8G&WVF}hY+#Ip_xf)9OaEBXUs|Ybe5eP*Ohqh8Hob-
ze130w8hc$mI6_CJ3WQ}7g`u8K_r?QcFoZns^?mT&&TKAmZ>za|i`a=IGpnmw)l!m!
zgTqgM`ZLGJrwGLCoO9kg@3WNBSHJqTi;rDoUdBYNr`#llTjJ_$_QYU`y?~;T0fJgJ
zFMY6+lS#Lmr<Cft9*@T>R!Ucd-~;T|{ZpU~vXR(8RCQ}>Yd9Q<Xt&#)&*vfd`Fx&b
znMm5(+e;}CQM-@5&sK+uUcP*}4PZp%%9SgvQLn+^-rinIc(ug!8u+vhZ4+#702UQ#
ze*A+!{+;K)d4n^VmiZvV8Wz(jW*!Y<jAc`!SjxdQ2{Whj8Zy@)S{3s=!;o2>+GiT%
z+0E1IHt5AX%SuF(DmFq!L=pupg`y+Ss7L^;W6c8f=?u>A5C9o0>M{z5D<lcTrjr#B
zQ~(;$CMq=|lj+I1!N}OmL>rNmQYy=8I-R}#`i<MSZ}t15l%zc?MFhZ9?Uh$vA;LfT
zlRx>`$3E81$tF$IhN5RO1*>gqW(<`1&LA*ef9b~2y;o=D;f)(NwzjsJxh%_PpMAF5
z?WUC43$*d=iZ$6_gowzNR%WdkrWBb20M&KlT@bNeZ(yn+_>@xXrrO6!QGD<!ii9ne
zLBuS}M8rAQE_oXowC>?d5cLo?S;c{701#8Ucya&Uy*s^rSHa`mY11E)v!dIJA`z@S
zK1{J<K*KOFcZ%uRpZLHFFTZ%ZB&!#7Q}<#)?5d{mz=zjwCWrIsDQ}-oP3q+Iz7IY7
z{5QUSG^-+5migo3X}{Bnq5x1$>wEE7>$;5yNag1~_OoC7%9mfdakrBdd7d9kPj|*!
zQkI5X#=0)y^yH|zGz#d1jM=FrRjBKFGM!9L=P7BPXR{eir;|L-Mbd0G@t&RY0Qk;#
zz7qgi2e66Q*RK1a=*|!Y(UIf+*q5b^wt9@`E*1MvUVSQLz9k;K_bZZZ4G|tl$1Kld
zvo`4gy2?)=Qk(v0*)cQ_Q7}_W>&SAITwzJl1`<n7eg$aP`H3~x`^fd)e4y4)aD}~9
z^zOa8&+P7<oJ=65szcSmJW+~Olo$n}X)2!wH9!GyXv%;2`7hO}@mm)$%cY*3o*ol9
zBC5-(iH-A|h3>)<WMoMH<}d!OOHuNiiY-^oXHA{v3uP1$AAHgpyM<F@WOVSaKlR5_
zCi0nj<QbKzxpQ!vyf2zm&aCR@E-$>QSwdsSjHqUolBC!;=f>lah+MjK@#f82)9EzN
z^AOzj&Uic?BjO+Y!5{qAZ~fL_FjxcR2f^UOdDJCoq%j@enUmwAgF7$2@XafaUw+|*
z?_7NBeA6`Ve)qdulR`u@Y-x^7t1>g!8VDjTq1gio5WOF75SXfSPL)=P)O9La+jRJu
z{e3@-elS<@$fVBtgAZj39T<P*D_=$AtZ?=IrRm}xFBWCfq?8bVkhrll?)nr{fv^Ba
z-WXGPY@=aWFI-A>wICu#oC1dm;%>K-gfcIcdWK*yjR~Bum|2@Di9|ER+UkDZsR!G6
zijFJ-Ao<L5ZQ3|w&nBr(wJTG@3-(#hVDiNN@J>ly70CcpjhUTuz20E4SiIq>tDpUM
zpSybX>f?_;QP&kQ?C+m@>BSd6{NWE1!9V?{|7}rp)~bjz*_}sb(%WDju>wg+^s)WX
z-rkRzrmIgrR+d$#(_!YOX<FR9PB^tk^Zm>az>-XOI9a(iG&3V4BWkyJduF%pg^0{_
zZ4>KEV|&XpvW0c1b0$4{CJ0<(LjW+9l%yy+7*p9S-0{5J8xL-j8KfpN7Io&K*OhJ-
zj>=?Jx6{*@$a|N{sU+Uoi8l{Cdhn<!od)9eDE13}?Ou+K$$$YA3i5TcNIOG%?Vg}G
zBsKHVr@_c~M)kc{eP9Q`2{l-^ktRVoEMQtvN{NXH@`hz=H)Nge@o@wrR3>)LIp@0F
zE+P>_2w79h=RWuOJkN&1K~*ol_r34_%CG#&```Z~d7jmE-HHj;O!*mVZ?!j;s8H1a
zl!*+YX_bW>#5^$~GgFpj?PPA^%7-`uYH@&yL;z?x-5*3TipXNIVCJGITDS3lkmNzO
z|4hPunKfTQfgeNwJnRyf9T5;AAS^s6=KaaxsoOrhc*#vqvJ+*O>Py?2x!qUq0$81Q
zAcD2t$mQKdorXnv!+tzD<@ub#Xtp!<=XNfB>sG^F)Di=e7e@inPiM1i6yNxyPEX2N
z?Xn!Uhd3N=-MH%-REIzyj!3Ow@>SBUo^Cykw+?#^Qjd>zM%~@Ly;tv^%A~_Nq9CAi
zoMjmig%I*Q8x6Pib}u~j)RV8idi~(w-lfYI|IXj}nP;APOG*)u^1Rrbv9<3cB4&;;
zt`pzh`xc3>!UT5gbVC6kV(WC}LpjDx7Xkp_+KF!UUK<?KSw&Y}*T3-_zj5{I)!+W@
z-)?XJNN2uw5^ED_t#2)sTDyTCbR0Zr0kkq+@aTLbaVei36|tdy-VA(lHky|Gn~S?4
z`e+mxU}^8foL5#(rrPhjoqaeDNZ37^!FOLi%9RYfAgKY8_uc>!V|P+7^wjmYcq;>7
zUM%RPYfjUw%WUAIIzT0YjL~%Gw5)3=pSU9Eh;?KzeCd_S(Eth|V`|fz*pg%3`=Tfi
zad#J=diqIb4#974ZvjAzX`Kr_BNkdGkN_advUVSOAIu<x5Mx}q=QHbif5-n26~!8F
zx)yY+8r{#iuBpVe<jA+%cQ%`?R3xwt-X7+y9**@l^I+?E;f&kZq=Pm@E9jg9WWb1E
z;C$XllVWvT=IruXJ@Ju;js$^3WTvf{49qkY<dnv<MKha&XI4!BeR9wwAu<qM=Jx;r
z0x&Xk1j>6iIAju~@vaDoYVIaA149BtYGo$C$l$cxx_vNZ4K8zwlrmz&29?baNKKvd
zE%VV5&xq)pYw5hBqr+aW>zuP?))Ve$dkGl%=FOY0zWQn#G@4nj*K5JnbUJs=?d|Qo
z^{sDPGwz#gF>}imKH331NGC6mAhuGvO%>N#tkN`1yJlvn3M)Uc)Qmiw9BaSYaPiKa
zJ9(a`l)Bw+JMz|*ZThz}huqp|TBw4E#IBXZ1to)&3n7JIIJRQeR10r{P)kBrI6{<k
zpCJbYG$mq3S&E*qLUl&98ap&J6C-NVC)%nU0ibHi-~%Sjnfp~T#7V&{(JA5*b{PU>
z#6hSk907HdV!a5#5we&dw8}~ZL?R|)0A=<7*rGxrYMO?~nNf^d6h(~9a(NICLCu)(
zYhU}??c29!vsu62-`?K-^FROdr=EK1;NW0?e}89Z=aZlO<9EON-7%&!c{L)4v2m_l
zR@@|G*1+NnJ?vHhjn*0E*67S^Hk;kMcMkwU$hzHLo4r5;B3x5cX8_$=L4=4ez4THm
z^SXNV>iP5MtE$?hy4JAq;c#HnuMnZC03b05WEH4EWRcp~_lj}ZBxeYMsWx~ZL1M-h
zW9EZ_AyZk`07{4%VG>a%rU1z6`zrzicC&mDQ(_DNbw(YbL6s7dE>Ff~NUdm-%<_b}
zf*}q#Kjugxfr!-rK}DIVWkf_;bqTJqGczMvT`vHzY07m{A3)l4X!2RN+r4@7X6rl-
z4-c<iy?X81wefg-_wL=p!^7R(-Kr|9s#@)$HN#^q3^zmXeS6c*Q~O~M=2n+@AgSMO
zP$FutvVOg3YD6?sGgzysHUWGac(ktX*=K)ZHk$!}bG%qAM8r&)mm1b~H=Hq}&0Nu1
zZLDgJ(Zm9~*fdB8MZfM9izr-lbvDUORg(gmA(1X86gfF{O>@-m_-@A&K{7c#aj_Cb
z1y78S)W`^1ttYBkm2n=5i8TqxtHoFes#C(Hr~uH6m<-J&@Ei`eTvF)^_Apz_=cov(
zLgt9qvr5E7#O&0}Gn*O$9v&P>66c*`heRnQVn&dbo?k2u2E*Y$_y_+v5=u%fh+g4A
z5|!B0&pr2z^XJba0+CxO89_C*Hg|Y<G#CsnUAh!wbk4PM*@u+Ma9=69$vL#|Q`K&_
z+v#+ONL412IRG-ZA%p^m*#jA!_Bt^}BC6~9GoSgh=brm!)6~6Qx7+Rg<WK%Yr<1qh
zQs=^&^;{C0E8x1$C*6m{BD74)Lo=VkpmW+OT<F;0sWlM+6Is*gq{-AHI7lkaOo7NS
zk<nsr+~KUeaVIc0h<Uen((iN*W<jk4%Z1gV5)hIIa<@Ban&R}p2akyJoW@y(v5855
z2o%5&J;jt9an<Y0nBuJi4?H8R`<>A1#aZJeg7>l%pe{j`_aUX^LjVL5C8n6<(&fiP
z2qMW;TI9km7y=O;pPbC+<%Ru==m69Zab7NpEJr{!bI#qncPFLv#MLM3DthM)v}u}_
zift*KkY#mU=Xq`jn5=B7c2_?L2GPviIy~Dv&DXhkRTU9LP%$uUx0kA%K@*$gPg$0~
z`@6sU+;iUwJ~z|-{r#etef;B}_~=J}s^9NB=Zm7#GS#hv`5_TOYZJ=^1SjicQ<p>0
z;cKrALnoo215iGl&$ZLlW+7z>VW6rW&`g`WJFCPUznXc@b2mnRR8O}S;b?c5Ucbw^
z6F^ZhQzn-@Pv)oDLU1b}U{U1fV)lA<oQf<=qBAtJ05p$H@E&}xj;VU}mB+{15s|&i
za^DciuvZ>cAtof+WKLs@na}Rtx_#~1wPm>P++ThEb1iG~<kL@|KYvcE=EWCZI=Xug
z%piDG`Rrf(xvGll+urf^i<cgoo=#qT;f3SFQveN_YnpiN<yV=#0sPqef27yzFBXfx
z`urECvnewpV3PE$=e{|w%B}9;$A0_+d8c?J4dW2u!PFeC2qS25_$susl!VB&`*kyU
zv*z&4tZACheCE$ydHJ;>>l$E6@#N%mG#n|U=bn51J@0upQKM?kxjfI?8?^}KVLRB!
zDw&zF;Ue?-V8E~6?RGjPniC&8?s7J$Ze3qsNTy<Bu@3Ak+RR(%<-MD?hMmm3!vyN>
zi3=C+-MrJ8mG!U#^SPJ+7%`T04ZWbVX|c7%QJJV{$dB_Yvj6>Z>I$eQQ}11ADRX`r
z1ze>!Z|`;o_dIm~`#$78+v)V~NHe#7b~pjIR4p$7FJTkw|N9ev#EOm?X(?1rDIOo4
zyz;%5{_ZdTV%LTL^MC)tOZ)p;NA^Cd#FPYJR##P14@SeXEI;vyPh8%=1ZFA`nVXo1
z+|Ap!^CElS``-83_3NMd<R{OaKTky3iXLV&WttzI?3~+u+dJR!NEVLPYVM^d&Jcjt
z6)LJrwY;h@Q>$fKZ`{^Hq?BTeP1DqMeR?{%efw@v^s=mjK$-8DQdO0s;rNwTUP~#p
zgktM>)-uRPieZ+wFcnRYUwu3j84t$I$?c2d0k}>OM3ABZb%eyeY1jpF9!<(}UdO7*
zv-$D8PT{O55^)2d1`-?Vdnan%$0UdvQ`DFuKw8W@nalEk-U~s)P&G}593D@s(_zA>
zO(Rm43k21w29vDU8$dTGiI}Q+h921N&R${zRYGi~W&qkAV>?@0yW892Ucbols;P@k
z(d+jwUB1-mbxmZpm_K&$Vo`Lu{k{?3xOuB6x`SSScR2FMgea0O?VlU;dV~G|fR7H3
zJ9*LXcCTE#;MoxqV%ooWZa5x<JkNYSn=ZzqtwC>i{`|i4zU2=#rgz2m+SytTT*@mq
zszJ;k#;9tdqN;UWw-jMr*L7XT7+WJK%W^uMPEIDn;lOzgnKKd&+(-f>Q!q33u1#32
zAo2a>Z?WeY`DoK5Vto5M-Xz*o9)=ZELAylGJ0Mg|i>fTk3hJb$Q6sZ^*ZbbPb8asq
zuT>ohvygfAfG7lYicrPrVvb^+ya3a8zV{vd@z_G1L{-6)Cjc~vb)~}B@7<MDdSWMr
zU?2Lx-`F0HHHnJ(yt7y=LSP_jVw0L!C-DLm7$T8#wj3Y<ps6;oK?HJ~6-Az9sYw77
ze2(PWk;jyn37L>7N-_ZFT`MIZL;!5++Bt3kc%J7?-DDwSA|`5e-${}%Dj{e7_R+!J
z<D)XgPSGJ`H9ey`S?dbj85!p$96-e7qE1duj;GGCsXhPv^Uk@Jc5ZVIYu2w#LTqhs
zUAuNYc&_V2DxBhWZzy$~FXpON<VC;VZ?neh^?ta_al<H81-|yRZ_TEQd<ds~4wGr#
z>Dj_Gsgd0~xJN`nU}6r*5MR0WN;R3mXykoRk$GKt7Z`~Q@7}vxFG}AXL=j2J0l)VB
z>!&9Z=y(d5E#|Y+Ig%2RfF2*+nKV`4tRfK7K<<0r{_f=HSeX(5Bne)pv(vzyi01Q!
zsCeXh(5+ECc@HhE$t^pgqGn(KNrVi}@9ZHkDLGt9HCq(y*cr%RFa(2^Kd34tLXl(u
zOuXj%&Ye5gB5Pu{rL0;5aqfI<<jE(WEXyi*FERPxV@&I`?U@7DZPvFEp@Ep8iYZ#~
z?!^~h_{x|5I)qTw&ENd#zggCc&wu`lPdxE>2+sSkySqCa4%<PtaG}#FeBklUI8_bQ
zF=Vv2w>Rkauibq8iN_!B_Bw-MFVFIpCR;;_4L*z4jNuAmDuBKG(yL@_i$yi+;nC5}
z`VbqH7(q=^%`TUKjGUp10E_8dRZ>;!xW@;l(}P1QGN~E_QfnaNn4)73V#;uOJT)+_
zYu_JZy_n8VwJja=lopF_Z%__KRxP?&$6`7<Ire}if`iPZx~|K*sn{^b%z;*^D0a7D
zRx=VTOM8r&q$LRfP0cx~V&#~<_lO{(W-2Lpc47*ODJByYg#Mr}N$OOWb!7&kih!mh
zQB>7CABJ8erj+VfsTmr!Y5G`3M?9ZT!LSh}htq0~0H&r7mKrWqVrTXFW~yd@NmWhR
zh4U9K#wO-TLhww?OxxSrRaK71Bk#TUzD0}ca-R=<=!5_KfBS=Re|Y}FekWvQU4Q@8
zSB&v}Kk}Z(9=nuf!EvC~*E$KdPB}hYTFcH6p=wO0$G)>S8}z#;r}Mg&6azamazrrb
z@w`TK-eF1!h{K|WQE$>M@N^Q(SyXXAeek{x(~ASYXpjlX(34B(Rm)sa#)Ff)bBK*$
z(fB+cmw;}&&o}Q9pb{e?5OQiXNi4brP;WLX9nL*Uz<n&H{oUf$^_)EsrPXF7Brwan
z-8a7VnLDrF$a`HgBc>)s?_G@Pjc<MvE9{N8ufBX<Uf}=$AOJ~3K~(AKqk|)dWYDyH
zpQ6jP_xO`n^N{gq{KOlcoK8-hV*oIqx=GG45L|uoYL;bt=k~5X{X~iain(S8ENTe6
zy}Nz+%4LK5>Wxhj&aG3o?Mk%LB@s!I*!x=abD#gb5BX@kHJ{J7x3?}|xFE?S*?2q(
zA<Of;Jq}vpDaN?7v;B{L;~#$fU;Q87d+|jwRF%iBT>klA_>ZqVe!0`>s2Ve^Ny^P!
z@R{<G_E#%GDM1KDrF?HzUL3n<WEiZhJ;`vp>H1<;=c?u$GpH&tr8>gl37or7h#O27
zDhYxs2ffK|K0Yav)IRelrVQTHBb>V>$LG6u22Z%V2UJPf_tNc#E9d#nQ4hGW=z=3j
zi6l3VW#e|XqR$e$&`3W~9>%9norme9!?B4Bv`kSh$AzF~Z+qXn-}T=2EX#)h$a9LR
zB?_y$4$l3^hyDfwdFH4MA|Zex6QRm7rg-+Je~OU^DM>Pb5JFW|A@i!RSS)t7cRu>j
zzePSXAkc)!%$`7FOd*8XblU6nTJ-(EDX?CKcCK|v0whZboew_r`lBrG<VB|uQKUsv
zx2K1yg%GkVTT9!VbKZMZ-Pzvyum7umd~|$#e0Vq*4j;RG$xL}^fDZ^v&P1piWsWVi
zY0RwyP_LA7c2&tY;{4o5xA#EJCCrjiUG^0OF%p0RJFjM$CuNtvet5dykGp+DV56XP
z?zq$@j@8dd8wO`YfmHQ0OE^8+>2J-ScnU!49CL~Jt%H0vtsQgln#7YABg!z-l#Y*e
zyj5MeEXt|lz;S-(aO-q34cuc!qSmKv7C0iHLaUm0fKb;>7D7xZNeVuQsv}M!3To&;
zjfeoPiAhDWkTJ8U8ek)dh#8@&s>}ydRk41*+oYPnSyV$M1wxP*Bc?25Raths-MX$f
zWLjvZB67cOaHDihonuuYuuB&&?C<Xn2E%Tzb98!=<=NwxFAs|1#_bzfmbEF%cGX)u
zXp1Akh>$MsU%dF(1;->Y5;H7iBahAtY<m8+L}FD^2yHJ42cUr%bo=%Zq8Fq&q=FDq
zMCVlz!4R1ch!9N)Knfino?2i80z*~<NXEDzAfNzXfQEp?xso~7#dI{ENN@wQPNc-3
z!K0d~F%p1rtFd&B3H$T;a8YUIJs4;bazI>YZV*khEiqVzS_<ptAMB{bgxm@z%^b0*
zg4wEK#E8_)ObrYXpshApb!U;b0e~4ec9Iej5n)UX0TKbU*?Dw;>JSr93q{x3yG;ss
zsTM!85!;+&mSr!z@ZHy5yIIX=8k@yDOc!%N0@2;Q?c9Yw{^TG3?0@*P=g*%%!!v{s
zoM$r-6>hcX!5NYm(n_E7NO{P^Fm=^|X8B`lVQSVm1TzN7(G1auYf0F)H6cI)GE`Oo
za7Y>eNr}M3j7$OTem&~4PYg4Yv$hpYbY2Nj?W8bo$c^}AwQXCr2!!B}Q7t(@hAcGz
zh(u@>k%J~7IMXj-voOo<>o}yX6CzwPZ)Z|v0Dx+QWJz(E^WA7S0iXzqK&!qofGA+Z
zguqGSs(#*Ywxu~!m!Yc5?C@q87l1j(brs+6)YET%%iBKwul^_mZwBMxP()&^`~9Aj
z<<I}z&##aFmgYniBYxo<Uw{4i=f`;mGf#+(2>Gn4$~Qdomhn?hdgo2_VG3x|3W-Q0
zwMocj2LePk5kvv-MiI=~@)|KOro;(>fM8WFt|}>Tp(G|nq!_44k|QM*FmgubFiBd<
z;{eSpSt7&);3aw>K+I|&N|8{M2n`HO6bTie0crP%V_tY<^sE@9R(3$Js%*tGw91(j
zXnTeBS8d+>5xF~rmW^L)>L2Fy9?Yc?n5h~e|8NP`+6bIT+pcGs9bQ$B3p=M~ufKld
z`!B!bd@xl)^1&O}#m6pKY$m6t-EQ}O{UU%lF3b9*FMVap`Jw5oUO-9%?@1R!gYSRw
zOYeW%8=G3QRs6XrN7&3{Z}w3Dur`Zi-~@4yU{1*+OXee@6RYYfHI)*xDYrE*0ESRR
zX}qFBl@U3gyP{0dRTw0S(-ettqjQIvCuaam2o{7fITVc@F%P1O)^;jtxj8c?@Sq`t
zn!twuF?zEIfRYpd$*r@Rt(vdR-fpJQ&J>D2tdUvo+P3>iYo?nWNzPQ@Z_qi*UO{Wf
z^@D1+?^lnv%>>p(YqspMgN|rEpAUz_JTKZ}P$u?y*xlJFCzEckcSiH?D2s9S*w$7j
zS*YQa8@K-Y+KZzi`<eIuSVwWU+ly6=7~oN<Alv9WvYLo?1DUE30U%B-_0>@^I5@Z0
zoE&T)FH%m`Fn1GlRa69{`&Ip=gHD4@zq8mI+nq@&qj2gD2X$FOGx0_WjEd{DNR5<}
zn~lP8)li*$!cLaYErjW;bV7hZ01=}BdItsz1L=3>i&74!koivLhu$?+;{ef&*J^i_
zRp$PO)$*O$&~4cEL$>>wzH4VT_bO8?o9X`hO*vM`{eCQfMryKZf3eg884?lC=kt_O
zt3*gCIc7yWK0RTz&8*-ukgK8+q}T)`M3ewocYJ(&G|2j{j<qO@q9Y{)5ko))bgT-_
zfYc(8qBzV+qj1|kZe4kf(!@7Wcp+nVuGfA3JD2;}J(wlwhS%$AZ@^;KR1GLuD@nz!
zWVgJIvy8v>0tOeD3d~RL-r>Q>`7UUqF@h&&4D3v!nY%M`di~CMmw{tNP@7MYVz;|9
zSv07PLqKo_(lljOV6S)l{g=1ZXM+rZoU!_Drqnc*7txl2S$0%eqnY~=?-?2A8L8ab
z4jyj0^T=ZRhr881{Qieqnyqnwnjx)9&({(&0rTwZIyOyIUFuU208(=7GEbXezP+M=
zWMY9_jhHcShyEK!3WFXrwR0{22$~0^lpONZL!P`^)v<Jno_$KhJ;6HF5fi9WYk)IV
zQ8h#e-hD?azyC(Q{sL_IW6>gva+m8kgXyB_cFAgM8gwB^1hGn_bNRyb+V{6QU1cI9
z@BP(_+qaHRBoB2HJ4{u*0M~KEf>z}jT))>1&X^oiC(8!I{<LaNfluZ$1jPvG5X3BS
zVw>K0wdcuaT`O|OHt6@MTR7>zaB{FSEfb)sillb;uR+`yh1|oz@Y)W3klOPHRa-pJ
z!oqA*^7AnI-h97Xw#9ld<*%x3moyO>kH;cH#GOtj&vVDTH`+QpIoj-f*d8T@$c7*y
zB*gC0PVe&0Aa_pcYD&bNF0nHKa$ZeG^9kJe-fq^zZgt-E)EbD?%u?-&ll=>gfhnk{
zh>FB+uQNM7NSB}J+`X~ovV}27f&>V<wYOVce+`;glcQ#lmOU|mI)nMqy&cN2Q)rA1
zn8+dG?s#x(a*Cd-retzal1Pf))n(I(sW%!4sHwKNXJ<T~+_)jbE+5ZWQxg*+mkOz@
zxGZ~nI{;2q6o7~VLbq2K@8aZDNdhaD^UOZpB);zZXG9fY*9v!)esKBsX2Y)^P!Rse
z1}k``D6ehv+13l7u3BrYA%mI%Sgh;w=gu)P5vnNwdheUMVx(@b)0U;J3)xlGJFZ8a
zEAM-c9?wXvG0BQhr<hSt)6uzI^BIAQs)=Cx#&<&c-0pj}-1u9I-uDp5Nm1W<3~xLp
z)BohOyKO=M7-PC}<;vFf=;n2I60)uI`f&GgoKC8C>qugQST~9YNG@<Ky1O%4%oh`H
z+0l%L14n+<3Iq&nGChGNQpjttjA%UGnw=bJ==iEi4szF-)Tan8nKg?=X=yQ==(#;q
zGmx#l(dogd8c1p~b`H>IzHE{iOy`R}aVOS!&qopVnE-C4zRqm)&Ayn*)T?afNf4P#
zfEuJs#28tZ-Pj&g!~rp>^KL0%RA8r~?vcRa;a+iTNtkP!DgyF47HBb|q^Q75w3yET
zuxhHwWRg<s6!~a4%5oy2wlToktRkBi%HH^NgXRE9fz=v^pq42_#*`#L?3m3IGdI2P
zM2GSd>Anv0>h^2J_SK_e@6AY+yD>rSA~-WhDSGeotk~V({r+(UK4@$lD>{{=NOX8`
zR|qU7@7S1}xeFKeUw`%byeKft>R9fKwomF)F;SsKSypw8z=9Njopbw-UAR-0*Q&A`
z96`u(pC?Yy1g);B3e_b|<_N8p^u8zF_|5sZr}N740H%pKp`j@wHK}2P+tn#|_Lg()
za1GrbY;^TNJch~gMovZ<M<FS_0J@^D<*5&OauE|hSckK&`4RzkZJ<CYVph~m*(F!C
znB$Btbkj*}I3ZnDj1aLw8@E79!?lf>MACRXy7KrHLj1<pzwzl${pmB$yyX{v@fW-=
z_AhKZ=UXM~eJa-wk<lO+wj`G#vXO`+#5Q}YU<PW$R42nu)H-DH+dntz4*4JbbAjfj
zqc`QyAqs3HAV_fS+N(!Lr$>he#qRED-si(vEHV&7)OoXzn4A$ft`Ks-2==Y#o>MiD
zEcW}o#q{9h1PK5orc~9~)X{ab2Bd44QvCnE^3^S4E=W29AFEq;ZWIL@iYZPt)YBPv
zIu`59F<JQH7rqR@gc$PNCy7UMEt=p-V@%QCx_hS>kET**O9qnGLH{E+;_?T^s^=}r
zxT2}gs(xs0+)0!Vw>l>@Aq>#U`P2X(Y&AvXJ!?~+9v>A0OU)$<CusSq(C^U^#DfOW
zMuTmvvVlR`5Z3$PJ!M(8wLJ<x+`M^nI-TxpkKgm&_s(aP4<@3UU80ucr(s)K0g4Q4
z7MUQT5_q$Oz!AX&)p_3-drR6)vi+<7{q1^IBX_#8RX2r7$qt*e9J3i59UY?>O-`G=
z-5E92^dMtW5RJkK$!tE#?Re&lj0h=<Ng$Ru+|o*_`5ZFJFj*awl7(R1T+1rULQ_?p
ztSD9bVsmhkcl>-YUBsfmm|`Q+RbOun>2yv2q1%a71ES8PvgCZPSsZb!Bm0CAEAd6R
zmybG=gT&|ny-_Q(Gt+hBvCU1Xpk#nbK@gdfr=BjF&-}&C_g-A|I`KdHSmAn_k(nw1
zn9+)MVRqJ*Wi$@0`t0Zahw~R`ck8F0dGjxn=@h60Elk^n*w!l0A12?{eVPf0+QzRN
ztg!Pw^FDLVg%F6?d6(yz0kB7AUR9+)N~vuQwF(NP04Eu`E^$VXL&*C=U7w9I?~;oi
zd_M^UmsJ8Ga2$zotUFgeIC=9&G7O!jz9K+<K~1%`(rIfO@Hz0rQSoG2Z;jLq<_*MJ
zgK_OcbFNP*^#GAQGpmvF*`O+`YOxrPIm^2>#fUNF;zQgXvGGo&hR7kyn9(sA!+c)%
z`X}Ao3qc7UXoTJQg<WGuscBFpHiSsbju@-@a3{y%PA)-2x=nB#Z2O{68r@xFE+f|p
zFl`e7JP@@bfm-g}#AN1hB(|8nzVq?_>&l(idl!d~K^Xn@^RdW8#2GjN&+EQ_7L%f5
zq{OcI>#zRVdp|IJ^ILfM*uVJlcRo5lc^#VlGS%J_P^qxkB$^(A2ur1COIj_HI&I(5
zGaZYTUI)oJXV!X^v#J6j$~e0ti^u->3zuKNG1?i@s7u~}(H5zkRMW?w`p!Gv4SA{?
zvmQo&p}O_m0t%2f$C%I^4yr4Dno?Q3+P$ESLpE5&PAwGy0`+9lnBjPfwNJV@1;b8G
zC)1sIoCQ)d5^VD*WtaC)7Iy{~_XoA8PbiXP(8(L^-I^{kqGW(bM8Ocjwwt+3ak4co
zdqXE+<hYlcr2d`5<Q<rynj)Y<#sn#iZq8<xcc#!QYP#O#V$gx)hp)aIa|R_qwPo#n
zn=v@c_zFlL{WR4hLVtSP`RX?=zWP0Vyq_w9gK`l%+4rCO>YY1p{M#S(d50Xw6x$@3
zs%BYHH??>9@twP_^Z#s(-eicw{$hV`cKznxzVrHj_V}fLSXa?!B*}cnYs&eNU4xg^
z<16O?>xQIjw6fBHnVBw|`>)JrO=S?GnpG)1HqkLP!?(YI3wKzZ939*pjz{As_Ok_~
zdcH-?X$1o&Ez^rnzjc0m6{?{3=EdD#RmwmX7IBt4B|~zEHL+Xiq7cx4pzDK_bCU$F
zV@|t>a5&AHYMw!+PE?Vx)nDW>uk+Mz7XBbvmJ2fx4(?3ra#44)5D^iKPzhKG94G^%
z+2CGm23_YpSkj|A{RTZlQe76-5t0D`i1VWL_G`DQQ5QR%;ljE{_hPjmpNYjx8YQ2=
zx4l3g8IrMD5`~=k%U?bBt(V=9$|rh@dnZ`~*Z%73kA2`>Zg=PJ6}!Lx*I)bavmX*$
zFI!t{5Q$jp^4cqZboouY^Hd!l-m_Qg{l{OueE#is{inY1^=rQO({Fp@$3iBX8Ha}y
zcxKi%-UCMDmPsaJL|gsY#4H;zDr(z`@;=H%bbw?S!G`FaxO%SN`0{uD>fe5KaqrYi
z1k>C*mP&%u1cA6)Hm_{^hONZjA$JZ!c`J8!`#9$mQc+V{wkuer37Fc-Ml3?slN6ds
z5o7<f^eI{qd_>S?2BkgQTuzMGA?!x!%wyKX{$v*8<b3W;n`Qs=^@<=m3xRz`&2TdH
z)01K`-%U~&Gz5;BmX}@r-vq(1!yXFPjY!7}H#uCuq%Jb21`1Ozh9;&W(ac2b3?b9@
zr1!+PQr&&!b=)x@K}#*af89<_t~~pr-SIhey}Muk!Ux~{j&D5w9H6#s002P6lvDw4
z-+FoX!m(*5_}Qh)6LaM^zTSE6yXxpYZ+He?eBp~RRg!SCgm+!jw+TpTv!Ub<`X4Y_
z>r+!gQqwb?AY1kx02m@@r5rWZSrIPpZ$EZ^?=S!I%jgHl0f<DAf%-8W<wz-9o`$0y
zPdbvYQE`+!r*05`%4G)ZloglkbK4;k3;`^Ph%f3aTM(TN3(P{A%qavTgpH_4kt+yI
z*pB_JqhWVpaGFuQHM#|4m>HR<Q7c$>j)*kyB6OSHsO%TX<zoIdM<<=U$-00mK^qY<
z0fOh;QxCYPgYS5@tm9+Rt58@g-vkQ^2~1Ws@O-8<5C9;%c{P7S*qXibNi<h`JNVLf
z!}iEMb1B;&#{%K>0Is|}jk}#*|M1=s8l)yNQt*M19O?G<?&-AK(93A2eZT*%A6>lv
zNA{{&Q1B<y^p>Z8I?FQ9`+YoqP)Az#!|9~;8mFYVN@K5az@}p{#9$<Vo&)x~F*bv}
zaW3WG{mBocvP3f#K=vpQNi(ssplL|k3vQU$1x&`EU^H<dSR`cwC3241LNhj7a#*6V
z4xN}=(a9SEy?wCV8ywWN$F_y|s_uoN&w7;g>t3h}JAZh(D2h3{P9MU-NmI?dXE0JT
zVpbCg+}pWamZ#8Ro>#ef?sf?f=BF5|Rc~TLVCMiJm1zd~$Zc;$jrWL0lG*j|JIp#3
zP)WIQOlwKj!$~}gf`i<hly`Qw`rmY;VSf2t?@hyi&0K?%NJsB`fAf}ixpF=(`p1)5
zo@GQ>HVqIhn!3{oMQ7*fr~kV<_x`=~{@H@Rke$4f!i~4Q>0PB9U4QZ1hkyKUMXd?c
zfH&EQ2Q`?s$_y<RQa4(S!TtYX$P9r}5?&@fRW+R@m<R!}G)#eJlX?(O!qA-?bkCi8
z1{#C%|F7)LqvX2I`@rw}?!9lRt*fgyG&W)-2m&Apk`hT#B(+$gCEJoM*|8!yo>A<?
zbL5<4a&kOAlf*ebIg`;6XRJ(|cuwXdnrOzdE!#QXWXW1BiPTC01VNBk2oPI0y3uP@
zSJhkYz27%~ysGNz1yIrlr%$80y6V+?_kH(UeoF^(mf94MButT_NDyZ`mCLh@z0<}-
z3?3A*&B@zk*PY*25fUScQ6Z>4vK_olcTVhi_|0LtM$t$lYSF+*yNRo9cjLT&{nDDp
z9DxddAoS9xbsu;H%dx6W<OW)ngEYalZg%yx^35wK!U$R^5g|}X&A6inFOQ8wr5pyh
zq=K;9GHY$VdX=xNkWxYJEg@-d6lP%=-+Hn(I$^@m+mQ!Gwk@}MEWB|wyK>d!*d-1`
z@~yTqgUK%du;Y~o(pkPZS$%wN#g9a=oV!M2asNHM^Padedg1l6d8<1=zwr9C>+RLm
zZjy*dtu|608QFW!-o`|uHWBXM^Y%{bh)urTls`eVI+(rmYW3vNk6F&0b6U}`iq96I
zr`}{}IPV)Qg3UOEV#+xzalC0XEC|Ml3d`5u_U0=L2ihtE$VQYn5UWCvW{KN|OPL_(
zHn_t#(uw$BfU#1D20&>qC9k;QLfvvV8eklFLY5w;lz>p^J?}XAf&a1YqqVU{JB)I0
z^I1N%xa?ngG5o}DG=-Ij0`P!hJwN``<lp**YJKEpx!f&PngZ1AmToUrUi?A*KmFEC
zYhwZL9h)Fby?yB6!yo>ru2+-t*vs`ASa#ix?X=3TyfFRg*E8>oq7Xz-S;XMv$;L1I
zVtH&ND~%<le9L7(zS3EV&%CUE>r<SI5CkB0K)l?r@Agwd4a%UTaiu-FV?l@N(lvbZ
z{k->~yb?8+Zk_t(-(0yh^MmJKSZS^*r9lb_4ps_9SR2!5jJ@fxH(lO4bM)xFJEq<f
zWj`^u_|=!Le&*0UKUJ+CNZnkZ%ia;GyYc*cUExjGm4U*{Mhc${E6ogdHhP*5)~}UR
zok?_K-1>{--ZP9avC@FZKnZX?#{|6r0Ky0;tTcQ`SI&R;KmV8CJ#ys8-}*cMNTmwH
z$jX{UjEd%TZ@vzwROX~HRtkb83ahtno|~R5r}?;qRIBV68*2gNO2;lo00#6(s#;Ma
zB_sacX7&xslc2AM0OQr^MtvnP#<^Hj$54=ExsE8R%#zuoD?aH|+pFzLBaCsEsb}Sy
z4}(%W7a~unh1Q&HjMUoI$|xXrc&>drE>|3N_mtwGTJ0D~oo^A2u-{$`Y@k5zK@ebK
z$3*k$joHT@-v7wsUROJqKY!|PE}Xu2=G5t%m*;G3454>c2L=@e#yIBzsom*hnf;?b
z_~Si$cD?EGhaZ39@dJmackg@0p?iKdl~%{jRKmKoPAQa)V(!B#{>3%axDIUQ{v;dw
zP7FQ&BqH$M4)hrYFEQ}OD$3rvrCb~+xD~001M=Vm1(j!~0n4a!;f%;)UXmzTOK}tf
z9i2J-(mS4g@9N^M_HuJ#d^+`Fuv-!1m7^ZsN<_eWYl4s{K&h^^n^&*GtLHDa+TEw#
zd4zCL=?Ii&wncplqQr^Dj46%FGvEE@2$fcrZcqNiPlkDxDH;cy17Yy!*=ZFADpovI
zi2|b4(=SO7>-q1Rx4m=CC=Hq%cqAq49Ds&Mj1UJXc~H*Jo!yth;{2=}+JEcFp*@w7
z1_u-<VW;(GDAb0o8~`Z-mNd`DA2@RPhv%wKJ=Si`zwqrJy!f5d7cO4>;75M`FTVWc
z_G<IsfrI<^?i-(~*J@QnY_;0AZ!cVZ<Lbpr7qnON3rk=4i?7_cKKu4}yz_wvj*X5|
zp8HDN$kI$}<-G+^+qR_o4lbgHMaqlj<;F|_`@%35K$N1bI#2^=p%fWeX-~l56?%o*
zf$>&@OTdOI$gCG_41>;)qzs0WcKg|%{OBt${q??+&s3|En1ixbN2=_oOAI~P{6g!}
zdD}6j!m=DkV@Hp>xKc&G#^7vclA$FMI=@Uff<OSEbm;7E=gOSOo8G)!sZo;Tk>MOh
zoMcD}$)hI$FU;OKQO<i#JK^IGH%w`UaK&1!+%dFek-6T@K<J&Q*byX(YMksm@!%=N
zi8<NG*HXvIVpx*JHICT!IF?&V@PS{Q6nIHQDu<<!vHG=F&tJTB?n__(#?@;xu_=G|
zd*A!X_da|0z=1t`_SQ!lBDSC#YaOI%mZs^A8`u8&OJDx8KmW^#@yVB7dTD8Cf#~Os
zpExp78_m)Lv5@nm0t9*o4R<Vrs5US(hMzrr+=U?QklqPw1ebyU1Wd8UoH`{fA*Z2j
zy1=alNh|b%k+m3?OL6W{L{LF6<~t>WQXUE3^vrvA9Xc7tku@#}O3kuLw0Gnc_IRuS
zqQHPKa}9M)*fc7ulyzjrl{?|Z31omi3`&KJ3IclSWYP6Z-)Q+L96yvA6V`Ro9ru!v
zX930Dc@`wm8nZ0bY&5F?u%nx~uBE{^`V&@evw8)|6?`kf9Lc9yuBEC7m=;_*nyH<+
za#^q!a^P&WgP?OVWEf<tf6F$(fZ02{@8H4LFTVc#b1z+a{U%|6T7B?mfA-z)eAm7A
z9t;AVq#f(5_nujjBvnd<VR+!cp^yFQuYCH`zh$jeDwvyF{Oo5xd;Qu>l2{SyV?Tei
z(Wf4)hAm=qz4mZ1Ir82WSm$9#=cWXLQCugV`SzEx$@b2jJL;1vhyo*}WU;)q6t3u6
zXZMH+A_vX@&)(zdwyL7`-8?GQ!04pY#ysMX4dfPs)GO)RoE2*fb@=?L?@+xSM|N~P
z&UrDYUhTHaomTMT_o6IKND)gWtN|&=gEwA?&Q4b=<%R3<t<jM@7hG!2=AGTAzwg@Z
z91yiuUOYoL2+HTrEk6IeFI8o{0=m4$k~eL$voLk}?2){)Bm#_rVt@?i`S~lW-+#WW
z!dA83i7VI8nQqeUwsu`QuM<k!rW^ulAn;xh<r@J%n>Ntm>|MNg;nfT0zyHJ2Q5;n&
zwSV>-zkdJG`#bG5t#yynpcM#s$BGbyvET-HK5^{$$3OAEed?2+T3v0gEO-9vpZvuy
z{qorK^qvAFco(c#Uu_(<2yANqt%J^rVITGo1jQWrPGXQEqKVeB{J??fsY4Xk^r_cQ
zUzxe}=0_fV==j6oQiqr1w&Wq!0%t)o+OOt!-CPCZ6e*eW2nsL>P_fSgr86>USWx<=
zwLqXL{>HbT``R}_6MD}8h`s{Y7O*N1gn+;UVbR>C(7$qa=H)Z9NCvS2VweL00qznZ
zg#;i0+9)O042+-zEJ|?gs{GEsxsAvPVOPHZ03ZNKL_t*GD?kx&z(QIf(26!t6v0Sj
zZ61br=C$<YSFQuHV?eW8tkYrWvpExnAe9KcQkWq~VMfD4?uSE>wJ*9X?e^LWFTD88
z=e`q{!YB%V<2U~4!9)96tyLmQlFVqWfNsrRzjpPS%{dNBlT%Z*`bec()&f4u4(!|W
zv0we4KJkfv%}&l=xb*d}fAd2h`jFPR8Hli5A%F<96+pPI5(13q1snhith-EZifPnG
zvwO#0Tqa#B!CHFz^qK44yFdE#uXs~UF&FI!!3!W6A>{zNfm<=Qt?i029i^LLVDiX&
zEh#7B`dLXI?B*Fk5d~owsz{LzqS6S(YZzuaR>DMt%u3h*C`1tsjE;jqmxIuNF$%&E
zO`uE|=pY!VbpjAbAj0ge2|x!v4&q3MQ7GlAs4!DHTUS_OAs}(2G*QGV(qUNwomN65
z=^8zzmDbwp9&^0^MLm7IjgFz~d&HG1S6+PKOqx39^N)V?f0&$_Xth?H%kn(Q^L%c0
z?n{6A=fC$~K6n1r*Skqxt<|fQS{zjzW99@^;DjE0@WBs#=;t~~v)k=nx_J4AKm6`M
zRrW`sll8!;aVzi?QT0t;3cU~3Mh$C|Zw@lzL>wCQ;7dWAbM5N2g`GQ(tR*)U1qh0A
zBg#6?3CrGk1qKa)QvqepBYE4#+@n#7z<XMklIuAwI_Si4ZQs6Kt=4j@mG0Ux(OfIV
z<q%QbxHjutE~1bC5egX<7E}~wl!#UUN(n1s4;D}X2Su%3G#i*bps*Lhkz6eyh6n~+
z4uXJ+xyM=Xz=4hs^QnoccA9mP<%#+znIMd6th}Vl%GIN4<F&IFR1`R1FT$|ikJ}oG
zvn<Q=?8c3oH*VZeWR4y^bpNsYSu$stS=jp<w{D(4`|8a%uJ1f>aC~ZV=k7gIljF6~
zvC&3D2SL)!SC(!INS?X(|MX9N>94;qe{1#3>6acpc`WT_q?CZ%U8!k<+uR+5e*54?
z0VGhW(2-vEP2DhUBt<~R2cR^0`juaPXQk@8%agq34UA-YrcJ~mA;c-CIxayLI+u%z
zI+{4t1YYN^Mrj9(286=>s}DzJaRH8#Nk<!%dcAt)?1jMKzJ2#}v(-jp^7V@|S_j&I
zwN?ar!x$6>2ToycoF&Xi6fw&H5?9D#7s6VF6CrX8Ab<`)_2^=Ka1LvQ-6WVOpE!1C
z?&dXZqmhyN^yKK$a+_T?dGIY~m#$5PyIzPd#R$Z}8>tbSe7=fsn`QZ_Q>U{mH$imd
zzI#ihDCs8NQ<8M&=Wox<-k6=6pW3}=Y<zrttnsa{f9>Y=>-Qde=x09ofkTH5Mdix4
z_@%{L$^>cB`S6E7{E7el-$W*wxiRzl#Y^|yf7E(6kczz%$hZ*o6Zlb@UU_Z{mL15Q
zUJhBDj!N@Ol+~7OL}A`lNlA&#l6bXkE!T7<%Cas4A_bSOHfFNXse3g~>s6IkQ2Ep)
zD?EBTJy2c@bS5A~x=^<S07504Og1J?9)Ei{GWX28?=6RG4BT9c(iAec|GVFMoh{r+
z@OZ1K@z8lGR(TH}Rh%;BZO}F7Bo8~-isoMX#;^Xw<kY^p4K4v_3uLT!YuAro$o|!r
z&yV}Sl5`M;!T($DwBlE5Ypwa)^TpKb#EJX!JWmpvW!b{Q!qU<bcsP9MNS3>|zwPa}
z=B}=_I=5z)!}85*SFbfj>y5^as8kE%wJd8Rs(TOLH#*wLy4jgCXP$cN(^_lS_tCnO
zqgQ_}P%L$jI}r>3uuzcGT0@U`3|krlqK)#P_dNVWo^?D0t;{yPvECw=H3Eg&IY$)a
zY>;dgk0<YdnUdO#-IG-BaDFY<bJJOOy0P=R7@47-j74EENg&SV{4?+QM~^+bV1mPy
zM$-hKb(W;%l_j3qHS+!Myb3<tO1}IN933u{^HE@XJM`hNz*DoNr&9gaNB2JU{!%tM
zXG>Q!NiIMj;!5|OKlquib<S0k%Du{qQ6&vDn>Ka^ds=z(^YeM;3eDN+ozqE@crWet
z+Ty~Z()#`*N3Y+yb?CkWJMP(A8><{S_xjZ<H$L*=kDPqdgIRZtea=dSm5TET=Ny>Q
zzWsYHzIJ8d_Ck_$lQazilUsWiIJpS)(5Bm7{*4P3`er2{f<(ydHX%@%A<&osfUe|9
z2}b}BM&*K!;KhkBfbx`)RXLZl${Ko5#{A|#d1ZQU{KVg_cvI?@!THFW09@Wr0||fy
zQ0oLaLetXP5OsBg;0$|G%ETqWUPX)Bl6LPn_$1=>?O-!~XLCE9B$P^*+eSwa83@X$
zvN50ra3C5~IR-!u5|1dNRnPklw+C%{FGRSqvf``<fG7$A6RowD(llwcn$}q&edNCT
z=9ZSmC&$7tjLh`A-~H_2{rleX*vV9UmZ!0JQUMYbNxD%oG0}MS>;)pz?R0ZzX(K^#
z(4y|&C)i&BeJJX-`$R#&C~gtelPuoe*R)sliU@#ru8^xP6y!t%Ng*m=B!ym7x5XU}
zA`LHJw71UQeC*i=A&NP7!jxM99RlV9;1VGU3cIq11BnDv0Hq*iT<gs$5kUyVAm(?8
zcw6rQ55hRGvfh5b7>w90a%e>iA_0NH9D^7fiy~x1WdmZ<5ez+86j&EP*_I;R3!hn*
zA)#j<1R*n9YrEYfP18b2s@51OmCHWO(=0Cq;aFqrk50XG_T1UShYwe4^*22+O)N^7
zMMwk*OXUdAqr~1j@4V<9YUU0&!JZ#!PoLo~jvs?owjwPuY#>xDv7Wmr3p?j50)UWF
zIRYX%M5mR6fe@$y>%kx>MtOxEJ&f<q-~abdHV!qGK^q8I5J;0y;T|qB@QxSnJ&Pk$
z?6tL!=aOf{&VUQ7PXV#03AZ(5x!WTGyj`-k3Ee9qiZtXoqmjImqIFzC4gw+VRp5>H
zqhQP4L=IVc6KzK94b_GNz(A?hdk(`81PE20=Sh+l`i$QDg0Pij`P9_Zg$ow~g#y{@
zZ@hYYVeW@N{J~#+;R~zFs|rMcOFJC`@SYK++wBxwdm<!7)ORkpOI)Phlwt_Rv}u$i
z#mu<pMyrO-VBuy<eVb-Lq&SSmma=%M9WAu>tog~c)#=vCo@O#Jo0qt3a?Xut3tEQ;
zvK>jhMt(7@A9#OV@68ge7a#~6m(eRp0T5Ns?UzN^f;Qf9>XXqd1)Hk0i%Hj~N$#9?
zxd9*nFLFnp&S73=AL0fmpk(vq-wNsJJ?@1fN|GF14fC+%Sm%|l;SRYRz0N9&dZy8C
zS%pFXf-H6b!`$i<RSa21M;lt3EVJEiJ56%$xsc{ALhs9G&hFm5tKC`q(?9;N?N)nX
zX;BBJTp67h-*NQlQFeLSZg;yKL{eILv1_dkpi0wD6op|JvX`yYkxk1{iwGgyp<&ej
zGr+<;eb6i#uwgc^5>fkwX7ZOW9;#I8<D*p4F02Hq!A;Dq%p`VgN?V<kA#~E29BG^L
z-qcHxQWL-vy4=Y9Ztoi1tgM>?j*zjxJ&Ul?fwkSQKKBQUYmbEGRb|pjmAvb8J87QL
zsngflgMcmM5bhNBHvLMr-}*L3g5J*po;)!4z<uMzTa*7!q~mThGO5)BLO1JLpN1E%
zkKAl`T!qMbl+8@ufofmzy%@DjPEHnBwLEv%u3z7=W6~H?I5;!Q8*f}OMvvC(XJ0vc
z>GGw$dw1Wnci)l2@qv5x0l>n-f^#ki14LoYozLF5G6MpQ(R#UDR-}-K#SP@Ux7Cr*
zL?pn%G7J&k^t{4OO2ijH>^r4bj(shM{X6#rlaXk6?aU8H>-BbE_8&bNdNa=XYFLVL
z7Q+g%9rfA0woD6^OoRe0j`sp-<udJ!UvmnIu5_{K6iY2IBNs29I(P1(NLGktP+^QA
z3Uxqg*!*at0sY47I>0nw-$`q$ncFZaw*NXpqEVEn{M3B-gU`O61-8x+h+&Fyu9R>o
ziLhqC<PJbPW*Jgv9Zr}A&dWg%#BscH=Z@RAm+~yVc;UkSeS4kDopXq4X>mD-g4bR<
zr<BU`Y<X#EabdAmsf^a^*NM7mcY1oq)YKFad1kHk_3PJXXXZ<Byzk&X9fV4g%X|Tz
z9tQOfeW)H*7;uxv+!miRW@hm$1>tmqE7>4R5)lw?yRKwy#n8=?7hihe+NBGNGq(&7
zTkEs~VZ$K{5!AuN-C!j*i(apG;g$=R({Lf<74KJbOc7DQv2f88Dx5Ezx4^Y1<{;Ez
zgi)+W8&F0Cnk8d)!!dmQs1SN`$s3J)ijK1Y7ir90wv6?7K*(al92I*VnTjfNsC{6O
z(8Rh;KC-ZaAea|TbP$4`v@mRvTPS$9X_|&%c<lI5@2yhi<+EqAJk?s4%hgJyGCn?L
zt?hQZX_}5y%Iw_S&DqPBE?>EPd1-mMJ~}eBV-ipV7?F&QKmUiHkK)o=r*q%2WA_|5
z;H)cHH|qhvPx2gWg+tCMTf_%8X+9SQHia$3uxG_^;!Q~4GQdm&7@;-hubn;h-uM5k
zWQp=xSpXJqC<e}8&1F>;81`V0L^<Y+6qAW<+R+ht&P8kx#OrlC1CaoN_X0rdQ9z*Y
zy{fhPZ|Q;mcYO^${=hdA(Gn>kD45hDz+7nBdGuY<#xoKLdqapoxl+17QE!uh6J=Re
z$nx&rzkl!EUDvMPxOVmWtLHA9eE6X_hBQs%I4)Jn&1N%6lln+~$JDgeL8%l^?U)=N
z8*en~NIf7jvwP{x+3$bvbh$iw=+MD>y*AotBuO%a?8U8ofu3&MU1oh-ZRCb6q=utq
zKh8Mi6x)E5R>;5dv47|R!l+_fP8&_gMi2oqkBn+mcl~PMO%eo2B`+hON~H@##Lnk{
zVKG!;;UW_SAOS9F5(Gx-`|crlw&+zKOH#O>DnKZhiafM32uZj=LJADEy?Z#|fr5^}
zy4hA?Ue;sMi1#SsfkZ*^7OVgTwTKj$aODJ00E9p}=k8Qw6)Jpbnrf|g?b`LkTi!e~
zdtEsE{ontC>FJ$^?>(rscFx76(!}^Ui-;FMESIX)TB%yCgkflm$#bg}I`3}Xy7}>s
z|ErOaF=jY^?AV(hd$iDa-sJ7KY4$tddTg+l*sAqjw5|q8-=+rVU_d~Kgt{|PS+r?#
z;}*@Y;!HDaS(s^>)lBA^tq@mBg_SK!sOC>C#Pc|<Mh=Gc-Sy;1CD}dh_qs4@``9B>
zMj!~p?0dp#*l$}E^_%zDQ#Al0g_r|of*q6!f+>8S5Q`KC5tImlK%Qr+AjkD5b%+Jx
zffN!mFlrQ!;*niXUzw%fIRO$-BnV7W==6FJPQ*723ugr4!3#2?D#%yPIaLszHg+2a
zgQDWWMx$}$$bFAL{)DqxmSw;DyZ`ax#Y<yjje5OStyIe8@>rv>V|r@$?wylUje32g
zTrMl6oOMbm&wgcP`BR_zKk`gOc=w*kr=NLiA^K5-uHlVuSn_9}Gom-}Shmzr1JU>4
zG-}vhbbVhzi~+G!o%^{*W(`LY0Fx0=zyO>Bl9;&T9FY$w&(<D|4u=nAzPjRTsfyf9
z?Sq!l)rEUIJFm_s?a(12fhz9v4Ja!@MCWWS5`q}$;aUxdC&Zj@8>|ah<iJ@KmDyQV
zM95IM|0PK$2!g^7wvee(Cdi!+fQ?e#<xHAM96+RiKzqxij0fvkL<ipHNP$NHu?hns
zQ?W4KajO9<Uqq(@P1`#~g)-zouW#%90|0rRKltE-%gf8n=GrS~Uu`y5{?C8=JAd!*
z{qjR6PZTz7h30Re%8!UymT9dWXMxi5^9#TJ>;HUdVKt0GrPTZ0_frQB9w;1Pha^H}
zTZixBVLd<wZ`LL2gTspB9@vHt%c5+cBQduF<u#OjXc9mctrdvNkV$LpRYI7R6*{L_
zmnmwsFYnT0^NX{Wzj^weqt?H6?N(Yo^1+XG-3(Awsi2rPdgnLe?OB}DV<RUby@O2`
z0R%il=zZ=zB*|-OK2J~rViV-(Cq}Cktra2xisM`<aNc%0Yu4rkz%_SXYtyI^3I{s$
zsCyyN<{s2itDSp7AwtpK9UP6ST9Gz|1f=(_3(^tbWn3Xvf?5WM+MbUe-g#J6tN^Wb
zmSs;q`DBtLtE;QmuHNuoKK<!``|fxD*!$l9Q;o)GwOS2=u*aTL8W6gjZoA$5%9p?T
z+0Xudxl~b_0NmgC#Sb4lb}WwLqPIApD{Win7xq!ow=V8Ek`GJVY&1HDzU&}u>TRv>
z9YjIS3Wzm2;}O83&6!at!{E><!7%a8P=Z{}P?A{)#%EuA{mcue$0v?`{l!Hw^Cw(N
ziXEk%5g0rxStqFxVsX%1uiSfh=ik0O^OvYVIA-jC2s%GGdTiB<CyReVC@e;a?A&p`
zcWa(AR1Sz0mjLrHESKVV_SQ{i52z4%ygIUDvUKZqi%}6f_CC<cfu5Wi%`&@|dgPF?
zyQ3OEc4XrG8><QdNF%ZV6C~;J>7D1(OUrATdSEJ8S7;WNA)D20`;yMNLW|>>XPyay
z__=RC|H><`X{EpRwXZ+-+;a~+cw+C~-Me<~tyIdSFiq2$nd@`6ZohKw{OZbT99Oc;
z?%TinvBw{N;w^8U-my2!(!u^xP!%?n!JGL4MM4O;ZWKBwUe_IBMT&%mNUaysTRsvS
z@<<@u+XjKO0D2GvRouOrl;EzezcFfdpz|($@X2Gx-nAb9-oI;y>Q=n%aAMHg04g3l
zlE~(f6$-P$Qedh#7rw5I4T7=U2?B~+iDOpke(7t=GKP6rnxA{Y*{=7VnUpG%?o`VY
zlatflb7A^ntqTK_cKO!aQms<SI%`@7NtP?E$0ll|lt6|MGGQ17;MCvTSki$mN0euY
zg~&TwukPps2ANSI^tD3zXe(Ne33vD!RMb%vzvCV6sE>?J@0@t)%u92#E8gSPH)g^h
z{@!<A+PQN_o~J}Ky>rreTv}L(OHrD3o_XeN4?Xn2Q%}Do41?m<zmr$_wt7T*YXZ1w
z>|5O5o1{>-hZI|Hn1v8^Pug)PbL@>@8DO@;qicCitXhPXKti}(zh0{B);5oBW6LXN
z7&8_sWY`9q*+4yoooAaxq)4gqbmQHJ_x$|x7ysw9)j<k5kBhej0k$5+gdsRf2ktpp
zE|*6}Yv2397q!+lD@?91vlc*5Bn$)Z^G6>&(Oj6VjZMCM?zNQ_4s>9d5t-Si;%sh9
z3F<H!wb=(AI5|H%^TwLJw6Z`EPA$u&jT*^_)bF1C2sz1hX+N!#BzyA7ClB9y;NYP>
zmo8j8efn&t(**RZ&84@${jEvbX|-BPLzZ=GBh|x)4?p(!Bm4L7KYsi~U(mx9mc(~i
zbDOT3?g^0s6zRq=Y<!@BQ&@-;JC-mC$hI-9Fx4uWA%jm<V7L4FWPpfeG;5LX9^Hpm
znqe7Dh*7l9k#p(9qD#|2XQkMCMgkC&zURccI-U@_su#&|jih?*_Mcyv{Uc>!MH#yw
z=U9;xV-nGXj`G~Fq*rgAMhFTkSZ7%W#YDp?g*Im(cEW<{+u!=0O)@gqtoJGi834$8
zPa&bh&}n9^yz6Er`rM1BGn<Q!9Oxv2mB?T#MG=RrKo!^CdtVp{57*3tvGmqKH2{`M
zr8Mh~)*J8n@%P<-|Mh3y_Vyb$Zd|_n`r*R|UU=~b%p65wwN|<J-h<CR`|Q-z)b8E8
zMn*=`G-GB(3J|vHMQp0<Hf4Ld;8_+p!NFx_=5Dup`SRsXr(=vcc<@jmJ1ZhYMSi}S
z58E@#fo&wL22HpA4k9JeFgdwADGFE#lP8ZN6+kLmV2?mU*$F^jShlBo1Dfk0Odmb?
z3!#hyq?u}RK7z;-BL^<OAp&ePS#C3$Kt;8Q-Qc^#0$#M1+~y;-Nra+S=0f;@nWrZj
zeE1N8i1J0N(6dxSjF8$$!Ysm32BT5@#I6aYv<Kx2WgnLP^%5^LXaeDaX*B>HZ9AOp
zPlpF8?*k53uk$7%%(QpUzI*oKkt0W*eDcY{y`yi}sz|9?wOSascDqSWX~nxiCO5bV
zH`6;etiqvPVEEvQI<izMoj7r#NF}s3&U<DKgK$$0xb4hwi*xKY0*Bst1A>5zs;LB!
zm?X+rD;2Of^n*stCdM5iw&dG$3%^9c-!~MI)DVGj)<Ju5#n}9}mD0>!+$a$==dy{Z
z#~9m&7%2nj0y)oie&JdeD(_JQ10A)R?RICeUey>vNCB`I21`Eetfomo=)Ce;WGPE8
zug;$s8#m;u4ojk(_!Vte*Oo4HQe{H0Oe~;q*e|glKHr77GyK@V?`05@<2WvS9D4c>
zq?OWHmI2Vf-f7q*bjX+mw_I}EQXlr&<FK*ESC~vY=b|X;cDq3k7B5PQytjq*!L~!#
zK|6iWcIZdHUfAeqpA#w~k3ECSie*7XDoEJ60>Lh-xZ_#C3m4rt=m9r1Lwc_LZbyku
z0ThGw;A_ZbZPp|o(}4ygG6|~oeDa6$Zv^ZKkqMC<>p(HH)&UU|UaCq{vy*)1%ylJX
z#Uf*%H9N0_lwz;gP?RtjR4X3-_`)n$W{tw=Nj#zk)Zil>h-K*m8rJVc;jNm_cLri@
z02UVp1pPT77n`iLd2Vx~jrVTYtY;G(d8+{RM#v8iSYvuvUUI->C=8@~cBx^&%+}^v
zmJ$&X2m>MA8Bo+K78Vy5i@=p6iT5rHgCyxrPw$MwXmN2dNs{r=QR|$DM5Pj<ic95C
zhs+j`1$2>ZFbg6faWA7PM!E_jZlFVLN)9Xtizr38@;WnGL;yViuq`VBQMr~7m=GQ4
z!qJQX#Cz#`tb)>k5+FrdG-wnM6&Mh2IYi8ufe=Lso01TP142eGB822grQ!`}V($ys
zaerdYTnxC2?h|jiNJC6=*j7M%5RcczWCc{uW{S6M)7?c`dZXqG_gj;NhKLlWP;Ih%
zEta15LWm#!$VYzcUGEqf8TpaQ=+I9pS65fd<#Myt)JltRv)T0CH&<8HXrt9?HJhu1
zSPYqT5E`Sj(#~gqLMRBx+><!y7pVPDB1AqY>XjTaFN(n!v=-R{BW56~fEKc_B841)
zcoYvq%$<RN{Sdf;djzT8u(bHE10e=aKt;M_06j4>C|~GXkYiUS<-Jv;2WNKuEd=hw
z+*n^FcZBM1AeU|3VecX_J@}L1g|cbXS^TV_FQw!~sQ=QNEFcBsfd@{A+&}CQymgPk
zo~pAbr82hliLtAf-vGx!7y?n2+c*waSDMY$CID3`BXcXaOL0j&w{NuH^w?u-YwgIC
z1)XQB6c^l&-t`c`LgNGwKonYsnGFNWNY0kInkov@iVZ!(qEN%|iYF;B`$`l73$g$*
zu|fk5SeQr)SV9od3{2=X<jiFTMY=`4CX{%`hN>y%F>?W?bLcfGO_r6iASkvZI_I<`
zK_(usIKuVLselZ+ZCgCGc$lEQZ4o~}<rG+LqN2*!;5M=8st*!~!3rC`WLt+jT9{D*
zB6<)Iz>N&zL2tkqEuyw6Aq*dcbI#`W)DK@!UF(od5QL}*u)Dk(mdbB_{NZ3EVuu8z
z2+qI!s?0iNR1ijK(mDObv(A%6X-qW^Jvh~;3iU)v3y+Or2;@N=$9D3!O83Ogi8_yE
zbCHxflq`2-DIfd6|LyUN1*KdK)iSF$#o<X4b_10Vu;tY-%sEfj+Vh=sMxhdqULY>1
z!^f*5rF^8(i9!*tIM1>cG#5*!&U!(#iXG~h=$9Ln9kr2$QKOz6kzzOBRkxh|)0G=n
z^H_ls@#|wn0Fi?E#M|b}gJ*6U-3}u#ifa)B+I#Pu)!KN^!v=xFn1f>EIBeU#N!EOm
zqazXNfx!nN3JdnPpTU|P7{A?#j4?QLrDgv5+@JmaAFp&;sn0d4h*ZkX=6<R+7R2h<
zV-Msxs6fX|fA;_V>CO3DYe@&R0?(kd^-`wNJD>i*j(v?%O$ql>zX6(~PY1J}e`fKc
z&(^;CJIYG75S0?6qTJdP-ubpg`NjGR|0Iw=RLi;X+UU=h{LAQ;QCf_tg3RWqbYp6&
zcJ<nAWI$z-ynA%l&L<w9IsIZc%Y4^`TDLiGIPac%``YYc?b18}2&MRFz5GkZPyEMA
zFJ<}E&8n$n;*k=${hpl%GH!lyer-Yp76BOmHw>SL>H@rzw&%7joWfkodw=H4nX$34
zy?gg&neDlKXg$cf1~`Vo2BP?rp8C!m%w~uE1$vnv4JCw|3W-5f7_Kt6(MV#>ybmZ?
zXf3J0XrQoMGdgHzO)#{UbUXlRa%@u%AaU1sR`QifX~dvem|K7dmh&_y8SkX0b&n{0
zj}ZjHLRQFon+0mOtNr1BS{dJ`9(%A6j<nBT-h1|2%auKAJ4SR?bEV9&RzxVi+x<$K
zy^&)?YM0Mu4aL<)Bh>pfRh_?0qQVf2lBAt~|M@Ud<)GGFo!b>goh(v^_D5P*0$fwh
zWX7;wl|VjsY0*`ahi01l%OhV|Y&J0%Z|t6M${OoDDi2IOtb&LPa0qvBQX52LS>H<s
z_$?w4n46pX{O3Ra;Dg7{oja!#eei=nm!@g8TD3MWl}fEvD+q$((kN9FP~e=6!cv-L
z0KjaOHigk=kymcQiRc120U#(Fn8t%^JnV0^l>s@NG&m=%wCkC}FfNxXBh~u#8&{PV
z7ZC%q^WJ--OrBe%p_9AJky1f>u?v7l?>TmP{)!JF_w79I0@R9uMXw4a^p066W(jma
zwk<Z)m2mXXWIl6W^YOj;=%J;X=Na3WMxIk<WxXZxhG11fhm9cK{on)df92)>*S5O~
zawXurWysMXC`g@U3bH&DDgEcadgJ&01AXZ)w6|3RVr)AOa}DSd;*ziph=X?izy9qH
zp4@HT_}xD^-3@fHTxJRY00^T=L_t)w+)dK)BqAeLixGmJB-=VV4L0|Thj$7tTFAYS
z3{Y$(%-(xBb?Vgn-uM0k2lgy3E<XSK^D{Fyr>Cc{U%xstGwVG+@W2BrD=X*EpFe*5
zxQLuS{le7Lj-yA9&Mw`I<JejoMnRf1E0vnk*mHvf7`8FdKeAtaCnC|HbKFBA0w|&Y
zRqnk5TwYySZ8k^8Mr}9kq$#3iVefsQL6f#w*INr3+N<rI6VsW?&s{wu#w#j;C>H6r
z9k*#Wk%yMAd)rLg#=m+Y*stl<{qXt=<+a-jN^3Tt7(j;<W&n;s`^0C<h}o;>KG*5q
zBr%B#%g7>tq$rRiVUG!;chC)t{l|ZI_qFTnu>|6sEP1a&9PLUcU>0ziO)EE1tpD9N
zzW#7|{8SdDkaY5N6oS-bF%bwNVc#>mkm}*#Bz%+a-&Xs#VF*`IQcX=wBI=KS{8Pt|
zAMbY3h1<*j{@?#!jmGHy{rf-jna@1^^wTRVD_{KL7ax7}(N|u1`RS*h{^Tb=nPu6@
zlMj96D_{BXAAipaFPwVUk3IX;(@$=@4e7#SsIV=(%Tm}{(hTB6FS%d_#I&1)rTEzB
z5d`n8RT^o8c6b>rRl_ijqd2z~NW5U?`NXdKo8x;5v7<ec+FBPzHJh1osRBTV!t9HG
z{e7U=Rnv%a%X;b4|5!;)b!|?Wet3toKW*Q8vh*)65O`0PED@sNpfdd~pU(rh+`H<;
zuFwdY$g)Fl3RUJ3r2^}MgZqOI{IqSvS*zsVDj6j!ovgHbmoR8e9b_b>+38ZH{11*C
zh$|8&YPGEYmwWHa^3{h&YTu3^Zi3d3Du6zz(t8g>_4n4AxxbSZD&%S?HWbdI#0>R%
z{l<+OCr_UIwO{+S=bn2`DIJDkDK6c6?-B3avHOn`(XL&)q9}UHTb@{4Tv%FKI(hQp
z+w+TYTzl-Xw?6X7BWGTExyVL`;T=P@z;HIQ9mQ!l7!3D(iuhc7FE$9rcHe>hd+yom
z(+molDL7`?Wg;*B*zw~&_gbL`uGQ;rdFGKTuV30ZJ}usm8YeJFXx-Az{S$E&6=~=5
zIMzxRRu~9Cz=Gf`1)~0{{x6^H?tUsiaLetUh8-%sx*E<^OaEfkUid;}Jg^a+h@>ms
zFICjh7)F#?i-MNXYg#e^)oRZIWNZViLLK4Cl6&r}u2QoSXS+tCKvmXOGBohqx5iiI
zD`1<(_#E(D*82RJZ$40-3<9Z)MkS^^<*u4ozA*p!&T^|<YFHj&NQl|_0YYSJA7<l>
zZi6@Fu=hhz$36P!qrdlipZn@pzmg=~g9i`p*}FRo!+-YA|Ec%v9hb}HEK8A~NDn{u
zv0v>wkoclHSX^8@aPZ*4LkEUYqdYWk!*6}_y8r^n+jcIu5^1-pk_=^};2qa0HS1l0
z#Uw23s;tp^b#>K*VPUFdv$R~P3n~OFpb;_w4}glo+)D1+1PVb#QS{rt{oCJp{(DK1
z^-r82YLGBjZ#bz%&9M}n$2>M+Mr$_68g|S_7A+}p9@v3Tl~>*?0cI3o0u7)@u~7kt
z1}vcaAT|=Q$OOveoQn!IW?F;6Dng!vCq$CiW?e73;@N}qQIH8GdQGEgNJ&ASD6oW4
zA6D_i6Hol+Z~lwc+Uh{Lf2;XuarX~*05`!&3VinB!eSi9wULoL&#bjtYeZ61(6O;-
z5pL?t4)Ca2>m*6G+Ku)6!V9-i+}L>9nz?Kp&upa@F$*bdcAC8!lDTMy0C>nm88zrm
z7+vaSmM{~@5fu6o7ixyoRSLBPfF4k6?w)$;>5*C;Q6WNM7c4-IbMo)i6kBL%5hP4e
zM=Su|0eG|o2wXTQYKxk@?Hk;9B3)q2Sp<oNL6P<hM4muU(1C(N?8b#0bciDs-@lc8
zLWVd80HuhSHEZe}mG_F6Q8Rf}_?YtX6DJBK_)W8tjT<ZWZ3$%{ML+nhQp#GZw5rwW
zM3f{cA_PIu^Ri?q;M9dN<Hqc=SG`+ni+S<z_1(}E`sUw!r|<0xo0^C}QjKBT7UpoV
zp_D2TqCrK}M__{>c;}E%D+Qu@%^%|a>-3(6M15u|_oWUBdJ_o<h%tzX66HODMilXm
z6f!dvtaxF;qFZlZofsnn=WU`M%v``BARu_xOTh-hn**K<3lb5D_u@zpy%MFFECLq0
zzZXG5_FUMoL{T(o%WS1^Z{p`{Oas-{`)#-o+ZvU(eS9kgYO6P8&uNOqZ`(J%lU32(
zDHg-M>rF(yb@>^AAP9;~g2)v<;Kkt)QDK7xI6TS54g916QsEF8LO=nH2nZxf13=-B
z2f!jMpbN11;971g@kkK&9QU*xK&WwJ9;ZQ6u`1DkA`gJ10_l%K`-m`)zJQ1gqWbXe
zyY<ozO720R9?WI7vUj$%qTO`Pw_Om!Tf^{{p_Nk14xu;4+|s*-yG;7;?tEE%fKAq^
z!)@vz9O4j>TbKy@EfE}E0sS-BTctz@+ULxKNQ0w8#h!_%;IsFoatD0<fqc;Bc*tzA
zCtf6*_P8RkQc4N3LqSp4bJY^%9d9I<ZZ-HEX6<aN=Qb=oy)Aoxn7TAzQEnA?-MUt`
zwIkhRdab?py@u;xC)fxnDc16xP-X*FsbLq3;UsaZD{gT+7EkOSR*!Dq_wg8XrHW=*
zzp(;^$ffl4<^=%>74}@(5fGSN0V)<j6aqj2E}l8$riFuDV7tTHX7<s?#iOtg6;mQ1
z7RIeZci2{IZ4mr6y<Pt^^58cGFnU`g<|eCRxkCjv>=896|F*R}1^{iXHAXq_lu|>m
z%ykiJ=iGM93K@Q<P3gj5#oU38IB1ghYg5t8z90M<kndEHZeqP`O;d(d^zU+Z2GjaG
z1-Y$nu}NZR_~UPl>RSc)he6m|Iy1fb;c!3TPTCiDQb*Y8BC_@5Y%4YU{{yV=df(_1
RGgklr002ovPDHLkV1kK9xrP7$

literal 0
HcmV?d00001

diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h
index 0c259b480f25c1d3cdb30f9d7226edf1f3768fed..0b7e5fefb5be80699545ab5b6360f82b93b634f8 100644
--- a/libs/ardour/ardour/debug.h
+++ b/libs/ardour/ardour/debug.h
@@ -80,6 +80,7 @@ namespace PBD {
 		LIBARDOUR_API extern DebugBits BackendPorts;
 		LIBARDOUR_API extern DebugBits VSTCallbacks;
 		LIBARDOUR_API extern DebugBits FaderPort;
+		LIBARDOUR_API extern DebugBits CC121;
 		LIBARDOUR_API extern DebugBits VCA;
 		LIBARDOUR_API extern DebugBits Push2;
 
diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc
index f52181611dd047cadaa08d2a8defa6933e1a5643..8c88dd4d1d34a56194df6d123e3598ae8e29517e 100644
--- a/libs/ardour/debug.cc
+++ b/libs/ardour/debug.cc
@@ -77,5 +77,6 @@ PBD::DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("backendthreads"
 PBD::DebugBits PBD::DEBUG::BackendPorts = PBD::new_debug_bit ("backendports");
 PBD::DebugBits PBD::DEBUG::VSTCallbacks = PBD::new_debug_bit ("vstcallbacks");
 PBD::DebugBits PBD::DEBUG::FaderPort = PBD::new_debug_bit ("faderport");
+PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
 PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
 PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
diff --git a/libs/surfaces/cc121/cc121.cc b/libs/surfaces/cc121/cc121.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5e68b7b4464992c31d2e46387fa5a627d06a77fa
--- /dev/null
+++ b/libs/surfaces/cc121/cc121.cc
@@ -0,0 +1,1290 @@
+/*
+    Copyright (C) 2015 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    Thanks to Rolf Meyerhoff for reverse engineering the CC121 protocol.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cstdlib>
+#include <sstream>
+#include <algorithm>
+
+#include <stdint.h>
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "pbd/error.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/file_utils.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/compose.h"
+#include "pbd/xml++.h"
+
+#include "midi++/port.h"
+
+#include "control_protocol/basic_ui.h"
+
+#include "ardour/async_midi_port.h"
+#include "ardour/audioengine.h"
+#include "ardour/amp.h"
+#include "ardour/bundle.h"
+#include "ardour/controllable_descriptor.h"
+#include "ardour/debug.h"
+#include "ardour/filesystem_paths.h"
+#include "ardour/midi_port.h"
+#include "ardour/midiport_manager.h"
+#include "ardour/monitor_processor.h"
+#include "ardour/profile.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/record_enable_control.h"
+#include "ardour/stripable.h"
+#include "ardour/session.h"
+#include "ardour/session_configuration.h"
+#include "ardour/track.h"
+
+#include "cc121.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace PBD;
+using namespace Glib;
+using namespace std;
+
+#include "pbd/i18n.h"
+
+#include "pbd/abstract_ui.cc" // instantiate template
+
+CC121::CC121 (Session& s)
+	: ControlProtocol (s, _("Steinberg CC121"))
+	, AbstractUI<CC121Request> (name())
+	, gui (0)
+	, connection_state (ConnectionState (0))
+	, _device_active (false)
+	, fader_msb (0)
+	, fader_lsb (0)
+	, fader_is_touched (false)
+        , _jogmode(scroll)
+	, button_state (ButtonState (0))
+	, blink_state (false)
+	, rec_enable_state (false)
+{
+	last_encoder_time = 0;
+
+	boost::shared_ptr<ARDOUR::Port> inp;
+	boost::shared_ptr<ARDOUR::Port> outp;
+
+	inp  = AudioEngine::instance()->register_input_port (DataType::MIDI, "CC121 Recv", true);
+	outp = AudioEngine::instance()->register_output_port (DataType::MIDI, "CC121 Send", true);
+
+	_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp);
+	_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp);
+
+	if (_input_port == 0 || _output_port == 0) {
+		throw failed_constructor();
+	}
+
+	_input_bundle.reset (new ARDOUR::Bundle (_("CC121 Support (Receive)"), true));
+	_output_bundle.reset (new ARDOUR::Bundle (_("CC121 Support (Send) "), false));
+
+	_input_bundle->add_channel (
+		inp->name(),
+		ARDOUR::DataType::MIDI,
+		session->engine().make_port_name_non_relative (inp->name())
+		);
+
+	_output_bundle->add_channel (
+		outp->name(),
+		ARDOUR::DataType::MIDI,
+		session->engine().make_port_name_non_relative (outp->name())
+		);
+
+
+	StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&CC121::gui_track_selection_changed, this, _1), this);
+
+	/* Catch port connections and disconnections */
+	ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&CC121::connection_handler, this, _1, _2, _3, _4, _5), this);
+	buttons.insert (std::make_pair (EButton, Button (*this, _("EButton"), EButton)));
+	buttons.insert (std::make_pair (OpenVST, Button (*this, _("OpenVST"), OpenVST)));
+	buttons.insert (std::make_pair (InputMonitor, Button (*this, _("InputMonitor"), InputMonitor)));
+	buttons.insert (std::make_pair (EQ1Enable, Button (*this, _("EQ1Enable"), EQ1Enable)));
+	buttons.insert (std::make_pair (EQ2Enable, Button (*this, _("EQ2Enable"), EQ2Enable)));
+	buttons.insert (std::make_pair (EQ3Enable, Button (*this, _("EQ3Enable"), EQ3Enable)));
+	buttons.insert (std::make_pair (EQ4Enable, Button (*this, _("EQ4Enable"), EQ4Enable)));
+	buttons.insert (std::make_pair (EQType, Button (*this, _("EQType"), EQType)));
+	buttons.insert (std::make_pair (AllBypass, Button (*this, _("AllBypass"), AllBypass)));
+	buttons.insert (std::make_pair (Function1, Button (*this, _("Function1"), Function1)));
+	buttons.insert (std::make_pair (Function2, Button (*this, _("Function2"), Function2)));
+	buttons.insert (std::make_pair (Function3, Button (*this, _("Function3"), Function3)));
+	buttons.insert (std::make_pair (Function4, Button (*this, _("Function4"), Function4)));
+	buttons.insert (std::make_pair (Value, Button (*this, _("Value"), Value)));
+	buttons.insert (std::make_pair (Jog, Button (*this, _("Jog"), Jog)));
+	buttons.insert (std::make_pair (Lock, Button (*this, _("Lock"), Lock)));
+	buttons.insert (std::make_pair (ToStart, Button (*this, _("ToStart"), ToStart)));
+	buttons.insert (std::make_pair (ToEnd, Button (*this, _("ToEnd"), ToEnd)));
+	buttons.insert (std::make_pair (Mute, Button (*this, _("Mute"), Mute)));
+	buttons.insert (std::make_pair (Solo, Button (*this, _("Solo"), Solo)));
+	buttons.insert (std::make_pair (Rec, Button (*this, _("Rec"), Rec)));
+	buttons.insert (std::make_pair (Left, Button (*this, _("Left"), Left)));
+	buttons.insert (std::make_pair (Right, Button (*this, _("Right"), Right)));
+	buttons.insert (std::make_pair (Output, Button (*this, _("Output"), Output)));
+	buttons.insert (std::make_pair (FP_Read, Button (*this, _("Read"), FP_Read)));
+	buttons.insert (std::make_pair (FP_Write, Button (*this, _("Write"), FP_Write)));
+	buttons.insert (std::make_pair (Loop, Button (*this, _("Loop"), Loop)));
+	buttons.insert (std::make_pair (Rewind, Button (*this, _("Rewind"), Rewind)));
+	buttons.insert (std::make_pair (Ffwd, Button (*this, _("Ffwd"), Ffwd)));
+	buttons.insert (std::make_pair (Stop, Button (*this, _("Stop"), Stop)));
+	buttons.insert (std::make_pair (Play, Button (*this, _("Play"), Play)));
+	buttons.insert (std::make_pair (RecEnable, Button (*this, _("RecEnable"), RecEnable)));
+	buttons.insert (std::make_pair (Footswitch, Button (*this, _("Footswitch"), Footswitch)));
+	buttons.insert (std::make_pair (FaderTouch, Button (*this, _("Fader (touch)"), FaderTouch)));
+
+	get_button (Left).set_action ( boost::bind (&CC121::left, this), true);
+	get_button (Right).set_action ( boost::bind (&CC121::right, this), true);
+
+	get_button (FP_Read).set_action (boost::bind (&CC121::read, this), true);
+	get_button (FP_Write).set_action (boost::bind (&CC121::write, this), true);
+	get_button (EButton).set_action (boost::bind (&CC121::touch, this), true);
+	get_button (OpenVST).set_action (boost::bind (&CC121::off, this), true);
+
+	get_button (Play).set_action (boost::bind (&BasicUI::transport_play, this, true), true);
+	get_button (ToStart).set_action (boost::bind (&BasicUI::prev_marker, this), true);
+	get_button (ToEnd).set_action (boost::bind (&BasicUI::next_marker, this), true);
+	get_button (RecEnable).set_action (boost::bind (&BasicUI::rec_enable_toggle, this), true);
+	get_button (Stop).set_action (boost::bind (&BasicUI::transport_stop, this), true);
+	get_button (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true);
+
+	get_button (Rewind).set_action (boost::bind (&BasicUI::rewind, this), true);
+	get_button (Loop).set_action (boost::bind (&BasicUI::loop_toggle, this), true);
+
+	get_button (Jog).set_action (boost::bind (&CC121::jog, this), true);
+	get_button (Mute).set_action (boost::bind (&CC121::mute, this), true);
+	get_button (Solo).set_action (boost::bind (&CC121::solo, this), true);
+	get_button (Rec).set_action (boost::bind (&CC121::rec_enable, this), true);
+
+	get_button (InputMonitor).set_action (boost::bind (&CC121::input_monitor, this), true);
+}
+
+CC121::~CC121 ()
+{
+	all_lights_out ();
+
+	if (_input_port) {
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
+		AudioEngine::instance()->unregister_port (_input_port);
+		_input_port.reset ();
+	}
+
+	if (_output_port) {
+		_output_port->drain (10000,  250000); /* check every 10 msecs, wait up to 1/4 second for the port to drain */
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name()));
+		AudioEngine::instance()->unregister_port (_output_port);
+		_output_port.reset ();
+	}
+
+	tear_down_gui ();
+
+	/* stop event loop */
+	DEBUG_TRACE (DEBUG::CC121, "BaseUI::quit ()\n");
+	BaseUI::quit ();
+}
+
+void*
+CC121::request_factory (uint32_t num_requests)
+{
+	/* AbstractUI<T>::request_buffer_factory() is a template method only
+	   instantiated in this source module. To provide something visible for
+	   use in the interface/descriptor, we have this static method that is
+	   template-free.
+	*/
+	return request_buffer_factory (num_requests);
+}
+
+void
+CC121::start_midi_handling ()
+{
+	/* handle buttons press */
+        _input_port->parser()->channel_note_on[0].connect_same_thread (midi_connections, boost::bind (&CC121::button_press_handler, this, _1, _2));
+	/* handle buttons release*/
+        _input_port->parser()->channel_note_off[0].connect_same_thread (midi_connections, boost::bind (&CC121::button_release_handler, this, _1, _2));
+	/* handle fader */
+        _input_port->parser()->pitchbend.connect_same_thread (midi_connections, boost::bind (&CC121::fader_handler, this, _1, _2));
+	/* handle encoder */
+	_input_port->parser()->controller.connect_same_thread (midi_connections, boost::bind (&CC121::encoder_handler, this, _1, _2));
+
+	/* This connection means that whenever data is ready from the input
+	 * port, the relevant thread will invoke our ::midi_input_handler()
+	 * method, which will read the data, and invoke the parser.
+	 */
+
+	_input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &CC121::midi_input_handler), _input_port));
+	_input_port->xthread().attach (main_loop()->get_context());
+}
+
+void
+CC121::stop_midi_handling ()
+{
+	midi_connections.drop_connections ();
+
+	/* Note: the input handler is still active at this point, but we're no
+	 * longer connected to any of the parser signals
+	 */
+}
+
+void
+CC121::do_request (CC121Request* req)
+{
+	if (req->type == CallSlot) {
+
+		call_slot (MISSING_INVALIDATOR, req->the_slot);
+
+	} else if (req->type == Quit) {
+
+		stop ();
+	}
+}
+
+int
+CC121::stop ()
+{
+	BaseUI::quit ();
+
+	return 0;
+}
+
+void
+CC121::thread_init ()
+{
+	struct sched_param rtparam;
+
+	pthread_set_name (event_loop_name().c_str());
+
+	PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+	ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+	memset (&rtparam, 0, sizeof (rtparam));
+	rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
+
+	if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
+		// do we care? not particularly.
+	}
+}
+
+void
+CC121::all_lights_out ()
+{
+	for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) {
+		b->second.set_led_state (_output_port, false);
+	}
+}
+
+CC121::Button&
+CC121::get_button (ButtonID id) const
+{
+	ButtonMap::const_iterator b = buttons.find (id);
+	assert (b != buttons.end());
+	return const_cast<Button&>(b->second);
+}
+
+void
+CC121::button_press_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+	DEBUG_TRACE (DEBUG::CC121, string_compose ("button press event for ID %1 press ? %2\n", (int) tb->controller_number, (tb->value ? "yes" : "no")));
+
+	ButtonID id (ButtonID (tb->controller_number));
+	Button& button (get_button (id));
+
+	buttons_down.insert (id);
+	ButtonState bs (ButtonState (0));
+
+	switch (id) {
+	case FaderTouch:
+	  fader_is_touched = true;
+		if (_current_stripable) {
+			boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+			if (gain) {
+			  framepos_t now = session->engine().sample_time();
+			  gain->start_touch (now);
+			}
+		}
+		break;
+	default:
+	  break;
+	}
+
+	if (bs) {
+		button_state = ButtonState (button_state|bs);
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("reset button state to %1 using %2\n", button_state, (int) bs));
+	}
+
+	if (button.uses_flash()) {
+		button.set_led_state (_output_port, (int)tb->value);
+	}
+
+	set<ButtonID>::iterator c = consumed.find (id);
+
+	if (c == consumed.end()) {
+		button.invoke (button_state, true);
+	} else {
+		DEBUG_TRACE (DEBUG::CC121, "button was consumed, ignored\n");
+		consumed.erase (c);
+	}
+}
+
+void
+CC121::button_release_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+	DEBUG_TRACE (DEBUG::CC121, string_compose ("button release event for ID %1 release ? %2\n", (int) tb->controller_number, (tb->value ? "yes" : "no")));
+
+	ButtonID id (ButtonID (tb->controller_number));
+	Button& button (get_button (id));
+
+	buttons_down.erase (id);
+	button.timeout_connection.disconnect ();
+
+	ButtonState bs (ButtonState (0));
+
+	switch (id) {
+	case FaderTouch:
+	  fader_is_touched = false;
+	  if (_current_stripable) {
+	    boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+	    if (gain) {
+	      framepos_t now = session->engine().sample_time();
+	      gain->stop_touch (true, now);
+	    }
+	  }
+	  break;
+	default:
+		break;
+	}
+
+	if (bs) {
+		button_state = ButtonState (button_state&~bs);
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("reset button state to %1 using %2\n", button_state, (int) bs));
+	}
+
+	if (button.uses_flash()) {
+		button.set_led_state (_output_port, (int)tb->value);
+	}
+
+	set<ButtonID>::iterator c = consumed.find (id);
+
+	if (c == consumed.end()) {
+		button.invoke (button_state, false);
+	} else {
+		DEBUG_TRACE (DEBUG::CC121, "button was consumed, ignored\n");
+		consumed.erase (c);
+	}
+}
+
+void
+CC121::encoder_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
+{
+        DEBUG_TRACE (DEBUG::CC121, "encoder handler");
+
+	/* Extract absolute value*/
+	float adj = static_cast<float>(tb->value & ~0x40);
+
+	/* Get direction (negative values start at 0x40)*/
+	float sign = (tb->value & 0x40) ? -1.0 : 1.0;
+	switch(tb->controller_number) {
+	case 0x10:
+	  /* pan */
+	  DEBUG_TRACE (DEBUG::CC121, "PAN encoder");
+	  if (_current_stripable) {
+	    /* Get amount of change (encoder clicks) * (change per click)*/
+	    /*Create an exponential curve*/
+	    float curve = sign * pow(adj, (1.0 + 10.0) / 10.0);
+	    adj = curve * (31 / 1000.0);
+	    ardour_pan_azimuth (adj);
+	  }
+	  break;
+	case 0x20:
+	  /* EQ 1 Q */
+	  break;
+	case 0x21:
+	  /* EQ 2 Q */
+	  break;
+	case 0x22:
+	  /* EQ 3 Q */
+	  break;
+	case 0x23:
+	  /* EQ 4 Q */
+	  break;
+	case 0x30:
+	  /* EQ 1 Frequency */
+	  break;
+	case 0x31:
+	  /* EQ 2 Frequency */
+	  break;
+	case 0x32:
+	  /* EQ 3 Frequency */
+	  break;
+	case 0x33:
+	  /* EQ 4 Frequency */
+	  break;
+	case 0x3C:
+	  /* AI */
+	  if (sign < 0.0f) {
+	    if (_jogmode == scroll) {
+	      ScrollTimeline(-0.05);
+	    }
+	    else {
+	      ZoomIn();
+	    }
+	  }
+	  else {
+	    if (_jogmode == scroll) {
+	      ScrollTimeline(0.05);
+	    }
+	    else {
+	      ZoomOut();
+	    }
+	  }
+	  break;
+	case 0x40:
+	  /* EQ 1 Gain */
+	  break;
+	case 0x41:
+	  /* EQ 2 Gain */
+	  break;
+	case 0x42:
+	  /* EQ 3 Gain */
+	  break;
+	case 0x43:
+	  /* EQ 4 Gain */
+	  break;
+	case 0x50:
+	  /* Value */
+	  break;
+	default:
+	  break;
+	}
+}
+
+void
+CC121::fader_handler (MIDI::Parser &, MIDI::pitchbend_t pb)
+{
+        DEBUG_TRACE (DEBUG::CC121, "fader handler");
+
+	if (_current_stripable) {
+	  boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+	  if (gain) {
+	    float val = gain->interface_to_internal (pb/16384.0);
+	    /* even though the cc121 only controls a
+	       single stripable at a time, allow the fader to
+	       modify the group, if appropriate.
+	    */
+	    _current_stripable->gain_control()->set_value (val, Controllable::UseGroup);
+	  }
+	}
+}
+
+int
+CC121::set_active (bool yn)
+{
+	DEBUG_TRACE (DEBUG::CC121, string_compose("CC121::set_active init with yn: '%1'\n", yn));
+
+	if (yn == active()) {
+		return 0;
+	}
+
+	if (yn) {
+
+		/* start event loop */
+
+		BaseUI::run ();
+
+		connect_session_signals ();
+
+		Glib::RefPtr<Glib::TimeoutSource> blink_timeout = Glib::TimeoutSource::create (200); // milliseconds
+		blink_connection = blink_timeout->connect (sigc::mem_fun (*this, &CC121::blink));
+		blink_timeout->attach (main_loop()->get_context());
+
+		Glib::RefPtr<Glib::TimeoutSource> heartbeat_timeout = Glib::TimeoutSource::create (800); // milliseconds
+		heartbeat_connection = heartbeat_timeout->connect (sigc::mem_fun (*this, &CC121::beat));
+		heartbeat_timeout->attach (main_loop()->get_context());
+
+		Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds
+		periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &CC121::periodic));
+		periodic_timeout->attach (main_loop()->get_context());
+
+	} else {
+
+		BaseUI::quit ();
+		close ();
+
+	}
+
+	ControlProtocol::set_active (yn);
+
+	DEBUG_TRACE (DEBUG::CC121, string_compose("CC121::set_active done with yn: '%1'\n", yn));
+
+	return 0;
+}
+
+bool
+CC121::periodic ()
+{
+	if (!_current_stripable) {
+		return true;
+	}
+
+	ARDOUR::AutoState gain_state = _current_stripable->gain_control()->automation_state();
+
+	if (gain_state == ARDOUR::Touch || gain_state == ARDOUR::Play) {
+		map_gain ();
+	}
+
+	return true;
+}
+
+void
+CC121::stop_blinking (ButtonID id)
+{
+	blinkers.remove (id);
+	get_button (id).set_led_state (_output_port, false);
+}
+
+void
+CC121::start_blinking (ButtonID id)
+{
+	blinkers.push_back (id);
+	get_button (id).set_led_state (_output_port, true);
+}
+
+bool
+CC121::beat ()
+{
+	MIDI::byte buf[8];
+
+	buf[0] = 0xf0;
+	buf[1] = 0x43;
+	buf[2] = 0x10;
+	buf[3] = 0x3e;
+	buf[4] = 0x15;
+	buf[5] = 0x00;
+	buf[6] = 0x01;
+	buf[7] = 0xF7;
+
+	_output_port->write (buf, 8, 0);
+
+        return true;
+}
+
+bool
+CC121::blink ()
+{
+	blink_state = !blink_state;
+
+	for (Blinkers::iterator b = blinkers.begin(); b != blinkers.end(); b++) {
+		get_button(*b).set_led_state (_output_port, blink_state);
+	}
+
+	map_recenable_state ();
+
+	return true;
+}
+
+void
+CC121::close ()
+{
+	all_lights_out ();
+
+	stop_midi_handling ();
+	session_connections.drop_connections ();
+	port_connection.disconnect ();
+	blink_connection.disconnect ();
+	heartbeat_connection.disconnect ();
+	selection_connection.disconnect ();
+	stripable_connections.drop_connections ();
+
+#if 0
+	stripable_connections.drop_connections ();
+#endif
+}
+
+void
+CC121::map_recenable_state ()
+{
+	/* special case for RecEnable because its status can change as a
+	 * confluence of unrelated parameters: (a) session rec-enable state (b)
+	 * rec-enabled tracks. So we don't add the button to the blinkers list,
+	 * we just call this:
+	 *
+	 *  * from the blink callback
+	 *  * when the session tells us about a status change
+	 *
+	 * We do the last one so that the button changes state promptly rather
+	 * than waiting for the next blink callback. The change in "blinking"
+	 * based on having record-enabled tracks isn't urgent, and that happens
+	 * during the blink callback.
+	 */
+
+	bool onoff;
+
+	switch (session->record_status()) {
+	case Session::Disabled:
+		onoff = false;
+		break;
+	case Session::Enabled:
+		onoff = blink_state;
+		break;
+	case Session::Recording:
+		if (session->have_rec_enabled_track ()) {
+			onoff = true;
+		} else {
+			onoff = blink_state;
+		}
+		break;
+	}
+
+	if (onoff != rec_enable_state) {
+		get_button(RecEnable).set_led_state (_output_port, onoff);
+		rec_enable_state = onoff;
+	}
+}
+
+void
+CC121::map_transport_state ()
+{
+	get_button (Loop).set_led_state (_output_port, session->get_play_loop());
+
+	float ts = session->transport_speed();
+
+	if (ts == 0) {
+		stop_blinking (Play);
+	} else if (fabs (ts) == 1.0) {
+		stop_blinking (Play);
+		get_button (Play).set_led_state (_output_port, true);
+	} else {
+		start_blinking (Play);
+	}
+
+	get_button (Stop).set_led_state (_output_port, session->transport_stopped ());
+	get_button (Rewind).set_led_state (_output_port, session->transport_speed() < 0.0);
+	get_button (Ffwd).set_led_state (_output_port, session->transport_speed() > 1.0);
+	get_button (Jog).set_led_state (_output_port, _jogmode == scroll);
+}
+
+void
+CC121::connect_session_signals()
+{
+	session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_recenable_state, this), this);
+	session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_transport_state, this), this);
+}
+
+bool
+CC121::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port)
+{
+	DEBUG_TRACE (DEBUG::CC121, string_compose ("something happend on  %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+
+	if (ioc & ~IO_IN) {
+		return false;
+	}
+
+	if (ioc & IO_IN) {
+
+		port->clear ();
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
+		framepos_t now = session->engine().sample_time();
+		port->parse (now);
+	}
+
+	return true;
+}
+
+
+XMLNode&
+CC121::get_state ()
+{
+	XMLNode& node (ControlProtocol::get_state());
+
+	XMLNode* child;
+
+	child = new XMLNode (X_("Input"));
+	child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state());
+	node.add_child_nocopy (*child);
+
+
+	child = new XMLNode (X_("Output"));
+	child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state());
+	node.add_child_nocopy (*child);
+
+	/* Save action state for Function1..4, Lock, Value, EQnEnable, EQType,
+	 * AllBypass and Footswitch buttons, since these
+	 * are user controlled. We can only save named-action operations, since
+	 * internal functions are just pointers to functions and hard to
+	 * serialize without enumerating them all somewhere.
+	 */
+
+	node.add_child_nocopy (get_button (Function1).get_state());
+	node.add_child_nocopy (get_button (Function2).get_state());
+	node.add_child_nocopy (get_button (Function3).get_state());
+	node.add_child_nocopy (get_button (Function4).get_state());
+	node.add_child_nocopy (get_button (Value).get_state());
+	node.add_child_nocopy (get_button (Lock).get_state());
+	node.add_child_nocopy (get_button (EQ1Enable).get_state());
+	node.add_child_nocopy (get_button (EQ2Enable).get_state());
+	node.add_child_nocopy (get_button (EQ3Enable).get_state());
+	node.add_child_nocopy (get_button (EQ4Enable).get_state());
+	node.add_child_nocopy (get_button (EQType).get_state());
+	node.add_child_nocopy (get_button (AllBypass).get_state());
+	node.add_child_nocopy (get_button (Footswitch).get_state());
+
+	return node;
+}
+
+int
+CC121::set_state (const XMLNode& node, int version)
+{
+	XMLNodeList nlist;
+	XMLNodeConstIterator niter;
+	XMLNode const* child;
+
+	if (ControlProtocol::set_state (node, version)) {
+		return -1;
+	}
+
+	if ((child = node.child (X_("Input"))) != 0) {
+		XMLNode* portnode = child->child (Port::state_node_name.c_str());
+		if (portnode) {
+			boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version);
+		}
+	}
+
+	if ((child = node.child (X_("Output"))) != 0) {
+		XMLNode* portnode = child->child (Port::state_node_name.c_str());
+		if (portnode) {
+			boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version);
+		}
+	}
+
+	for (XMLNodeList::const_iterator n = node.children().begin(); n != node.children().end(); ++n) {
+		if ((*n)->name() == X_("Button")) {
+			XMLProperty const * prop = (*n)->property (X_("id"));
+			if (!prop) {
+				continue;
+			}
+			int xid = atoi (prop->value());
+			ButtonMap::iterator b = buttons.find (ButtonID (xid));
+			if (b == buttons.end()) {
+				continue;
+			}
+			b->second.set_state (**n);
+		}
+	}
+
+	return 0;
+}
+
+bool
+CC121::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+	DEBUG_TRACE (DEBUG::CC121, "CC121::connection_handler  start\n");
+	if (!_input_port || !_output_port) {
+		return false;
+	}
+
+	string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name());
+	string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name());
+
+	if (ni == name1 || ni == name2) {
+		if (yn) {
+			connection_state |= InputConnected;
+		} else {
+			connection_state &= ~InputConnected;
+		}
+	} else if (no == name1 || no == name2) {
+		if (yn) {
+			connection_state |= OutputConnected;
+		} else {
+			connection_state &= ~OutputConnected;
+		}
+	} else {
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+		/* not our ports */
+		return false;
+	}
+
+	if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+
+		/* XXX this is a horrible hack. Without a short sleep here,
+		   something prevents the device wakeup messages from being
+		   sent and/or the responses from being received.
+		*/
+
+		g_usleep (100000);
+                DEBUG_TRACE (DEBUG::CC121, "device now connected for both input and output\n");
+		connected ();
+
+	} else {
+		DEBUG_TRACE (DEBUG::CC121, "Device disconnected (input or output or both) or not yet fully connected\n");
+		_device_active = false;
+	}
+
+	ConnectionChange (); /* emit signal for our GUI */
+
+	DEBUG_TRACE (DEBUG::CC121, "CC121::connection_handler  end\n");
+
+	return true; /* connection status changed */
+}
+
+void
+CC121::connected ()
+{
+	DEBUG_TRACE (DEBUG::CC121, "connected");
+
+	_device_active = true;
+
+	start_midi_handling ();
+
+	all_lights_out ();
+
+	/* catch up on state */
+
+	/* make sure that rec_enable_state is consistent with current device state */
+	get_button (RecEnable).set_led_state (_output_port, rec_enable_state);
+
+	map_transport_state ();
+	map_recenable_state ();
+}
+
+void
+CC121::Button::invoke (CC121::ButtonState bs, bool press)
+{
+	DEBUG_TRACE (DEBUG::CC121, string_compose ("invoke button %1 for %2 state %3%4%5\n", id, (press ? "press":"release"), hex, bs, dec));
+
+	ToDoMap::iterator x;
+
+	if (press) {
+		if ((x = on_press.find (bs)) == on_press.end()) {
+			DEBUG_TRACE (DEBUG::CC121, string_compose ("no press action for button %1 state %2 @ %3 in %4\n", id, bs, this, &on_press));
+			return;
+		}
+	} else {
+		if ((x = on_release.find (bs)) == on_release.end()) {
+			DEBUG_TRACE (DEBUG::CC121, string_compose ("no release action for button %1 state %2 @%3 in %4\n", id, bs, this, &on_release));
+			return;
+		}
+	}
+
+	switch (x->second.type) {
+	case NamedAction:
+		if (!x->second.action_name.empty()) {
+			fp.access_action (x->second.action_name);
+		}
+		break;
+	case InternalFunction:
+		if (x->second.function) {
+			x->second.function ();
+		}
+	}
+}
+
+void
+CC121::Button::set_action (string const& name, bool when_pressed, CC121::ButtonState bs)
+{
+	ToDo todo;
+
+	todo.type = NamedAction;
+
+	if (when_pressed) {
+		if (name.empty()) {
+			on_press.erase (bs);
+		} else {
+			DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 to action %2 on press + %3%4%5\n", id, name, bs));
+			todo.action_name = name;
+			on_press[bs] = todo;
+		}
+	} else {
+		if (name.empty()) {
+			on_release.erase (bs);
+		} else {
+			DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 to action %2 on release + %3%4%5\n", id, name, bs));
+			todo.action_name = name;
+			on_release[bs] = todo;
+		}
+	}
+}
+
+string
+CC121::Button::get_action (bool press, CC121::ButtonState bs)
+{
+	ToDoMap::iterator x;
+
+	if (press) {
+		if ((x = on_press.find (bs)) == on_press.end()) {
+			return string();
+		}
+		if (x->second.type != NamedAction) {
+			return string ();
+		}
+		return x->second.action_name;
+	} else {
+		if ((x = on_release.find (bs)) == on_release.end()) {
+			return string();
+		}
+		if (x->second.type != NamedAction) {
+			return string ();
+		}
+		return x->second.action_name;
+	}
+}
+
+void
+CC121::Button::set_action (boost::function<void()> f, bool when_pressed, CC121::ButtonState bs)
+{
+	ToDo todo;
+	todo.type = InternalFunction;
+
+	if (when_pressed) {
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 (%2) @ %5 to some functor on press + %3 in %4\n", id, name, bs, &on_press, this));
+		todo.function = f;
+		on_press[bs] = todo;
+	} else {
+		DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 (%2) @ %5 to some functor on release + %3\n", id, name, bs, this));
+		todo.function = f;
+		on_release[bs] = todo;
+	}
+}
+
+void
+CC121::Button::set_led_state (boost::shared_ptr<MIDI::Port> port, bool onoff)
+{
+	MIDI::byte buf[3];
+	DEBUG_TRACE(DEBUG::CC121, "Set Led State\n");
+	buf[0] = 0x90;
+	buf[1] = id;
+	buf[2] = (onoff ? 0x7F:0x00);
+	port->write (buf, 3, 0);
+}
+
+int
+CC121::Button::set_state (XMLNode const& node)
+{
+	const XMLProperty* prop = node.property ("id");
+	if (!prop) {
+		return -1;
+	}
+
+	int xid = atoi (prop->value());
+	if (xid != id) {
+		return -1;
+	}
+
+	typedef pair<string,CC121::ButtonState> state_pair_t;
+	vector<state_pair_t> state_pairs;
+
+	state_pairs.push_back (make_pair (string ("plain"), ButtonState (0)));
+
+	for (vector<state_pair_t>::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) {
+		string propname;
+
+		propname = sp->first + X_("-press");
+		if ((prop = node.property (propname)) != 0) {
+			set_action (prop->value(), true, sp->second);
+		}
+
+		propname = sp->first + X_("-release");
+		if ((prop = node.property (propname)) != 0) {
+			set_action (prop->value(), false, sp->second);
+		}
+	}
+
+	return 0;
+}
+
+XMLNode&
+CC121::Button::get_state () const
+{
+	XMLNode* node = new XMLNode (X_("Button"));
+	char buf[16];
+	snprintf (buf, sizeof (buf), "%d", id);
+
+	node->add_property (X_("id"), buf);
+
+	ToDoMap::const_iterator x;
+	ToDo null;
+	null.type = NamedAction;
+
+	typedef pair<string,CC121::ButtonState> state_pair_t;
+	vector<state_pair_t> state_pairs;
+
+	state_pairs.push_back (make_pair (string ("plain"), ButtonState (0)));
+
+	for (vector<state_pair_t>::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) {
+		if ((x = on_press.find (sp->second)) != on_press.end()) {
+			if (x->second.type == NamedAction) {
+				node->add_property (string (sp->first + X_("-press")).c_str(), x->second.action_name);
+			}
+		}
+
+		if ((x = on_release.find (sp->second)) != on_release.end()) {
+			if (x->second.type == NamedAction) {
+				node->add_property (string (sp->first + X_("-release")).c_str(), x->second.action_name);
+			}
+		}
+	}
+
+	return *node;
+}
+
+void
+CC121::gui_track_selection_changed (StripableNotificationListPtr stripables)
+{
+	boost::shared_ptr<Stripable> r;
+
+	if (!stripables->empty()) {
+		r = stripables->front().lock();
+	}
+
+	set_current_stripable (r);
+}
+
+void
+CC121::drop_current_stripable ()
+{
+	if (_current_stripable) {
+		if (_current_stripable == session->monitor_out()) {
+			set_current_stripable (session->master_out());
+		} else {
+			set_current_stripable (boost::shared_ptr<Stripable>());
+		}
+	}
+}
+
+void
+CC121::set_current_stripable (boost::shared_ptr<Stripable> r)
+{
+	stripable_connections.drop_connections ();
+
+	_current_stripable = r;
+
+	if (_current_stripable) {
+		_current_stripable->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::drop_current_stripable, this), this);
+
+		_current_stripable->mute_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_mute, this), this);
+		_current_stripable->solo_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_solo, this), this);
+
+		boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_stripable);
+		if (t) {
+			t->rec_enable_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_recenable, this), this);
+		}
+
+		boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control ();
+		if (control) {
+			control->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_gain, this), this);
+			control->alist()->automation_state_changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_auto, this), this);
+		}
+
+		boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control();
+		if (mp) {
+			mp->cut_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_cut, this), this);
+		}
+	}
+
+	//ToDo: subscribe to the fader automation modes so we can light the LEDs
+
+	map_stripable_state ();
+}
+
+void
+CC121::map_auto ()
+{
+	boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control ();
+	const AutoState as = control->automation_state ();
+
+	switch (as) {
+		case ARDOUR::Play:
+			get_button (FP_Read).set_led_state (_output_port, true);
+			get_button (FP_Write).set_led_state (_output_port, false);
+			get_button (EButton).set_led_state (_output_port, false);
+			get_button (OpenVST).set_led_state (_output_port, false);
+		break;
+		case ARDOUR::Write:
+			get_button (FP_Read).set_led_state (_output_port, false);
+			get_button (FP_Write).set_led_state (_output_port, true);
+			get_button (EButton).set_led_state (_output_port, false);
+			get_button (OpenVST).set_led_state (_output_port, false);
+		break;
+		case ARDOUR::Touch:
+			get_button (EButton).set_led_state (_output_port, true);
+			get_button (FP_Read).set_led_state (_output_port, false);
+			get_button (FP_Write).set_led_state(_output_port, false);
+			get_button (OpenVST).set_led_state (_output_port, false);
+		break;
+		case ARDOUR::Off:
+			get_button (OpenVST).set_led_state (_output_port, true);
+			get_button (FP_Read).set_led_state (_output_port, false);
+			get_button (FP_Write).set_led_state (_output_port, false);
+			get_button (EButton).set_led_state (_output_port, false);
+		break;
+	}
+}
+
+void
+CC121::map_cut ()
+{
+	boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control();
+
+	if (mp) {
+		bool yn = mp->cut_all ();
+		if (yn) {
+			start_blinking (Mute);
+		} else {
+			stop_blinking (Mute);
+		}
+	} else {
+		stop_blinking (Mute);
+	}
+}
+
+void
+CC121::map_mute ()
+{
+	if (_current_stripable) {
+		if (_current_stripable->mute_control()->muted()) {
+			stop_blinking (Mute);
+			get_button (Mute).set_led_state (_output_port, true);
+		} else if (_current_stripable->mute_control()->muted_by_others_soloing () || _current_stripable->mute_control()->muted_by_masters()) {
+			start_blinking (Mute);
+		} else {
+			stop_blinking (Mute);
+		}
+	} else {
+		stop_blinking (Mute);
+	}
+}
+
+void
+CC121::map_solo ()
+{
+	if (_current_stripable) {
+		get_button (Solo).set_led_state (_output_port, _current_stripable->solo_control()->soloed());
+	} else {
+		get_button (Solo).set_led_state (_output_port, false);
+	}
+}
+
+void
+CC121::map_recenable ()
+{
+	boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_stripable);
+	if (t) {
+		get_button (Rec).set_led_state (_output_port, t->rec_enable_control()->get_value());
+	} else {
+		get_button (Rec).set_led_state (_output_port, false);
+	}
+}
+
+void
+CC121::map_gain ()
+{
+	if (fader_is_touched) {
+		/* Do not send fader moves while the user is touching the fader */
+		return;
+	}
+
+	if (!_current_stripable) {
+		return;
+	}
+
+	boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control ();
+	double val;
+
+	if (!control) {
+		val = 0.0;
+	} else {
+		val = control->internal_to_interface (control->get_value ());
+	}
+
+	float fval = (val* 16384.0);
+	if (fval <0.0)
+	  fval = 0.0;
+	else if (fval > 16383.0)
+	  fval = 16383.0;
+	int ival = (int)(fval + 0.5);
+
+	MIDI::byte buf[3];
+
+	buf[0] = 0xE0;
+	buf[1] = ival & 0x7F;
+	buf[2] = (ival >> 7) & 0x7F;
+
+	_output_port->write (buf, 3, 0);
+}
+
+void
+CC121::map_stripable_state ()
+{
+	if (!_current_stripable) {
+		stop_blinking (Mute);
+		stop_blinking (Solo);
+		get_button (Rec).set_led_state (_output_port, false);
+	} else {
+		map_solo ();
+		map_recenable ();
+		map_gain ();
+		map_auto ();
+
+		if (_current_stripable == session->monitor_out()) {
+			map_cut ();
+		} else {
+			map_mute ();
+		}
+	}
+}
+
+list<boost::shared_ptr<ARDOUR::Bundle> >
+CC121::bundles ()
+{
+	list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+	if (_input_bundle) {
+		b.push_back (_input_bundle);
+		b.push_back (_output_bundle);
+	}
+
+	return b;
+}
+
+boost::shared_ptr<Port>
+CC121::output_port()
+{
+	return _output_port;
+}
+
+boost::shared_ptr<Port>
+CC121::input_port()
+{
+	return _input_port;
+}
+
+void
+CC121::set_action (ButtonID id, std::string const& action_name, bool on_press, ButtonState bs)
+{
+	get_button(id).set_action (action_name, on_press, bs);
+}
+
+string
+CC121::get_action (ButtonID id, bool press, ButtonState bs)
+{
+	return get_button(id).get_action (press, bs);
+}
diff --git a/libs/surfaces/cc121/cc121.h b/libs/surfaces/cc121/cc121.h
new file mode 100644
index 0000000000000000000000000000000000000000..a5f0363a224b98e4bbfe23e6f201b9c76cd99312
--- /dev/null
+++ b/libs/surfaces/cc121/cc121.h
@@ -0,0 +1,342 @@
+/*
+    Copyright (C) 2006 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    Thanks to Rolf Meyerhoff for reverse engineering the CC121 protocol.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef ardour_surface_cc121_h
+#define ardour_surface_cc121_h
+
+#include <list>
+#include <map>
+#include <set>
+#include <glibmm/threads.h>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+
+#include "ardour/types.h"
+
+#include "control_protocol/control_protocol.h"
+
+namespace PBD {
+	class Controllable;
+	class ControllableDescriptor;
+}
+
+#include <midi++/types.h>
+
+//#include "pbd/signals.h"
+
+
+//#include "midi_byte_array.h"
+#include "types.h"
+
+#include "glibmm/main.h"
+
+namespace MIDI {
+	class Parser;
+	class Port;
+}
+
+
+namespace ARDOUR {
+	class AsyncMIDIPort;
+	class Bundle;
+	class Port;
+	class Session;
+	class MidiPort;
+}
+
+
+class MIDIControllable;
+class MIDIFunction;
+class MIDIAction;
+
+namespace ArdourSurface {
+
+struct CC121Request : public BaseUI::BaseRequestObject {
+public:
+	CC121Request () {}
+	~CC121Request () {}
+};
+
+class CC121 : public ARDOUR::ControlProtocol, public AbstractUI<CC121Request> {
+  public:
+	CC121 (ARDOUR::Session&);
+	virtual ~CC121();
+
+	int set_active (bool yn);
+
+	/* we probe for a device when our ports are connected. Before that,
+	   there's no way to know if the device exists or not.
+	 */
+	static bool probe() { return true; }
+	static void* request_factory (uint32_t);
+
+	XMLNode& get_state ();
+	int set_state (const XMLNode&, int version);
+
+	bool has_editor () const { return true; }
+	void* get_gui () const;
+	void  tear_down_gui ();
+
+
+	/* Note: because the CC121 speaks an inherently duplex protocol,
+	   we do not implement get/set_feedback() since this aspect of
+	   support for the protocol is not optional.
+	*/
+
+	void do_request (CC121Request*);
+	int stop ();
+
+	void thread_init ();
+
+	PBD::Signal0<void> ConnectionChange;
+
+	boost::shared_ptr<ARDOUR::Port> input_port();
+	boost::shared_ptr<ARDOUR::Port> output_port();
+
+	enum ButtonID {
+		Rec = 0x00,
+		Solo = 0x08,
+		Mute = 0x10,
+		Left = 0x30,
+		Right = 0x31,
+		EButton = 0x33,
+		Function1 = 0x36,
+		Function2 = 0x37,
+		Function3 = 0x38,
+		Function4 = 0x39,
+		Value = 0x3A,
+		Footswitch = 0x3B,
+		FP_Read = 0x4A,
+		FP_Write = 0x4B,
+		Loop = 0x56,
+		ToStart = 0x58,
+		ToEnd = 0x5A,
+		Rewind = 0x5B,
+		Ffwd = 0x5C,
+		Stop = 0x5D,
+		Play = 0x5E,
+		RecEnable = 0x5F,
+		FaderTouch = 0x68,
+		EQ1Enable = 0x70,
+		EQ2Enable = 0x71,
+		EQ3Enable = 0x72,
+		EQ4Enable = 0x73,
+		EQType = 0x74,
+		AllBypass = 0x75,
+		Jog = 0x76,
+		Lock = 0x77,
+		InputMonitor = 0x78,
+		OpenVST = 0x79,
+		Output = 22
+	};
+
+	enum ButtonState {
+	  ShiftDown = 0x1,
+		RewindDown = 0x2,
+		StopDown = 0x4,
+		UserDown = 0x8,
+		LongPress = 0x10
+	};
+
+	void set_action (ButtonID, std::string const& action_name, bool on_press, CC121::ButtonState = ButtonState (0));
+	std::string get_action (ButtonID, bool on_press, CC121::ButtonState = ButtonState (0));
+
+	std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
+
+  private:
+	boost::shared_ptr<ARDOUR::Stripable> _current_stripable;
+	boost::weak_ptr<ARDOUR::Stripable> pre_master_stripable;
+	boost::weak_ptr<ARDOUR::Stripable> pre_monitor_stripable;
+
+	boost::shared_ptr<ARDOUR::AsyncMIDIPort> _input_port;
+	boost::shared_ptr<ARDOUR::AsyncMIDIPort> _output_port;
+
+	// Bundle to represent our input ports
+	boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
+	// Bundle to represent our output ports
+	boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
+
+	PBD::ScopedConnectionList midi_connections;
+
+	bool midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port);
+
+	mutable void *gui;
+	void build_gui ();
+
+	bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
+	PBD::ScopedConnection port_connection;
+
+	enum ConnectionState {
+		InputConnected = 0x1,
+		OutputConnected = 0x2
+	};
+
+	int connection_state;
+	void connected ();
+	bool _device_active;
+	int fader_msb;
+	int fader_lsb;
+	bool fader_is_touched;
+	enum JogMode { scroll=1, zoom=2 };
+	JogMode _jogmode;
+
+	ARDOUR::microseconds_t last_encoder_time;
+	int last_good_encoder_delta;
+	int last_encoder_delta, last_last_encoder_delta;
+
+	void button_press_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+	void button_release_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+	void fader_handler (MIDI::Parser &, MIDI::pitchbend_t pb);
+	void encoder_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+	/*	void fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);*/
+
+	ButtonState button_state;
+
+	friend class Button;
+
+	class Button {
+	  public:
+
+		enum ActionType {
+			NamedAction,
+			InternalFunction,
+		};
+
+		Button (CC121& f, std::string const& str, ButtonID i)
+			: fp (f)
+			, name (str)
+			, id (i)
+			, flash (false)
+		{}
+
+		void set_action (std::string const& action_name, bool on_press, CC121::ButtonState = ButtonState (0));
+		void set_action (boost::function<void()> function, bool on_press, CC121::ButtonState = ButtonState (0));
+		std::string get_action (bool press, CC121::ButtonState bs = ButtonState (0));
+
+		void set_led_state (boost::shared_ptr<MIDI::Port>, bool onoff);
+		void invoke (ButtonState bs, bool press);
+		bool uses_flash () const { return flash; }
+		void set_flash (bool yn) { flash = yn; }
+
+		XMLNode& get_state () const;
+		int set_state (XMLNode const&);
+
+		sigc::connection timeout_connection;
+
+	  private:
+		CC121& fp;
+		std::string name;
+		ButtonID id;
+		bool flash;
+
+		struct ToDo {
+			ActionType type;
+			/* could be a union if boost::function didn't require a
+			 * constructor
+			 */
+			std::string action_name;
+			boost::function<void()> function;
+		};
+
+		typedef std::map<CC121::ButtonState,ToDo> ToDoMap;
+		ToDoMap on_press;
+		ToDoMap on_release;
+	};
+
+	typedef std::map<ButtonID,Button> ButtonMap;
+
+	ButtonMap buttons;
+	Button& get_button (ButtonID) const;
+
+	std::set<ButtonID> buttons_down;
+	std::set<ButtonID> consumed;
+
+	void all_lights_out ();
+	void close ();
+	void start_midi_handling ();
+	void stop_midi_handling ();
+
+	PBD::ScopedConnectionList session_connections;
+	void connect_session_signals ();
+	void map_recenable_state ();
+	void map_transport_state ();
+
+	sigc::connection periodic_connection;
+	bool periodic ();
+
+	sigc::connection heartbeat_connection;
+	sigc::connection blink_connection;
+	typedef std::list<ButtonID> Blinkers;
+	Blinkers blinkers;
+	bool blink_state;
+	bool blink ();
+	bool beat ();
+	void start_blinking (ButtonID);
+	void stop_blinking (ButtonID);
+
+	void set_current_stripable (boost::shared_ptr<ARDOUR::Stripable>);
+	void drop_current_stripable ();
+	void use_master ();
+	void use_monitor ();
+	void gui_track_selection_changed (ARDOUR::StripableNotificationListPtr);
+	PBD::ScopedConnection selection_connection;
+	PBD::ScopedConnectionList stripable_connections;
+
+	void map_stripable_state ();
+	void map_solo ();
+	void map_mute ();
+	bool rec_enable_state;
+	void map_recenable ();
+	void map_gain ();
+	void map_cut ();
+	void map_auto ();
+
+	/* operations (defined in operations.cc) */
+
+	void read ();
+	void write ();
+
+	void input_monitor ();
+	void left ();
+	void right ();
+
+	void touch ();
+	void off ();
+
+	void undo ();
+	void redo ();
+	void solo ();
+	void mute ();
+	void jog ();
+	void rec_enable ();
+
+	void ardour_pan_azimuth (float);
+	void ardour_pan_width (float);
+	void mixbus_pan (float);
+
+	void punch ();
+};
+
+}
+
+#endif /* ardour_surface_cc121_h */
diff --git a/libs/surfaces/cc121/cc121_interface.cc b/libs/surfaces/cc121/cc121_interface.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7483b39d3593202660a3d15b75b4cde10f542db2
--- /dev/null
+++ b/libs/surfaces/cc121/cc121_interface.cc
@@ -0,0 +1,80 @@
+/*
+    Copyright (C) 2012 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <pbd/failed_constructor.h>
+
+#include "control_protocol/control_protocol.h"
+#include "cc121.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, Session* s)
+{
+	CC121* fp;
+
+	try {
+		fp =  new CC121 (*s);
+	} catch (failed_constructor& err) {
+		return 0;
+	}
+
+	if (fp->set_active (true)) {
+		delete fp;
+		return 0;
+	}
+
+	return fp;
+}
+
+static void
+delete_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, ControlProtocol* cp)
+{
+	delete cp;
+}
+
+static bool
+probe_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/)
+{
+	return CC121::probe ();
+}
+
+static void*
+cc121_request_buffer_factory (uint32_t num_requests)
+{
+	return CC121::request_factory (num_requests);
+}
+
+static ControlProtocolDescriptor cc121_midi_descriptor = {
+	/*name :              */   "Steinberg CC121",
+	/*id :                */   "uri://ardour.org/surfaces/cc121:0",
+	/*ptr :               */   0,
+	/*module :            */   0,
+	/*mandatory :         */   0,
+	/*supports_feedback : */   true,
+	/*probe :             */   probe_cc121_midi_protocol,
+	/*initialize :        */   new_cc121_midi_protocol,
+	/*destroy :           */   delete_cc121_midi_protocol,
+	/*request_buffer_factory */ cc121_request_buffer_factory
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &cc121_midi_descriptor; }
+
diff --git a/libs/surfaces/cc121/gui.cc b/libs/surfaces/cc121/gui.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ca06887102c1050d03c92da42bfc139dd56ce027
--- /dev/null
+++ b/libs/surfaces/cc121/gui.cc
@@ -0,0 +1,629 @@
+/*
+    Copyright (C) 2015 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm/alignment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/liststore.h>
+
+#include "pbd/unwind.h"
+#include "pbd/strsplit.h"
+#include "pbd/file_utils.h"
+
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filesystem_paths.h"
+
+#include "cc121.h"
+#include "gui.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+
+void*
+CC121::get_gui () const
+{
+	if (!gui) {
+		const_cast<CC121*>(this)->build_gui ();
+	}
+	static_cast<Gtk::VBox*>(gui)->show_all();
+	return gui;
+}
+
+void
+CC121::tear_down_gui ()
+{
+	if (gui) {
+		Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
+		if (w) {
+			w->hide();
+			delete w;
+		}
+	}
+	delete static_cast<CC121GUI*> (gui);
+	gui = 0;
+}
+
+void
+CC121::build_gui ()
+{
+	gui = (void*) new CC121GUI (*this);
+}
+
+/*--------------------*/
+
+CC121GUI::CC121GUI (CC121& p)
+	: fp (p)
+	, table (2, 5)
+	, action_table (5, 4)
+	, ignore_active_change (false)
+{
+	set_border_width (12);
+
+	table.set_row_spacings (4);
+	table.set_col_spacings (6);
+	table.set_border_width (12);
+	table.set_homogeneous (false);
+
+	std::string data_file_path;
+	string name = "cc121.png";
+	Searchpath spath(ARDOUR::ardour_data_search_path());
+	spath.add_subdirectory_to_paths ("icons");
+	find_file (spath, name, data_file_path);
+	if (!data_file_path.empty()) {
+		image.set (data_file_path);
+		hpacker.pack_start (image, false, false);
+	}
+
+	Gtk::Label* l;
+	Gtk::Alignment* align;
+	int row = 0;
+	int action_row = 1;
+
+	input_combo.pack_start (midi_port_columns.short_name);
+	output_combo.pack_start (midi_port_columns.short_name);
+
+	input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &input_combo, true));
+	output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &output_combo, false));
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
+	l->set_alignment (1.0, 0.5);
+	table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+	table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+	row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
+	l->set_alignment (1.0, 0.5);
+	table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+	table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+	row++;
+
+	build_available_action_menu ();
+
+	build_user_action_combo (function1_combo, CC121::ButtonState(0), CC121::Function1);
+	build_user_action_combo (function2_combo, CC121::ButtonState(0), CC121::Function2);
+	build_user_action_combo (function3_combo, CC121::ButtonState(0), CC121::Function3);
+	build_user_action_combo (function4_combo, CC121::ButtonState(0), CC121::Function4);
+	build_user_action_combo (value_combo, CC121::ButtonState(0), CC121::Value);
+	build_user_action_combo (lock_combo, CC121::ButtonState(0), CC121::Lock);
+	build_user_action_combo (eq1_combo, CC121::ButtonState(0), CC121::EQ1Enable);
+	build_user_action_combo (eq2_combo, CC121::ButtonState(0), CC121::EQ2Enable);
+	build_user_action_combo (eq3_combo, CC121::ButtonState(0), CC121::EQ3Enable);
+	build_user_action_combo (eq4_combo, CC121::ButtonState(0), CC121::EQ4Enable);
+	build_user_action_combo (eqtype_combo, CC121::ButtonState(0), CC121::EQType);
+	build_user_action_combo (allbypass_combo, CC121::ButtonState(0), CC121::AllBypass);
+	build_foot_action_combo (foot_combo, CC121::ButtonState(0));
+	action_table.set_row_spacings (4);
+	action_table.set_col_spacings (6);
+	action_table.set_border_width (12);
+	action_table.set_homogeneous (false);
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 1")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (function1_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 2")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (function2_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 3")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (function3_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 4")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (function4_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Value")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (value_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Lock")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (lock_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ1")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (eq1_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ2")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (eq2_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ3")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (eq3_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ4")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (eq4_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQType")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (eqtype_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("AllBypass")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (allbypass_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	l = manage (new Gtk::Label);
+	l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Footswitch")));
+	l->set_alignment (1.0, 0.5);
+	action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	align = manage (new Alignment);
+	align->set (0.0, 0.5);
+	align->add (foot_combo);
+	action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	action_row++;
+
+	table.attach (action_table, 0, 5, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+	row++;
+
+	hpacker.pack_start (table, true, true);
+	pack_start (hpacker, false, false);
+
+	/* update the port connection combos */
+
+	update_port_combos ();
+
+	/* catch future changes to connection state */
+
+	fp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&CC121GUI::connection_handler, this), gui_context());
+}
+
+CC121GUI::~CC121GUI ()
+{
+}
+
+void
+CC121GUI::connection_handler ()
+{
+	/* ignore all changes to combobox active strings here, because we're
+	   updating them to match a new ("external") reality - we were called
+	   because port connections have changed.
+	*/
+
+	PBD::Unwinder<bool> ici (ignore_active_change, true);
+
+	update_port_combos ();
+}
+
+void
+CC121GUI::update_port_combos ()
+{
+	vector<string> midi_inputs;
+	vector<string> midi_outputs;
+
+	ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+	ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+	Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+	Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+	bool input_found = false;
+	bool output_found = false;
+	int n;
+
+	input_combo.set_model (input);
+	output_combo.set_model (output);
+
+	Gtk::TreeModel::Children children = input->children();
+	Gtk::TreeModel::Children::iterator i;
+	i = children.begin();
+	++i; /* skip "Disconnected" */
+
+
+	for (n = 1;  i != children.end(); ++i, ++n) {
+		string port_name = (*i)[midi_port_columns.full_name];
+		if (fp.input_port()->connected_to (port_name)) {
+			input_combo.set_active (n);
+			input_found = true;
+			break;
+		}
+	}
+
+	if (!input_found) {
+		input_combo.set_active (0); /* disconnected */
+	}
+
+	children = output->children();
+	i = children.begin();
+	++i; /* skip "Disconnected" */
+
+	for (n = 1;  i != children.end(); ++i, ++n) {
+		string port_name = (*i)[midi_port_columns.full_name];
+		if (fp.output_port()->connected_to (port_name)) {
+			output_combo.set_active (n);
+			output_found = true;
+			break;
+		}
+	}
+
+	if (!output_found) {
+		output_combo.set_active (0); /* disconnected */
+	}
+}
+
+void
+CC121GUI::build_available_action_menu ()
+{
+	/* build a model of all available actions (needs to be tree structured
+	 * more)
+	 */
+
+	available_action_model = TreeStore::create (action_columns);
+
+	vector<string> paths;
+	vector<string> labels;
+	vector<string> tooltips;
+	vector<string> keys;
+	vector<Glib::RefPtr<Gtk::Action> > actions;
+
+	Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
+
+	typedef std::map<string,TreeIter> NodeMap;
+	NodeMap nodes;
+	NodeMap::iterator r;
+
+
+	vector<string>::iterator k;
+	vector<string>::iterator p;
+	vector<string>::iterator t;
+	vector<string>::iterator l;
+
+	available_action_model->clear ();
+
+	TreeIter rowp;
+	TreeModel::Row parent;
+
+	/* Disabled item (row 0) */
+
+	rowp = available_action_model->append();
+	parent = *(rowp);
+	parent[action_columns.name] = _("Disabled");
+
+	for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
+
+		TreeModel::Row row;
+		vector<string> parts;
+
+		parts.clear ();
+
+		split (*p, parts, '/');
+
+		if (parts.empty()) {
+			continue;
+		}
+
+		//kinda kludgy way to avoid displaying menu items as mappable
+		if ( parts[1] == _("Main_menu") )
+			continue;
+		if ( parts[1] == _("JACK") )
+			continue;
+		if ( parts[1] == _("redirectmenu") )
+			continue;
+		if ( parts[1] == _("Editor_menus") )
+			continue;
+		if ( parts[1] == _("RegionList") )
+			continue;
+		if ( parts[1] == _("ProcessorMenu") )
+			continue;
+
+		if ((r = nodes.find (parts[1])) == nodes.end()) {
+
+			/* top level is missing */
+
+			TreeIter rowp;
+			TreeModel::Row parent;
+			rowp = available_action_model->append();
+			nodes[parts[1]] = rowp;
+			parent = *(rowp);
+			parent[action_columns.name] = parts[1];
+
+			row = *(available_action_model->append (parent.children()));
+
+		} else {
+
+			row = *(available_action_model->append ((*r->second)->children()));
+
+		}
+
+		/* add this action */
+
+		if (l->empty ()) {
+			row[action_columns.name] = *t;
+			action_map[*t] = *p;
+		} else {
+			row[action_columns.name] = *l;
+			action_map[*l] = *p;
+		}
+
+		string path = (*p);
+		/* ControlProtocol::access_action() is not interested in the
+		   legacy "<Actions>/" prefix part of a path.
+		*/
+		path = path.substr (strlen ("<Actions>/"));
+
+		row[action_columns.path] = path;
+	}
+}
+
+void
+CC121GUI::action_changed (Gtk::ComboBox* cb, CC121::ButtonID id, CC121::ButtonState bs)
+{
+	TreeModel::const_iterator row = cb->get_active ();
+	string action_path = (*row)[action_columns.path];
+
+	/* release binding */
+	fp.set_action (id, action_path, false, bs);
+}
+
+void
+CC121GUI::build_action_combo (Gtk::ComboBox& cb, vector<pair<string,string> > const & actions, CC121::ButtonID id, CC121::ButtonState bs)
+{
+	Glib::RefPtr<Gtk::ListStore> model (Gtk::ListStore::create (action_columns));
+	TreeIter rowp;
+	TreeModel::Row row;
+	string current_action = fp.get_action (id, false, bs); /* lookup release action */
+	int active_row = -1;
+	int n;
+	vector<pair<string,string> >::const_iterator i;
+
+	rowp = model->append();
+	row = *(rowp);
+	row[action_columns.name] = _("Disabled");
+	row[action_columns.path] = string();
+
+	if (current_action.empty()) {
+		active_row = 0;
+	}
+
+	for (i = actions.begin(), n = 0; i != actions.end(); ++i, ++n) {
+		rowp = model->append();
+		row = *(rowp);
+		row[action_columns.name] = i->first;
+		row[action_columns.path] = i->second;
+		if (current_action == i->second) {
+			active_row = n+1;
+		}
+	}
+
+	cb.set_model (model);
+	cb.pack_start (action_columns.name);
+
+	if (active_row >= 0) {
+		cb.set_active (active_row);
+	}
+
+	cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
+}
+
+void
+CC121GUI::build_foot_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs)
+{
+	vector<pair<string,string> > actions;
+
+	actions.push_back (make_pair (string("Toggle Roll"), string(X_("Transport/ToggleRoll"))));
+	actions.push_back (make_pair (string("Toggle Rec-Enable"), string(X_("Transport/Record"))));
+	actions.push_back (make_pair (string("Toggle Roll+Rec"), string(X_("Transport/record-roll"))));
+	actions.push_back (make_pair (string("Toggle Loop"), string(X_("Transport/Loop"))));
+	actions.push_back (make_pair (string("Toggle Click"), string(X_("Transport/ToggleClick"))));
+
+	build_action_combo (cb, actions, CC121::Footswitch, bs);
+}
+
+bool
+CC121GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
+{
+	TreeModel::Row row = *iter;
+	string path = row[action_columns.path];
+
+	if (path == action_path) {
+		*found = iter;
+		return true;
+	}
+
+	return false;
+}
+
+void
+CC121GUI::build_user_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs, CC121::ButtonID id)
+{
+	cb.set_model (available_action_model);
+	cb.pack_start (action_columns.name);
+	cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
+
+	/* set the active "row" to the right value for the current button binding */
+
+	string current_action = fp.get_action (id, false, bs); /* lookup release action */
+
+	if (current_action.empty()) {
+		cb.set_active (0); /* "disabled" */
+		return;
+	}
+
+	TreeModel::iterator iter = available_action_model->children().end();
+
+	available_action_model->foreach_iter (sigc::bind (sigc::mem_fun (*this, &CC121GUI::find_action_in_model), current_action, &iter));
+
+	if (iter != available_action_model->children().end()) {
+		cb.set_active (iter);
+	} else {
+		cb.set_active (0);
+	}
+}
+
+Glib::RefPtr<Gtk::ListStore>
+CC121GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+	Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+	TreeModel::Row row;
+
+	row = *store->append ();
+	row[midi_port_columns.full_name] = string();
+	row[midi_port_columns.short_name] = _("Disconnected");
+
+	for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+		row = *store->append ();
+		row[midi_port_columns.full_name] = *p;
+		std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+		if (pn.empty ()) {
+			pn = (*p).substr ((*p).find (':') + 1);
+		}
+		row[midi_port_columns.short_name] = pn;
+	}
+
+	return store;
+}
+
+void
+CC121GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
+{
+	if (ignore_active_change) {
+		return;
+	}
+
+	TreeModel::iterator active = combo->get_active ();
+	string new_port = (*active)[midi_port_columns.full_name];
+
+	if (new_port.empty()) {
+		if (for_input) {
+			fp.input_port()->disconnect_all ();
+		} else {
+			fp.output_port()->disconnect_all ();
+		}
+
+		return;
+	}
+
+	if (for_input) {
+		if (!fp.input_port()->connected_to (new_port)) {
+			fp.input_port()->disconnect_all ();
+			fp.input_port()->connect (new_port);
+		}
+	} else {
+		if (!fp.output_port()->connected_to (new_port)) {
+			fp.output_port()->disconnect_all ();
+			fp.output_port()->connect (new_port);
+		}
+	}
+}
diff --git a/libs/surfaces/cc121/gui.h b/libs/surfaces/cc121/gui.h
new file mode 100644
index 0000000000000000000000000000000000000000..107a81ed3279259ca073ea46efa3c02af9820501
--- /dev/null
+++ b/libs/surfaces/cc121/gui.h
@@ -0,0 +1,116 @@
+/*
+    Copyright (C) 2015 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_cc121_gui_h__
+#define __ardour_cc121_gui_h__
+
+#include <vector>
+#include <string>
+
+#include <gtkmm/box.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/image.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treestore.h>
+
+namespace Gtk {
+	class CellRendererCombo;
+	class ListStore;
+}
+
+#include "cc121.h"
+
+namespace ArdourSurface {
+
+class CC121GUI : public Gtk::VBox
+{
+public:
+	CC121GUI (CC121&);
+	~CC121GUI ();
+
+private:
+	CC121& fp;
+	Gtk::HBox hpacker;
+	Gtk::Table table;
+	Gtk::Table action_table;
+	Gtk::ComboBox input_combo;
+	Gtk::ComboBox output_combo;
+	Gtk::Image    image;
+
+	Gtk::ComboBox foot_combo;
+	Gtk::ComboBox function1_combo;
+	Gtk::ComboBox function2_combo;
+	Gtk::ComboBox function3_combo;
+	Gtk::ComboBox function4_combo;
+	Gtk::ComboBox value_combo;
+	Gtk::ComboBox lock_combo;
+	Gtk::ComboBox eq1_combo;
+	Gtk::ComboBox eq2_combo;
+	Gtk::ComboBox eq3_combo;
+	Gtk::ComboBox eq4_combo;
+	Gtk::ComboBox eqtype_combo;
+	Gtk::ComboBox allbypass_combo;
+
+	void update_port_combos ();
+	PBD::ScopedConnection connection_change_connection;
+	void connection_handler ();
+
+	struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+		MidiPortColumns() {
+			add (short_name);
+			add (full_name);
+		}
+		Gtk::TreeModelColumn<std::string> short_name;
+		Gtk::TreeModelColumn<std::string> full_name;
+	};
+
+	MidiPortColumns midi_port_columns;
+	bool ignore_active_change;
+
+	Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+	void active_port_changed (Gtk::ComboBox*,bool for_input);
+
+	struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
+		ActionColumns() {
+			add (name);
+			add (path);
+		}
+		Gtk::TreeModelColumn<std::string> name;
+		Gtk::TreeModelColumn<std::string> path;
+	};
+
+	ActionColumns action_columns;
+	Glib::RefPtr<Gtk::TreeStore> available_action_model;
+	std::map<std::string,std::string> action_map; // map from action names to paths
+
+	void build_action_combo (Gtk::ComboBox& cb, std::vector<std::pair<std::string,std::string> > const & actions, CC121::ButtonID, CC121::ButtonState);
+	void build_user_action_combo (Gtk::ComboBox&, CC121::ButtonState, CC121::ButtonID);
+	void build_foot_action_combo (Gtk::ComboBox&, CC121::ButtonState);
+
+	void build_available_action_menu ();
+	void action_changed (Gtk::ComboBox*, CC121::ButtonID, CC121::ButtonState);
+
+	bool find_action_in_model (const Gtk::TreeModel::iterator& iter, std::string const & action_path, Gtk::TreeModel::iterator* found);
+
+};
+
+}
+
+#endif /* __ardour_cc121_gui_h__ */
diff --git a/libs/surfaces/cc121/operations.cc b/libs/surfaces/cc121/operations.cc
new file mode 100644
index 0000000000000000000000000000000000000000..67aafda777d38d87ee85126ecc51cffcd4ede0ac
--- /dev/null
+++ b/libs/surfaces/cc121/operations.cc
@@ -0,0 +1,340 @@
+/*
+    Copyright (C) 2015 Paul Davis
+    Copyright (C) 2016 W.P. van Paassen
+
+    Thanks to Rolf Meyerhoff for reverse engineering the CC121 protocol.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "ardour/async_midi_port.h"
+#include "ardour/monitor_processor.h"
+#include "ardour/monitor_control.h"
+#include "ardour/pannable.h"
+#include "ardour/plugin_insert.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/record_enable_control.h"
+#include "ardour/session.h"
+#include "ardour/track.h"
+#include "ardour/types.h"
+
+#include "cc121.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace PBD;
+
+/* this value is chosen to given smooth motion from 0..1.0 in about 270 degrees
+ * of encoder rotation.
+ */
+static const double encoder_divider = 24.0;
+
+void
+CC121::input_monitor ()
+{
+	if (_current_stripable) {
+	  MonitorChoice choice = _current_stripable->monitoring_control()->monitoring_choice ();
+	  switch(choice) {
+	  case MonitorAuto:
+	    _current_stripable->monitoring_control()->set_value (MonitorInput, PBD::Controllable::NoGroup);
+	    get_button(InputMonitor).set_led_state (_output_port, true);
+	    break;
+	  case MonitorInput:
+	    _current_stripable->monitoring_control()->set_value (MonitorDisk, PBD::Controllable::NoGroup);
+	    get_button(InputMonitor).set_led_state (_output_port, false);
+	    break;
+	  case MonitorDisk:
+	    _current_stripable->monitoring_control()->set_value (MonitorCue, PBD::Controllable::NoGroup);
+	    get_button(InputMonitor).set_led_state (_output_port, false);
+	    break;
+	  case MonitorCue:
+	    _current_stripable->monitoring_control()->set_value (MonitorInput, PBD::Controllable::NoGroup);
+	    get_button(InputMonitor).set_led_state (_output_port, true);
+	    break;
+	  default:
+	    break;
+	  }
+	}
+}
+
+void
+CC121::left ()
+{
+	access_action ("Editor/select-prev-route");
+
+	//ToDo:  bank by 8?
+	//if ( (button_state & ShiftDown) == ShiftDown )
+
+}
+
+void
+CC121::right ()
+{
+	access_action ("Editor/select-next-route");
+
+	//ToDo:  bank by 8?
+	//if ( (button_state & ShiftDown) == ShiftDown )
+}
+
+
+void
+CC121::read ()
+{
+	if (_current_stripable) {
+		boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+		if (gain) {
+			gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Play );
+		}
+	}
+}
+
+void
+CC121::write ()
+{
+	if (_current_stripable) {
+		boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+		if (gain) {
+			gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Write );
+		}
+	}
+}
+
+void
+CC121::touch ()
+{
+	if (_current_stripable) {
+		boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+		if (gain) {
+			gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Touch );
+		}
+	}
+}
+
+void
+CC121::off ()
+{
+	if (_current_stripable) {
+		boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+		if (gain) {
+			gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Off );
+		}
+	}
+}
+
+
+
+
+void
+CC121::undo ()
+{
+	ControlProtocol::Undo (); /* EMIT SIGNAL */
+}
+
+void
+CC121::redo ()
+{
+	ControlProtocol::Redo (); /* EMIT SIGNAL */
+}
+
+void
+CC121::jog()
+{
+        if (_jogmode == scroll) {
+             _jogmode = zoom;
+        }
+        else {
+             _jogmode = scroll;
+        }
+        get_button (Jog).set_led_state (_output_port, _jogmode == scroll);
+}
+
+void
+CC121::mute ()
+{
+	if (!_current_stripable) {
+		return;
+	}
+
+	if (_current_stripable == session->monitor_out()) {
+		boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control();
+		mp->set_cut_all (!mp->cut_all());
+		return;
+	}
+
+	_current_stripable->mute_control()->set_value (!_current_stripable->mute_control()->muted(), PBD::Controllable::UseGroup);
+}
+
+void
+CC121::solo ()
+{
+	if (!_current_stripable) {
+		return;
+	}
+	_current_stripable->solo_control()->set_value (!_current_stripable->solo_control()->soloed(), PBD::Controllable::UseGroup);
+}
+
+void
+CC121::rec_enable ()
+{
+	if (!_current_stripable) {
+		return;
+	}
+
+	boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(_current_stripable);
+
+	if (!t) {
+		return;
+	}
+
+	t->rec_enable_control()->set_value (!t->rec_enable_control()->get_value(), Controllable::UseGroup);
+}
+
+void
+CC121::use_master ()
+{
+	boost::shared_ptr<Stripable> r = session->master_out();
+	if (r) {
+		if (_current_stripable == r) {
+			r = pre_master_stripable.lock();
+			set_current_stripable (r);
+			get_button(Output).set_led_state (_output_port, false);
+			blinkers.remove (Output);
+		} else {
+			if (_current_stripable != session->master_out() && _current_stripable != session->monitor_out()) {
+				pre_master_stripable = boost::weak_ptr<Stripable> (_current_stripable);
+			}
+			set_current_stripable (r);
+			get_button(Output).set_led_state (_output_port, true);
+			blinkers.remove (Output);
+		}
+	}
+}
+
+void
+CC121::use_monitor ()
+{
+	boost::shared_ptr<Stripable> r = session->monitor_out();
+
+	if (r) {
+		if (_current_stripable == r) {
+			r = pre_monitor_stripable.lock();
+			set_current_stripable (r);
+			get_button(Output).set_led_state (_output_port, false);
+			blinkers.remove (Output);
+		} else {
+			if (_current_stripable != session->master_out() && _current_stripable != session->monitor_out()) {
+				pre_monitor_stripable = boost::weak_ptr<Stripable> (_current_stripable);
+			}
+			set_current_stripable (r);
+			get_button(Output).set_led_state (_output_port, true);
+			blinkers.push_back (Output);
+		}
+	}
+}
+
+void
+CC121::ardour_pan_azimuth (float delta)
+{
+	if (!_current_stripable) {
+		return;
+	}
+
+	boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+	if (!r) {
+		return;
+	}
+
+	boost::shared_ptr<Pannable> pannable = r->pannable ();
+
+	if (!pannable) {
+		return;
+	}
+
+	boost::shared_ptr<AutomationControl> azimuth = pannable->pan_azimuth_control;
+
+	if (!azimuth) {
+		return;
+	}
+
+	azimuth->set_value (azimuth->interface_to_internal (azimuth->internal_to_interface (azimuth->get_value()) + (delta)), Controllable::NoGroup);
+}
+
+
+void
+CC121::ardour_pan_width(float delta)
+{
+	if (!_current_stripable) {
+		return;
+	}
+
+	boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+	if (!r) {
+		return;
+	}
+
+	boost::shared_ptr<Pannable> pannable = r->pannable ();
+
+	if (!pannable) {
+		return;
+	}
+
+	boost::shared_ptr<AutomationControl> width = pannable->pan_width_control;
+
+	if (!width) {
+		return;
+	}
+
+	width->set_value (width->interface_to_internal (width->internal_to_interface (width->get_value()) + (delta)), Controllable::NoGroup);
+}
+
+void
+CC121::mixbus_pan (float delta)
+{
+#ifdef MIXBUS
+	if (!_current_stripable) {
+		return;
+	}
+	boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+	if (!r) {
+		return;
+	}
+
+
+	const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h
+	boost::shared_ptr<ARDOUR::PluginInsert> plug = r->ch_post();
+
+	if (!plug) {
+		return;
+	}
+
+	boost::shared_ptr<AutomationControl> azimuth = boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan)));
+
+	if (!azimuth) {
+		return;
+	}
+
+	azimuth->set_value (azimuth->interface_to_internal (azimuth->internal_to_interface (azimuth->get_value()) + (delta)), Controllable::NoGroup);
+#endif
+}
+
+void
+CC121::punch ()
+{
+	access_action ("Transport/TogglePunch");
+}
diff --git a/libs/surfaces/cc121/wscript b/libs/surfaces/cc121/wscript
new file mode 100644
index 0000000000000000000000000000000000000000..478834aa3a2ea766f47e08b86fed8ab7921234d3
--- /dev/null
+++ b/libs/surfaces/cc121/wscript
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import os
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+    autowaf.set_options(opt)
+
+def configure(conf):
+    autowaf.configure(conf)
+
+def build(bld):
+    obj = bld(features = 'cxx cxxshlib')
+    obj.source = '''
+            cc121.cc
+            gui.cc
+            cc121_interface.cc
+            operations.cc
+    '''
+    obj.export_includes = ['.']
+    obj.defines      = [ 'PACKAGE="ardour_cc121"' ]
+    obj.defines     += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+    obj.includes     = [ '.', './cc121']
+    obj.name         = 'libardour_cc121'
+    obj.target       = 'ardour_cc121'
+    obj.uselib       = 'GTKMM GTK GDK XML'
+    obj.use          = 'libardour libardour_cp libgtkmm2ext libpbd'
+    obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+    autowaf.shutdown()
diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript
index 0c736733a12da7c8602752215b349128af6a6eb2..7ab04ef9f8fc82c91390117dd722d83842a902a6 100644
--- a/libs/surfaces/wscript
+++ b/libs/surfaces/wscript
@@ -22,6 +22,7 @@ out = 'build'
 children = [
         'control_protocol',
         'faderport',
+        'cc121',
         'generic_midi',
         'mackie',
 ]
@@ -74,6 +75,7 @@ def build(bld):
     bld.recurse('control_protocol')
     bld.recurse('generic_midi')
     bld.recurse('faderport')
+    bld.recurse('cc121')
     bld.recurse('mackie')
 
     if bld.is_defined ('HAVE_LO'):
