From 2b0b65a13090069d8a71c9d2cabf10471857404c Mon Sep 17 00:00:00 2001 From: =?utf8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Thu, 25 Nov 2010 15:09:54 +0100 Subject: [PATCH] Imported Upstream version 1.0.5.patch2 --- CHANGESLOG.txt | 40 + INSTALL.txt | 69 ++ TODO.txt | 19 + documentation/documentation.cpp | 124 ++ documentation/html_footer.html | 19 + documentation/img/jack_main_settings.jpg | Bin 0 -> 113741 bytes documentation/img/jack_routing.png | Bin 0 -> 57002 bytes documentation/img/qjackctl.png | Bin 0 -> 21890 bytes faust-src/Makefile | 99 ++ faust-src/Makefile.compile | 15 + faust-src/Makefile.ladspacompile | 22 + faust-src/Makefile.mspcompile | 44 + faust-src/Makefile.pdcompile | 45 + faust-src/Makefile.qcompile | 17 + faust-src/Makefile.qtcompile | 49 + faust-src/Makefile.sccompile | 41 + faust-src/Makefile.svg | 12 + faust-src/Makefile.vstcompile | 31 + faust-src/minimal.cpp | 97 ++ faust-src/net-ks.dsp | 29 + jacktrip_doxygen | 1418 ++++++++++++++++++++++ src/DataProtocol.cpp | 61 + src/DataProtocol.h | 191 +++ src/DataProtocolPOSIX.cpp.tmp | 222 ++++ src/DataProtocolPOSIX.h.tmp | 213 ++++ src/JackAudioInterface.cpp | 675 ++++++++++ src/JackAudioInterface.h | 305 +++++ src/JackTrip.cpp | 466 +++++++ src/JackTrip.h | 313 +++++ src/JackTripThread.cpp | 102 ++ src/JackTripThread.h | 64 + src/JackTripWorker.cpp | 178 +++ src/JackTripWorker.h | 122 ++ src/JackTripWorkerMessages.h | 75 ++ src/LoopBack.cpp | 54 + src/LoopBack.h | 70 ++ src/NetKS.h | 137 +++ src/PacketHeader.cpp | 265 ++++ src/PacketHeader.h | 292 +++++ src/ProcessPlugin.cpp | 31 + src/ProcessPlugin.h | 81 ++ src/RingBuffer.cpp | 250 ++++ src/RingBuffer.h | 142 +++ src/RingBufferWavetable.h | 71 ++ src/Settings.cpp | 376 ++++++ src/Settings.h | 84 ++ src/TestRingBuffer.h | 49 + src/ThreadPoolTest.h | 77 ++ src/UdpDataProtocol.cpp | 505 ++++++++ src/UdpDataProtocol.h | 197 +++ src/UdpDataProtocolPOSIX.cpp.tmp | 145 +++ src/UdpDataProtocolPOSIX.h.tmp | 92 ++ src/UdpMasterListener.cpp | 186 +++ src/UdpMasterListener.h | 123 ++ src/build | 25 + src/jacktrip-1.0.5.diff | 26 + src/jacktrip.pro | 68 ++ src/jacktrip_globals.cpp | 196 +++ src/jacktrip_globals.h | 126 ++ src/jacktrip_main.cpp | 86 ++ src/jacktrip_tests.cpp | 103 ++ src/jacktrip_types.h | 85 ++ src/tests.cpp | 122 ++ 63 files changed, 9241 insertions(+) create mode 100644 CHANGESLOG.txt create mode 100644 INSTALL.txt create mode 100644 TODO.txt create mode 100644 documentation/documentation.cpp create mode 100644 documentation/html_footer.html create mode 100644 documentation/img/jack_main_settings.jpg create mode 100644 documentation/img/jack_routing.png create mode 100644 documentation/img/qjackctl.png create mode 100644 faust-src/Makefile create mode 100644 faust-src/Makefile.compile create mode 100644 faust-src/Makefile.ladspacompile create mode 100644 faust-src/Makefile.mspcompile create mode 100644 faust-src/Makefile.pdcompile create mode 100644 faust-src/Makefile.qcompile create mode 100644 faust-src/Makefile.qtcompile create mode 100644 faust-src/Makefile.sccompile create mode 100644 faust-src/Makefile.svg create mode 100644 faust-src/Makefile.vstcompile create mode 100644 faust-src/minimal.cpp create mode 100644 faust-src/net-ks.dsp create mode 100644 jacktrip_doxygen create mode 100644 src/DataProtocol.cpp create mode 100644 src/DataProtocol.h create mode 100644 src/DataProtocolPOSIX.cpp.tmp create mode 100644 src/DataProtocolPOSIX.h.tmp create mode 100644 src/JackAudioInterface.cpp create mode 100644 src/JackAudioInterface.h create mode 100644 src/JackTrip.cpp create mode 100644 src/JackTrip.h create mode 100644 src/JackTripThread.cpp create mode 100644 src/JackTripThread.h create mode 100644 src/JackTripWorker.cpp create mode 100644 src/JackTripWorker.h create mode 100644 src/JackTripWorkerMessages.h create mode 100644 src/LoopBack.cpp create mode 100644 src/LoopBack.h create mode 100644 src/NetKS.h create mode 100644 src/PacketHeader.cpp create mode 100644 src/PacketHeader.h create mode 100644 src/ProcessPlugin.cpp create mode 100644 src/ProcessPlugin.h create mode 100644 src/RingBuffer.cpp create mode 100644 src/RingBuffer.h create mode 100644 src/RingBufferWavetable.h create mode 100644 src/Settings.cpp create mode 100644 src/Settings.h create mode 100644 src/TestRingBuffer.h create mode 100644 src/ThreadPoolTest.h create mode 100644 src/UdpDataProtocol.cpp create mode 100644 src/UdpDataProtocol.h create mode 100644 src/UdpDataProtocolPOSIX.cpp.tmp create mode 100644 src/UdpDataProtocolPOSIX.h.tmp create mode 100644 src/UdpMasterListener.cpp create mode 100644 src/UdpMasterListener.h create mode 100755 src/build create mode 100644 src/jacktrip-1.0.5.diff create mode 100644 src/jacktrip.pro create mode 100644 src/jacktrip_globals.cpp create mode 100644 src/jacktrip_globals.h create mode 100644 src/jacktrip_main.cpp create mode 100644 src/jacktrip_tests.cpp create mode 100644 src/jacktrip_types.h create mode 100644 src/tests.cpp diff --git a/CHANGESLOG.txt b/CHANGESLOG.txt new file mode 100644 index 0000000..1849861 --- /dev/null +++ b/CHANGESLOG.txt @@ -0,0 +1,40 @@ +--- +1.0.5 +- (added) Compatibility with JamLink boxes (restricted at the moment to 48KHz, 64 buffer size and 1 channel) +- (added) New port structure that allows the communication between a public server and a local client +- (added) Option for packets without header +- (added) Option to change default client name +- (fixed) General optimizations and code cleanup +- (added) Improved, now cross-platform build script + +--- +1.0.4 +- (fixed) Buss error caused when no physical inputs or outputs ports are available + +--- +1.0.3 +- (added) Redundancy Algorithm for UDP Packets to to avoid glitches with packet losses +- (fixed) Now compiles on 64bits machines +- (fixed) Improved exceptions handling +- (added) Basic Karplus-Strong model added as Plug-in +- (added) Some functionality reimplemented using signals and slots for + more flexibility +- (added) Multiple-Client-Server in alpha testing, expect it working in the next release + +--- +1.0.2 Alpha +- (added) Port offset mode, to use a different UDP port than the default one. +- (fixed) Improved thread behavior + +--- +1.0.1 Alpha +- (added) jamlink mode to connect with jamlink boxes +- (fixed) thread priority in both Linux and Mac OS X (still need some work on the Mac OS X version) +- (fixed) Bug that was causing plug-ins not to behave correctly +- (added) Loopback mode +- (added) Underrun Modes: Wavetable (default) and set to zeros +- (added) Check for peer audio settings, program exists if they don't match +- (added) Automatically connect ports to available physical audio interface. + +--- +1.0 Alpha - initial release diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..cc95c0f --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,69 @@ +Jacktrip : Build Instructions for Linux and MacOS X + +JackTrip: A System for High-Quality Audio Network Performance over the Internet. + +--- +MacOS X (10.3.9 or higher) Installation: + +If you are installing on MacOS X, a universal binary is provided. You just need to have JackOSX installed on your machine +http://www.jackosx.com/ + +To install (using Terminal): go to bin/ directory and type: + + sudo cp jacktrip /usr/bin/ + (enter your password when prompted) + + sudo chmod 755 /usr/bin/jacktrip + (now you can run jacktrip from any directory using Terminal) + + +--- +Dependencies: + +You need to have installed the libraries in your system: +qt4-devel +jack-audio-connection-kit-devel + +If you are using yum (in Fedora 8 or later) you can just install them (as root) with: + yum install qt4-devel jack-audio-connection-kit-devel + +If you want to build on MacOS X, you need JackOSX +http://www.jackosx.com/ +and Qt 4.5 or higher. +http://trolltech.com/products/qt/ + + +--- +Build: + +If you're on Mac OS X or Fedora Linux and have all the dependencies installed, +you can build by simply going to the /src directory and typing the following: + ./build + + +If the previous script doesn't work on a different Linux flavor, try building +the Makfiles yourself. You'd need qmake (e.g., on Fedora, this command is called +qmake-qt4). Then you can build by: + qmake jacktrip.pro + make release + + +If you want to install install (using Terminal): on the /src directory type: + + sudo cp jacktrip /usr/bin/ + (enter your password when prompted) + + sudo chmod 755 /usr/bin/jacktrip + (now you can run jacktrip from any directory using Terminal) + + +--- +Post Configuration +Detailed instructions at +http://ccrma.stanford.edu/groups/soundwire/software/jacktrip/ + + +--- +Using JackTrip +Detailed instructions at +http://ccrma.stanford.edu/groups/soundwire/software/jacktrip/ diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..9b5e4d7 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,19 @@ +High Priority TODOS: +-------------------- + +- Add redundancy to UDP (DONE) +- Finish header implementation, add run-time check + + +Plug-ins: +--------- +- Extend Plugin structure to include more than 1 plug-in and add the mode for local effect (not loopback) +- add the offset option to process starting from a different channel +- Set the faust compiler to automatically generate plugins +- Add low latency compression www.celt-codec.org + +Protocol: +--------- +- Add TCP clacc +- Add SCTP class +- Maybe add a layer of OSC communication for control messages diff --git a/documentation/documentation.cpp b/documentation/documentation.cpp new file mode 100644 index 0000000..54908c2 --- /dev/null +++ b/documentation/documentation.cpp @@ -0,0 +1,124 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file documentation.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + + +// Main Page Documentation +//----------------------------------------------------------------- +/** +\mainpage JackTrip Documentation + +\section intro_sec About JackTrip + +JackTrip: A System for High-Quality Audio Network Performance +over the Internet. + +JackTrip is a Linux and Mac OS X-based system used for multi-machine network +performance over the Internet. It supports any number of channels +(as many as the computer/network can handle) of bidirectional, high quality, +uncompressed audio signal steaming. + +You can use it between any combination of Linux and Mac OS X +(i.e., one end using Linux can connect to the other using Mac OS X). + +It is currently being developed and actively tested at CCRMA by the SoundWIRE group. + + + +\section install_sec Installation + +Download the latest release: +Download + +Please read the documentation inside the packet to install. + +\subsection install_subsec_osx Mac OS X Requirements + +You'll need: Jack OS X. +The documentation explains how to install it and set it up, and it's highly recommended. + +Jack OS X comes with JackPilot to do the audio routing. +An alternative is qjackctl, +which I find easier to use. You can find the binary here: +qjackctl mac binary + +If you use Leopard, you won't need to configure the UDP ports manually. + +\subsubsection install_linux Linux Requirements + +Please read the documentation inside the packet to compile and install in linux.\n +The older version of JackTrip is documented +here. +Please follow those instructions to configure the firewall under + + + +\section using Using JackTrip + +Type jacktrip in a terminal window to display a help list with all the options. +JackTrip uses Jack as its audio server. You have to make sure that the settings in +Jack are the same in the local and remote machine. + +There are two parameters that you want to tweak: Frames/Period and Sample Rate. +The Lower the Frames/Period, the lower the latency. +The higher the Sampling Rate, the higher the bandwidth requirements. +You have to make sure these settings match in both machines. + +\image html jack_main_settings.jpg + +You also may want to look at the internal buffering +-q, --queue parameter in JackTrip. If your connection is very unstable, with a lot of jitter, +you should increase this number at the expense of a higher latency. + +The audio bit resolution parameter, -b, --bitres, can be use to decrease (or increase) +the bandwidth requirements, at the expense of a lower audio quality. + +A basic connection will have one of the nodes as a server: + +jacktrip -s + +And the other as a client + +jacktrip -c [SERVER-IP-NUMBER] + +You'll see a JackTrip client in Jack. Everything you connect into the send ports +will be transmitted to your peer. You'll receive what your peer sends you on the receive ports. + +\image html jack_routing.png + +*/ + + diff --git a/documentation/html_footer.html b/documentation/html_footer.html new file mode 100644 index 0000000..979f8b8 --- /dev/null +++ b/documentation/html_footer.html @@ -0,0 +1,19 @@ + + +
+
+

 

+ +

Documentation generated by Doxygen $doxygenversion on $datetime

+

+© 2008 by Juan-Pablo Caceres (jcaceres at ccrma dot stanford dot edu) and +Chris Chafe
+SoundWIRE Group at +CCRMA - +Stanford University +
+

+SourceForge.net Logo +

+
+ diff --git a/documentation/img/jack_main_settings.jpg b/documentation/img/jack_main_settings.jpg new file mode 100644 index 0000000000000000000000000000000000000000..822fde714d8b6b2ecb59004b8c4946b7e5d92d91 GIT binary patch literal 113741 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%0DHJBw3?>1LSIBA8w`=os!{tHw*gjni;^~^d03t-u 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

NQWCk9FD%_E2_Zr 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

}n}-bAUj)zB%M7Db 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 L&sR71ZT0;Nav#f` literal 0 HcmV?d00001 diff --git a/documentation/img/jack_routing.png b/documentation/img/jack_routing.png new file mode 100644 index 0000000000000000000000000000000000000000..77e42e24de1786c9fdbd8fd60dfd11d7bb10055e GIT binary patch literal 57002 zcmZU)1yoy2*ES5ri(7GbiWPS+T1fE%#ogVlcyTN4UZhBHO@ZQ8+})kv5|WSme*Wiu z-?jdg$;zBLCnI~$>^;{$*F=3*mB)Nb`W6lj4pUJK&xn4O0AMbvHC>R|L)560U&`^sCs5cA0!Q=FQFM!4# z9A&z_yZz&x2amtcMx96Up^%!k;0+!)Dx-s=u4u`_+tvi|AhEu#VeP~l>Q;}`QvK4FIc zPvfr{Sj&~%5H&lyLzpeGoE44ibcksXdT^P|Y0XdLejTHFx4lV3Pn}Tut+C<>rrCTJ(yhfS8aeZadcZy0?2)KWg<<>&i&s8 z7a;SswEPN(pK_?7)v;f(&e!wRWB|0a<9VY5kY;o6xQ0vsX5O)ZahRkcU9B0BJ-@zm zp0nWC`3MDZ4;;S^>5>o9&q~{)$?ba*BZJONz2(1MZt6Q<&_B<;CA$-gV(4TQ$pp1hIr`^nJ%SVU?rY$S@ZwM5Vzhlpa z;e<)k1U{O;SCxxKuIv>#m@olFDBzN19c*w@8CJlL{x@BltiBX{ow}J7yAYS9H|r2{ z?xRfAw{JzJYaCGA`;2vbK3J4mAaVx`&)zM!?gXSn5&C_ zVntu0Oqty*wNB5#|Lui8))}L$gmV=~@6uqsu`L_MSAN%H z2*)%z?!7nz1}^XcSEtW$P}#cJql7M%4dvQmBa!F)c9?Sd`0g*Hw>n<~@Eo>HH`^T~ zE=N`*xFL{Hl2yJglKkpgRdTT~p4!fD;y%a{-7jxMd~=2$=-!ThpkUt1B{CdzneC6I z`ns5$ZhE_*k(83_@NB%o_#e zgAz#IkL$v!yZcDob`>}}H@#hFU#uV(UxZR@?3P`3d}QiiShwjM&)<&7gk5E-Avo{5 zy>oT@QJil}2U98#AGgj97MFIaGjhK`7q(#vhI|;sOZKV+$@FL{Ghd!x?%A>+h~Y~@ zH%CjMp#afL1b`5s%)%1yHU50yEfQI?z3J30Q{p$72uvBZA7O9|tg?+`DiR#VFJuXS zySNf$knakwlLlu@hKR%H8|=X81ne38Sr&GSTzenPb+>=hMKZDTJQ>XJzGF8U8oiK^ z9z~i(suhkuk!KXAC^s$-}(RoQZo-|&$3ja|~@yE!wDJ~c0 za~J9Ua-u~X@l<{uwAPXdu+juCi~@dtF7oMRd%g)2f2dB=%9OtxnUksOzn@F16$FPo z<2~8VWc7FQZsB_71@U*eIn1(W4!7U!aA6?O`1|u6ndapNu^O$p!uby6s|VsPj_6}| z{pl^(+}w$9vE5kRn1!y^s0ueL%JX4h1TS$C8N}6`fTV+vB~(C>C;7R`TZbzTms?3c z^9-sXV2$%k2#p!ciC*_A-yK3qnKq{XsiQBU%q>kQt+AbO?779M4_n={XDo$$s`6-N_`Vbbos|oY!8m6H|h84?4qnM2Ye`y>-Bgq>- z6$=JsBcG`ArOcc0(V|>7XPqCotKFD5t?x$@ljse%-b8ff;+3HoQj)KH2hfxVbAk%aJj+v@)|6s&#;tZfY~^L*Y(=m z4Dl)tHAj(Cf{19p4D}ZHK-P3eN#+g& z$*0#_jAjrkSqo(uZ}|Ft?;rNs+4195*A`9GUNk|awMNI_j{l%MJRE@+Ma9FW^YUJx zy}CpwHr7AstRX^!wI;lmBLVnAUdQ&r7Fl{nKGQSy!gQZLE;+_-6bD9z(Fe03u2V?3 z&SP?3w#Zw#`s;`^t#cXgoVMt=8UDa*njnWj%C^lH==;%2p#&x7tUqsRT?;a;bRF*M4kj8~m4lCHUD znD@@c%?8lYyP0VJ{fV`Z(8_fZE zWAPJVkgY{R?v@0n3k^9f+-^;q7ucDowNlcK3Jjj@BjV0O$C0jyCQSfm0Bs$+cwPVN zol3ST#u=4oeqD0GOH@|hSZbMvp(jUf2O^dI^xOVw%?|GK(}tt3f79-Kgi6ZW#;J}N zEquLd_Kqy1ID8+Zuv{<7+M6i>G8A)Kgr}=oaf4LSGrW~89Q#{S@;vT`pChm+MjG2r zQ02_0A^oC3;|OC0^&vLcah8j9Cb1oEtSh!MN$s6ueqfHWN&Rs^zJx=X%gsh+L%ZmF znRCDg4Jz{SzlAa+o?{kc`bLCIRrwz15}V0p5&g&cpsrw*jqZuQJyMBP7&Q@<*pDN0 zrN5#eE|*oK%!Yc7UX(9<_Y@7QCVj-dz4U30zoMEz=3r3UMckvkzCZ$q!XfSD4~oQA z+z}|A%1V@CXVT|^3<4{J3Sc0F;fmDQkm{!g_Uj!Si9YU9;6TzIN1F$4=i1*rSN0Ie zE_fhPcbv}J=l5A)kMB+h>`yHi#0ab=S9d&`99P|RUj|aPLlK(;{ISX2%hY$Syz3rj z=j8ZJwIwE!4o>*c(qI}Xpl$g$;Irz!$`vnUNh}}^=H{`SHvteGW<~5jhWiZ~5u6aI zTsJ(ec#VmZ2=h*Jc4!>en)dlWLRBb+}?)LEhA zgP@aNvLoVr(x#s%I-Sqjs^<_B#N84421J#kbgW*80IutQ0G9*HS*xmM-GNggtFZky z&A+k-`*B5bZ=iVoET@xPzf*VQ(9gERl^u(pGgI33|HNx9pwRk|Jsft&_?*r(SwrPW zsC+JcBl2&^84O2ZdXVltGaj{i)R;fHN2V+9uttW9a#d(FQe9XJX0>R4eSPzd=Wwu0 zVUgag;+K~vvers7j*;UGcdk~|(RvF)O()xFrlY+LmS$uyIfpc=bIB32t#GCC$NH6{ znHB_#IXg28Qfb~5F(bW(yl#1S22cQ*y(f~Ec48`Z)0P9OiLo2$5Wj$GU+~P{eqxUS z3%k)I;5>y>Lp6nz*mKXr3E2@5j_r){vg)2Z zZh=k&z%nMcF7j1{`8r+Bk=VpJU1O=yv!Gh3fHDGsRRE6d#=>y(x4|^Z zXaT8T@^DXtSX4}owiB51Kcu?ccaFm-_DkC5H;P^!mB-=EjPT_?aPuT|R*tlj*NH`jv52tdSk`F;Zq(*!b zUUVz*R|0t9I%*)Vd?1GyVIMWbluyRZ~Svu z=f=AHh}SDgNf(sO-*lv@EuSdW38a4*r)*OEcRg**m7PGqA~WGQf+x1tJB>+Oo?F^l znpz+0mn79xn9N#=k4b@LG`*xg?O`o7jKEqNM^d&L2wO*bXFP@TuI2WSvzePn5(O); z8t#n@Kn7Fj^Qot~R;k0FXlGt7J)0Q6Zu!k|)6Ma8_zkZ>Wl;euH;}$iZe$jN(17@` zJ-DxIre0b6y{DtqtCL|20zR<^PD0{Y6Iz z#KEoGoco$-Ii49cwqe${b zvU#+ZMNg|4d?Y_YxO^yH= zFfH#L6h}fHdescx-bkY5i(ex#t;58b?uSd)ow#l~tAt?v313>fbNZG&qQAzG4~iH` zNI7|-b`1(fh^$M0=ZY|q(HYxI=X)=pewi-rev}nl-a|wLl=b+j(O^f^U6v4)a@u(I z_nmw)R6r=W@aPb4)=9soQ8-j*;I6Dv>EOpR=T53!zS-Wq8u=Vw@zmqP3$dgA+vg8; zZRY$c*}aULcUYO4+J0lDeLwEX>piFFOZXQq(^Y9mZvsD&=T5w&5V_pdIqnX>p&4sF zj4%=I9^w~5yH;`?d_q!K3OWF~yr>-`kaKU-Qc~Q%1NdR^8lcF?%5s^$aPJ3W3$_6` zJj8Pg4-Dq2jY!Xo`cP4UB6~P@HbX1}fZ?aMe`?tOp4(pR`LswrK?N1KB1m37Lhe{w zeb3A%M)f_X#e^8S^aK>%I79qb(M0}ZqBxV01(m#LtdTRJYJIN7_FpwLkW}vctosVg*NQ)W#4l5;9R?grb>~>GF zVGq!pktWpYcWl{G8vS$bS}2&SaPCfDj!ut;i$M)Bqz6&;T?|s@5s^xf^cSM&cPzV1-l2edruAm-ew&8HsveCBm2Uf9Sbs)6#g|m(>5+Fqu znd)dFh7^?HPJ2iL<7*BD&php;+@(pse|+5G$JfRv}(L|q*NYI3)Ms9Th!dfyLga(R-* zI;lRloA>y@J(@;6-nHM7n^T2c8w2rIwqdAr_Z}f!w9S?U0>l`?-6FT8B2$S!?|mf4 ze@6t5JFExa{kGWI=1|7kac#jwAk4<-OE{GE@M&s8TR4a@zry80w5B{h9t)6_76`h; zlo|XTuM$D-mvOar%ebX;$ip3)u9Ln^H4oo+`4@aRS4h2Ye(%< zz=JDChBFBOq5SJDCx=)7-1vnHM!oBRhvco{im1vR9PnlWnvI+f++_mOt)iG z7tYZ4ilyC3@pSz%sZHW&a2(@&mUCXG35wIsQC>$$u~CCVBnHKjpYQCq(8-a07(U8z zb~Zd#DF~+R_U(GR6_hp%&@95+U#k`UT~)p2Puu5fo&kJ|U@$YL0>&kx#h~cB)Mzy2 zSd&ww|6&+NgGgzFxqrtR%!P3?#0f04i+eFe?|)*btExBhmqD*%S_H*aV_`L`7<|0# z4;a=;iOZ)ht$H`*uxkymNZr?RnpM61nM=xR9q0L$R7s*-XvdbgfXv(da8bL|U57Ba zM1BI@eQhv)T=}>DsIHlK)Z$^G!V!Xt!W#zmh1F!Hj z!in@O)Az^`HFSzmPdL(>*Hs?^5KTROMZ=4dN7m%VREzIsX61?HgivCGqKB=>a@_Y- zzB+cKmve~Y=~u&Uhp;n(O{{r&lcugf??APWmR<*ZYodFHpA`z4@$@}8T=j%q1T4We_S!-_p4tFSe_BJJq}o$)vJ@wjczLP2{4Pb5V?it4`@ z^!*qGng|eN;-klU-C}c0+l?~(?g(nn{s;ptmr7RE{@}iS$v1EGaEt`M71@L}a&<@G zoUOQ<{^Jv1dBT6TQ?A>&hDMa=c*cXZ+rmAylppP@BCi|=r*}9*r-JLV>CyVQP?1NI zcI{7-<7=|_B04ECT1Z6sI5bF53o|DvulaDeS5ZxAB$_ieHq>>D3`GL7vQu7ZaMhuzTM*;cwWZ+8B zY#y(aVb;;ZM|!54OiA;P>|M_&#w?+VLo^8wE*6V`9~IiU*>S)A%ucZ+xHRVZ~&r+X-&Z$X8p?1uskH zwLP81h2W=^L<=7uOcT~dr7A#eTizom!pEAuSn*p2zi6PmkXBLvK^nJyX1`mesH~OJ zYw&|V`07_|+o>Tm^NtID;9kC8vR9Ir)-jfG#QLAO%R;9;aU7X!age-(G%E0!QYjo> z&hnj-BiU}J|E}km+H#Y;V|GH$!h~pAN=#CwOgPq3BstzrcA6D2a(sNGnzk0YCt<&d zJa8pZ6KSc8c7=exBig>DP>@^!OT7kI|GK={{iad5vuM<>swK_3Ci@ zSfAitwcFvm7hLMg0RCI#qnm32()~`!C4r02ojzvMPo<54tAPv5&Hk5g3*s=oel3rO z7fR1`C##-V8@6>r<-iTK?^!wzzFVB$p;D|dX_tpDMjtk>Qmm`8Br2zRicZ=ei%lrM z&xW?;5IjoZNoPpVqVm!xyeI!d=s@5oe4%FfQkNV@ws1Xzy-{yjj$QbUE3lWu{^>rq z^ZrcFR?Op+b@})5SqHB4K(5oyiPeF+`T-pQKbP!IAW5O;W;Ge|^%O!vox2}HkFnUv zen9`MaA;E3N&z}!3W&9ZtFX*{yp2rJnI!~mNG(Z1Ud)U2F@rm=3j}7ihi&Oc} zpUmeP(K^agFx0=Ke=$Of_|O+OMN=C9qFLgRlmnYRp+{byB~;rkRWBz^kCt;4Y+hP- z&UcosdP2WGLl%B$F6`X<{fxiWLTJaG>cTDY)#0Oae9TypF9lE9O>RY=VUC{xY7ff2 zHcA$dIJs<%C7!TJ$2O%RT@HUIeRV&hZuW<8e7r?QKANr089zSTBlwlqubVD`Gm}z7 zMnL&MB)dm%>EH1h^1|C!?f8ab<>y?I89=eYxmfga#jM<-gNHiXf2+))#|>M$kgc&U zzJwSMU9ER|%=$tex^7PwyR(isJ4dmMzC&d2&KgahO=na&$z=4K)R1wl(nAI$|9nTh z2M5QuCCC^Pn+Q_|vKHO(Il>lJ*o>~x$porRpaSSidkZdJq+_odI|rQ$r8%;c7uH0K zpDbwL^M>*$hV)l8Z{DIzeRTKXHMLR-u1I*+2Wkv|h2Ttq&C+bX6F}))lhk$erSw2< z-mrS~$E_x`Pf$~b5>bSPp`nbpp;qfsmg0ZomV=#Hwoz=P4oy_N-?R9tzEI5yiM~e> z5+M`fAzQNGI526z?fSCzSt}-IA>I=KMeX9g+@DcqrF&3~_nwL0k)?R!w_y^T5GrpuM~rHmF%PXXA3f5H7(pnbu|mJ zx5%LCu_Ye;d8bCxjY|^wzmCb>8)>c zOX~WH%Vj`B|lO2N&NAh6Vm)Thp^{I7R5tCda9^oA5ilH+6} zV+-dyv&xQ}sY4YSj(KAgR-R^FAK6i*vffm($=5_<5==ep`YA>m9?5}}hoaFHwUfeC z{~l~7Pg*A54Z&QlOD_g1gD!6qhTP_+eE!6B3QqMC-mi_#4z;0Q_~jyqSh^inc+sqP zhbvD8rmQf+vQ2V}feMF(zP@n*u}lcqt~-Q8e?|fhn}#p@-z5RJ%gjs>g@f+DPH0Ay%m>77Xl^?IJJ2dvPjRi-n3Mx) z{xf0q>JYj_r1#S~rKYwf)6H?c=QXfEDS{H!Dx1N;QCB>-6Ix(Le-kt0sFu* zeIRTTnKO=#uhe$6m7Vzp0|de1pa5fB=e$j>F(WQBB#cUXE)D%;5%4p!Ta?ce3Y}wX z?*faqNVxJi{TUY8&54R_BKvuyczRQ2MS3=C-8gf|-GFCPN4eCJTEm!^j)~hLm^l4> zJ&pqS0PZAfGtT;Q*7U0(O8VFX>Rz{7>+`YGm|G3Lx0{|(qK&w0fWjs;+aXUFP+2M?&W=G zT3V-iH)1HBBHs{1JpSY*MVWhjW1|U?lE%ceUvEo^({D+Uu*TVKV!*+mXXn%_)7FqZ zOG`KuX%#@BoF$p7`QxyW{I^#)dp^C+ci0z*Er`HSu56a6GD^7VE?j}Fec9K3baS$j zYuF`{vhQw^a)yd%LKh~0WBk&7R&CgoT8t&0z-HC0Yl(gac%;qMjO{Pqt*Y{Bu$*H( zJ~^ouA-_3a%bv&;$sQmX-nmEl@_0QtJ3G7S*>N;iMRbO~T>jID@qI0YkmJwVYcsfL zvA8kb)GUM2S!DNVx8)grnf?>(LS~LrU_KD2Bpo5C&U3iRg6(91re%m(H&x?C6K~Ad zzjymB?k*R-ez04{a%G}XF*JJHv=dpO*Tn$U-d-Aq{ zb;uWeh*qhlckkk=1Iw7(<~>Rhfq>`Ir`1@#4u(2d&wL zJFob0x=b0q`7`@R3qBQZCW;OMV?DEQepevw!*M z;uCs=w96~(IWowIX6u#u8Q;2dQ^UZ=-8MbA+&7Y>^UrW?`?#Bl6QRW|cQ_KsLl?~{ z2az6E?C4$rP0i#Af%|V3=`auHB?kAo;PFd!b@gDz@|jXMeSa+mO<(mJWf`On+fJ5S zCKku5m6nfuqTVmU1)EBMK^l~$rsAA$97EO`l7{UegG91gYl{|C78Y7)p3BxcDyO`R zW-O_gCW+e<7nLaAZ>TE%%LPDN)qUJOXub4#VUETu*C+&*ASQ5pB0K;4ZtbA|vwdq0 zt5#{hH(w?{5`{}N94TveCHHQQ>TbI<0pVHnSJ8{`+&9U|*5xW5^Slz$MJiHE+_`UQ z07xR0Aq4K{1XfH+MX{+OHh9WVUY@j2D zjUy5D*6njebwy!ON!V%V({nqNJu{B<|XhU?Y^8ABJY>9 z)BrHb@)`c(x{pHoWQFGrz=w_CbS-jELNu-$oWi=PS;0D%x6+4UeNPv(prYkOhTwPe zS!=^IRh2?1g?5L&7@|N1y^R^ zbL%n3T9!1=SO>jW4JL$DvVKcy^q;eoiJoQ8TBa5@8D9*2xzMEorNh3bPhI7|19AEs z^7E(fPNVOFAA zva@jR?(S}4W@gkHrH`07vur?sSclgIBf;CGQ$B8XBz3Gvv)YkWjp92^OBEH&9^*vK z3k*n8bn}179xV?rc0=F$HLvV(Su{jE^$}eb-8B4l2K=>(b<%D=3%T?&J)YuJO&V0q zk88y_>|j4S(EsvFU>D#@T%U-y<9W+0$>DZM`kjjAcOifQ;w@($@sF(;ofGu8k$h(Y z|G$4tFV>sgw=#|VUp`5cVr5OuyQrQtg-6ZFc~Oy`ZT9tT7%{X-Anoc%M}MigZw7MA zDjZ5aN%lt3QC&YyWmlFz_lyqOMDQyIp^9voVXItN9 zhbC1B9*5SX7PLFQc}m4vXFW zp69exwm>hi)O`FkSv0Hs$N0*NyF>|-r_IIV{5;kC&U#6_LM6zWdPXxyCE*3Pv&_ai zE28iGw`D`24QXYO-ksBH|EaERHJHU`^4@;ppR)kJ%KAA1tYUO|HEQIxN_hXF&Trl+%eay=bG)sXbL$0DICm}ZUAMmgA&NmCNgvijg6ijR zkE!iYui_r%ZMLNRB~FPEoox4sPAEq_v-_Xi>&t$ggliugk#pZorMKiE4jU?4U`_(Y z^&s&xaz+uW$Y8o6SmWv&UrJZOK@2k4cro?9n*P*t)i+GoL{6p_8|pNHsa#n>Eo z&yf8NMfQK?<>b?L)MGBC1>x16*?AqvJJ=vN!erA`B+(*D7$iw#SvPf4+#j?M0^!<| z7Wcm=$;;)Z3Id9zt829*M_-%oa72Z$|Y`+D=+36 z9Lx4ccbNh*4pqOw(J*kp-770+DeDQ-MEng6DLF|y?la}H2oxX9*q&scv0i}cA^z$! z9mr%WvTRdH7+9EX@iw@`k{11%p;H=FF4u)6-N2JXU^Ni?+LrafeR=~YH6??~qCz); z94%Qk`DrH}{*|}z$}Kx`W$1d_e7dpC9thjs6H{?$YR3DAZ3Q#)E?)^|8$OgSo2Emx z2`R9IQoZ~M+IU#YwpWF{_Ywyady8A1hs1@^9UMm=IPY9Ov)FLaK6Z`y+jBwO#VwxukN1_LMO+oR2H61u@jZR zVoRij|7hsKPdw|C10~tqv9|^t)!#&{nWbZCz(H*S#MCKQdu=)8FeF8O)*5G%FlM}o zE3ku1I^tE|d1n=E=K+C7qjBeV{L)+VChm++_&cjO@l<+0irAkns{BaxXPyf~U*Qsi z-|?gfAen>oTcf(FKH27l!v`fRmzRebV$T;0N+A=RJobF>@43(={|luF@xoU$Grm!l z_zC*-P4zd`clOMW_mrhiD{PYq<&0ynH6FCRBYmH{Ym3-Z2oY&>;9%cvvG+k=iqHWB zfQATpU_eFmz!46XgaZ*!SEDt4=F+as{7N3suI51+2AwxQuf&6Vq$K$w#+k1NppAA15LF+`OzFrcVuj90*828Pipqbj~( zaZSmV?bRDA?TBNRS@Fir5edzF=G(E-M;6T;M}jkIEbZl=DWPwO`ubkGD4@f0_uxPPy)rBAAv*ePbWF_f&=9ql7{xz?%b;m` z#OR_;1-Dn8Cy>6l5|3XfH3hYPJDUvt;rJG1@vmHw>Cz6Ie~U2^f**NgnSz2Xzh`8u z245&M6J7+{j-~~#7V@(i!LG22T;6d73!VbEr&g~`iWci;pk6RkBt~l34$mtJGx`fP)%)7tpTlk40?ksGBnb? zMSDtPWII=LEJfRHDrpaQDQhiM#dt(R>L%LXDfDiPBaSVvJSAmSRn-&2ZpT*&=@GW_ z*5~iZ$#}1kYY7R^<0no3Fl{ZZkA{YnnVFeoW{#yZ%h^o9djI$`r-P5?zebPLljsvI z>W1+=^{*g$XG-GPdVLvaGe;W?>51>Q{}ziEuZ)2^B|JzwCPmUPSi*k#p@S|gPJLMA z>SBQm@TbK+jtJQVA%E|^L*%L{aVxp4s_gMR2{pB()p{J=X4+s;1YJO6845dbnYW3z zxdqLAdkXA!z!Fy15D6tdhJ5dJj0o8;OkK`6_>t{xS2r_>R~N=I`R=g}mOsp&+J^|s z=riFp&E)`Tr)WC)axcE?8C-FOJ4Qt^?XesmnWps8IN!;Bq;%PI?HC>(wz=(S|Ibpm z_jgBL+H(G^rA3$>7oDfGQ=3@C!o!2#vf^METz{{lSg}s?ud9U#8B#kxkuMtXn=8sC zNhbA_G3Ku_8-O`VNrEhTGp%OC&-d#L&kQ%aGKuPJ=OhHTe#gT>fR2QZD94?C%Q&yG z=bf*XIH_y(*pWZ+sJiYcDL6@^oUSn(J8nozszIh2zs;d#!%~L zjQ9xy(4A?t46farV@6TZLkmr0WIA!GVzD_VDDP3gQrYa`?!4Z7i2F8a1)pK*2WRT_ zygMe#wq33&lWHVF`RLNI*QM^iJB|yV;P0Y;RC_P(XLv1bo9kD%++RtyDZ*7^4g=AI zc}4L5`fjRSIv!@MbP6Hvl%mQ_AX8}77qLB|_zoLN10H;UkZ;^}XRzlx|BkTw!MLrv zlZ34#JLC^Yt&m4u_q-4Bwg@@=1>+{GHy12lMWJtMe9%iED{c3;2dx(a41)HXjiEBB zpUrtd*9G8C!CS(bR%4u6`D2l-iOjFQQ@Dcnt`-wD{2T{Oj_;J-5SWt)n`bEJp(M0L z=D#yg&93R9eJ(Jk=Ofeo5X#)2iB^fdfV z=h~{gW%`-oUd(M32 zZr>KyR$W9{_|j|j?KQ7Ux?_+*|KGg`%-e9mgZ2IUqv=xRZ4958ii)z!b*#$=57~d+ zJB81fXeL%1TZ@u5sd;OUiE?NB6W_3jvYk*UJEoPMol^9yliQ}dP+&NH+||nLK*z3L z73g6{Do})h-`gBcR#8%74BB(wnuceDH)x`{t{f zx6-V!n`mTX-R=9uCQ~X4k=-0@p7A&JlU!-~+B&RnWT%_E>B6Bk+pkIIhd!NxD~A3x z!>~4=CO!?rUtLeYSAg^oFuHQ)$$g@;RV)5&;;duGGq^~hqBwb;@)`e1HIm1K(KbjK z$VNv~AF$%d@3cH-5=m*f^a_t4fq$5kZSA=E*z1<$*c6xF2miavm*}gi9C?kC`&_J* zZ|-UwNgO(JyRBSqWB?oXUi6?@S)^$k1|wscJVGbc4W^@h|M3h|{y3bpTK8-4X4JpK>PzPHEWm6d7G*S7ER zyN?F07R84T*L&jJ`uq=8ct9yhx)%btzy=I<8Hh4Lrxv%oL8=I5UiUj}nOhL=TOD14o z$gh%`WL2GDf%pGl$X8@V$JxSrHiwtP8m-KSWck0%y#s-+hT|`8Lv_^I@6T+G$Nqs` z|6d9W+S8bjEq$ZE>^+y2d-;F60dw#liQD^?d9^%qllH%0^|hh*8`~Y%>RJyI*r`9c zTW9|eeP{H`w7z1g+x^ehS-jH-!9c;htnDzXd|F(YL4Y0XfcT37RzVyCm`Fcg41PWP zQ2N42=y{sq4q>`RvW3jf=B*p&?eGekNOpALmGJ@omFmMr0?hU!5+;pQ?j-eSmzRzW z__oE9r>tL~ES^I6%fB#?3JfNa_(cMe^!<=hKIq*Bm?-FzZB+T~lX7!VTLna2S@hQe)i*9ZN6;)DXOngi z?z6yu@-5myQ}y$`8Ox(!o`=M-A7DUbx4MRf!ky*LAx(nOPGTU%9WD8%qQtJZO#f&x z&}n9wPXxcbuyod}^QeBlb7^&3e^B6|_eQL-_+JzsWEyyul?$?kRK04`R?j%psCc5B zN$!tg$tTawrop1YruK0*L&9y&J}fzv{*w+}=o)J~wpf;Z$B*B|b9MeurPr$n`;JDP zTa>P**U;vYfnY9{xuo?RTRwem=Hvje{1;Qb6M4pf-f-^C204wxE+q>o%lKtEGmZB!&y}t7PE=` zZ+FJu7yq3TepbARi-K~tIoU5k!$2iS8(*4HHd-%EC8h+DFZFLH%_2WMZRis(4tD_t zofUpn6TW~d+ovr1c4X^F? zRm?9hYr#)VmJS&{<4OMM&v29D;*o5T(tDiwt~;gjJ3*Et#aMQa`*4NnSF>Ol!Mk7M za6`W}dWg%t`zS7vH*KQ>IGYwLF5F> zq`*T{7b&d%K$gKzT$3yN5J;qj44yWNYOAEQd z&|@F^auLwgXa&Y$2@>2^1`|Y(vd3JdL4DOdGbt?z+v3p zqf7|06T?R5leku367UomfabLJLO-fAu{E_L>;k=%-se~g79cQPR~~E-W%EAGLGV5; zU7AGZGHSjS$T=ehgzI!Jc=;)=v@!E;?M?e~z zW6j2ct0;|>#gvZd?HJLl+;R)sbho@wmWl(j+5%>Tq>E*-h{&|VCLwAN?i{7m@RRPU zSGDe>ikH~%{zyUQhDNWxm&Dlig~TpoE1YfhYwnl97$Xh*8>TrEjTeSz+1vEv)%>X1 zN(bZNkT!cx4yEB|8JC9sA`fM)Ut(6^Uz0mA7ep$hFTWdfFZU;BBM?jVb>Qm2qkEc2 zn5-?FRv)2#B~yRqTV`iAE6gX}Uuf(J3?5#!e$YGlM{D&g0XzHj-BV=96eK|kjUxiu ze;x;TU zYGBGIy3GZMT_f@J7>{|$?bTp1jA(C+9i-N+*gbVdsrVaqlE=IocT1Ae^mB36rM8Vj@H>z=GE{G z6+I*(UV`mX5q_RifBaj4WQC2rRbiE&_8t6Dpb{wmPmUIm%k{~7lBO5JJb!5$pqv4z69yg=JEolqHj||CC0^UI7uiK4}q_kv#sgQ(a!U1o`Zz84B z)nt|Hq?KG5y%aYRu|aBEiNeY)T33s>WR+}iI1&emC?)p$cQ!cz^~;}XJ{NqakPx3# za-m9BF;Z!LP^Rn>t2BCjlnj4Fw_9PSI!3EKAAqw~Sa z`=ODcp6^4Xzvv7j&kO>kd*98NbVBd$+CByG2d+i;BTAFnrEyyR{Vr|Z<`dj4SWc}N z$FuTljXBpAqVInqkcIFdgAA?vew*KLNgR8{RB;(s>J9da&3@oMq3nzWX4#>>K(OR< zo~gIKYtl1nl$R1&&~dE-WDS+H%n>>UwKKZ5 z3o?wAAg~<2>c4T$UWGOxit1 zn3xM>U?~+(s|gbHpG(Mr%ZOhV!s=-*nGsqq+tFQw=mnkWgX)zv=TL^+P~G8!6QyKi z(+C?j%nd%J!6E53XY>XHhBk^`M#xT=D&X?sC?HBB8^(TF>OV=Rx3T(mry4B(TPx*) zK}4f>qfBIo6q+dMYM#Upy0#X<#cHCDf~Cb^*bWm{1zpnm-knDXgcmdG-Qmhiw1t2E zf@5pSHN}reE2TWv7WT0k<}X`IirJYQHpz8Ca~Fx|n)5ZQ1aBUZ(u11F5T^4L3r(k1 z&E<*jZ=%`W1OCV`qcU>#dm8{ZDzGk=@jUv~~3uW*2@b||_H4Y`|A|B!n)CHv#)i#j04&ct-X)ZmD z>e*VsWH0oEw`PexeRyrA8y^SNCUqlfs}9h@fo~BHjeQ-2XUGjS(f8eNJsN9Pl~W za^clo>S@u^yuPwmM!qNdqrye;UV+6>lNPs@uvWlM{EGea+-p{U%$v^iJeDoXNG^lz8{M>QWb!Sv&-kk2GOIT)4D4OBtsJb`sddiA~eA1(lS+||W$BLT}9HSb!1xP5`moHs(*MCUpjst}`&fox60 zw|HD$RF);Qr`y33fv-y?BFOFSjRI0`E$OT`37rSbst%WgD87AOENJdQPbNJH zqO-<%_jhxqqJ<q5D<8$=i7-CvqqA@?zVI zl@dGuLD*1NkfV%I`3KTp6MIk6x+|Cb)4NU_0IofqODNd2+ua6cD$0T&a=it^q%J$- zan~4BKd3x;ysU>u^wUqpE#P?i*C16*eZEQRBCr;kgGp{o6i`Y9jrLbiD1nCPd4zf;eDHdU$d&dzUYq=$9(PJWaigqWTQU zYodz2q$~W-?Xpd|SA@N#*v;P~nc72rV?;Dy1DfL*te-jdYL2%{W)-5kiFYF-62HCj zC6t-*RVEkzl%yQ<8;TCpl{%V>s0QH#9x$=t=-)ag(Fe*X=_f1;hBR5TO$pau>AfZO zDxyV_PRWg2DfUvwvr;7rLx_7Xlw>rsr2YVjcH}P-oEK`yr-CiNz9>=pI=y(2e%qp! zvtXAY{92RPog}(n@a^1-T;0%5RiCVcqWa$U?24UzJI|*+S$BSwk!Rmu?AlgoF0q1N zIK?#d{Zo|-Gq6rsX{}J~!1O+}o^?J~eQ9Dz88Mj93~}!HK|LEeZ!%drsoS8TuNl+= zQ<)bNjnTK6LQai1#${Fq`(w%@f{Bl-uu8}ShBD=gV+y7``FW&MCbQcVS7^>?SES_D z!I8%m+Y);}IN~&E$0@b6fI*Tw$G)rLxJ0^gQIamA6Zxqs$9Wy;ieFdiG0$u7K;={O zjj4Sc@*ut@sk5-E);Dc_Se|<2I-3KoczVU%)_9k=;8h2@Loi;16ybhj7LU`;`&^`! zH>k+;W_9W!t6QDDT%2hq%Ng`03UPfhEohNRrvTd&;}!wE^NkJJd1!RIaR~P-gUu z_jn;@d5Xj|ch(w(Q0?v#YU558H4kzNGtYY~&;VJ@6!f5mV^psG&e;Ce+I0?U6QeI& zYQE^vd1u1C#CgDn{CtFfCauAwbH#zy?RgIvQpW{;90gy#P}S?PGC~w4@@7g6o7JkQ z85qDJq&M?!o~>t_whL#sd8|tVxB7UAsrJ{l68%g#B+svfV}Orpw$X(*>7wTQqGUJP zos}6^yhef(*$XDTa*)2QoOR-YUoM&b!B^v%Y}EQih0X}8oqpt|(FI@KzoEYmfbFzV zKsgTr{MMul^B=UB<<}T#pH_UD_Ji;69f44%#%NAGH;--CU{V?Nb3!e z;aT63rmraqr_6D9)clOiARv|<(rZ$yw-TWhd}FJZXAg?N_oggeo2aDAPFTzF?_m~lX|)!OiMVPFT~o4F=S@cMnAlQE=c^-XR1 zLSH>FeMrTthsibr8uiA#w^*}R>bUu&#;#4=s!U3Zi-oYCiM-nP^|h_(TWSXVuuwDu zk9Ace;GdUij=F!_y-I!HnQF?&Lim{y!qb*Z8L6F}%_lImSQNnFMLvm6+v{d1IJ~2T910^Kv7haQAaBwW_;MyN`e#hJf zEO6wzw4aE3eQC*pG_I_JLebn4*Q~aBpBIx038g#qzg9AE_jw%PZ;EABkr;`B)>jh= z-=RI-cYZh`^;jGgjt=_Hr68N$@u0UW+z-y#qi+=c5DqO>XS}emU~s*4 zlC9G6OCJ$yiz;MWeoJUbK{IKmW#sGh)aZ}d|FniF-E}{3T*qQc?x5<6m>R33Q$lrE z;iG5QkIQlKo7Qx;u>5M8=k9s0TEZ!F7%`_PTGn&0ks#P!7W3ryJ3}SQ!*x59s)>se zSnr|-2MuR?b?&zc-1%~^d5YocQH$16?l4^ zWVmw5xPLBC*FKX>x@)RslhKBczx%aQp+FX!41V@W+^4(^oXQ|W`*W{5<3Ae(^l&yx zvTJ5kCfgu8t1fXT&PdlW5*OJYyX^9*T*vdf$BemRfA>`C2+&K3NJJ0t+Eg40m@tRr z*o=58cM$lW7gQk(?Zr7bCg@)!XJja?wRotNiADI{G#H?n&+Z-;EY%$^iyq8k7vH$yv^DN^@KNcfTUK0VCXuCG3*%UnoGewXLkh7Wa9Z$`NaFlsSl zfC6fMc-vuW*FUf5ze(AFx&Xo4wV25=5UsGPOT|-ZQj2q8g8f*~LJpHY6HkiavOW{$ z+h9z31ip9LSWut+WyFH@P4VpR?d8Cpzo>BAo3h4T*ZzFQir8QM9=!SU^KX@H6P-tZ zC0lXi63W3z1erz&O82Ahw59_LmfP_h>-5=*=^Zs!e6n*NwDjF^)093I)HBlH+A=NM zx(2+|{Omm8#LN?@5GKR+OnbC|Vci>DfFD4c*1+xfYT${J(q#Oj$bO%m!XKMg6c09B z$==RI?&G#xBQPexNA*YR>#65qTdN+hvb5B0V9pkO$2GsP0)cd03zO#2WxdB+V+GIS z&F^0;cJ}rXQd5tbpSy6PCWlqI+w#c-gU1lqbxSehBR+7hiesNF=mLB|cHuT>z5att z=RUb~cqM7~RWfLTShaHnZk;jWBk5k})P|ZW)|P(FfJrQ;89jY|c zXjM(cgO!UI=R-Ixep@?CPROd>;G5pNPF1}OrtZCckGbz*!iy5KFf9BzHw&(M)<7}Z zYlBL;sv!O>Lnfvx%U?6!Uyzn0msH?rO;E#qyFc;*MBQTKecBi_p)($1bk4r2TYX13 z_o5GXJSu9&Z-CT2dBFJFTwGbeLQ4+XrE&3oJ$J{e=!Is~SO8oiA?7|^_sovs`-4-4 zvTQWES{t%o2ClmV#LwbRg{EZV zaoZ3+on5!iGC=z81;bOw7mQFlXR0IO;}(V_V_r`_J)ULki+tHThw$>hLW4Wb=j=N; zUpyy#=!+8|PP&WYY}2Ooy)Fmb)CAQ6xyqTw3St**?TK*|?qdYo(zeTHjmP?1uL=b2 zq5Y^N(8(l;t)I%r?lfFzT~^P7g(yxuu#CD`Vdv+<8BqG>uIrk%mChhM^s1Q%Vw|Aq z0K&GQ&hQK24B}spc7Qj5jXxWD2S}0!z3dwAf6Jl#i3s?TbI8gTR<9++?mTOn*;iV4H`=WHs1KuZshxPne#ksZ0 z5AMJ}B>|QOA@1U!eqU_Td^{riNV%@fWEr-?LpNJvc7Q05MDBK4AZcgD|%1;(#BFQAE(!Zi-H zMUViA;I74`l6zxmMEssxy%BTT%F+bQi+ytqec3cDfx?yjVhuetr_tI!LD5se+w0+detoM0Cw0szcAgoD01z8&iH zg-f0n8V^x^BVGD6=qz>s*jZ7(|43xv9G*`KH+AO`=n(CB z2wQObSjDicmZkRHtn~|)n{=phXYweTFdnXv0Mrb(zWLY10wZ&1rCfn)-aTJJPr-$N z<~}5S&i3Z8PhQYTZ-SNW%*{MOvFgu5VD0A)or^=Z58lVT8kVuy9pZqu(Py^V@=1+0 zmbORycHiZrW$?)EZr@}+)(E&|9>-8H#RlqD`J%>F=>5gJ$ZJ-yS&!5#4l5AUoQnZP z^_gNGm`LLqXKtJ((|_@)VZ34eImOu0{xKCyYFsL3PZA|Ry9mDw>-2sidRa?pYrU@+ ze6Wf&P?79*GBWc(Y-ieLdKsx%Be{Fr)=xB;L9nxLpBuAa49s|V0A%gN|NcFAFqOyp zK-J-^aJ4r^%gQ>k?X^f)7%QpKrJT>vlwC5BeW8(#fXF`udvRQwbOWD4<|W2BZ_@z% zET?+$Sokl-4^_LAlWr}LSuZSN@2zpiML(%QlXKUxb2d}tY?%&+DNJ1o<&&aTGa#t& zo!67!_bWvEkd-$CAo=8HjOQK@uNH0S)rq{sG}Q-q1n;4(r%@d0s+cTy*M~ikgskZ! z&(<#d?{6!T&{G6mOyw4IdgEQMmtYp~D|6g{*(aw_hYtu#b}vsGbH?6WdvL}*FKr>m zp9QrogbQrYjd!}$x3nQQxD9}?_N)(x7GQvSy8QaE_xaxGCppJft<~A)gypt%3*vpK zxW!ggt#S4JQvAsnAk;>HA3YN8ez$gFd+(%A?w1&-A&z1w@XNtF?%Ns(bA?fScH4u= zTba9BASxvpID8k zCmRe;H!U$@t&ndM*pQkcJIcZvC+p6=2#XW@#7|}0ov!;MaH<2{OYp}~kFuikK$=go zxerU7!^Rw=>xjd{LtZEFYFYQ~>B>y@r(gyD?3tJNWhF;n449gtojl2Qt`8+YWS(P7 zqE*lvqfEIbygxjQ{@omg2VCuozz9hef=S zx!rdlxE-P+qtSRx_cx!xeh~gx2a4f|s1*^|G_KC!EY;rC7C9@t(<#x%_JPm{KK=EP zh=O_Nf_eQ?T@5<`?*8oN=7kk5K{Zbmpj3YIkcJ}bl5HZxX0(CtrMLgj$TyPfRy{fD zAp`^$q#;=IPke#C-<}eb4ZYSp$b{X)YihXn;RgrSmF>67!yegZa&#`cHv3M{?c}fD z(C%U>@N_xT6p9@tsWK5EiGACD$d~?(rgIyu!`=jwiKXP{_p?J;TAjn`<8F60+cg$} zCM?Y245fZ)xu(O7)9_6#fW)!o0Lt}SKhDw9R0@uO2`@aOmwyQ-+WQ5tzt(OQ+4~mM ztLb;r7_Brk8je0nYra@QMnMY5IOttepO2xtJFl4Ub+J48kx&oAf2PIidl!3zzQ~!T zSVE4Idx*jqC1pS0d7+sz_y7&#Zu%M&%yHbzX`=UuJ&F>MB#X4RI|RyHr+lOF z+&p@8r?sf%?did@sE^Sn)6*}S?gEctXMrQ90JuBBdO%GAp+rW$j=ot46~RRpsB5Kj zxIIuIr7rmJo1y2Dh5PaR7`>e}(zDO?gsKPF+zLb)`BhyWg|x z{H23zS9VW7EV6w8{M!R^j*Ef^?|<(^QWg-=xmsPiDyPLvs#E@XjVbEjT3Qql<3Fjy z_C0$_1g3tR{5&sV zYKUmA4J~%j7p5wyDF4AxP16h8*#gE<(LXz++GA&Zi7(ZCoJJ0_dqNW6E2F=@&*n@I z&A-IB?Tiubdq#lyw7tzi8QJLurupzM`1XQwnY7;uv!b;PyG2V#(f~ixkc3DOu&He3hl!B_(VX(JbDvO2OL~0x%)6hgcvcW}-FDHa(;E;PP2a)J7dO^yo=7#3!VM5IFAxyGw7rRU_MPTm6 z1!+69AH~s*H;U@XhXAe|i z4G>e7$Usp8Oui^Eaum)2oW+)!Altd3miYoQ6_p5R34gSXKF8q4b*BD%X}8 z+BiHVIk8}}F}eGJ#=T$A-Ij@!J)3~MI@Gm?eRM*0_JE7{S~E-NUA42bWB_?n1Omra zA>b32z9iWc7A4b`q^ql|ZaW)@he0gH&Q7kZjK02s|6mt>Mp>i^f*KFQ%m(6SsOSr1 z&MhvgGuql7c-io=iLVE#9W!Rb;usn|)9ckVRfFbD+|Mnz=yO8_mwxN1eR`vfM96A7 zF#sFW&2s_R&E5)ZJ)7Y2N^z1T2FsXb3&cU+>OmCM*PX&+C8wn1QSB2W%DWqbVSul! zwchh16+&mup}}^(AFWp2x}|NTd-oEG1DYUL#m6Vo0poG^`hI9l&Z-*s{(P}#ifdqu zWgQ+BqoLnU^XYatIP2Bv$`c_#5SIEERQzq>K?26j7P_C=9(&QFKcZa9*-wJJozFKE z;qarR1aca5ztrc1E-u3q{*gb%vjR_yx1%uHLh8!gzDa5&#!jHzJ=1_90Lg*`gx=I->B@})AJbbfy7u|DNi#XNR~kMlhi2JTez=6%)KMw!zXx55 zcnI!wP-mhr>)seE{qnimLz=PWVyMu?1Zw$~mqh|PV!cK) z1v{!3L4SKA@L9h}h$Df9wSN8j?p>E40hTxbkT0jL{SN61kEAUaq^Iv%th(nbbx!Oz z#KKLwen9?T;aELi=C-`s1%m+K43qnXiO=xIW`==5IsG8IU)I;X^aDb01-KGdW0y&@ z>5RlGF^cO8hkf-^+BS)= zarkV0`-dQ`wB>@YD}H!*xJGt+pZ_zj@u_<$japoc-XZ22PTn2bKL)A_ElIOi&$PW<%V8~Ma>p1Rp&bw52wBV?9_%xD3EPVd zE8uB936o1MFaN-xEWG8Oud4KE@=>=Wb~cBNwP~u@^Ku<@k3B+8mGz)AS~P=xFh2B55U58+NeGpIlMflX|O-~?ElD|so*qb6-?x|bK+qROzxXYX{_Gf zky-A~G?noc=DBJ)-;8TL0Mn)Bg}Fw|VNhk7TaIW0&VpJq$Qgn8hCy=w66B>V`Pb}u zTF1W8BnY%^#}OK7>|d+KhZ4Jz;QJdORpw5KHu$Dd5#y{~c)j!Oe5@R|vw(5)n2KIq zZx7fiju8JW1hSBBwt<;rs>o#*-xD}^PPXeZ3(fKD&tncd`-h^7iR|e}j z{6x60dCJhZVmXwEB8U5R1A{xiqXdgDhHXhi5Qx!f9=~gZoL19}O*PfC70=U6`;vHm!7Tk*eJZDN!6`CDeE5#(MI>x8bM!=OmI(W?sQt+j*jAIWArF z)7kJFd%{W@YK7Cyu>*XOTKg~Ld+dwRdZObsL#*BpF(SpZu!a9D*nCFn@A%J$)!9D9 zQ#EFRdprR?1-ec8eR+z&(&DGo4LSLEW;KF;XgNgvFNT^@%EjXVUAqq@Tf(-D@4o$Q zCOb|fq8GrrV6+}Dh|ZXtg8h2drC9pUi-VS_d}X03eZp;>`~+HUsudBHj{U^@CoaAS z;vL*1|J;1q7g`ax1mDN(nIZPc@1C7soV{?gBiwdfgNxl>05u}|wka$M;NmW-TeqEo z$Nl1C=VitwMpx|S$>3gNvFR5_c-nMcijz!6no=bG-^YJ8CNoV}mni{H?8H}V5WpN96CE^rXU-Y?Oe(kpug{!45N_hub;3S;#fyHN&sg zOQZ2AGGxNU#j5Fg=vBz(a}}nYN)v zFHa2hPB2ia!dfCPwcgC38s~di8t?^I`Yt{}qG~k@8$$gZNlths7hG_u6)J&_j2G9P|hx)smCAwN>m9|$J z6}~v@`wFdnKnU1$e)E-?jsM%#CH18B^?|#>E0O74WZF^=zjL2 zw|A5MU$7zrW1}0ZJtl`3i&&zB^WSy0m#g1UiUg!5?U*AOSUY!8w{f0|zmrBbYHut_ zd;3Tv_mmLvl{8qR+PSY>l4eo}HL8a!Vv{sIk)g<8XlQ72@nV}-PX6K#uxw7j(UHBR zq=fzVcb{B?p4oVZ17tR&TqIAjUVio?)mQR^i+K27Vp7?1B6OD5>;{hw-D1~r;a%3e zZ}H6#Y>(H*e>AF#NvM!Uh`ZP?&HpIqGj5Bix0Z2jto8uu8yz&Tkk7-Z#av}4gLGlO zTvBg+jzZGZ!AxK&2C~fptxL5lI+ne(Uo%5jGKB6)!&@bSKg1T1YBJa;s`GhAJC-0Y zvZ(^AR8^C!LKn!5m!^*-qa7>Wv6A?gtAFB)CSGNffv2IrKjCN8AmZ>4R!9DgIkFdxiJVFi&63~7?slp>D(%jtqXMFsd=j2*^ z7nt<~rmA$Wo~{X$irWhnV?OL`KO!mZmrPv{sRI)-EMr(n6v#LKGX^U1Gwk|L$Uq)H zmD-Q@x2I7}+OCK?D`I~4*M`eQtPFXB^t4=Dff~lBoIzq_Pnf zjMw*+;g{?PiGmQGOYB>4=FF zAO-+l6?Cq?YC^3OTp97iOL9eD)LF0Q7>A#~wB1_c1RJ$ywf9PYJlJbvD# z5B6h>{rBhII#hb+3bbRtbGvfy+8uvCBmf=`94q_Hz2mB1XZ;-K?w(4Nl77v6V*uq06gS$m`!gRgV$krjV^)WKVm1 zcQFTJKHnJKLL1dY6T`3a4NjMhyXAnllvn|!_TCfzBfm>$J3XWQw|AZba#ge*AG)ps zdQNw=h@RGJ9|2W@Qd^DS=iU}+R7r)%>QxEukhj9Vz z7;agzNngAc&qID|0leFmqJv&67n~J@M@d=!X!qdr4b<(NDf--sPDu6jSUc%ubEASs z?XYp(D2X6p84@0|X6D z+Q8bs$GT;OXKE}I_3Pe`F0|$={ZNGao=Lq6xDiPcPq7$ict5(lt|D}^goW5UMLx5| z!%p8)bhI=J`kg_%cppT@bU#+t+gn0=iQK-)^UZUWzXa%kzPfx}fvEx#D;Bt#eNx#Q z=V*m0#>${DqG}5bqN8IKu39x#amu#e$Qh97NyL@$px12g#MQ+J^^NY0rNl3!{NvT< zlyJ!4_q;A7&wCR^=Sm5)aEwOdYZu}lwchDif%{_KN}%-o(2Pc4q*oCiO_7J6St}BY*2h+q;Wj37pyM6PD zXPmsAm%Jhj!IUz}W%B(Zj7JDjLM%`9d{XuKLV@>=Qm(VEhGRBdlT)aGWm>Y*764*R z4dxsaDsMb#-v#_(Vm}Di{7cbsBj^;J@?|Se5nf{&u+1peXo0S6;>A=mb+@~9ncNpJ>Ut!6aU&Z-e>k(^a8sDD3KsIexco&raSBX zwydK$>w|cMQHV-cSSX%{-1_^7QetM= zhL`jQ0p{H>%|GpO^Q$uKY4~BRg#cDV3V1ZcDkk4;Kzk9_!Q4{$y_u7>N|HQPg&Cf3 zltk^lt~e2{y@`zS`zj~c?@w!A6O(3tSFW}E%nrmx=jWy^ispJ;&f^HFixoTQX7HpO z@d|iFZ&mm>2BqE`GpnHOzFr}^QY{djxFNT7dHU}3*ro84Q*}63gGWxGI}|kxD3S>EQ(Vvw|w5>OUFo5J)fUT#P{4!eB) zv%LyEpJenjUHUCNYWOFG`$BMpp=zUg0bO18yh?(2=*foqLaB294ppKSUJy8JP>eyq z_0ei@MS;L)c5waD80c;Mut^5Kaz&V2O#e(JpvEGkEPj}tG(j|iLPDynt$2y8%3ID| zWwAEek*(Um)6)MUATo@+X$bhV9)7=`A-jf`m2V7HzRo;UF;{ll@JdmgRp)Wh47J`kHZKKlj!_}xh1okk{Sdl&Bk#}%A zie3)L&l)i(mh%H*b(nfP-9oCpG8^SJ3FT?Lsv}neQ#r+xeXhvg6f3xAOR4o*HV7g( zSlOO!Y_FlN&JTM~ojg$ZbwAUHnA;YAr;(#Vmelm6GtH`-U8db(VJ~zO&mQVN{F+4x0f8lXLa!67Flio+?t2 zX$xykO@K6P`YRl1&h;N0=c*HMe~q-4I{s^kpVI@`OWvoz`3f|RI@UcOV0XoH+}m6^ z+lqjqG4IHk##z<26)y0VPLeE#?tK$^EJ$N_D7`F*dR{e0l|)PB7qV;!3Lw-iPnPZWd2nX*jz4aox3 z0YVIkVIS{y)k;2k+uRsh9Dziv!2ArA){pS*dAV-IH_TG;-z3qw++k~NjqY!tDt-(f z9&_Pzq@C--CD@i*M+KAYG9grpw5nV-DGx;rsS!PUwcE}V^z1K57VcLu=K`qnK!Vgl@)6-+W%4Asyygbsh`7byVpG;;=>)a51f_J=_etUEU ztCW~pT}c){J)uOS4xqb_^I}jl&KkHqq4spuskDD;BUL_?R$=KeZV4-Y`%0+KX{=o;;{OAYTT#k$phkS{kT@FMwdWVWWuQ7cRVM*;1i_~5`>q(Zd~XFjS22y z?v!{8YzV<@e0pK1qvvB+iVYEtSfz+3;lEZs%eswLlwv`;+fpEfu_o83A`^7x(H5KJ zo|SKAJzx->U(-i9qz*MVLatr6U4~>ewB1 z{|D6-sQauw{IB#NAOAiuVfRM@S;^5jx&_h*XRh@vn$K5|8j0$3wS1#x+6S7aw_c3C zBkUJ63rQ!^d3^V!&k|IOY?(Z3OYYk)bMcwR;QtpIZ0hTIE6b?s8|H(lR@Z@?lIk4M z#Anqt90Y9=k7uOs(Q6@wO?Md)LKbBZ7X1J<0-=B{6dZMRCMG6~_mt(q0IrS3lf{Wv z2*>`TMHDf&J)Pc!3Z7nPN!<@_Y~mO-qnk7ZZ06cxu3jk-#!EDffGGoo_@y9mVYG@w z!HVAsV~bLv{moiI{MO(yW@PjODSr9#YCt8Oie6}G8)-eE7Z3o=&T101F%VZ(0HV2E zB^dtsH+Xnv4tXCnB6x2?bpd?=(bX>EE^48=`%*W<(@v~+QFgZV7Ewz)=}Y&(&Z`^P zZ6uL;Vy@cf4GP_C>pBAp*BwB@L?RX{1oC?)9kun}64x?nhgpEq7hM2i_IGW(I<|v< zS~HZBMfC}z1N=4XRE!9u-}q~OQGG)dM}O496sfW*~c%-A|j&7TyCm1SYz?}Rh%1LVkGja4%7{$3DkDBV!woJ zzlOCi{H(=~#)a>=K>ZYBQ$@E3ZQ*Mgw3>TY_cVt{6BcU>(j!Il5qTj7H2|$LnShcW zh#gKO$$!<|uW-M?oEuJ)@TeUpNmyRHjnip)?VQz|Lf6)s_f7%cUcg?=5?0e9y{RZo zP_jH;San5WOy#8xV;rRB;o&hKEmTTNYOD&Up+Jjs6W08L&lW82w}s6ymYfiJ_ps@I z>bI}h$!mUKr`{@J)NxnYl8MNA9l}AVme2hky`i6y%ixuykD=2#@$`*b5zA5(fm&}X zF`veM9!h{vp zCMOa}YZJRYP8(uHNdRv)gU61Bd_5jt!65rf@21_V!K;xz(`uAai2`k%3`sX+Z{TFH z^@=@f1FJjtFWS*s@loG7`m)2WezdiTs^R`ja8N*FlW6=eEu>x*AZCF3de;!y!pK2dV=YY$j)Q zn)*5C#UcI(thFb(PN+5GNo6l^d9a@n4Ii-(SJ!UUnZab-8FTTdk^HMtZgR_JXUZ9@IU9){7EQSaqTdjxW##)D^?sPuEd-_J;(?sIAFxL^RrxiC0 zyQtHz^?>TC?;T?bdJh{^0d`_hLS~ij=LN}>@1pllXM%Ol<(y)DdI|>qT+LV!do7}v z^-p<>PP~HeJj1(o3gSahxltYrCx!TAANLofiSPZ2jrhowrQDg;DWa6G1XjO`@y9oH z+}>Zq4qvbR!S7G`Y-4V&6*du5LHJgc&c5T7Wf`$nlfv}OjJ%2p)OW`7bb0DOR;i{L zFFI|d#c|hED@1+rj%?NTZ0wtd3pI(hIn;o~A$B(#t$*3GyTho%GNXOixGfmn&g4s@WWmu)DU)zu4Rz4>2VOpQy6RX0n(ce!C*B6PX(-nCSVQ z?J*G^ysLLG^LO?uU2N|o{z+87D!c_8DVBVS;4bnij$>_WL!c1G&fTt+s=ZCajKvN! z+BkE-kXDSX8@%&rFO0mi2DVAZ5DhRy9#f_LxaPNINU-tb$%1~c*4pR>PIMO+7Ea9+ z^V4p5ncQ?)7e?#hYeFxmt?fr_b6km^&Neqw=%zSryL7KcUO9nc_l6Z%vmXmHTeEDi zO(34+dv1R#^lim+KWY2z$=rL;YIrm|%aMTX$CZ&8xiZo*c(9*$sj9jCy5==Qd|m9= z0p0H<|8k7)(=P^zQ-&toDvKmn=3l)>`h3=Z(zb@Vc&u2iisy_V~=L8M^)6Yj429QqOOZ@tb%wHj|8&!C_^g(33#$Rr7awpIhAc}%$@ zDHl35HD%y;TAzIOQOs&_XH!lq(rkBQ#Gw`H+A#YujonaA@sET?SO9+ZIli_dvLa_> z&Z*|6CXCqGLhO&AbL*{h!|dQLw}HJnsqjr~L4Ymka)X0nVlOc}+2a3*Y?(~>yl37x zhOm^iffLExy%($*ltz6M9P5fOHkk;2n5<^qldIoEiw2g78!PdfsNGEXe7L!jc z@bLF=+NL7oOZs?4Il1hwjgIX5ezzwPT_(ks=%W6zG!c}^BHEiWr6~T~JmSrfNh)45 zrN>`+N$=kKDW^T3q~qTW?qbRGb!H^`Vx`MV8|RmGq7Yd~26`A`XLNL`)H5X+mU@<^ z5vOgoetpxKCqZ2@LPK(M7i%m{j;UsA1)$1vsIdpOag%tJNIJB;%Zh9k6Dq6Hoxt#) z(|*WV1af*a3-8!L#B5Bx1@CsJ7@fRrJhJZ}%Z}k|yEP;1ZkGi=O4vKxy~?{iic$PE zECC2Ec_*-J)Xs?YKgnxvxkoIuB}4b=qrc_TJ}Y|?h1JxK7qP%TyNI#Okjn+T;2#sH z#|LFGl9&uwKh#08eMgFfAJX%#()E)5Bhr302&?(dAeK4;n;vf*UY3;Z-Wz>(%R#Pz zIy1t;h(2Lv*y>q;JTac-=jTs07_KM)fA=OAWprkYC+G&8UH_HZ=i8K<-Zj*Hgi6H% zJ=oX;5Mzs|Nc0qDL|gUo!}8b;X4a)`No=)1q3=elkh2+h`SfzAg$kP6ld{9e~DyB0upo8yAF(z z!SRvkgNr3URO<OH0QcO;Pk>sLDk$t zLF3A`!3Z~ox9Zb_;X1S!Ev_AHZA2<`HhQpeS@#s-jFAig^QMBloP3^J8wuGI&E(s} z-2LD~Rw3ug!Ew;x?LAUaWd6Su#qzWx#>i}MjYv&=pzo`Fq4od{Y6oLCW(^IE_oCjM zH*b%%MuN5Ij&_(W=auzUdRUDHyy323xsr)8ug0HxW^AFNI+WjS5VpI#ew?+I)4n7P zjoV=RpL9Jo&6Y_2^E7Q;(-w|6*f2c1q|dB#bqISQW2@9CD%=6iVAHN$eH=X}+gwL% zJ2dnerKOBo%%_~7_W}R3@vaf#U=OFo%Tn*t+}-5m@?bR7#;Vv zg8|%Cu}53cCWa!m6k9`#@>PUA1FalX|J5ExDbD$I_^ZOEh-Gbkzj|gcUl}1jf9PKh zs*Q;Ho*k<}1C9K#%j*LLN>xoUQ6memLNIYD#lM)E<|=Lp*yvRzF|sf-mkd)Dwm^fp z3DY?Bq_<4-R>v1%M@xt)po@kp^qTS0d1!ExZu>qqR~i#%AeT0Tn;%&w)o-r$EjmiQ z|Aeqa{h!u$RZ%p=Bips7Apr zz{&Ll=0cL6KP%VqfsLix9yxyA%6r!1Ecv{3QVqdGu_dksG_9jW-z4aFhHwNoT(?|OG_Ln7dK5;WD6D8-p(uN{W8mF4H=3LH%NxmCJ) zy7ky;Tehza1;2jSe1?*dDVQM;-Nh(_ETZ5g2_ddYe9Z*UzwhtL*=i?Gtcm*vNdI(qcC$im7oMOY2#uFk3q8WK_439kCb?js`Xt#PAhUh zTInXr%P)~I20*)wlqo!h`?^8e9yG?~@c3~t66V4}xn6lr&zmlG_(W?cSt?9jl^F;GduX{UZ+T~q%T4PSPcmJe<+z}3q_g!j0A z6}2WNm_9yqbw@34c$Q(hm-V_&Gcej*Lu_kTvM%bN>qbHwzKAoFJe7ID=udnC{qpT+1nS7&f_ zT4~YL?4-gNN6FaSvTGUjqo0cg0LlN47od6FNSdLRHFvOY{o>U)QqlBP1L<4eoitw= z`0OU{Wcfvk0oS^w@?-^lj_UsHI(24}6yKBo|nmgUCtgZayDg;C$ zeOas~ecg&Uwe#lC1t@uDGeoOMPb!%o&sWUl&neu3B{<)wO}BkB=!4+TB<}*d~yP`6h|^-F`n}34m>)d!8)j)X@=fxt&jB!K@Dz zSC{Qb(4&%^19tv-^?z{OgBr9i-uYvpLE% z0#l)P8RK%AGBTmEyrq|U8kXV^FM9kALEWzVq)VU9FAM;e;)D?^*H!4B?h?L8j^KSf zrEH^g_sT+O&HO`6{Pv^cx4y)<8P)H#RueT&>*9{ByZur5KeTTFjj4Gq8HY1ma7yOD z3TuI}Wsk-L*@UVGfg7KwLIh|1^BK?~uLI~%L#^QKh| z5LT@1aiR{GWad4yK^q|N+aW~tB5;_49^oFRFvMjwk+~bi(P4t@aLIn7uaV9?6!`i6 zB|amT{aQ%dp$US>#f)KKK@?d$Dx3L{UQ~fvp*5_W&=}NRa*so=o!C33s0xB{^w=; zO@~txOdhLOwN_|XqhX}5jYBZT-PsB2+v+_^^KmhcbYX1=JWntvumw}$*Nt3qZm#Ja z^mv3ePP`lWr2|4fm-#c_T;VSE{~uX@85LL1v!Nd58OX&iU?dowfF#S+jfZ>gww5tFEr9y|OtDfAlDR z$WF{x>!d5e_;Wwaj@{M4Vdky-L=4}b4$?P4wzoN)mIWvRuGCLDpvFkz#ZLBWAMU`_ z7NB_|VGBgbMTT=yfWuBDk}p>W+-O!0ceh0W*$UNUOgX0K+s4RHMl9`x-{LIq2L4%h z7LS}ORu(Pzi&(#WAN?Xa%H+|lo?jIqj#1L)hLGHi+V2r%50KJ}%hHE%b^IxN`@Ysy zHh;NSV>M=WXHx0%C-SQUQzOg~=hE3HW(=v3b@J|*0M^dz6oA|GSJ=3zG$KfKgv_fOHd zT9^C(3XQv3Oj%g1KD_6=K18Roh5|>cSrvyE_m@EqOJ7Wwu%CzVa|FO#h13lGrl=T| zR*Uy(MC+d~toKQzm_& z@y%OiOE3z1vhe2^`I)&&gGVQQ$~w2xe!nFmRa0-M<^d&3t{1)+G(SsfX$Jq+%t33h-TUL@~;#c&lYWczSF2KB! zK0Q8XYy_#zTckEUH{9iTKK(*Hf6cx+lM@CN9g9a?%l`Uo`&U@8^MTT5$|#!u%X#CS z@o1C;QZVeQk?m?gXZcwG`_WoAGOY1Gi9Kx#^;z_^yq-5g|49Ji%#0|*FOU>LV9`eo zytO9a*fD6ay7BTCFTz045kb6I>zvLY6t|qV_a^1?9WYaMlv-+J&FT;c((QTNQQEcd zvQCu~+q$=QqQVsD0@8B34y6I5{PLC2Y{~s5LapcBL_jycKN%Y3f6t+oefEKkkEnhj zYSgNrp&@B|+roh_Q|BJazrDl$XAAq((b9zB*7-_FopEr#O#TjiBT*xPGdW|Tls73Q z1sMpXSud2$7S9l@k}1N^lcL5zEW=soXrp|m?c07Ux+ZGlHC5&9DMm`vXu31LaOE`h zm)dpfPhP((&hZ2Qjlj6?f)c(vh^lgDer({-ar9B_L?4&oW*ZDy{o4BpqpSG0%QE=e z;BjXoHEG)Z`jsR_*2x+FC2sxpWRCI@ck<(XNkYC(DbJ=LchKW~w(f~=h|j1?ZX;bu ze3J)O*TW-!Z~n~D{VsE((@KB2)-Pn*jbua0M9bl9o=!X6iy&`6riA+6{+(*)>vcIv zFTxe!rLf7FI$uYlu9#S11CGd>wZOH=H}=p3%P_7*ey(hzE#hoM z5J{o?uQ?S+FGC&K@4KOMU(meTWbh*-6Wvv4FVVf-10cSNmSZRyQOp^kq^UuzvDz%L zMUinKN5)J0&VvccBG>fLe;QVw&fV^Raf{T24Ki8)NmYv6KMeanxa_GW+0^2fmHj<% zIXTMFG8Da4@0|C_>D)g*Lbx-ro{V|8OC7Px7bR9-)F2#ZlA+pc-Xvi5EN|7_q50vx(yY<*JVf z&l30BnXcvRq{|;#=)YCI#qUp*%V!dnOG1W`OY`GA&IpG^pa}U!XM`N@m?~2sc(E?q z>MDVULB3{JaS%u+T!6eg$$U4M)@1tf%57QXL-N&V$*oSRbf|{?OW=z-#D47I1p$&iBaQEC(yq>=fAR%{_iPbF3s9%8zbvK`qMqY z2Bx8S{tSSdGiYj{4i1ar`u8$gOKnf&b@>!HFDJ6kbk7U$L~#1awFQhrzLW z*^~%YCZ8j{FytL*e1G6tqi%8_MI*LNp}z#W8BW8BULBBOa`RuunT$V6xLt(kQ>6xC zP@Spnp9u=KNUG*)ZiC(sw-KQO*Ne&dPkPhn=?^8vL)0R0Cg}|yrsd2K$Xq|zv-)^P7J!Jtc*+AmX z%eO|wswLSDIVFa%3+}6-CQ9T`R>tP_p;21x&1t{$W?WUT6eXK(72GPbg~#i6bH6E= zn?RTs`=iE!vlXAg;*0yQ``}tVan&MV<-%c70BgRatC6waql#0Pd8vJou2LGWJ<)Hw z+081`kU#e}wuw*KO(X4a4OoM{w11_v(A6E~bE5ddVZc_I@Mgglz{ z2%JIico0*9kqFJST77X?#% z=C)ah#N;TIVLgc+k?9~{ zL3MmO<Dv`B_VJ2@8KRbjjy6@c-u*% zGx@J48oTkrn*hykSY32Rfy~^eKOv2(WjV_%a}XC?RghY+j4>D1*RS)#etK6DY9~jU zAj;U37d>V79j*tap&E!uRcnSD_U)aW@Qb+Fjoz>_gC@53(bg9=vrDcmwHoBeU{UJ2 zZxkT0Xe7FVKr@g<-#P8)Sf0N7g?Y&Qzv)ujO1i8Bgaie=fAch?^juJB^J4@Gl0!pC z&knm)9sr-YFR&seqz6o${jl@Pexh-|GmjqgOG%Gl;FL0~;4HjIkHF&4(~9xa<0Q{blfA#?nt@q=-P+6`$kcHWww+mA`r)MSBVF-?zu9P89@#Rnuj86%3#+l4=!_ zxVm}><@b-PX@Am>!fE~)NA@|$PV9d+r|Zg{iKGBRuLt%!|9&f*uW+{eeqLnJL;;vK z`^e7^HovYMGN@)u+Rlup0{LH|k_#DbKr`68bg?B8=~TS^VGWpvQQ?u)bG`FC@63;7 zF>`tU;#oJ4+CC*=ZKtByZJLRaGoVNUXjFG4P;JNuJ`r|&obzEfm^7|ZcnX*m0-Bi} zA)rqIvsD?>;EPs6{?FA)ftAQyqALZ(0vo}xa6Yn2xW)xdx%}#c2)ybp@fz& zrHz@F4qL(gl(E=9A1js16f*7LH%q6e1)smJ;o40Xt8C{88IRJ=Oy94J?;BQch<_1h zGFO@djr1047bJ`-HtAO)k4qywW@-3`Vxuw45f{s9RL%t~0g?>JXxM57WBpnv;wan8 z+i*4=Ar4(-Opxu+olRJF>Famxw;zpp8Bj^ z=0Mw_SvCeAv+VB*JmStD`!DOIT6WqOMOO^}9acM;t=3&&t!mdfDmJV1d1q?O&urI6 zcOK@ACC#j^R$(L@92_9e&n488ICgwIbV&I%38OO38iKlQ!T^HR9^FH?XxzznbW9f` zE*Ss3!Bpf}+ywBJ6kz8a;;v;p_plZ5GW}8=ZeuWp|EBrfk!9hIh!J>_Cwgv9n~90( zTUC0#k7=Il$FoK8M@V=sjhj(ymix)Lb@I1UtPUW}`Kj@Qf>+bI;vv!vU#K?9*1RV6 z@4d+$k7!>;!gVZx&!S#;pWzt@$|o#vCFR5eYm`Z-^}{2{DnT ze1<;>TwCxJ!>}uT&<#10-x%=o12F8w%06s%G2Pyc1D*WE!kW&XJxj2xd!vR>2_Ml;A>KNa)h=hytsdG68x}T+O>`q$P&mx{Ztd}yMn%8SYI{ZS}wo5y!=nTIz(J(XM4w<*mW(lWc*x^&A8L^ zEwt14GH>Q|6&$7^o_r7u+#M?_gMZ%%%kc>l+vqj?46!Y}=_O!xAdEcX-+eY7Clwvg zKyo3!wdRo=S>SUyf3xhiEsqxv5WvIR^x0#fsC`Wo?||K9pd4)o!}o5mEhe_pfADYn zKHqYs=juFoe4eDmP&=cKAK-PF+~rQ&@gT=8uGwTx$wrYOLNVwQ;UNHMKmL*sjX1W3 z6P!PEY@~oja0qsx&8K2p^Qp*woZbIum(!$*lg|eA3;epv0wpzmh<4GQ_5pwzp7ZsW zyoR*kz|R-h@L~|^+)W7Ri5Qf53-rg=9J9m zygUk?G-uct<#DT9^fszW=Iyi6s-7=&SoFFnp+vD!uwkU>S$tVuMd6n?_J^KrMTvCJU>KY2SO~kTuhtVM%I7H9ToaO^D{2`5bJ|YIlCq7r|Wsb zCFW9JY2+%t;e3cGzRfF1!SL$;YEC{qA?ICSQ+U=@^T#lGX??_%fzwryivK67Y_QL7?fM?Sg<)PoCy6)u;W<;y6O>Gf3 zuNtbm>M_7-G_%9s*W=X=-sg+~%G2?ZV`**^1MqkTdurm^)DRdx7edo>5F5Qy%tgF* z?})|QI)ZLekvqRCB`5nL(Ury+*2`%8#o=LZjtWyF>Ywy=%nORedCI0of1D(j*cgr3 zM^Og79r4Axo^$$q*(`S>se~75!55BMB%}NC+bYeI{kY0P&;ZC1-p9pE5e4!97+Bx) zWck%DQ*ZDEg@g!TwY}#2>n%=Fh=;w{cn^t*rT!zJWd{!Fu1gcIM2L%Z2uvBPF&Tgm zoRLnz?py9hx*Q^G4b4`#@*%lF_h}h$7s}dn-Uc+Vskyh;y`K+R zb!CI79U?Ya2YTlqKs@;Joh*4nr#}4QFYet@M#*cE*gIfW)#F=2#-88PFx89OuGP^z z+qE6=nr<+yF#)p;w;D4^I$aGGy=9@GVm#mc^{E);V5zh|M%05M;1<$N7Gnc8r|l0+ z@&}>MUJNnw?C!V~$!w&H_CYi|9aMH|+Z6vLy2qRXc2?fwCNfv>w4o?`Tq&<}Q^H?* z?@`s&`g1VVlH1;#^4=qypkCV8hiJhQ0|1;D-&S%X5&!iQ&^U8(&_hv`_O)+uWf&%f zKA-s_j;01#gR_VUele|~k~XE;HS(o$)%r%j+sluAwuA8jB2FAXeY&nT?!ghb@s16o z^T((}?L16KbmJzIgE}m{3@-*IhBWRkLx}v%0S==08$U&9M1i2?c}jai`e~#z?H@JY z=4zBczC8&aYIR}Hqo2)g_s)~x_TW>o2eeDXG49bS!qM{qPP*Ju_-#`|)R32rN1I2& zOO5Xk3Nn+8|0_<>l_l)t7p+y&HPj#|;}6dPofR1%hOSs%{dQwtgF)8LL$7g7V_*kf=EBC?!AcpWOrTTw~>qqbx6#n&;LDvJYcBs$oK8O@0Jl`FE zg()aq$<^bgDI{OsTd4K+Xxed4C=Kd&d#r<%E;QS3K7KQCG*VFL6=W^8cf)p0B3AQ$ zGL2-7G2;8;bZ^F5o4xcco%G9 zi~}zWy^!OvxqYZ^trx!EC04-@yG}2NoMilXg!?F)6W=fLq>Eo1F%1X8*hsr<1#Ihq z52Sn#<2=URT`9l%qj6i7`per+QAL`w9=Dg2_lyi|T>V1@!K+vush69()5?f>@0%`| zu%`tu^EVbIs+_lAtk@u|q~!czSHJ2#|H{1~Vd}J`amK3-duElG>fG5%$bGa^V8_0U zlfjo~5-u*5aYY_p;Ik3*;XBpqCV{}sr6m2ZxxY8lfclFB`?SkzVxJ>!?(Z1fpQts| zQ!mk^G-_YVRiwKV?);Fp9;4(1a$pu`3PkMDQdlo$Ll%H=eCq6Pd|=r4dv; zzY;$YQ|~c7*yG-^krh2-rTP)aac_FjcGTfcAwD`er`=iOo+plRfHgey&Oloh(T!pc z?!YAB$8PZ_sP@XhJ388v(9)tv)zMvOI)5#`x^HoP;OdlF`fR1dry;13@BMNCP70;> z`&cIn|AJqgJ3Qf>`*ah_aC`X=j0qsnuBgZ5zg?luu0BHE#kXaTR%49o-1Uw;`=PO2X?kj0uPahWDF7oggME(9$W~$o2K1a(+Y;ZAas@cT{w*6=4S$H@TmX^$T1~o zFqfEo_E6XN9=$yfSy?}^PN4I|K>5!97nBcc_!P#txaD;b?YFvXdynQB;Gloy>fUFf zDtE2K((#B2cWFqpr?+-{0IZn}8TGq)#JYS7ofhuFjx6MDzJ+n0*0k@)`9J)?q;s_2 z)wmp>xoT^RMPv`rdR^XT{`PmY6hxz@UgRNoOvTs3jcNcZx9|KoYig5yfv{&b6fiL0 z%ogn}VNXYH;3!Bd+^2lE9t(tZaX>gHFK|zrrmLiOvdlVv&-X{w5r{R3gcXtCE$sh5 zGjO2WRU6)H4JVSIVph5kQWDxH^zk=$dEZAu)EVf8%D8v5Xe;+=V1Kff$kJt4mpfGZ zl|lk1V_XJc>&Kw&!@bc~=}_v#>`TG-t1!w%uu2x<$oH#RhYn&4rgev14kxs4`%W zlY4#gu3Ig~<)+IYSoXz#5>4Q5NpX1AT|LkHHE_?4x~br{cS#&;U2Cg$0&Jq5i{nQd zU0qktTJ1`}rrW{0mrCtl-Y8a0l1h8=AIWeSZE3mt{e;URVZ4%9rbv)vk&$6mHfzl;uOBtnd3`ga-)Dk z2l1>2LY+KMS4UT=u}lAeT0Xpsfe(j&&%$>prDBzofr(9?im|{+0gg26^oi)O*+7gT zFHlvhZks20V*^i>F0w@Yj{R65e64=8p@m?Vg=u%~l)-#<*`Z(8bh=YjKbynDnA4=K zGw4JJ!^uK>l_!N+BZ^ZMOoP02!`g(^dhA`Oxp2nq3M00Aauf6h&C02z;~TK5F6d>c0h5K z;-`W+xsUxz%3s?n;(#BA-P&t*e0GV_{`ey{_d)9zpb@j{W!H;gcJoE{oNeD@+T9Ud zsd|jKh~_OF1ZKAv5)Rqej@asuaN`HIId@~jFvIsM9gELXLF>5Li1`x7C+YY~l3nhsm@ZY4AQ@w}u?21`)ca-| z_o^Z6Y-|*~A%o)}1_*MUze~MpbXdSk!sWes%qJ5G@FQt+TKK~b$o#po8n%$4U^jxD zC31Bwz)@mprrv$LXNrPFVyoTVYk(UISzKFIGdK=-_ALLb*-)Mc3sdz-RCUBQZ>RKX z@xErBN)&95<#$wCN0>R9(M_Sjq#A~=_MKXKWS4-2=y`B_=#L*S4oKfKFtcK(7MD*g zE-E5J_?=ZX3%SicfuHv#Gs0dAB-F<8q4*~~jE^`yV|wrtm)TtF$i@Gcmzx{JHLT6& z#VS&A5zF67)%npQf8iM7)zG$=gfjSj@U`}@TJf1YI-S33rzhvm&HT*|RmQkR9a4ZU zm?!bIaSSbg2qn-|S-N-K))7*VSn`qFp}+0jqQ}z48do{KnWcgksbqan-0HgAJ0nJh!qed6<^Qbwf4S>o|QxvK^WXz za_?5Ok}U*72&TBH#ld0Su3M6aqJIc|xED?^b^a1|Ee|h24Vl_g-o8td{6Db(Z^=9& zc1c6YCCKc7rFmo$WP_yxq;JF&;bqgj?Q0zjY{1R$ubxijPpZaZ8{w@(RRKoZ?pO38 zxjEw(M&zO*uvF9vre`eIiFOIjS8eRg56=+GcYpF=Hg^RJ@$Ykb30^T)++GfkFq8Qi zACzSDpTT}*rZpY?p|>60Pd(Gt5YEVd=fk~PmFKIeJokE|mZnyU(0hddX31lwo9{t} zPH8t2(umrO@&Iik?%NUX5DhD70zE4>41yhsKs}e9JBVjXU01zj7Vn`HTRZ){A1?f? z_8K|&H)5^i4jv>;4>pq_$F{c5Q)>=j@l#(g(**_pkdi#d|Iv+*x?W5ASwa}me$S03=G{(!9i*CuSur|J{nABK1(2qj*dpfB(DJ1@ENWVs1r zM^Y6F{L!SCLPBl2&CVZhPQVK-))XEd9y4J6a0=SEtNstGZ#g!g19QM0%J=`(&Ml6u zm}CxF0jjjB>cyQalG*%=45N*+)VpF~S9N;$Ee=pkpy@=+_`;;?qint(dLH$LZn4_- z31{h^;Tzx)ym|BP?uKhMf#N$&3hp>VEKYl?2lq8N@Eb8=%P&b`Vsf&l)_kDZ=Ng;3 z@CVZ@smX@&u1Hl&#rC(E+^%1VAaN)Kc$9f}rdYh@z>PWgmIgP_p?#urn-f3K`m2B+ zEo}li%`KILZj*n+QrSA7j2ii{XZSm+h zecyR^@NxUqL>1Pq^r-G5;C74{qC+>qyXlIGE@c2qFP0C(&U)WF<`KNr4jZu6t#6J93%q5(eLR~q75=Obzy|u? zD#0z;Th4@M1fy-H<{*?LhIqrx>BNnGYDxn=t-MX=XKsgghiU-+DudKB2sHW-11vhC zW0%!_(^p;7&2)KdU-CeVJj1?g*us!$v`H{_$^X+i)zUIIf82jz{_#)n$H}X`OBytFIx)eEFjaXHT zgT1ZTNkikF>}h(pX;!bSOz45_X5L`p-H}w7kJI$W_wQBfEssYn7*NpNp%(Rl2*!*| zwev2h(@xBiaqo#ExN{e~qQ(5V_HUwB>OSElQX58Y+1fUoj<+d;&{ z)Gf2{R$U$(J%8gmP3-T9bdMdA!~aRCjrz_l*M1uy2U*^ytbIwE8o^~t9|Ca&sv5~* zW#;IO9mw$4c5%Pm@D-@F7Ctly$We<<9~m2ejK9|oFKV~Q(CfP=GeA5?7~Z0xp%sh= zW_&_AAD{FK2KjEuOX&k;-DGGjv4~x+viZmFT2=Rs^$sn7LeZl&yD1 z7~y+_60&KAEbxE5K}MGFE=0B$WoJA=?)`0~f7j$_brmSu1Et@nG3riaWTSp%WN0wv z^3X7A`I-+Q;GJN7{C;aZr>M*$C~K1-&r~7zHk?jM4vd_8I8SrW=h#~0F}6)jb5C`r zU@+Kjr8By+M76g0cTTX^s>MJ|c7;Jx43cuMGhZs+5H+ktjY}rkcPJL)6GyyEu=tbV zLmu01#Zo{5nI$>)A52>=RsYcB^g#Kc$T=^O!d(WfoGm+Qe3Uai%6FySPAHVtoFTJ4 z;n+}BRgvoSU19A(Bw`F-?SVoK`c|y*e69KXu$t?GyW?Lf`ReOibpQ31^j?=YNGGZ@ zD5(2imOG9Nl8k+40^;Y>KazB_$M9H!)-)BT44kx&c72CVDY0{U9B3YS3X8+ew*-J~ z*}GZ6w1;DgJ=Cwqro#LxW%7+{nh^`p8tT3qG>jVf2OGiHMRLA;T_zPaWqDk;@29WMhBkG+9`bA`XWYvxCc z!2XF%I9WZb3qzg9OST@Lx>lUiUicn0fEIdz!2MV5v#M&Kw?=hVOd@wXl(icY=xo-+ z0Fi5yaU7OS4ILx2w0{i0FOD*Z1tu55l%9Vkv4NiTq^!*F7p9HmfS!%Lypz9@s$N1{K4Dq&^_hz)PKM=?^|OfevbBady^KhCR<(Q^ z0^d(>lk@hv)+1?!QG+9U1Aj=VZWrf?I2Ug5cio#PMq+tifV;4jsKB$R6h7N;UESR7 zMoIvr#vz#zwb;VkMqUfAl`-{ZB=mY9F38v&d~#Zz({=x;YYPlsy$Yb)DXzb9=8x+s=M^_S z#RuirS+L;dbR>&EZhB^k_QaRx!qOw}oIH3DKD`<&S%yDHQxy6N)5+{2o3w%xS;adu zhW>G$vbVA_*%oE5COHFoCIO-&X4PDMi_F6g-0 zcPw@KRjz1n&rC>2C^?pd-0pcZtaR;797yYwjsq>LS zCC}ubl_DumOdpRHda^>yk4dY5r=_LkuuvB|w+oAH{-Le?a7VaH7}N3Ln0kqA<9OGT zM@I4{T%W%kkjN`#qd7(T>|y_NIGv*0G_XfaWYQ41)alXZ^uSIpqd#4*5e$EgqAPea z9*m~zQR$+(+5uhlL$xdA@HijBn*a0!qCUCF{-!4snI90Eqe+2d1)BLIH)e|*Z`nqS zJRv}YmsD<|2j@Dnx_4en5xNn0p>-!9=iwS41HgOtyuN&1mP(s|?k&C48TDyB{f7^6 zQ8!=IZW&5T4amMzw0m)m)cqHzYCm}P1gffxYYnd$f(!Xx{D9+m#Km3-=D@Mw6HhzN zpanZV7}0D%W~j_v5i`a{#Af)jd$JyhIyEXE-^Ju9_?W&x?X7v%x3%E}_BP?S; zlb-%lm?Xl3HU4GfdgNXr@-$FZmxd{0b3mxfzhNV}GPAVn24Cb3ntFE)@N_wtVHCSv zM|Uy~=zUq17j_WMe!hG11IrkHHt=UPYgY)u$CNmLg7Yg)=+r>^YZ;+ZfhSG+Kdt3^ z&Pi1J!1)EAa60v9*(adZrbs{0Xf9l-w=kll#lScO;RbcGI_O+tVMMEVt&Az*9&ct0 zE3$nRg?XR!5Q^A?AJ8G zc_}&VmH_PUxc7DJc#V|{p}6<$fXG#^T-dRMZNTq`^DvVOQnUYHCsYxK0Qg%3^h^I} zdrimKuxpLesN)j`niKO3b6IKqF5U$&6mLEW^DpWjL{1b;tO_o1zw1Jfxj&!t#i-of zZAR)oRP6E;cWtb9yb`iNStU`&wyO(@u@fIO_k!FH>o&CsZmfB3&r)9Q(`WdYuhDg; zSrLcf#(qoJLPF$O;}|DPE-| zU`{6xpR};)K{)>(X)Ql3)bK<+ef=m-5FUC34Y?oA4 zb1_^EBLHM-xneJSaOLmTWtG)A?wZx;z|f?q?A5RXVf{&b)Q66_@Y1dL&d5SU{OhQ7 zIY?w_QjmnJkc!kKP=l)9>nyl{8Ev-HT-ZP@dvi<^UvAAOkzCmG0*Or2_4OmFa5w-x z2_MA`_(}?R}5ys+lp~9>WUI`@ARVhHJ&v0y{>j5MAz7(ZKiepNdGS_qnuEZjG_4 zooG3QRs>jbBARa0i+2n{Z)lZxFh%ozDz?8$UZ)>>nh&fu+@IDhO6!X)_=$uY9gC}r ziH0}OEA$KrlhC!0YF%-H!0%5fZ!YWU*dRS-++IePDyu@yFZ`^T?;8Onv#%aK&9}ya zbDaK*k8E0VcCARqR13V5a}bya8eZrl?E(f_V-UFvTnq+7OEY3IkSaj>Y z4@1VS`)0vGmaijqSWO*b^lbDm(!QsKPn^>!#9z=qM{EjHC?S1Hs<)5W9de!Xe9`x# zr*H{91ynUStjSbiyH%*0I#%QpvMe3w?#i*3G=HSeA3A|bq2Cf?Z$i|vV!H9BSE2-UXN%U`{HRysXdFm zD-th$QQW@r%ef^;rZx9DJF0xRBW~I{3a9XhQU%dgo}zJ@Iy!QalBf~+-vu?RPwcjSl%qvNMkdw; zcQ)+`Kdr~+tMzUW)h7TsS7qckwsU#q zd5Io8Xwhf(R`?Sw^?OoMA4_ZIN_6dh;B5F{2r{rYfw?hhFC z8f#HDkfkGeBCfu%_B!XHjM$P`6p{$aJT8%C)!?FFrju+S|Fck_=I#zgj=p=1Lbz;FV_Zz8zZ|rm#NdKF}G`0_arzzrS){2y?Afr?GoQ`zZH5L_USBa{An$0k(UNa zoyPYSyC7lw>4=>GPVhSYlVabAHgaB3tm*k)w7e&H!m3E+h8+~_BF!brIBdu(CkKh} zX+1Xxk?ZLTRG^S%e)!bn)A+0LJPpfSxV0IQySr>aiXSSR2-i#i30T4}pA)wgv2J?81n@GwW0 z^GZWiPS%iV6%-)ywzLFru+kn%sR^70gZB%}Nh-bYIL5$wos=Sh6Y3~x@%(*@&& z*SzdZv%!2BWre}af=B51gBFke`7>FJtEX|Fgye{c@JkAP!!&a)$#JI#d=0T?BBtw=`Cn?^|_)j3-61KCL++mqo!G^AvAkQTl8=`f0`E z$}UE3t64wKqZ`3dK|vCDqs4{qaJ2t<^@{FFpV4lJAg%rGi5p-aXk|>1qD30F;g6nQ zJ(JlLrU%H_u}p>!&Mx!g&i|ycdBaIko>cI(PlO8)d=viQZ3Y9m3X+vSmv(zHmTYgjVuY)Rni5iH4c=dRe92FBOKhG7_{ppjd^Xe5;F)Hl ze?E1vCsv?N%+IIA5fcwlMrhF8enZ06FhBzD6bl~Fb7e&{Qdzn$o$CX;_sF{#VAaTw zxP;3BL5*VX7k|7|5@=epFxed(X=g3Ab#EDhAG%MeAIbOqdXT|+NQ{WXZ^K}*Xi?{Y zS!TtECKp?J8V`@Luy`gegm4JxNSi{EE4BnoZy)*lFbzVD+sxJBaiXo>6glfyo$J7c z2VM0f-N_UiS(!6xAR99);iKBwbMf%I^OUO({V_Y?-Ztm2e?Q&&zZcwQU9Od$rFOXq zD!CVZej^p0G|2YUpZ~o;M-LKf49?R=g4**;lZiu8)dL1xZ~UKR{hv#XD6pX@P%wd* zsIuE8$0)z;2J`PP%!lftuN8$|qIEX~%&Ka&PT->5hk zPJK&BVRE&0IiOZpIW7}e>@CX|k0A?Fo#wT(x3jgiHSOM)2J-+t+vZC)mv8G>sF}GK z=Lq_vNThsx1nXFEl>#TXX%>MN=d=o+v@I%#m_tKDlXQ}(mAHB*O~?b}xrmR&eQ!}B z6i6D#33ue>AuGtm^6d8fsCEw#ejaecRRbDM@E^N~^zP{B5dG&nH#hg?^XH&^hF;jm zjAC^I10_dC)|QqQ0z%^W#B3==#Ry<-EI)UQ&VFQewH?zKDt)$5HV-e~+5~!UkH!DoW)yRBa7b9|&_`oOyDzmpazO<(Ts)@jbHjdRehM5M z$32qEYouQMK)ra{3%Pf%FE0Mk-roMRm2r{XC9oop!ageM+CfXp*3q#DS&v3Qyu7kf z1^0n!BK-XU?i5dS2nOz^vp||yfRK!gOf8t8V_--Qd$v!nzjg0!^`EhZ2L9~qEHdz# z14)cDqnqVsA&dLSx+u8(b(Ox473qw@X?3A6@bKW({*|m(ZzU5Y-9IzBnJ-EGVG)tP zb-1^ep=xeUEf+)fs-E0T0={?KS{1fm{5>h|A)6wb&Erg%JI*>;27%j5VZ)A3PEBoe zDX5O*WZ;LU@eD@bvR}U<--Rn${BT?EMm1z3eyTjh%gXz2OQ}NY?vHhRbzkJBk;6kb zb~o80j_2uO^nwEp_m4aZSWgIsi%_(ae)*>tTUhT(&qLzlbYXcFyl^?uxC$yvKvF2_Y4WNil- zM`XF@`7a4HU0JPdZF)6dgH2S0;?*kV3~{bbthU=RY4mW|Omxn9C}iDQqF`3BeHjMc z>c0_k~;M500Ln`;<}i(r)Q!jJIhY| z$m2OpP0g=-nIU)^{y)T%@G~qwsh3gpoQTS^>jxZw>SC8l!NHeSJ2Syu#S%P>jYLJtmNe6EK$Fdgeg20E2TH% z^d&<>ib%Uaj$L&|+}FNawjmpkOF? ztuJqsv~-{J$UTBKx&03poeD*)%K2j;F$RC93^?tMC0(0?Y6W$>7r2%(t18x4KY@0*Xhy$LPi1Oq z!66%B4P?l}mv@0qUl;5VZXt|Ui>M9kLi+57tuafyrI~c~jJq%5P}+oeR2!I^Gupfl zuk@VsI0u~Hxf7~Pf9Ap*RMOGqEMeIswd;8tAM8;%Ec1{LTp3{^8?Vqyluz7(n#fHX z!*6y2w`X2&2UCbF&|0JxX45~q_}yP~Id%9S)m*VBbUdrkWr_MlW_@*;fX70lq&dK% zemAf}&y@0+c2Kv;u@vxul{Ff}jh5)j+UhO6-_rN)23tXPGmu&X5As%=<&{V%ilnf~4*60jD* z|54z~y1x#m+&5O?t%B_A#HXalN(J<;XVgI9p9RY@gl@B5Zj5`<#u_)pEbA+6!5!iD zCyPb`c4}_ra@GbVV`HkNIvQJZYpctOI=1cFjB2J|bDfNqsZA&S5|9SH8@z6OS#yy^y zQ$3)dkZ&qTOKLte>)r_oVPl8fyvzQuGV{qoYw4G3yH;M;$+N0C^Q1yjZW1GnNm$$G zHqr@ATykF=dQu_eAgdIV6h&82M9*kqtyHp-iPt1|Rf|VuNFV@Jg0C*Twbve8PHwNV zN7z(Ffv^Hw2JITAshVr{D^Pj)#$$V}3rF<<*)|{oB6D9QgXQ6~Oo3d_21iF_)Coi= zaOH#YseQnnvq z*ue87|5dDc_)X(}Z)AvCls|PygPGYhvz6a=WcKN$)IG*>jma-ll2 zwfN((u|fG^;L7qW)s};8|MW`tl&yK1fD2V?F?AED&k?X)=R%^nWaOPh;5mE6=~ID` zXcn;m#P$=3?5mHABZ6M^w5FEX0<0yb6{4pRDs|s1_{`@p3Nj&TK=(L_Sy~<~?u!nK z=RPlCkowf%M??w zkDZSy@Qp4z^graa-MjEon%#X{u&}bq^{p<;c<&)Pwcwdh&z7APj#W0o*M9fa+u2XT ztFhkv5@V);bU0wH{oJtDf=y8S)b$UHe~&FqGVf5pPt`Nn!fdGhS91f`l5UDF;)T(3 z?sr=vW2+i@U`bM&oe*qN+r3FiE>4dPMXQ)mj)NlyYv|l@mcz+8e8#ZKIDi*jZq7|{ zdSm-u#BWM!@@j_#M{n$B=GT$+^#gmv)s_3=($cxN2%MHIcHk!tY@25AOHdXKk3!@; zZn2RMYnTZz-mDK2YE>ySJZhx%*7;v+K#;U^X*#bCs9ohOzn zIwewl*{}W?f<2r)UMuWM`ohOZ-D62IM$oSfj{%m${iX+Z;iDpcRE;B~F=YKxLW%U2 zE=S&?VX-`)dDpn?f~i?sT4DOkET?2IwZqbkk^My> zrs08pd1vT?c@tERu~1in(wS@B)jWZEsfomLpNC?(dzad?dn^(ts0g32WXX*0?eSX6 z6}89ITL}}G%dbD$;#bdY3KiM1LmEMpIXJpi;RL7L>%?mOTv9YkD?a&5Oe4Nu9;E!~ z-Y5Se@~R{JBh}2W86ND!!S4%gjeEqM1fs8t4}})SR1K|Tg3bOIa*R3`^F$NWk09$x zY&qTT9lkDa;!aN5{k{|X`p;N#DtNjO!U0_PQkR6&2w3bhU~g+aDV8Z5UVr~CH_sPC zg;7S+NG!JaQCP6`k^`TaDE@^2dewNFzZmwt=QG2m%M)hO{eM~iX2EKOTemo#fPl!X ztl6aEo0{{T>xKi&L|e63MKDY7 zTi@1R?(nB6(*ecwX$0>qNhDQ+cMMpF`i)#fu}?dta})|3hwU|uYcpmVagg6N%OB#8 zhmk_((>U0&i?=A2Gh(Vj3IIVlf#!2Tb3HNSa<$qcmVFH(zKg9jg(O`nQ`AkBT5}WE z6kQ__Rs+Sxsh${>|F5U(jB0A>+9HZl1Su+rluHR9O#@P;_udhuDnvkfCzPO|B3(M6 zOP5aQEx<(x5UP|AA^{0K6d^$9e0lGEzxVld*6ca6X4aY6YtB6TncW_Ec28whLW?fh z83(D+MLtLZ(C?krSw})mTfdlg%4(7{Oa8S;K|<0LJ2uWjG^b&I(1BxqNU`UJNR|{o zK@~HWC$)3o!4IUH(hD=?zE0ZfrRvlaUe2fUKXGYr2d54dOL{q6AP)i#gYCIwwncKL za@k!aEVEDXJr~rhI6*h@9Ojf0#rMO9ccB=JOkD}sM_oLa35s72YRTQbJ?-Pj1l5=z zEaLsK!P8$)qVa%nKkrh~hA&9od559@=Fs!-+*_kq@@><@J$7AieT%O@{Wjl2Ct0tQ zTph$${rp)!Q3dTYz&-%Fd$@PINyR87AHpQc;Y2zuIZg@#3ru*`qscORqpB-(bDoO~ zK!%d&KT+_HrX`NZl)Ek+pw%Dur}eQZh(BL%({`Ms)c40{>{Ds=*5=V~WBe)xYqEQe zkBHLKnZ2MGz#AQ;BB}>1spvznnIrT+4O3!a0m$xNx^c5X8CylJ}o`|T;HK@EAM3_AXqJwTRE*GB8urSRZYWQSTA#Q3Z%pGBIWwB z=Sq@|&79}QOmn{aXplH%N~~7MRvS_?m9vV#xNJpkpf6}&!bqxY52LzsK#^4N&<)fA zGg-*~9a=Y8GU{lazmT7^IL}VMrq3=+t9O}eZ?t$patkLgO$2jba5lZ$UZY$7FycXR zJ{~O;^A~~1*I3qo8Au#>4Ww(^N&DK44|mA5eHHE95hu|usJQw<%u)INH8lF9e|K;2 zd3{EmJvB@8j3dx)@wCYou(`PJA4PZb(@(dVT#Y}Fyom?%K=Jek$6JXKr&eEcmB$b5po~Rp=GL@HVE0|SP7%V4 zpy4X``*PYj z00cvyAuE6cl9C2noZg--k$^DS>%Lt6}OdschP@`lL^#1p@tLyuYUlAq3U!uP zX!)`v;2V&LFR7mJFTbB|d06smy(sf^9Z%#;$m&(xwlSt?b4J%gVQPX)qG$M?APy;X z6u<|^Vv{TV6dKu-XR!Lxd9@6PD%_|OI;+79X>$!&#S8dA-v@n~6SY=};Up~HF6`gn z?sqcp+_rvb97{&A_@hp2SP1GGD@3-a_S6ViSYg+Fd;_RxuGE-IVYK|E~H&YLq*-^C@I;6a+C4(3Z5a8>iddM>plbY z&||y1u>6?+>2}tQ&-apPVwm#rO&!-&7Qb_aHj5)fz?P1k z0`bs;2N$N6Hl_tJVni~^Nsa{Mjd=z?&&`itbeVO+dRCb%X?DiXfhkN-l-d9r4HXxK zXoq1~L1#7VsgR301%OhYO{S-KrT(c#dCMxryh@D;F@Uyt^$*H6t#jUtb?dpdqN6cW z-xrGV#2S2NV#1TqlM$;|kItjJ3MaPpGI*UBpQ#wdu9asp5X(}>7k0zUq#f=%cQh7L z3<8p}m3tA15`pC7uM%+2!M6iy;`u^ptff<9>+*Vj8EIe=FPQYRk{7}1(NF2W4y$LK z#0G?s-_oBm`dRuly*z)>%)z-<&l@SalC}@^ea60$6Bm4JM|75N4WRmc_`Ej zpIJo!eJT<`J7?SS&`c)A=z>p&lrMjB4~3W~K3gZsK9qw%Z_Nm14l~NpJ zR=QBxwp{CuEt7Krj=>%5{t%sqo4MPDo1JQgwQGkuztW*_|BLta0Izz}W16DB*D z6w8COF`^xT9H;wqjAm4CKrr9R%e$%=>bjy-oo^^)bChLi-iXWc?xucutyO|WE#UpL z$T_`iyYJQILm91#f+}JE^saBre%2DNk&b?=8hsT>Dl@eyhE~I}M(FP_s zgmD+s41W0b-NNX*Q=-72U`R8D&7lk=Af-$4uccN1tjkYujh4Me&d1VDvc?lm!F18L zu9?(TxEmYy=dViA6L!xY9e3{5KMSTZ!%hD2WncBE+0F0U>E8DLIf?#pNhjgi@|^>d zu}9LUM+R&a&j0RTaRQ-cxH(r$p7u+uCsL&Fo)mB;(f1^F)Nj<{VX8qBOjMvK6;c|C zlEw$k7%fK^@@?AG24&n_R28e#+<4#n!i4(9X9vzen8V*m*F&F8H^}^QI$b0%UR*#T z)^g$~?qqr^9&oAZUpfXm&VKOznU(Ow(}|osQesi277DoaXfu_?#CT{6BNK0wYosk8G6XTRSq7w=!70hnHIS=L5EP*S2QErM?7tpF zJtlM?xyX1i(Xp5cXCS9GCR=@Hgh~u@M$f^?in}Yb-~D%CLJ;VQpIX}IUt#BYDqFrz z3ek>NI5pA~rdq(j-J3C8zwH-WSY-T1MI^AVCQV=`82MXrBJ*LkT_OZ^lIBc&DqaS7 z;I195^9HG)`oNn{c=_iW>Kue*T@8G!#xYU?u2B`Bi+|@*H2*Cq4{xc?K6Pa{Uw2RM zwQhII`d-Vqgz-$$(cO!>zSI5<$^S`8;9w+<%g$p_+hFP9u`eOT>*u-2eM zs+lVsn}63CoqsUCadU=}Q%e`_`*50-c5kNbK}Os8{nfM*z`>b{k3G-)?ZI{{kzel3 zi49H?C-WSCOA@J5+#oiza$XJVhZ=&d=?>DtzQTv~d=qxEKHD8XmF zacwXy`w^x!r$XL{{W99w>4KM~??!xma3F3g|4Veg^-#x2{Xo+(S6#$t41JQ>_xa{H zx!~rY74zu@OP1usk?3i`*}DM3o+b>cm9CHTd_P<<^mD1%_Tyoo-lU==4Jll^ z-&Mk10(y=bdCO>C@l02eJkMo0$N69j)Bhgx#Zbym>Sv)?XpgbBw)VA*)_vTgpl#`W zCP_qqD+tXuWIuO@dlO(E{TD%~zF9#B<{`1R{rGX?SyR2^ZWAYzT)kamqEyI*Owgkx zJOF!p!l!1~Z@#N(v!?JbpL@vyieu!EWVATHkJeyx#oDfFSvrp(J&3(U5BVL(Hv9O2 zk;9!)BVExPb?AyzTz1`Ay~Msqg1Y~$HbPd z9R4r@SM`Ff_!#VT83Ow{5=FjU>=7H=q~*wSqBd39T~FXVzP6+an>I5mZg0vTcQCU&bC>Sv)MgP=rY@=wI}Anhx1smFQ6d769nYZzvnx#?U1j=XfubUd@M>pw zzENgiLSyWr~$u0w@6eTZh#H)KoUg_^wXHwJB)K6siNRA9>pI0w4P5g8&uM8Ij|HSR_@DLQ|b#-)l9e_8x zG!>66Urp#I?(Xht7?FgIF<)(y2%$3UH5{R{wDWxf3DWLCeqx#DzeFSv`ZI=@*$^%@83x^fk5Qr`Ms%x zkXOcvDLvB`T;i@Z9u;ir-}OpsgVnukr8|InqE+ug~=<^-0`y z_%2hF<^?}iUZXBe>N5~xReA1&dzrde3mgDx8`-jd> zpG2V$8H_)`I1GYNwRC0u{MWfvk3vXaGgBe!#V|N|iFm@g&DKWYSUfYc`j%A4$Rjm3N zvE9mS>AYckF#KpP;fHFJg3i%9No3!S%`vj+uj0yv75yD-Vx9p<#mhO>RnBsn-2#$5$^oK3j-1+hcc$6Wjhed>{XI zF&V6)x{2t5%6sgr6VD8_J4F{k$_KKc;v5R{@=GTt(!{yt6U+J1F#_84b75iOkC2y= z5T(1zHBoaR1=_sj>vl?a?HXV85S|aO`H-R!NYA`YFKX)fD|hxVOMcdo&YA0nLA65E zoi=nO&Z{Dtx@YTRC)P_p_O8TiZj()ldwF_Z!!(K6+bkEl`1}@L6n-9<+kDR_K8(-_ z5Z5%hKg#x~YXL90Ao(EX=JT;$??&B&5Zv)J0lWrIwZ0Yo*a(3@pe-Lcbk@pTL@e3J zD=TV8>6eltVYN0jdt*Uy&BV85At9IFoFDr3Dk7fPb%aq`|G==AM&*?_KPeuRdd9PA zR=qIdGTG$X-?6fmSdHIn>n{MqN6!tqvQXl2yIEIzAyDj(vx+6h6KTTJu`BN`X^y##D&970DKB0cQ6mTU6ge(9$%9C8yvVpYwlH=I@T-6B1QH7vDN8 z$qBSyA@Toj7P$QVo2>Wu-FTd$HdMFZXTsy0xEP6x!vAkMJiz(^ZjYH{7D(*^luvHh zb=sh`%ZA)3Nu0w!SkMRy2#Bnx5X{cYQxQnDU~HUz9|+IrCE9w8Z|_V^WX-p2{EMtb zlKlSZ!#GcG@9UG|d$=_^D_T)pbV{On6R6PMcd-lpH!+l!U+y1-;?I3$`U0Q#qtPrW z#u+1Ty{_{Xt1^Fn0@JqE7T_=T4^rCLXZg3T+=cu}Ru-$3tu1b8>AJCYi;1Mshs+kn zkh3#O@7LfqQWWhG{9x$tAmYEnhU8uR$gvZD*he|}melNzA32NOfhR~cmx6MKcWP+1 z|22EYuQXRF1-b|c3W|1bZttvpUUP0nR3kaPe;|pzqRya5&_&I3OI;&DOHE(3TG=lA F{{ZJ*gEjyF literal 0 HcmV?d00001 diff --git a/documentation/img/qjackctl.png b/documentation/img/qjackctl.png new file mode 100644 index 0000000000000000000000000000000000000000..afde566f9233e51cc5019fadd3c223069f444ae8 GIT binary patch literal 21890 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 literal 0 HcmV?d00001 diff --git a/faust-src/Makefile b/faust-src/Makefile new file mode 100644 index 0000000..14ffe57 --- /dev/null +++ b/faust-src/Makefile @@ -0,0 +1,99 @@ +all : jackgtk + +test: jackgtk alsagtk jackqt alsaqt ladspa ossgtk bench plot sndfile jackconsole + +svg: + $(MAKE) -f Makefile.svg + +puredata : + install -d puredatadir + $(MAKE) DEST='puredatadir/' ARCH='puredata.cpp' LIB='' -f Makefile.pdcompile + +alsagtk : + install -d alsagtkdir + $(MAKE) DEST='alsagtkdir/' ARCH='alsa-gtk.cpp' LIB='-lpthread -lasound `pkg-config --cflags --libs gtk+-2.0`' -f Makefile.compile + +jackgtk : + install -d jackgtkdir + $(MAKE) DEST='jackgtkdir/' ARCH='jack-gtk.cpp' LIB='`pkg-config --cflags --libs jack gtk+-2.0`' -f Makefile.compile + +jackqt : + install -d jackqtdir + $(MAKE) DEST='jackqtdir/' ARCH='jack-qt.cpp' LIB='-ljack' -f Makefile.qtcompile + +alsaqt : + install -d alsaqtdir + $(MAKE) DEST='alsaqtdir/' ARCH='alsa-qt.cpp' LIB='-lpthread -lasound' -f Makefile.qtcompile + +ladspa : + install -d ladspadir + $(MAKE) DEST='ladspadir/' ARCH='ladspa.cpp' LIB='-fPIC -shared' EXT='.so' -f Makefile.ladspacompile + +jackwx : + install -d jackwxdir + $(MAKE) DEST='jackwxdir/' ARCH='jack-wx.cpp' LIB='`pkg-config jack --cflags --libs` `wx-config --cflags --libs`' -f Makefile.compile + +ossgtk : + install -d ossgtkdir + $(MAKE) DEST='ossgtkdir/' ARCH='oss-gtk.cpp' LIB='-lpthread `pkg-config gtk+-2.0 --cflags --libs`' -f Makefile.compile + +osswx : + install -d osswxdir + $(MAKE) DEST='osswxdir/' ARCH='oss-wx.cpp' LIB='-lpthread `wx-config --cflags --libs`' -f Makefile.compile + +pagtk : + install -d pagtkdir + $(MAKE) DEST='pagtkdir/' ARCH='pa-gtk.cpp' LIB='-lpthread -lportaudio `pkg-config gtk+-2.0 --cflags --libs`' -f Makefile.compile + +pawx : + install -d pawxdir + $(MAKE) DEST='pawxdir/' ARCH='pa-wx.cpp' LIB='-lpthread -lportaudio `wx-config --cflags --libs`' -f Makefile.compile + +module : + install -d moduledir + $(MAKE) DEST='moduledir/' ARCH='module.cpp' LIB='-fPIC -shared' EXT='.so' -f Makefile.compile + +bundle : + install -d bundledir + $(MAKE) DEST='bundledir/' ARCH='module.cpp' LIB='-fPIC -bundle' EXT='.so' -f Makefile.compile + +msp : + install -d mspdir + $(MAKE) DEST='mspdir/' ARCH='max-msp.cpp' LIB='' -f Makefile.mspcompile + +vst : + install -d vstdir + $(MAKE) DEST='vstdir/' ARCH='vst.cpp' LIB='' -f Makefile.vstcompile + +bench : + install -d benchdir + $(MAKE) DEST='benchdir/' ARCH='bench.cpp' LIB='' -f Makefile.compile + +sndfile : + install -d sndfiledir + $(MAKE) DEST='sndfiledir/' ARCH='sndfile.cpp' LIB='-lsndfile' -f Makefile.compile + +plot : + install -d plotdir + $(MAKE) DEST='plotdir/' ARCH='plot.cpp' LIB='' -f Makefile.compile + +matlabplot : + install -d matlabplotdir + $(MAKE) DEST='matlabplotdir/' ARCH='matlabplot.cpp' LIB='' -f Makefile.compile + +q : + install -d qdir + $(MAKE) DEST='qdir/' ARCH='q.cpp' LIB='' -f Makefile.qcompile + +supercollider : + install -d supercolliderdir + $(MAKE) DEST='supercolliderdir/' ARCH='../architecture/supercollider.cpp' CXXFLAGS='`pkg-config --cflags libscsynth`' LIB='-fPIC -shared' EXT='.so' -f Makefile.sccompile + +jackconsole : + install -d jackconsoledir + $(MAKE) DEST='jackconsoledir/' ARCH='jack-console.cpp' LIB='`pkg-config --cflags --libs jack `' -f Makefile.compile + +clean : + rm -rf alsagtkdir jackgtkdir alsaqtdir jackqtdir vecalsagtkdir vecjackgtkdir ladspadir jackwxdir ossgtkdir osswxdir pagtkdir pawxdir moduledir bundledir mspdir vstdir benchdir sndfiledir plotdir benchdir supercolliderdir puredatadir qdir plotdir jackconsoledir matlabplotdir *-ps *-svg + + diff --git a/faust-src/Makefile.compile b/faust-src/Makefile.compile new file mode 100644 index 0000000..0681b10 --- /dev/null +++ b/faust-src/Makefile.compile @@ -0,0 +1,15 @@ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +appl := $(addprefix $(DEST), $(dspsrc:.dsp=$(EXT))) + + +all : $(appl) + + +$(DEST)%$(EXT) : %.dsp + faust $(VEC) -a $(ARCH) $< -o $@.cpp + $(CXX) -O3 $(CXXFLAGS) $(LIB) $@.cpp -o $@ + + +clean : + rm -f $(DEST) diff --git a/faust-src/Makefile.ladspacompile b/faust-src/Makefile.ladspacompile new file mode 100644 index 0000000..f9d7ec5 --- /dev/null +++ b/faust-src/Makefile.ladspacompile @@ -0,0 +1,22 @@ +DEST := ladspadir/ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +modules := $(addprefix $(DEST), $(dspsrc:%.dsp=%.so)) +os := $(shell uname) + +###allcpp: $(cppsrc) + +allmodules: $(modules) + +$(DEST)%.so: $(DEST)%.cpp +ifeq ($(os), Darwin) + $(CXX) -fPIC -bundle -O3 $(CXXFLAGS) -Dmydsp=$(patsubst %.so,%,$(notdir $@)) $< -o $@ +else + $(CXX) -fPIC -shared -O3 $(CXXFLAGS) -Dmydsp=$(patsubst %.so,%,$(notdir $@)) $< -o $@ +endif + +$(DEST)%.cpp: %.dsp + faust $(VEC) -a ladspa.cpp $< -o $@ + +clean: + rm -rf $(DEST) diff --git a/faust-src/Makefile.mspcompile b/faust-src/Makefile.mspcompile new file mode 100644 index 0000000..6d1572d --- /dev/null +++ b/faust-src/Makefile.mspcompile @@ -0,0 +1,44 @@ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +appl := $(addprefix $(DEST), $(dspsrc:.dsp=~.mxo)) +processor := $(shell uname -p) + +INC := -I/usr/local/include/c74support/max-includes -I/usr/local/include/c74support/msp-includes + +all : $(appl) + +$(DEST)%~.mxo : %.dsp Info.plist.template + install -d $@/Contents/MacOS + faust $(VEC) -a $(ARCH) $< -o $@/$(<:.dsp=.cpp) +ifeq ($(processor), i386) + g++ -arch i386 -fpascal-strings -fasm-blocks -g -O3 $(INC) -c $@/$(<:.dsp=.cpp) -o $@/$(<:.dsp=.i386.o) + g++ -framework MaxAPI -framework Carbon -framework MaxAudioAPI -arch i386 -Wl,-Y,1455 -bundle $@/$(<:.dsp=.i386.o) -o $@/$(<:.dsp=.i386~) + g++ -arch ppc -fpascal-strings -fasm-blocks -g -O3 $(INC) -c $@/$(<:.dsp=.cpp) -o $@/$(<:.dsp=.ppc.o) + g++ -framework Carbon -framework MaxAPI -framework MaxAudioAPI -arch ppc -Wl,-Y,1455 -bundle $@/$(<:.dsp=.ppc.o) -o $@/$(<:.dsp=.ppc~) + sed s/FOO/$(<:.dsp=~)/ $@/Contents/Info.plist + lipo -create $@/$(<:.dsp=.i386~) $@/$(<:.dsp=.ppc~) -output $@/Contents/MacOS/$(<:.dsp=~) + rm -f $@/$(<:.dsp=.ppc~) $@/$(<:.dsp=.ppc.o) $@/$(<:.dsp=.i386.o) $@/$(<:.dsp=.i386~) +else + g++ -arch ppc -fpascal-strings -fasm-blocks -g -O3 $(INC) -c $@/$(<:.dsp=.cpp) -o $@/$(<:.dsp=.ppc.o) + g++ -framework Carbon -framework MaxAPI -framework MaxAudioAPI -arch ppc -Wl,-Y,1455 -bundle $@/$(<:.dsp=.ppc.o) -o $@/$(<:.dsp=.ppc~) + sed s/FOO/$(<:.dsp=~)/ $@/Contents/Info.plist + lipo -create $@/$(<:.dsp=.ppc~) -output $@/Contents/MacOS/$(<:.dsp=~) + rm -f $@/$(<:.dsp=.ppc~) $@/$(<:.dsp=.ppc.o) +endif + +Info.plist.template : + echo '' > Info.plist.template + echo '' >> Info.plist.template + echo '' >> Info.plist.template + echo '' >> Info.plist.template + echo ' CFBundleExecutable' >> Info.plist.template + echo ' FOO' >> Info.plist.template + echo ' CFBundleName' >> Info.plist.template + echo ' FOO' >> Info.plist.template + echo ' CFBundlePackageType' >> Info.plist.template + echo ' iLaX' >> Info.plist.template + echo '' >> Info.plist.template + echo '' >> Info.plist.template + +clean : + rm -f $(DEST) diff --git a/faust-src/Makefile.pdcompile b/faust-src/Makefile.pdcompile new file mode 100644 index 0000000..c591c66 --- /dev/null +++ b/faust-src/Makefile.pdcompile @@ -0,0 +1,45 @@ +DEST := pddir/ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +modules := $(addprefix $(DEST), $(dspsrc:.dsp=~.pd_linux)) +patches := $(addprefix $(DEST), $(dspsrc:.dsp=.pd)) +FAUST2PD := faust2pd +F2PDFLAGS := -r 10 -s + +LINUXCFLAGS = -DPD -O2 -funroll-loops -fomit-frame-pointer -fPIC \ + -Wall -W -Wshadow -Wno-unused -Wno-parentheses -Wno-switch $(CFLAGS) + +LINUXINCLUDE = + + +###-------------------------------------------- +### Will use faust2pd to create the GUI patches +### only if it is installed + +helper:=$(shell whereis faust2pd) + +ifeq ($(helper),faust2pd:) + todo:=$(modules) +else + todo:=$(modules) $(patches) +endif + +###-------------------------------------------- + + +allmodules: $(todo) + +$(DEST)%~.pd_linux: $(DEST)%.cpp + $(CXX) $(LINUXCFLAGS) $(LINUXINCLUDE) -shared -Dmydsp=$(patsubst %~.pd_linux,%,$(notdir $@)) $< -o $@ + +$(DEST)%.cpp: %.dsp + faust -a $(ARCH) $< -o $@ + +$(DEST)%.pd: %.dsp + faust -xml $< -o /dev/null + $(FAUST2PD) $(F2PDFLAGS) $<.xml + mv $(<:.dsp=.pd) $(DEST) + rm -f $<.xml + +clean: + rm -rf $(DEST) diff --git a/faust-src/Makefile.qcompile b/faust-src/Makefile.qcompile new file mode 100644 index 0000000..9a12af2 --- /dev/null +++ b/faust-src/Makefile.qcompile @@ -0,0 +1,17 @@ +DEST := qdir/ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +modules := $(addprefix $(DEST), $(dspsrc:%.dsp=%.so)) + +###allcpp: $(cppsrc) + +allmodules: $(modules) + +$(DEST)%.so: $(DEST)%.cpp + $(CXX) -shared -O3 $(CXXFLAGS) -Dmydsp=$(patsubst %.so,%,$(notdir $@)) $< -o $@ + +$(DEST)%.cpp: %.dsp + faust $(VEC) -a q.cpp $< -o $@ + +clean: + rm -rf $(DEST) diff --git a/faust-src/Makefile.qtcompile b/faust-src/Makefile.qtcompile new file mode 100644 index 0000000..70d1018 --- /dev/null +++ b/faust-src/Makefile.qtcompile @@ -0,0 +1,49 @@ +###-------------------------------------------- +### DEST : directory where to put binaries +### ARCH : faust architecture file + +system := $(shell uname -s) +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) + + +### check what type of applications to build (MacOSX Darwin or Linux) +ifeq ($(system), Darwin) +appls := $(addprefix $(DEST), $(dspsrc:.dsp=.app)) +else +appls := $(addprefix $(DEST), $(dspsrc:.dsp=)) +endif + + +TMP = /var/tmp/$(<:.dsp=) +###-------------------------------------------- + + +all : $(appls) + + +### Darwin +$(DEST)%.app : %.dsp + rm -rf $(TMP) + install -d $(TMP) + faust -a $(ARCH) $< -o $(TMP)/$<.cpp + cd $(TMP); qmake -project "INCLUDEPATH+=/usr/local/lib/faust/" "LIBS+=$(LIB)" "HEADERS+=/usr/local/lib/faust/faustqt.h" + cd $(TMP); qmake + cd $(TMP); xcodebuild -project $(<:.dsp=).xcodeproj + mv $(TMP)/build/Default/$(<:.dsp=.app) $@ + rm -rf $(TMP) + + +### Linux +$(DEST)% : %.dsp + rm -rf $(TMP) + install -d $(TMP) + faust -a $(ARCH) $< -o $(TMP)/$<.cpp + cd $(TMP); qmake -project "INCLUDEPATH+=/usr/local/lib/faust/" "LIBS+=$(LIB)" "HEADERS+=/usr/local/lib/faust/faustqt.h" + cd $(TMP); qmake + make -C $(TMP) + mv $(TMP)/$(<:.dsp=) $@ + rm -rf $(TMP) + +clean: + rm -rf $(DEST) diff --git a/faust-src/Makefile.sccompile b/faust-src/Makefile.sccompile new file mode 100644 index 0000000..628b2c8 --- /dev/null +++ b/faust-src/Makefile.sccompile @@ -0,0 +1,41 @@ +# Makefile to produce supercollider plugins with Faust +# 'foo.dsp' -> 'foo.so' and 'foo.sc' +# + +dspsrc := $(wildcard *.dsp) +scfiles := $(addprefix $(DEST), $(dspsrc:.dsp=.sc)) +sofiles := $(addprefix $(DEST), $(dspsrc:.dsp=.so)) +CXXFLAGS := `pkg-config --cflags libscsynth` $(CXXFLAGS) +LIB := -shared + + +###-------------------------------------------- +### Will use faust2sc to create the class file +### only if it is installed + +helper:=$(shell whereis faust2sc) + +ifeq ($(helper),faust2sc:) + todo:=$(sofiles) +else + todo:=$(sofiles) $(scfiles) +endif + + +###-------------------------------------------- + + +all : $(todo) + +$(DEST)%.cpp: %.dsp + faust -a $(ARCH) $< -o $@ + +$(DEST)%.so: $(DEST)%.cpp + $(CXX) $(CXXFLAGS) $(OPTFLAGS) $(LIB) $< -o $@ + +$(DEST)%.sc : %.dsp.xml + faust2sc --prefix=Faust $< --output=$@ + +%.dsp.xml: %.dsp + faust --xml -o /dev/null $< + diff --git a/faust-src/Makefile.svg b/faust-src/Makefile.svg new file mode 100644 index 0000000..fd271fe --- /dev/null +++ b/faust-src/Makefile.svg @@ -0,0 +1,12 @@ +src := $(wildcard *.dsp) +target := $(src:.dsp=.dsp-svg) + +all : $(target) + + +%.dsp-svg : %.dsp + faust -svg $< > /dev/null + + +clean : + rm -rf $(target) diff --git a/faust-src/Makefile.vstcompile b/faust-src/Makefile.vstcompile new file mode 100644 index 0000000..658a69b --- /dev/null +++ b/faust-src/Makefile.vstcompile @@ -0,0 +1,31 @@ +dspsrc := $(wildcard *.dsp) +cppsrc := $(addprefix $(DEST), $(dspsrc:.dsp=.cpp)) +appl := $(addprefix $(DEST), $(dspsrc:.dsp=$(EXT))) + +# Setup this variable to access the VST SDK files +vst_sdk := "/Volumes/Document1/Developpement/ProjectsCVS/JackCVS/JackOSX/jackosx/jackplugins/JACK-ASinsert/VST/VSTSDK" + +# Setup this variable with the location for the compiled VST plug-ins +install_plug_ins := "/Library/Audio/Plug-Ins/VST" + +all : $(appl) + + +$(DEST)% : %.dsp + install -d $@ + cp -r $(vst_sdk) $@ + cp -r /usr/local/lib/faust/VST/* $@ + faust $(VEC) -a $(ARCH) $< -o $@/vst-output.cpp + mv $@/vst-output.cpp $@/$(<:.dsp=.cpp) + sed -e 's/vst-output.cpp/$(<:.dsp=.cpp)/' $@/VST.xcode/project.pbxproj > $@/VST.xcode/new_project.pbxproj && mv $@/VST.xcode/new_project.pbxproj $@/VST.xcode/project.pbxproj + sed -e 's/XXXX/$(<:.dsp=)/' $@/Info.plist > $@/new_Info.plist && mv $@/new_Info.plist $@/Info.plist + xcodebuild -project $@/VST.xcode clean + xcodebuild -project $@/VST.xcode + mv $@/build/FaustVST.vst $@/build/$(<:.dsp=.vst) + rm -r $@/build/VST.build + install -d $(install_plug_ins) + cp -r $@/build/$(<:.dsp=.vst) $(install_plug_ins) + + +clean : + rm -f $(DEST) diff --git a/faust-src/minimal.cpp b/faust-src/minimal.cpp new file mode 100644 index 0000000..2d1222a --- /dev/null +++ b/faust-src/minimal.cpp @@ -0,0 +1,97 @@ +#ifdef __GNUC__ + +#define max(x,y) (((x)>(y)) ? (x) : (y)) +#define min(x,y) (((x)<(y)) ? (x) : (y)) + +// abs is now predefined +//template T abs (T a) { return (a> n); } + +/****************************************************************************** +******************************************************************************* + + VECTOR INTRINSICS + +******************************************************************************* +*******************************************************************************/ + +//inline void *aligned_calloc(size_t nmemb, size_t size) { return (void*)((unsigned)(calloc((nmemb*size)+15,sizeof(char)))+15 & 0xfffffff0); } +inline void *aligned_calloc(size_t nmemb, size_t size) { return (void*)((size_t)(calloc((nmemb*size)+15,sizeof(char)))+15 & ~15); } + +<> + +/****************************************************************************** +******************************************************************************* + + ABSTRACT USER INTERFACE + +******************************************************************************* +*******************************************************************************/ + +class UI +{ + bool fStopped; +public: + + UI() : fStopped(false) {} + virtual ~UI() {} + + virtual void addButton(char* label, float* zone) = 0; + virtual void addToggleButton(char* label, float* zone) = 0; + virtual void addCheckButton(char* label, float* zone) = 0; + virtual void addVerticalSlider(char* label, float* zone, float init, float min, float max, float step) = 0; + virtual void addHorizontalSlider(char* label, float* zone, float init, float min, float max, float step) = 0; + virtual void addNumEntry(char* label, float* zone, float init, float min, float max, float step) = 0; + + virtual void openFrameBox(char* label) = 0; + virtual void openTabBox(char* label) = 0; + virtual void openHorizontalBox(char* label) = 0; + virtual void openVerticalBox(char* label) = 0; + virtual void closeBox() = 0; + + virtual void run() = 0; + + void stop() { fStopped = true; } + bool stopped() { return fStopped; } +}; + + + + +/****************************************************************************** +******************************************************************************* + + FAUST DSP + +******************************************************************************* +*******************************************************************************/ + + + +//---------------------------------------------------------------- +// abstract definition of a signal processor +//---------------------------------------------------------------- + +class dsp { + protected: + int fSamplingFreq; + public: + dsp() {} + virtual ~dsp() {} + + virtual int getNumInputs() = 0; + virtual int getNumOutputs() = 0; + virtual void buildUserInterface(UI* interface) = 0; + virtual void init(int samplingRate) = 0; + virtual void compute(int len, float** inputs, float** outputs) = 0; +}; + + +//---------------------------------------------------------------------------- +// FAUST generated signal processor +//---------------------------------------------------------------------------- + + +<> diff --git a/faust-src/net-ks.dsp b/faust-src/net-ks.dsp new file mode 100644 index 0000000..858749a --- /dev/null +++ b/faust-src/net-ks.dsp @@ -0,0 +1,29 @@ +declare name "net-ks"; +declare version "1.0"; +declare author "Juan-Pablo Caceres"; +declare license "MIT"; +declare copyright "(c) Juan-Pablo Caceres 2008"; + +//----------------------------------------------------- +// Network-karplus-strong, +// Based on 'karplus' from Faust Examples +//----------------------------------------------------- + +import("music.lib"); + +// Excitation +//----------- +upfront(x) = (x-x') > 0.0; +decay(n,x) = x - (x>0.0)/n; +release(n) = + ~ decay(n); +trigger(n) = upfront : release(n) : >(0.0); + +// Filters +//-------- +// Average LowPass Filter +av_lowpass(x) = (x+x')/2; + +process = _ , + ( noise * 0.9 : + vgroup("excitator", *(button("play") : trigger(300))) ) + :> av_lowpass; diff --git a/jacktrip_doxygen b/jacktrip_doxygen new file mode 100644 index 0000000..61e6740 --- /dev/null +++ b/jacktrip_doxygen @@ -0,0 +1,1418 @@ +# Doxyfile 1.5.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = JackTrip + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./WWW/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./src ./documentation + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = ./documentation/img + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = ./documentation/html_footer.html + + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hiererachy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = Sans + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is enabled by default, which results in a transparent +# background. Warning: Depending on the platform used, enabling this option +# may lead to badly anti-aliased labels on the edges of a graph (i.e. they +# become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/src/DataProtocol.cpp b/src/DataProtocol.cpp new file mode 100644 index 0000000..3168a5f --- /dev/null +++ b/src/DataProtocol.cpp @@ -0,0 +1,61 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file DataProtocol.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include "DataProtocol.h" +#include "jacktrip_globals.h" +#include "JackTrip.h" + +#include +#include + +#include +#include + +using std::cout; using std::endl; + + +//******************************************************************************* +DataProtocol::DataProtocol(JackTrip* jacktrip, + const runModeT runmode, + int /*bind_port*/, int /*peer_port*/) : + mStopped(false), mHasPacketsToReceive(false), mRunMode(runmode), mJackTrip(jacktrip) +{} + + +//******************************************************************************* +DataProtocol::~DataProtocol() +{} diff --git a/src/DataProtocol.h b/src/DataProtocol.h new file mode 100644 index 0000000..9f9d2bc --- /dev/null +++ b/src/DataProtocol.h @@ -0,0 +1,191 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file DataProtocol.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#ifndef __DATAPROTOCOL_H__ +#define __DATAPROTOCOL_H__ + +//#include //basic socket definitions +#include //sockaddr_in{} and other Internet defns +#include //inet(3) functions +#include +#include //for shared_ptr + +#include +#include + +class JackTrip; // forward declaration + + +/** \brief Base class that defines the transmission protocol. + * + * This base class defines most of the common method to setup and connect + * sockets using the individual protocols (UDP, TCP, SCTP, etc). + * + * The class has to be constructed using one of two modes (runModeT):\n + * - SENDER + * - RECEIVER + * + * This has to be specified as a constructor argument. When using, create two instances + * of the class, one to receive and one to send packets. Each instance will run on a + * separate thread. + * + * Redundancy and forward error correction should be implemented on each + * Transport protocol, cause they depend on the protocol itself + * + * \todo This Class should contain definition of jacktrip header and basic funcionality to obtain + * local machine IPs and maybe functions to manipulate IPs. + * Redundancy and forward error correction should be implemented on each + * Transport protocol, cause they depend on the protocol itself + * + * \todo The transport protocol itself has to be implemented subclassing this class, i.e., + * using a TCP or UDP protocol. + * + * Even if the underlined transmission protocol is stream oriented (as in TCP), + * we send packets that are the size of the audio processing buffer. + * Use AudioInterface::getBufferSize to obtain this value. + * + * Each transmission (i.e., inputs and outputs) run on its own thread. + */ +class DataProtocol : public QThread +{ +public: + + //----------ENUMS------------------------------------------ + /// \brief Enum to define packet header types + enum packetHeaderTypeT { + DEFAULT, ///< Default application header + JAMLINK, ///< Header to use with Jamlinks + EMPTY ///< Empty Header + }; + + /// \brief Enum to define class modes, SENDER or RECEIVER + enum runModeT { + SENDER, ///< Set class as a Sender (send packets) + RECEIVER ///< Set class as a Receiver (receives packets) + }; + //--------------------------------------------------------- + + + /** \brief The class constructor + * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator) + * \param runmode Sets the run mode, use either DataProtocol::SENDER or + * DataProtocol::RECEIVER + * \param headertype packetHeaderTypeT header type to use for packets + * \param bind_port Port number to bind for this socket (this is the receive or send port depending on the runmode) + * \param peer_port Peer port number (this is the receive or send port depending on the runmode) + */ + DataProtocol(JackTrip* jacktrip, + const runModeT runmode, + int bind_port, int peer_port); + + /// \brief The class destructor + virtual ~DataProtocol(); + + /** \brief Implements the thread loop + * + * Depending on the runmode, with will run a DataProtocol::SENDER thread or + * DataProtocol::RECEIVER thread + */ + virtual void run() = 0; + + /// \brief Stops the execution of the Thread + virtual void stop() { mStopped = true; }; + + /** \brief Sets the size of the audio part of the packets + * \param size_bytes Size in bytes + */ + void setAudioPacketSize(const size_t size_bytes){ mAudioPacketSize = size_bytes; }; + + /** \brief Get the size of the audio part of the packets + * \return size_bytes Size in bytes + */ + size_t getAudioPacketSizeInBites() { return(mAudioPacketSize); }; + + /** \brief Set the peer address + * \param peerHostOrIP IPv4 number or host name + * \todo implement here instead of in the subclass UDP + */ + virtual void setPeerAddress(const char* peerHostOrIP) = 0; + + /** \brief Set the peer incomming (receiving) port number + * \param port Port number + * \todo implement here instead of in the subclass UDP + */ + virtual void setPeerPort(int port) = 0; + + //virtual void getPeerAddressFromFirstPacket(QHostAddress& peerHostAddress, + // uint16_t& port) = 0; + +protected: + + /** \brief Get the Run Mode of the object + * \return SENDER or RECEIVER + */ + runModeT getRunMode() const { return mRunMode; }; + + /// Boolean stop the execution of the thread + volatile bool mStopped; + /// Boolean to indicate if the RECEIVER is waiting to obtain peer address + volatile bool mHasPeerAddress; + /// Boolean that indicates if a packet was received + volatile bool mHasPacketsToReceive; + + +private: + + int mLocalPort; ///< Local Port number to Bind + int mPeerPort; ///< Peer Port number to Bind + const runModeT mRunMode; ///< Run mode, either SENDER or RECEIVER + + struct sockaddr_in mLocalIPv4Addr; ///< Local IPv4 Address struct + struct sockaddr_in mPeerIPv4Addr; ///< Peer IPv4 Address struct + + /// Number of clients running to check for ports already used + /// \note Unimplemented, try to find another way to check for used ports + static int sClientsRunning; + + size_t mAudioPacketSize; ///< Packet audio part size + + + /// \todo check a better way to access the header from the subclasses +protected: + //PacketHeader* mHeader; ///< Packet Header + JackTrip* mJackTrip; ///< JackTrip mediator class + +}; + +#endif diff --git a/src/DataProtocolPOSIX.cpp.tmp b/src/DataProtocolPOSIX.cpp.tmp new file mode 100644 index 0000000..b92459c --- /dev/null +++ b/src/DataProtocolPOSIX.cpp.tmp @@ -0,0 +1,222 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file DataProtocol.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include "DataProtocol.h" +#include "jacktrip_globals.h" +#include "JackAudioInterface.h" +#include "PacketHeader.h" + +#include +#include + +#include +#include + +using std::cout; using std::endl; + +//******************************************************************************* +DataProtocol::DataProtocol(const runModeT runmode, + const packetHeaderTypeT headertype) : + mRunMode(runmode), mStopped(false), mHasPacketsToReceive(false), mHeader(NULL) +{ + //--------PROTOTYPE------------------------- + if ( headertype == DEFAULT ) { + mHeader = new DefaultHeader; + } + else if ( headertype == JAMLINK ) { + mHeader = new JamLinkHeader; + } + //------------------------------------------ + + // Base ports gInputPort_0 and gOutputPort_0defined at globals.h + if (mRunMode == RECEIVER) { + mLocalPort = gInputPort_0; + mPeerPort = gOutputPort_0; + } + else if (mRunMode == SENDER) { + mLocalPort = gOutputPort_0; + mPeerPort = gInputPort_0; + } + + this->setLocalIPv4Address(); +} + + +//******************************************************************************* +DataProtocol::~DataProtocol() +{ + delete mHeader; +} + + +//******************************************************************************* +void DataProtocol::stop() +{ + mStopped = true; +} + + +//******************************************************************************* +void DataProtocol::setLocalIPv4Address() +{ + bzero(&mLocalIPv4Addr, sizeof(mLocalIPv4Addr)); + mLocalIPv4Addr.sin_family = AF_INET;//AF_INET: IPv4 Protocol + mLocalIPv4Addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY: let the kernel decide the active address + mLocalIPv4Addr.sin_port = htons(mLocalPort);//set local port + //std::cout << "mLocalPort = " << mLocalPort << std::endl; +} + + +//******************************************************************************* +void DataProtocol::setPeerIPv4Address(const char* peerHostOrIP) +{ + const char* peerAddress; // dotted decimal address to use in the struct below + + /// \todo Improve this to make it work also with local ip numbers, in a LAN, + /// that don't have an assigned host name + /* + // Resolve Peer IPv4 with either doted integer IP or hostname + //---------------------------------------------------------- + std::cout << "Resolving Peer IPv4 address..." << std::endl; + QHostInfo info = QHostInfo::fromName(peerHostOrIP); + if ( !info.addresses().isEmpty() ) { + std::cout << "Peer Address Found" << std::endl; + QHostAddress address = info.addresses().first(); // use the first address in list + peerAddress = address.toString().toLatin1(); + } + else { + std::cerr << "ERROR: Could not set Peer IP Address" << std::endl; + std::cerr << "Check that it's public or that the hostname exists" << std::endl; + std::exit(1); + } + */ + + // temporary implementation to make this work + /// \todo change this + peerAddress = peerHostOrIP; + + // Set the Peer IPv4 Address struct + //--------------------------------- + bzero(&mPeerIPv4Addr, sizeof(mPeerIPv4Addr)); + mPeerIPv4Addr.sin_family = AF_INET;//AF_INET: IPv4 Protocol + mPeerIPv4Addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY: let the kernel decide the active address + mPeerIPv4Addr.sin_port = htons(mPeerPort);//set Peer port + //std::cout << "mPeerPort = " << mPeerPort << std::endl; + int nPeer = inet_pton(AF_INET, peerAddress, &mPeerIPv4Addr.sin_addr); + if ( nPeer == 1 ) { + std::cout << "Successful Set Peer Address" << std::endl; + } + else if ( nPeer == 0 ) { + std::cout << "Error: Incorrect presentation format for address" << std::endl; + std::exit(1); + } + else { + std::cout << "Error: Could not set Peer Address" << std::endl; + std::exit(1); + } + +} + + +//******************************************************************************* +void DataProtocol::setRingBuffer(std::tr1::shared_ptr RingBuffer) +{ + mRingBuffer = RingBuffer; +} + + +//******************************************************************************* +void DataProtocol::run() +{ + std::cout << "Running DataProtocol Thread" << std::endl; + std::cout << gPrintSeparator << std::endl; + size_t packet_size = getAudioPacketSize(); + int8_t packet[packet_size]; + + switch ( mRunMode ) + { + case RECEIVER : + //----------------------------------------------------------------------------------- + // Wait for the first packet to be ready and obtain address + // from that packet + /// \todo here is the place to read the datagram and check if the settings match + /// the local ones. Extract this information from the header + std::cout << "Waiting for Peer..." << std::endl; + this->receivePacket( (char*) packet, packet_size); // This blocks waiting for the first packet + std::cout << "Received Connection for Peer!" << std::endl; + + while ( !mStopped ) + { + //std::cout << "RECEIVING PACKETS" << std::endl; + /// \todo Set a timer to report packats arriving too late + //std::cout << "RECIEVING THREAD" << std::endl; + + this->receivePacket( (char*) packet, packet_size); + /// \todo Change this to match buffer size + //std::cout << "PACKET RECIEVED" << std::endl; + mRingBuffer->insertSlotBlocking(packet); + //std::cout << buf << std::endl; + } + break; + + + case SENDER : + //----------------------------------------------------------------------------------- + while ( !mStopped ) + { + //std::cout << "SENDING PACKETS --------------------------" << std::endl; + /// \todo This should be blocking, since we don't want to send trash + mRingBuffer->readSlotBlocking(packet); + //std::cout << "SENDING PACKETS" << std::endl; + this->sendPacket( (char*) packet, packet_size); + //std::cout << "SENDING PACKETS DONE!!!" << std::endl; + //this->sendPacket( sendtest, 64); + } + break; + } +} + +void DataProtocol::setAudioPacketSize(size_t size_bytes) +{ + mAudioPacketSize = size_bytes; +} + + +size_t DataProtocol::getAudioPacketSize() +{ + return(mAudioPacketSize); +} diff --git a/src/DataProtocolPOSIX.h.tmp b/src/DataProtocolPOSIX.h.tmp new file mode 100644 index 0000000..c812d02 --- /dev/null +++ b/src/DataProtocolPOSIX.h.tmp @@ -0,0 +1,213 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file DataProtocol.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#ifndef __DATAPROTOCOL_H__ +#define __DATAPROTOCOL_H__ + +//#include //basic socket definitions +#include //sockaddr_in{} and other Internet defns +#include //inet(3) functions +#include +#include //for shared_ptr + +#include + +#include "RingBuffer.h" +//#include "PacketHeader.h" +class PacketHeader; // forward declaration + + +/** \brief Base class that defines the transmission protocol. + * + * This base class defines most of the common method to setup and connect + * sockets using the individual protocols (UDP, TCP, SCTP, etc). + * + * The class has to be constructed using one of two modes (runModeT):\n + * - SENDER + * - RECEIVER + * + * This has to be specified as a constructor argument. When using, create two instances + * of the class, one to receive and one to send packets. Each instance will run on a + * separate thread. + * + * Redundancy and forward error correction should be implemented on each + * Transport protocol, cause they depend on the protocol itself + * + * \todo This Class should contain definition of jacktrip header and basic funcionality to obtain + * local machine IPs and maybe functions to manipulate IPs. + * Redundancy and forward error correction should be implemented on each + * Transport protocol, cause they depend on the protocol itself + * + * \todo The transport protocol itself has to be implemented subclassing this class, i.e., + * using a TCP or UDP protocol. + * + * Even if the underlined transmission protocol is stream oriented (as in TCP), + * we send packets that are the size of the audio processing buffer. + * Use AudioInterface::getBufferSize to obtain this value. + * + * Each transmission (i.e., inputs and outputs) run on its own thread. + */ +class DataProtocol : public QThread +{ +public: + + /// \brief Enum to define packet header types + enum packetHeaderTypeT { + DEFAULT, ///< Default application header + JAMLINK ///< Header to use with Jamlinks + }; + + /// \brief Enum to define class modes, SENDER or RECEIVER + enum runModeT { + SENDER, ///< Set class as a Sender (send packets) + RECEIVER ///< Set class as a Receiver (receives packets) + }; + + + /** \brief The class constructor + * \param runmode Sets the run mode, use either SENDER or RECEIVER + */ + DataProtocol(const runModeT runmode, + const packetHeaderTypeT headertype = DEFAULT); + + /// \brief The class destructor + virtual ~DataProtocol(); + + /** \brief Sets the peer (remote) IPv4 address struct + * \param peerHostOrIP Either an IPv4 dotted integer number or a hostname + */ + virtual void setPeerIPv4Address(const char* peerHostOrIP); + + /** \brief Receive a packet from the UDPSocket + * + * This method has to be implemented in the sub-classes + * \param buf Location at which to store the buffer + * \param n size of packet to receive in bytes + * \return number of bytes read, -1 on error + */ + virtual size_t receivePacket(char* buf, size_t n) = 0; + + /** \brief Sends a packet + * + * This method has to be implemented in the sub-classes + * \param buff Buffer to send + * \param n size of packet to receive in bytes + * \return number of bytes read, -1 on error + */ + virtual size_t sendPacket(const char* buff, size_t n) = 0; + + /** \brief Implements the thread loop + * + * Depending on the runmode, with will run a RECEIVE thread or + * SEND thread + */ + virtual void run(); + + /** \brief Set the pointer to the RingBuffer that'll be use to read + * or write + */ + void setRingBuffer(std::tr1::shared_ptr RingBuffer); + + /// \brief Stops the execution of the Thread + void stop(); + + /** \brief Sets the size of the audio part of the packets + * \param size_bytes Size in bytes + */ + void setAudioPacketSize(size_t size_bytes); + + /** \brief Get the size of the audio part of the packets + * \return size_bytes Size in bytes + */ + size_t getAudioPacketSize(); + + //virtual void getIPAddressFromFirstPacket() = 0; + + +protected: + + /** \brief Sets the local IPv4 address struct + * + * It uses the default active device. + */ + virtual void setLocalIPv4Address(); + + /** \brief Get the Run Mode of the object + * \return SENDER or RECEIVER + */ + runModeT getRunMode() const { return mRunMode; }; + + /** \brief Returns the Local machine IPv4 socket address stuct + * \return Socket address stuct + */ + const sockaddr_in& getLocalIPv4AddressStruct() const { return mLocalIPv4Addr; }; + + /** \brief Returns the Peer IPv4 socket address stuct + * \return Socket address stuct + */ + const sockaddr_in& getPeerIPv4AddressStruct() const { return mPeerIPv4Addr; }; + + +private: + + int mLocalPort; ///< Local Port number to Bind + int mPeerPort; ///< Peer Port number to Bind + const runModeT mRunMode; ///< Run mode, either SENDER or RECEIVER + + struct sockaddr_in mLocalIPv4Addr; ///< Local IPv4 Address struct + struct sockaddr_in mPeerIPv4Addr; ///< Peer IPv4 Address struct + + /// Smart Pointer to RingBuffer to read (for SENDER) or write (for RECEIVER) + std::tr1::shared_ptr mRingBuffer; + + /// Boolean stop the execution of the thread + volatile bool mStopped; + /// Boolean to indicate if the RECEIVER is waiting to obtain peer address + volatile bool mHasPeerAddress; + /// Boolean that indicates if a packet was received + volatile bool mHasPacketsToReceive; + + /// Number of clients running to check for ports already used + /// \note Unimplemented, try to find another way to check for used ports + static int sClientsRunning; + + size_t mAudioPacketSize; ///< Packet audio part size + + PacketHeader* mHeader; ///< Packet Header +}; + +#endif diff --git a/src/JackAudioInterface.cpp b/src/JackAudioInterface.cpp new file mode 100644 index 0000000..5ca3ba3 --- /dev/null +++ b/src/JackAudioInterface.cpp @@ -0,0 +1,675 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackAudioInterface.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include "JackAudioInterface.h" +#include "jacktrip_globals.h" +#include "JackTrip.h" + +#include +#include +#include +#include + +///************PROTORYPE FOR CELT************************** +//#include +//#include +//#include +///******************************************************** + +#include +#include + +using std::cout; using std::endl; + + +// sJackMutex definition +QMutex JackAudioInterface::sJackMutex; + + +//******************************************************************************* +JackAudioInterface::JackAudioInterface(JackTrip* jacktrip, + int NumInChans, int NumOutChans, + audioBitResolutionT AudioBitResolution, + const char* ClienName) : + mNumInChans(NumInChans), mNumOutChans(NumOutChans), + mAudioBitResolution(AudioBitResolution*8), mBitResolutionMode(AudioBitResolution), + mClient(NULL), + mClientName(ClienName), + mJackTrip(jacktrip) +{ + //setupClient(); + //setProcessCallback(); +} + + +//******************************************************************************* +JackAudioInterface::~JackAudioInterface() +{ + delete[] mInputPacket; + delete[] mOutputPacket; + + for (int i = 0; i < mNumInChans; i++) { + delete[] mInProcessBuffer[i]; + } + + for (int i = 0; i < mNumOutChans; i++) { + delete[] mOutProcessBuffer[i]; + } +} + + +//******************************************************************************* +void JackAudioInterface::setup() +{ + setupClient(); + setProcessCallback(); +} + + +//******************************************************************************* +void JackAudioInterface::setupClient() +{ + const char* client_name = mClientName; + const char* server_name = NULL; + jack_options_t options = JackNoStartServer; + jack_status_t status; + + // Try to connect to the server + /// \todo Write better warning messages. This following line displays very + /// verbose message, check how to desable them. + { + QMutexLocker locker(&sJackMutex); + mClient = jack_client_open (client_name, options, &status, server_name); + } + + if (mClient == NULL) { + //fprintf (stderr, "jack_client_open() failed, " + // "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + //std::cerr << "ERROR: Maybe the JACK server is not running?" << std::endl; + //std::cerr << gPrintSeparator << std::endl; + } + //std::exit(1); + throw std::runtime_error("Maybe the JACK server is not running?"); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(mClient); + fprintf (stderr, "unique name `%s' assigned\n", client_name); + } + + // Set function to call if Jack shuts down + jack_on_shutdown (mClient, this->jackShutdown, 0); + + // Create input and output channels + createChannels(); + + // Allocate buffer memory to read and write + mSizeInBytesPerChannel = getSizeInBytesPerChannel(); + int size_input = mSizeInBytesPerChannel * getNumInputChannels(); + int size_output = mSizeInBytesPerChannel * getNumOutputChannels(); + mInputPacket = new int8_t[size_input]; + mOutputPacket = new int8_t[size_output]; + + // Buffer size member + mNumFrames = getBufferSizeInSamples(); + + // Initialize Buffer array to read and write audio + mInBuffer.resize(mNumInChans); + mOutBuffer.resize(mNumOutChans); + + // Initialize and asign memory for ProcessPlugins Buffers + mInProcessBuffer.resize(mNumInChans); + mOutProcessBuffer.resize(mNumOutChans); + + int nframes = getBufferSizeInSamples(); + for (int i = 0; i < mNumInChans; i++) { + mInProcessBuffer[i] = new sample_t[nframes]; + // set memory to 0 + std::memset(mInProcessBuffer[i], 0, sizeof(sample_t) * nframes); + } + for (int i = 0; i < mNumOutChans; i++) { + mOutProcessBuffer[i] = new sample_t[nframes]; + // set memory to 0 + std::memset(mOutProcessBuffer[i], 0, sizeof(sample_t) * nframes); + } +} + + +//******************************************************************************* +void JackAudioInterface::createChannels() +{ + //Create Input Ports + mInPorts.resize(mNumInChans); + for (int i = 0; i < mNumInChans; i++) + { + QString inName; + QTextStream (&inName) << "send_" << i+1; + mInPorts[i] = jack_port_register (mClient, inName.toLatin1(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + } + + //Create Output Ports + mOutPorts.resize(mNumOutChans); + for (int i = 0; i < mNumInChans; i++) + { + QString outName; + QTextStream (&outName) << "receive_" << i+1; + mOutPorts[i] = jack_port_register (mClient, outName.toLatin1(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + } +} + + +//******************************************************************************* +uint32_t JackAudioInterface::getSampleRate() const +{ + return jack_get_sample_rate(mClient); +} + + +//******************************************************************************* +JackAudioInterface::samplingRateT JackAudioInterface::getSampleRateType() const +{ + uint32_t rate = jack_get_sample_rate(mClient); + + if ( rate == 22050 ) { + return JackAudioInterface::SR22; } + else if ( rate == 32000 ) { + return JackAudioInterface::SR32; } + else if ( rate == 44100 ) { + return JackAudioInterface::SR44; } + else if ( rate == 48000 ) { + return JackAudioInterface::SR48; } + else if ( rate == 88200 ) { + return JackAudioInterface::SR88; } + else if ( rate == 96000 ) { + return JackAudioInterface::SR96; } + else if ( rate == 19200 ) { + return JackAudioInterface::SR192; } + + return JackAudioInterface::UNDEF; +} + + +//******************************************************************************* +int JackAudioInterface::getSampleRateFromType(samplingRateT rate_type) +{ + int sample_rate = 0; + switch (rate_type) + { + case SR22 : + sample_rate = 22050; + return sample_rate; + break; + case SR32 : + sample_rate = 32000; + return sample_rate; + break; + case SR44 : + sample_rate = 44100; + return sample_rate; + break; + case SR48 : + sample_rate = 48000; + return sample_rate; + break; + case SR88 : + sample_rate = 88200; + return sample_rate; + break; + case SR96 : + sample_rate = 96000; + return sample_rate; + break; + case SR192 : + sample_rate = 192000; + return sample_rate; + break; + default: + return sample_rate; + break; + } + + return sample_rate; +} + +//******************************************************************************* +uint32_t JackAudioInterface::getBufferSizeInSamples() const +{ + return jack_get_buffer_size(mClient); +} + + +//******************************************************************************* +int JackAudioInterface::getAudioBitResolution() const +{ + return mAudioBitResolution; +} + + +//******************************************************************************* +int JackAudioInterface::getNumInputChannels() const +{ + return mNumInChans; +} + + +//******************************************************************************* +int JackAudioInterface::getNumOutputChannels() const +{ + return mNumOutChans; +} + + +//******************************************************************************* +size_t JackAudioInterface::getSizeInBytesPerChannel() const +{ + return (getBufferSizeInSamples() * getAudioBitResolution()/8); +} + +//******************************************************************************* +void JackAudioInterface::setProcessCallback() +{ + std::cout << "Setting JACK Process Callback..." << std::endl; + if ( int code = + jack_set_process_callback(mClient, JackAudioInterface::wrapperProcessCallback, this) + ) + { + //std::cerr << "Could not set the process callback" << std::endl; + //return(code); + (void) code; // to avoid compiler warnings + throw std::runtime_error("Could not set the Jack process callback"); + //std::exit(1); + } + std::cout << "SUCCESS" << std::endl; + std::cout << gPrintSeparator << std::endl; + //return(0); +} + + +//******************************************************************************* +int JackAudioInterface::startProcess() const +{ + //Tell the JACK server that we are ready to roll. Our + //process() callback will start running now. + if ( int code = (jack_activate(mClient)) ) + { + std::cerr << "Cannot activate client" << std::endl; + return(code); + } + return(0); +} + + +//******************************************************************************* +int JackAudioInterface::stopProcess() const +{ + QMutexLocker locker(&sJackMutex); + if ( int code = (jack_client_close(mClient)) ) + { + std::cerr << "Cannot disconnect client" << std::endl; + return(code); + } + return(0); +} + + +//******************************************************************************* +void JackAudioInterface::jackShutdown (void*) +{ + //std::cout << "The Jack Server was shut down!" << std::endl; + throw std::runtime_error("The Jack Server was shut down!"); + //std::cout << "Exiting program..." << std::endl; + //std::exit(1); +} + + +//******************************************************************************* +/* +void JackAudioInterface::setRingBuffers +(const std::tr1::shared_ptr InRingBuffer, + const std::tr1::shared_ptr OutRingBuffer) +{ + mInRingBuffer = InRingBuffer; + mOutRingBuffer = OutRingBuffer; +} +*/ + + +//******************************************************************************* +// Before sending and reading to Jack, we have to round to the sample resolution +// that the program is using. Jack uses 32 bits (gJackBitResolution in globals.h) +// by default +void JackAudioInterface::computeNetworkProcessFromNetwork() +{ + /// \todo cast *mInBuffer[i] to the bit resolution + //cout << mNumFrames << endl; + // Output Process (from NETWORK to JACK) + // ---------------------------------------------------------------- + // Read Audio buffer from RingBuffer (read from incoming packets) + //mOutRingBuffer->readSlotNonBlocking( mOutputPacket ); + mJackTrip->receiveNetworkPacket( mOutputPacket ); + + // Extract separate channels to send to Jack + for (int i = 0; i < mNumOutChans; i++) { + //-------- + // This should be faster for 32 bits + //std::memcpy(mOutBuffer[i], &mOutputPacket[i*mSizeInBytesPerChannel], + // mSizeInBytesPerChannel); + //-------- + sample_t* tmp_sample = mOutBuffer[i]; //sample buffer for channel i + for (int j = 0; j < mNumFrames; j++) { + //std::memcpy(&tmp_sample[j], &mOutputPacket[(i*mSizeInBytesPerChannel) + (j*4)], 4); + // Change the bit resolution on each sample + //cout << tmp_sample[j] << endl; + fromBitToSampleConversion(&mOutputPacket[(i*mSizeInBytesPerChannel) + + (j*mBitResolutionMode)], + &tmp_sample[j], + mBitResolutionMode); + } + } +} + + +//******************************************************************************* +void JackAudioInterface::computeNetworkProcessToNetwork() +{ + // Input Process (from JACK to NETWORK) + // ---------------------------------------------------------------- + // Concatenate all the channels from jack to form packet + for (int i = 0; i < mNumInChans; i++) { + //-------- + // This should be faster for 32 bits + //std::memcpy(&mInputPacket[i*mSizeInBytesPerChannel], mInBuffer[i], + // mSizeInBytesPerChannel); + //-------- + sample_t* tmp_sample = mInBuffer[i]; //sample buffer for channel i + sample_t* tmp_process_sample = mOutProcessBuffer[i]; //sample buffer from the output process + sample_t tmp_result; + for (int j = 0; j < mNumFrames; j++) { + //std::memcpy(&tmp_sample[j], &mOutputPacket[(i*mSizeInBytesPerChannel) + (j*4)], 4); + // Change the bit resolution on each sample + + // Add the input jack buffer to the buffer resulting from the output process + tmp_result = tmp_sample[j] + tmp_process_sample[j]; + fromSampleToBitConversion(&tmp_result, + &mInputPacket[(i*mSizeInBytesPerChannel) + + (j*mBitResolutionMode)], + mBitResolutionMode); + } + } + // Send Audio buffer to RingBuffer (these goes out as outgoing packets) + //mInRingBuffer->insertSlotNonBlocking( mInputPacket ); + mJackTrip->sendNetworkPacket( mInputPacket ); +} + + +//******************************************************************************* +int JackAudioInterface::processCallback(jack_nframes_t nframes) +{ + // Get input and output buffers from JACK + //------------------------------------------------------------------- + for (int i = 0; i < mNumInChans; i++) { + // Input Ports are READ ONLY + mInBuffer[i] = (sample_t*) jack_port_get_buffer(mInPorts[i], nframes); + } + for (int i = 0; i < mNumOutChans; i++) { + // Output Ports are WRITABLE + mOutBuffer[i] = (sample_t*) jack_port_get_buffer(mOutPorts[i], nframes); + } + //------------------------------------------------------------------- + // TEST: Loopback + // To test, uncomment and send audio to client input. The same audio + // should come out as output in the first channel + //memcpy (mOutBuffer[0], mInBuffer[0], sizeof(sample_t) * nframes); + //memcpy (mOutBuffer[1], mInBuffer[1], sizeof(sample_t) * nframes); + //------------------------------------------------------------------- + + // Allocate the Process Callback + //------------------------------------------------------------------- + // 1) First, process incoming packets + // ---------------------------------- + computeNetworkProcessFromNetwork(); + + + // 2) Dynamically allocate ProcessPlugin processes + // ----------------------------------------------- + // The processing will be done in order of allocation + + ///\todo Implement for more than one process plugin, now it just works propertely with one. + /// do it chaining outputs to inputs in the buffers. May need a tempo buffer + for (int i = 0; i < mNumInChans; i++) { + std::memset(mInProcessBuffer[i], 0, sizeof(sample_t) * nframes); + std::memcpy(mInProcessBuffer[i], mOutBuffer[i], sizeof(sample_t) * nframes); + } + for (int i = 0; i < mNumOutChans; i++) { + std::memset(mOutProcessBuffer[i], 0, sizeof(sample_t) * nframes); + } + + for (int i = 0; i < mProcessPlugins.size(); i++) { + //mProcessPlugins[i]->compute(nframes, mOutBuffer.data(), mInBuffer.data()); + mProcessPlugins[i]->compute(nframes, mInProcessBuffer.data(), mOutProcessBuffer.data()); + } + + + // 3) Finally, send packets to peer + // -------------------------------- + computeNetworkProcessToNetwork(); + + + ///************PROTORYPE FOR CELT************************** + ///******************************************************** + /* + CELTMode* mode; + int* error; + mode = celt_mode_create(48000, 2, 64, error); + */ + //celt_mode_create(48000, 2, 64, NULL); + //unsigned char* compressed; + //CELTEncoder* celtEncoder; + //celt_encode_float(celtEncoder, mInBuffer, NULL, compressed, ); + + ///******************************************************** + ///******************************************************** + + + + return 0; +} + + +//******************************************************************************* +int JackAudioInterface::wrapperProcessCallback(jack_nframes_t nframes, void *arg) +{ + return static_cast(arg)->processCallback(nframes); +} + + +//******************************************************************************* +// This function quantize from 32 bit to a lower bit resolution +// 24 bit is not working yet +void JackAudioInterface::fromSampleToBitConversion(const sample_t* const input, + int8_t* output, + const audioBitResolutionT targetBitResolution) +{ + int8_t tmp_8; + uint8_t tmp_u8; // unsigned to quantize the remainder in 24bits + int16_t tmp_16; + sample_t tmp_sample; + sample_t tmp_sample16; + sample_t tmp_sample8; + switch (targetBitResolution) + { + case BIT8 : + // 8bit integer between -128 to 127 + tmp_sample = floor( (*input) * 128.0 ); // 2^7 = 128.0 + tmp_8 = static_cast(tmp_sample); + std::memcpy(output, &tmp_8, 1); // 8bits = 1 bytes + break; + case BIT16 : + // 16bit integer between -32768 to 32767 + tmp_sample = floor( (*input) * 32768.0 ); // 2^15 = 32768.0 + tmp_16 = static_cast(tmp_sample); + std::memcpy(output, &tmp_16, 2); // 16bits = 2 bytes + break; + case BIT24 : + // To convert to 24 bits, we first quantize the number to 16bit + tmp_sample = (*input) * 32768.0; // 2^15 = 32768.0 + tmp_sample16 = floor(tmp_sample); + tmp_16 = static_cast(tmp_sample16); + + // Then we compute the remainder error, and quantize that part into an 8bit number + // Note that this remainder is always positive, so we use an unsigned integer + tmp_sample8 = floor ((tmp_sample - tmp_sample16) //this is a positive number, between 0.0-1.0 + * 256.0); + tmp_u8 = static_cast(tmp_sample8); + + // Finally, we copy the 16bit number in the first 2 bytes, + // and the 8bit number in the third bite + std::memcpy(output, &tmp_16, 2); // 16bits = 2 bytes + std::memcpy(output+2, &tmp_u8, 1); // 8bits = 1 bytes + break; + case BIT32 : + std::memcpy(output, input, 4); // 32bit = 4 bytes + break; + } +} + + +//******************************************************************************* +void JackAudioInterface::fromBitToSampleConversion(const int8_t* const input, + sample_t* output, + const audioBitResolutionT sourceBitResolution) +{ + int8_t tmp_8; + uint8_t tmp_u8; + int16_t tmp_16; + sample_t tmp_sample; + sample_t tmp_sample16; + sample_t tmp_sample8; + switch (sourceBitResolution) + { + case BIT8 : + tmp_8 = *input; + tmp_sample = static_cast(tmp_8) / 128.0; + std::memcpy(output, &tmp_sample, 4); // 4 bytes + break; + case BIT16 : + tmp_16 = *( reinterpret_cast(input) ); // *((int16_t*) input); + tmp_sample = static_cast(tmp_16) / 32768.0; + std::memcpy(output, &tmp_sample, 4); // 4 bytes + break; + case BIT24 : + // We first extract the 16bit and 8bit number from the 3 bytes + tmp_16 = *( reinterpret_cast(input) ); + tmp_u8 = *( reinterpret_cast(input+2) ); + + // Then we recover the number + tmp_sample16 = static_cast(tmp_16); + tmp_sample8 = static_cast(tmp_u8) / 256.0; + tmp_sample = (tmp_sample16 + tmp_sample8) / 32768.0; + std::memcpy(output, &tmp_sample, 4); // 4 bytes + break; + case BIT32 : + std::memcpy(output, input, 4); // 4 bytes + break; + } +} + + +//******************************************************************************* +//void JackAudioInterface::appendProcessPlugin(const std::tr1::shared_ptr plugin) +void JackAudioInterface::appendProcessPlugin(ProcessPlugin* plugin) +{ + /// \todo check that channels in ProcessPlugins are less or same that jack channels + if ( plugin->getNumInputs() ) {} + mProcessPlugins.append(plugin); +} + + + +//******************************************************************************* +void JackAudioInterface::connectDefaultPorts() +{ + const char** ports; + + // Get physical output (capture) ports + if ( (ports = + jack_get_ports (mClient, NULL, NULL, + JackPortIsPhysical | JackPortIsOutput)) == NULL) + { + cout << "WARING: Cannot find any physical capture ports" << endl; + } + else + { + // Connect capure ports to jacktrip send + for (int i = 0; i < mNumInChans; i++) + { + // Check that we don't run out of capture ports + if ( ports[i] != NULL ) { + jack_connect(mClient, ports[i], jack_port_name(mInPorts[i])); + } + } + std::free(ports); + } + + // Get physical input (playback) ports + if ( (ports = + jack_get_ports (mClient, NULL, NULL, + JackPortIsPhysical | JackPortIsInput)) == NULL) + { + cout << "WARING: Cannot find any physical playback ports" << endl; + } + else + { + // Connect playback ports to jacktrip receive + for (int i = 0; i < mNumOutChans; i++) + { + // Check that we don't run out of capture ports + if ( ports[i] != NULL ) { + jack_connect(mClient, jack_port_name(mOutPorts[i]), ports[i]); + } + } + std::free(ports); + } +} diff --git a/src/JackAudioInterface.h b/src/JackAudioInterface.h new file mode 100644 index 0000000..2b01ff2 --- /dev/null +++ b/src/JackAudioInterface.h @@ -0,0 +1,305 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackAudioInterface.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + + +#ifndef __JACKAUDIOINTERFACE_H__ +#define __JACKAUDIOINTERFACE_H__ + +#include +#include //for shared_ptr +#include //for mem_fun_ref +#include + +#include +#include +#include + + +#include "jacktrip_types.h" +#include "ProcessPlugin.h" + +class JackTrip; //forward declaration + + +/** \brief Class that provides an interface with the Jack Audio Server + * + * \todo implement srate_callback + * \todo automatically starts jack with buffer and sample rate settings specified by the user + */ +class JackAudioInterface +{ +public: + + /// \brief Enum for Audio Resolution in bits + /// \todo implement this into the class, now it's using jack default of 32 bits + enum audioBitResolutionT { + BIT8 = 1, ///< 8 bits + BIT16 = 2, ///< 16 bits (default) + BIT24 = 3, ///< 24 bits + BIT32 = 4 ///< 32 bits + }; + + /// \brief Sampling Rates supported by JACK + enum samplingRateT { + SR22, ///< 22050 Hz + SR32, ///< 32000 Hz + SR44, ///< 44100 Hz + SR48, ///< 48000 Hz + SR88, ///< 88200 Hz + SR96, ///< 96000 Hz + SR192, ///< 192000 Hz + UNDEF ///< Undefined + }; + + /** \brief The class constructor + * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator) + * \param NumInChans Number of Input Channels + * \param NumOutChans Number of Output Channels + * \param AudioBitResolution Audio Sample Resolutions in bits + * \param ClientName Client name in Jack + */ + JackAudioInterface(JackTrip* jacktrip, + int NumInChans, int NumOutChans, + audioBitResolutionT AudioBitResolution = BIT16, + const char* ClientName = "JackTrip"); + + /** \brief The class destructor + */ + virtual ~JackAudioInterface(); + + /** \brief Setup the client + */ + void setup(); + + /** \brief Get the Jack Server Sampling Rate, in samples/second + */ + uint32_t getSampleRate() const; + + /** \brief Get the Jack Server Sampling Rate Enum Type samplingRateT + * \return JackAudioInterface::samplingRateT enum type + */ + samplingRateT getSampleRateType() const; + + /** \brief Helper function to get the sample rate (in Hz) for a + * JackAudioInterface::samplingRateT + * \param rate_type JackAudioInterface::samplingRateT enum type + * \return Sample Rate in Hz + */ + static int getSampleRateFromType(samplingRateT rate_type); + + /** \brief Get the Jack Server Buffer Size, in samples + */ + uint32_t getBufferSizeInSamples() const; + + /** \brief Get the Jack Server Buffer Size, in bytes + */ + uint32_t getBufferSizeInBytes() const + { + return (getBufferSizeInSamples() * getAudioBitResolution()/8); + } + + /** \brief Get the Audio Bit Resolution, in bits + * + * This is one of the audioBitResolutionT set in construction + */ + int getAudioBitResolution() const; + + /// \brief Get Number of Input Channels + int getNumInputChannels() const; + + /// \brief Get Number of Output Channels + int getNumOutputChannels() const; + + /// \brief Get size of each audio per channel, in bytes + size_t getSizeInBytesPerChannel() const; + + /** \brief Tell the JACK server that we are ready to roll. The + * process-callback will start running. This runs on its own thread. + * \return 0 on success, otherwise a non-zero error code + */ + int startProcess() const; + + /** \brief Stops the process-callback thread + * \return 0 on success, otherwise a non-zero error code + */ + int stopProcess() const; + + /** \brief Set the pointer to the Input and Output RingBuffer + * that'll be use to read and write audio + * + * These RingBuffers are used to read and write audio samples on + * each JACK callback. + * \todo If the RingBuffer is blocked, the callback should stay + * on the last buffer, as in JackTrip (wavetable synth) + * \param InRingBuffer RingBuffer to read samples from + * \param OutRingBuffer RingBuffer to write samples to + */ + /* + void setRingBuffers(const std::tr1::shared_ptr InRingBuffer, + const std::tr1::shared_ptr OutRingBuffer); + */ + + /** \brief Append a ProcessPlugin. The order of processing is determined by + * the order by which appending is done. + * \param plugin a ProcesPlugin smart pointer. Create the object instance + * using something like:\n + * std::tr1::shared_ptr loopback(new ProcessPluginName); + */ + //void appendProcessPlugin(const std::tr1::shared_ptr plugin); + void appendProcessPlugin(ProcessPlugin* plugin); + + /** \brief Convert a 32bit number (sample_t) into one of the bit resolution + * supported (audioBitResolutionT). + * + * The result is stored in an int_8 array of the + * appropriate size to hold the value. The caller is responsible to allocate + * enough space to store the result. + */ + static void fromSampleToBitConversion(const sample_t* const input, + int8_t* output, + const audioBitResolutionT targetBitResolution); + + /** \brief Convert a audioBitResolutionT bit resolution number into a + * 32bit number (sample_t) + * + * The result is stored in an sample_t array of the + * appropriate size to hold the value. The caller is responsible to allocate + * enough space to store the result. + */ + static void fromBitToSampleConversion(const int8_t* const input, + sample_t* output, + const audioBitResolutionT sourceBitResolution); + + /// \brief Connect the default ports, capture to sends, and receives to playback + void connectDefaultPorts(); + + /// \brief Set Client Name to something different that the default (JackTrip) + void setClientName(const char* ClientName) + { mClientName = ClientName; } + +private: + + /** \brief Private method to setup a client of the Jack server. + * \exception std::runtime_error Can't start Jack + * + * This method is called by the class constructors. It does the following:\n + * - Connects to the JACK server + * - Sets the shutdown process callback + * - Creates the appropriate number of input and output channels + */ + void setupClient(); + + /** \brief Creates input and output channels in the Jack client + */ + void createChannels(); + + /** \brief JACK calls this shutdown_callback if the server ever shuts down or + * decides to disconnect the client. + */ + static void jackShutdown(void*); + + /// \brief Sets the part of the process callback that sends and receive packets + //void computeNetworkProcess(); + + /// \brief Compute the process to receive packets to JACK + void computeNetworkProcessFromNetwork(); + + /// \brief Compute the process from JACK to send packets + void computeNetworkProcessToNetwork(); + + /** \brief Set the process callback of the member function processCallback. + * This process will be called by the JACK server whenever there is work to be done. + */ + void setProcessCallback(); + + /** \brief JACK process callback + * + * This is the function to be called to process audio. This function is + * of the type JackProcessCallback, which is defined as:\n + * typedef int(* JackProcessCallback)(jack_nframes_t nframes, void *arg) + * \n + * See + * http://jackaudio.org/files/docs/html/types_8h.html#4923142208a8e7dacf00ca7a10681d2b + * for more details + */ + int processCallback(jack_nframes_t nframes); + + /** \brief Wrapper to cast the member processCallback to a static function pointer + * that can be used with jack_set_process_callback + * + * jack_set_process_callback needs a static member function pointer. A normal + * member function won't work because a this pointer is passed under the scenes. + * That's why we + * need to cast the member funcion processCallback to the static function + * wrapperProcessCallback. The callback is then set as:\n + * jack_set_process_callback(mClient, JackAudioInterface::wrapperProcessCallback, + * this) + */ + // reference : http://article.gmane.org/gmane.comp.audio.jackit/12873 + static int wrapperProcessCallback(jack_nframes_t nframes, void *arg) ; + + + int mNumInChans;///< Number of Input Channels + int mNumOutChans; ///< Number of Output Channels + int mNumFrames; ///< Buffer block size, in samples + int mAudioBitResolution; ///< Bit resolution in audio samples + audioBitResolutionT mBitResolutionMode; ///< Bit resolution (audioBitResolutionT) mode + + jack_client_t* mClient; ///< Jack Client + const char* mClientName; ///< Jack Client Name + QVarLengthArray mInPorts; ///< Vector of Input Ports (Channels) + QVarLengthArray mOutPorts; ///< Vector of Output Ports (Channels) + //jack_port_t** mInPorts; ///< Vector of Input Ports (Channels) + //jack_port_t** mOutPorts; ///< Vector of Output Ports (Channels) + QVarLengthArray mInBuffer; ///< Vector of Input buffers/channel read from JACK + QVarLengthArray mOutBuffer; ///< Vector of Output buffer/channel to write to JACK + + QVarLengthArray mInProcessBuffer;///< Vector of Input buffers/channel for ProcessPlugin + QVarLengthArray mOutProcessBuffer;///< Vector of Output buffers/channel for ProcessPlugin + + int8_t* mInputPacket; ///< Packet containing all the channels to read from the RingBuffer + int8_t* mOutputPacket; ///< Packet containing all the channels to send to the RingBuffer + size_t mSizeInBytesPerChannel; ///< Size in bytes per audio channel + + QVector mProcessPlugins; ///< Vector of ProcesPlugins + JackTrip* mJackTrip; ///< JackTrip mediator class + + static QMutex sJackMutex; ///< Mutex to make thread safe jack functions that are not +}; + + +#endif diff --git a/src/JackTrip.cpp b/src/JackTrip.cpp new file mode 100644 index 0000000..6384504 --- /dev/null +++ b/src/JackTrip.cpp @@ -0,0 +1,466 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTrip.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#include "JackTrip.h" +#include "UdpDataProtocol.h" +#include "RingBufferWavetable.h" +#include "jacktrip_globals.h" + +#include +//#include // for usleep, sleep +#include +#include + +#include +#include + +using std::cout; using std::endl; + + + +//******************************************************************************* +JackTrip::JackTrip(jacktripModeT JacktripMode, + dataProtocolT DataProtocolType, + int NumChans, + int BufferQueueLength, + unsigned int redundancy, + JackAudioInterface::audioBitResolutionT AudioBitResolution, + DataProtocol::packetHeaderTypeT PacketHeaderType, + underrunModeT UnderRunMode, + int receiver_bind_port, int sender_bind_port, + int receiver_peer_port, int sender_peer_port) : + mJackTripMode(JacktripMode), + mDataProtocol(DataProtocolType), + mPacketHeaderType(PacketHeaderType), + mNumChans(NumChans), + mBufferQueueLength(BufferQueueLength), + mSampleRate(0), + mAudioBufferSize(0), + mAudioBitResolution(AudioBitResolution), + mDataProtocolSender(NULL), + mDataProtocolReceiver(NULL), + mJackAudio(NULL), + mPacketHeader(NULL), + mUnderRunMode(UnderRunMode), + mSendRingBuffer(NULL), + mReceiveRingBuffer(NULL), + mReceiverBindPort(receiver_bind_port), + mSenderPeerPort(sender_peer_port), + mSenderBindPort(sender_bind_port), + mReceiverPeerPort(receiver_peer_port), + mRedundancy(redundancy), + mJackClientName("JackTrip") +{} + + +//******************************************************************************* +JackTrip::~JackTrip() +{ + delete mDataProtocolSender; + delete mDataProtocolReceiver; + delete mJackAudio; + delete mPacketHeader; + delete mSendRingBuffer; + delete mReceiveRingBuffer; +} + + +//******************************************************************************* +void JackTrip::setupJackAudio() +{ + // Create JackAudioInterface Client Object + mJackAudio = new JackAudioInterface(this, mNumChans, mNumChans, mAudioBitResolution); + mJackAudio->setClientName(mJackClientName); + mJackAudio->setup(); + mSampleRate = mJackAudio->getSampleRate(); + std::cout << "The Sampling Rate is: " << mSampleRate << std::endl; + std::cout << gPrintSeparator << std::endl; + mAudioBufferSize = mJackAudio->getBufferSizeInSamples(); + int AudioBufferSizeInBytes = mAudioBufferSize*sizeof(sample_t); + std::cout << "The Audio Buffer Size is: " << mAudioBufferSize << " samples" << std::endl; + std::cout << " or: " << AudioBufferSizeInBytes + << " bytes" << std::endl; + std::cout << gPrintSeparator << std::endl; + cout << "The Number of Channels is: " << mJackAudio->getNumInputChannels() << endl; + std::cout << gPrintSeparator << std::endl; + QThread::usleep(100); +} + + +//******************************************************************************* +void JackTrip::setupDataProtocol() +{ + // Create DataProtocol Objects + switch (mDataProtocol) { + case UDP: + std::cout << "Using UDP Protocol" << std::endl; + std::cout << gPrintSeparator << std::endl; + QThread::usleep(100); + mDataProtocolSender = new UdpDataProtocol(this, DataProtocol::SENDER, + //mSenderPeerPort, mSenderBindPort, + mSenderBindPort, mSenderPeerPort, + mRedundancy); + mDataProtocolReceiver = new UdpDataProtocol(this, DataProtocol::RECEIVER, + mReceiverBindPort, mReceiverPeerPort, + mRedundancy); + break; + case TCP: + throw std::invalid_argument("TCP Protocol is not implemented"); + break; + case SCTP: + throw std::invalid_argument("SCTP Protocol is not implemented"); + break; + default: + throw std::invalid_argument("Protocol not defined or unimplemented"); + break; + } + + // Set Audio Packet Size + mDataProtocolSender->setAudioPacketSize + (mJackAudio->getSizeInBytesPerChannel() * mNumChans); + mDataProtocolReceiver->setAudioPacketSize + (mJackAudio->getSizeInBytesPerChannel() * mNumChans); +} + + +//******************************************************************************* +void JackTrip::setupRingBuffers() +{ + // Create RingBuffers with the apprioprate size + /// \todo Make all this operations cleaner + switch (mUnderRunMode) { + case WAVETABLE: + mSendRingBuffer = new RingBufferWavetable(mJackAudio->getSizeInBytesPerChannel() * mNumChans, + gDefaultOutputQueueLength); + mReceiveRingBuffer = new RingBufferWavetable(mJackAudio->getSizeInBytesPerChannel() * mNumChans, + mBufferQueueLength); + + break; + case ZEROS: + mSendRingBuffer = new RingBuffer(mJackAudio->getSizeInBytesPerChannel() * mNumChans, + gDefaultOutputQueueLength); + mReceiveRingBuffer = new RingBuffer(mJackAudio->getSizeInBytesPerChannel() * mNumChans, + mBufferQueueLength); + break; + default: + throw std::invalid_argument("Underrun Mode undefined"); + break; + } +} + + +//******************************************************************************* +void JackTrip::setPeerAddress(const char* PeerHostOrIP) +{ + mPeerAddress = PeerHostOrIP; +} + + +//******************************************************************************* +void JackTrip::appendProcessPlugin(ProcessPlugin* plugin) +{ + mProcessPlugins.append(plugin); + //mJackAudio->appendProcessPlugin(plugin); +} + + +//******************************************************************************* +void JackTrip::start() +{ + // Check if ports are already binded by another process on this machine + checkIfPortIsBinded(mReceiverBindPort); + checkIfPortIsBinded(mSenderBindPort); + + // Set all classes and parameters + setupJackAudio(); + createHeader(mPacketHeaderType); + setupDataProtocol(); + setupRingBuffers(); + + // Start the threads for the specific mode + switch ( mJackTripMode ) + { + case CLIENT : + clientStart(); + break; + case SERVER : + serverStart(); + break; + case CLIENTTOPINGSERVER : + clientPingToServerStart(); + break; + default: + throw std::invalid_argument("Jacktrip Mode undefined"); + break; + } + + // Start Threads + mJackAudio->startProcess(); + for (int i = 0; i < mProcessPlugins.size(); ++i) { + mJackAudio->appendProcessPlugin(mProcessPlugins[i]); + } + mJackAudio->connectDefaultPorts(); + mDataProtocolSender->start(); + mDataProtocolReceiver->start(); +} + + +//******************************************************************************* +void JackTrip::stop() +{ + // Stop The Sender + mDataProtocolSender->stop(); + mDataProtocolSender->wait(); + + // Stop The Receiver + mDataProtocolReceiver->stop(); + mDataProtocolReceiver->wait(); + + // Stop the jack process callback + mJackAudio->stopProcess(); + + cout << "JackTrip Processes STOPPED!" << endl; + cout << gPrintSeparator << endl; + + // Emit the jack stopped signal + emit signalProcessesStopped(); +} + +//******************************************************************************* +void JackTrip::wait() +{ + mDataProtocolSender->wait(); + mDataProtocolReceiver->wait(); +} + + +//******************************************************************************* +void JackTrip::clientStart() +{ + // For the Client mode, the peer (or server) address has to be specified by the user + if ( mPeerAddress.isEmpty() ) { + throw std::invalid_argument("Peer Address has to be set if you run in CLIENT mode"); + } + else { + // Set the peer address + mDataProtocolSender->setPeerAddress( mPeerAddress.toLatin1().data() ); + mDataProtocolReceiver->setPeerAddress( mPeerAddress.toLatin1().data() ); + cout << "Peer Address set to: " << mPeerAddress.toStdString() << std::endl; + cout << gPrintSeparator << endl; + } +} + + +//******************************************************************************* +void JackTrip::serverStart() +{ + // Set the peer address + if ( !mPeerAddress.isEmpty() ) { + std::cout << "WARNING: SERVER mode: Peer Address was set but will be deleted." << endl; + mPeerAddress.clear(); + } + + // Get the client address when it connects + cout << "Waiting for Connection From Client..." << endl; + QHostAddress peerHostAddress; + uint16_t peer_port; + QUdpSocket UdpSockTemp;// Create socket to wait for client + + // Bind the socket + if ( !UdpSockTemp.bind(QHostAddress::Any, mReceiverBindPort, + QUdpSocket::DefaultForPlatform) ) + { + throw std::runtime_error("Could not bind UDP socket. It may be already binded."); + } + // Listen to client + while ( !UdpSockTemp.hasPendingDatagrams() ) { QThread::usleep(100000); } + char buf[1]; + // set client address + UdpSockTemp.readDatagram(buf, 1, &peerHostAddress, &peer_port); + UdpSockTemp.close(); // close the socket + + mPeerAddress = peerHostAddress.toString(); + cout << "Client Connection Received from IP : " + << qPrintable(mPeerAddress) << endl; + cout << gPrintSeparator << endl; + + // Set the peer address to send packets (in the protocol sender) + mDataProtocolSender->setPeerAddress( mPeerAddress.toLatin1().constData() ); + mDataProtocolReceiver->setPeerAddress( mPeerAddress.toLatin1().constData() ); + // We reply to the same port the peer sent the packets + // This way we can go through NAT + // Because of the NAT traversal scheme, the portn need to be + // "symetric", e.g.: + // from Client to Server : src = 4474, dest = 4464 + // from Server to Client : src = 4464, dest = 4474 + mDataProtocolSender->setPeerPort(peer_port); + mDataProtocolReceiver->setPeerPort(peer_port); + setPeerPorts(peer_port); +} + +//******************************************************************************* +void JackTrip::clientPingToServerStart() +{ + // For the Client mode, the peer (or server) address has to be specified by the user + if ( mPeerAddress.isEmpty() ) { + throw std::invalid_argument("Peer Address has to be set if you run in CLIENTTOPINGSERVER mode"); + } + else { + // Set the peer address + mDataProtocolSender->setPeerAddress( mPeerAddress.toLatin1().data() ); + } + + // Start Threads + mJackAudio->startProcess(); + //mJackAudio->connectDefaultPorts(); + mDataProtocolSender->start(); + //cout << "STARTED DATA PROTOCOL SENDER-----------------------------" << endl; + //mDataProtocolReceiver->start(); + + QHostAddress serverHostAddress; + QUdpSocket UdpSockTemp;// Create socket to wait for server answer + uint16_t server_port; + + // Bind the socket + if ( !UdpSockTemp.bind(QHostAddress::Any, + mReceiverBindPort, + QUdpSocket::DefaultForPlatform) ) { + throw std::runtime_error("Could not bind UDP socket. It may be already binded."); + } + // Listen to server response + cout << "Waiting for server response..." << endl; + while ( !UdpSockTemp.hasPendingDatagrams() ) { QThread::usleep(100000); } + cout << "Received response from server!" << endl; + char buf[1]; + // set client address + UdpSockTemp.readDatagram(buf, 1, &serverHostAddress, &server_port); + UdpSockTemp.close(); // close the socket + + // Stop the sender thread to change server port + mDataProtocolSender->stop(); + mDataProtocolSender->wait(); // Wait for the thread to terminate + /* + while ( mDataProtocolSender->isRunning() ) + { + cout << "IS RUNNING!" << endl; + QThread::usleep(100000); + } + */ + cout << "Server port now set to: " << server_port-1 << endl; + cout << gPrintSeparator << endl; + mDataProtocolSender->setPeerPort(server_port-1); + + // Start Threads + //mJackAudio->connectDefaultPorts(); + mDataProtocolSender->start(); + mDataProtocolReceiver->start(); +} + + +//******************************************************************************* +void JackTrip::createHeader(const DataProtocol::packetHeaderTypeT headertype) +{ + switch (headertype) { + case DataProtocol::DEFAULT : + mPacketHeader = new DefaultHeader(this); + break; + case DataProtocol::JAMLINK : + mPacketHeader = new JamLinkHeader(this); + break; + case DataProtocol::EMPTY : + mPacketHeader = new EmptyHeader(this); + break; + default : + throw std::invalid_argument("Undefined Header Type"); + break; + } +} + + +//******************************************************************************* +void JackTrip::putHeaderInPacket(int8_t* full_packet, int8_t* audio_packet) +{ + mPacketHeader->fillHeaderCommonFromAudio(); + mPacketHeader->putHeaderInPacket(full_packet); + + int8_t* audio_part; + audio_part = full_packet + mPacketHeader->getHeaderSizeInBytes(); + //std::memcpy(audio_part, audio_packet, mJackAudio->getBufferSizeInBytes()); + std::memcpy(audio_part, audio_packet, mJackAudio->getSizeInBytesPerChannel() * mNumChans); +} + + +//******************************************************************************* +int JackTrip::getPacketSizeInBytes() const +{ + //return (mJackAudio->getBufferSizeInBytes() + mPacketHeader->getHeaderSizeInBytes()); + return (mJackAudio->getSizeInBytesPerChannel() * mNumChans + + mPacketHeader->getHeaderSizeInBytes()); +} + + +//******************************************************************************* +void JackTrip::parseAudioPacket(int8_t* full_packet, int8_t* audio_packet) +{ + int8_t* audio_part; + audio_part = full_packet + mPacketHeader->getHeaderSizeInBytes(); + //std::memcpy(audio_packet, audio_part, mJackAudio->getBufferSizeInBytes()); + std::memcpy(audio_packet, audio_part, mJackAudio->getSizeInBytesPerChannel() * mNumChans); +} + + +//******************************************************************************* +void JackTrip::checkPeerSettings(int8_t* full_packet) +{ + mPacketHeader->checkPeerSettings(full_packet); +} + + +//******************************************************************************* +void JackTrip::checkIfPortIsBinded(int port) +{ + QUdpSocket UdpSockTemp;// Create socket to wait for client + + // Bind the socket + if ( !UdpSockTemp.bind(QHostAddress::Any, port, QUdpSocket::DontShareAddress) ) + { + UdpSockTemp.close(); // close the socket + throw std::runtime_error( + "Could not bind UDP socket. It may already be binded by another process on your machine. Try using a different port number"); + } + UdpSockTemp.close(); // close the socket +} diff --git a/src/JackTrip.h b/src/JackTrip.h new file mode 100644 index 0000000..69576f2 --- /dev/null +++ b/src/JackTrip.h @@ -0,0 +1,313 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTrip.h + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#ifndef __JACKTRIP_H__ +#define __JACKTRIP_H__ + +#include //for shared_ptr + +#include +#include + +#include "DataProtocol.h" +#include "JackAudioInterface.h" +#include "PacketHeader.h" +#include "RingBuffer.h" + + +/** \brief Main class to creates a SERVER (to listen) or a CLIENT (to connect + * to a listening server) to send audio streams in the network. + * + * All audio and network settings can be set in this class. + * This class also acts as a Mediator between all the other class. + * Classes that uses JackTrip methods need to register with it. + */ +class JackTrip : public QThread +{ + Q_OBJECT; + +public: + + //----------ENUMS------------------------------------------ + /// \brief Enum for the data Protocol. At this time only UDP is implemented + enum dataProtocolT { + UDP, ///< Use UDP (User Datagram Protocol) + TCP, ///< NOT IMPLEMENTED: Use TCP (Transmission Control Protocol) + SCTP ///< NOT IMPLEMENTED: Use SCTP (Stream Control Transmission Protocol) + }; + + /// \brief Enum for the JackTrip mode + enum jacktripModeT { + SERVER, ///< Run in Server Mode + CLIENT, ///< Run in Client Mode + CLIENTTOPINGSERVER ///< Client of the Ping Server Mode + }; + + /// \brief Enum for the JackTrip Underrun Mode, when packets + enum underrunModeT { + WAVETABLE, ///< Loops on the last received packet + ZEROS ///< Set new buffers to zero if there are no new ones + }; + //--------------------------------------------------------- + + + /** \brief The class Constructor with Default Parameters + * \param JacktripMode JackTrip::CLIENT or JackTrip::SERVER + * \param DataProtocolType JackTrip::dataProtocolT + * \param NumChans Number of Audio Channels (same for inputs and outputs) + * \param BufferQueueLength Audio Buffer for receiving packets + * \param AudioBitResolution Audio Sample Resolutions in bits + * \param redundancy redundancy factor for network data + */ + JackTrip(jacktripModeT JacktripMode = CLIENT, + dataProtocolT DataProtocolType = UDP, + int NumChans = 2, + int BufferQueueLength = 8, + unsigned int redundancy = 1, + JackAudioInterface::audioBitResolutionT AudioBitResolution = + JackAudioInterface::BIT16, + DataProtocol::packetHeaderTypeT PacketHeaderType = + DataProtocol::DEFAULT, + underrunModeT UnderRunMode = WAVETABLE, + int receiver_bind_port = gDefaultPort, + int sender_bind_port = gDefaultPort, + int receiver_peer_port = gDefaultPort, + int sender_peer_port = gDefaultPort); + + /// \brief The class destructor + virtual ~JackTrip(); + + /// \brief Set the Peer Address for jacktripModeT::CLIENT mode only + void setPeerAddress(const char* PeerHostOrIP); + + /** \brief Append a process plugin. Processes will be appended in order + * \param plugin Pointer to ProcessPlugin Class + */ + //void appendProcessPlugin(const std::tr1::shared_ptr plugin); + void appendProcessPlugin(ProcessPlugin* plugin); + + /// \brief Start the processing threads + void start(); + + /// \brief Stop the processing threads + void stop(); + + /// \brief Wait for all the threads to finish. This functions is used when JackTrip is + /// run as a thread + void wait(); + + /// \brief Check if UDP port is already binded + /// \param port Port number + void checkIfPortIsBinded(int port); + + //------------------------------------------------------------------------------------ + /// \name Methods to change parameters after construction + //@{ + // + /// \brief Sets (override) JackTrip Mode after construction + void setJackTripMode(jacktripModeT JacktripMode) + { mJackTripMode = JacktripMode; } + /// \brief Sets (override) DataProtocol Type after construction + void setDataProtocoType(dataProtocolT DataProtocolType) + { mDataProtocol = DataProtocolType; } + /// \brief Sets the Packet header type + void setPacketHeaderType(DataProtocol::packetHeaderTypeT PacketHeaderType) + { + mPacketHeaderType = PacketHeaderType; + delete mPacketHeader; + mPacketHeader = NULL; + createHeader(mPacketHeaderType); + } + /// \brief Sets (override) Number of Channels after construction + /// \todo implement this, not working right now because channels cannot be changed after construction + //void setNumChannels(int NumChans) + //{ mNumChans=NumChans; } + /// \brief Sets (override) Buffer Queue Length Mode after construction + void setBufferQueueLength(int BufferQueueLength) + { mBufferQueueLength = BufferQueueLength; } + /// \brief Sets (override) Audio Bit Resolution after construction + void setAudioBitResolution(JackAudioInterface::audioBitResolutionT AudioBitResolution) + { mAudioBitResolution = AudioBitResolution; } + /// \brief Sets (override) Underrun Mode + void setUnderRunMode(underrunModeT UnderRunMode) + { mUnderRunMode = UnderRunMode; } + /// \brief Sets port numbers for the local and peer machine. + /// Receive port is port + void setAllPorts(int port) + { + mReceiverBindPort = port; + mSenderPeerPort = port; + mSenderBindPort = port; + mReceiverPeerPort = port; + } + /// \brief Sets port numbers to bind in RECEIVER and SENDER sockets. + void setBindPorts(int port) + { + mReceiverBindPort = port; + mSenderBindPort = port; + } + /// \brief Sets port numbers for the peer (remote) machine. + void setPeerPorts(int port) + { + mSenderPeerPort = port; + mReceiverPeerPort = port; + } + /// \brief Set Client Name to something different that the default (JackTrip) + void setClientName(char* ClientName) + { mJackClientName = ClientName; } + //@} + //------------------------------------------------------------------------------------ + + + //------------------------------------------------------------------------------------ + /// \name Mediator Functions + //@{ + /// \todo Document all these functions + void createHeader(const DataProtocol::packetHeaderTypeT headertype); + void putHeaderInPacket(int8_t* full_packet, int8_t* audio_packet); + int getPacketSizeInBytes() const; + void parseAudioPacket(int8_t* full_packet, int8_t* audio_packet); + void sendNetworkPacket(const int8_t* ptrToSlot) + { mSendRingBuffer->insertSlotNonBlocking(ptrToSlot); } + void receiveNetworkPacket(int8_t* ptrToReadSlot) + { mReceiveRingBuffer->readSlotNonBlocking(ptrToReadSlot); } + void readAudioBuffer(int8_t* ptrToReadSlot) + { mSendRingBuffer->readSlotBlocking(ptrToReadSlot); } + void writeAudioBuffer(const int8_t* ptrToSlot) + { mReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot); } + uint32_t getBufferSizeInSamples() const + { return mJackAudio->getBufferSizeInSamples(); } + JackAudioInterface::samplingRateT getSampleRateType() const + { return mJackAudio->getSampleRateType(); } + uint8_t getAudioBitResolution() const + { return mJackAudio->getAudioBitResolution(); } + int getNumInputChannels() const + { return mJackAudio->getNumInputChannels(); } + int getNumOutputChannels() const + {return mJackAudio->getNumOutputChannels(); } + void checkPeerSettings(int8_t* full_packet); + void increaseSequenceNumber() + { mPacketHeader->increaseSequenceNumber(); } + int getSequenceNumber() const + { return mPacketHeader->getSequenceNumber(); } + int getPeerSequenceNumber(int8_t* full_packet) const + { return mPacketHeader->getPeerSequenceNumber(full_packet); } + //@} + //------------------------------------------------------------------------------------ + + +public slots: + /// \brief Slot to stop all the processes and threads + void slotStopProcesses() + { + std::cout << "Stopping JackTrip..." << std::endl; + stop(); + }; + + /** \brief This slot emits in turn the signal signalNoUdpPacketsForSeconds + * when UDP is waited for more than 30 seconds. + * + * It is used to remove the thread from the server. + */ + void slotUdpWatingTooLong(int wait_msec) + { + int wait_time = 30000; // msec + if ( !(wait_msec%wait_time) ) { + std::cerr << "UDP WAITED MORE THAN 30 seconds." << std::endl; + emit signalNoUdpPacketsForSeconds(); + } + } + + +signals: + /// \brieg Signal emitted when all the processes and threads are stopped + void signalProcessesStopped(); + /// \brieg Signal emitted when no UDP Packets have been received for a while + void signalNoUdpPacketsForSeconds(); + + +private: + + /// \brief Set the JackAudioInteface object + void setupJackAudio(); + /// \brief Set the DataProtocol objects + void setupDataProtocol(); + /// \brief Set the RingBuffer objects + void setupRingBuffers(); + /// \brief Starts for the CLIENT mode + void clientStart(); + /// \brief Starts for the SERVER mode + void serverStart(); + /// \brief Stats for the Client to Ping Server + void clientPingToServerStart(); + + jacktripModeT mJackTripMode; ///< JackTrip::jacktripModeT + dataProtocolT mDataProtocol; ///< Data Protocol Tipe + DataProtocol::packetHeaderTypeT mPacketHeaderType; ///< Packet Header Type + + int mNumChans; ///< Number of Channels (inputs = outputs) + int mBufferQueueLength; ///< Audio Buffer from network queue length + uint32_t mSampleRate; ///< Sample Rate + uint32_t mAudioBufferSize; ///< Audio buffer size to process on each callback + JackAudioInterface::audioBitResolutionT mAudioBitResolution; ///< Audio Bit Resolutions + QString mPeerAddress; ///< Peer Address to use in jacktripModeT::CLIENT Mode + + /// Pointer to Abstract Type DataProtocol that sends packets + DataProtocol* mDataProtocolSender; + ///< Pointer to Abstract Type DataProtocol that receives packets + DataProtocol* mDataProtocolReceiver; + JackAudioInterface* mJackAudio; ///< Interface to Jack Client + PacketHeader* mPacketHeader; ///< Pointer to Packet Header + underrunModeT mUnderRunMode; ///< underrunModeT Mode + + /// Pointer for the Send RingBuffer + RingBuffer* mSendRingBuffer; + /// Pointer for the Receive RingBuffer + RingBuffer* mReceiveRingBuffer; + + int mReceiverBindPort; ///< Incoming (receiving) port for local machine + int mSenderPeerPort; ///< Incoming (receiving) port for peer machine + int mSenderBindPort; ///< Outgoing (sending) port for local machine + int mReceiverPeerPort; ///< Outgoing (sending) port for peer machine + + unsigned int mRedundancy; ///< Redundancy factor in network data + const char* mJackClientName; ///< JackAudio Client Name + + QVector mProcessPlugins; ///< Vector of ProcesPlugins +}; + +#endif diff --git a/src/JackTripThread.cpp b/src/JackTripThread.cpp new file mode 100644 index 0000000..5b3f52d --- /dev/null +++ b/src/JackTripThread.cpp @@ -0,0 +1,102 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTripThread.cpp + * \author Juan-Pablo Caceres + * \date September 2008 + */ + + +#include "JackTripThread.h" +#include "NetKS.h" + +#include +#include + +using std::cout; using std::endl; + + +//******************************************************************************* +void JackTripThread::run() +{ + JackTrip jacktrip(mJackTripMode); + jacktrip.setAllPorts(mPortNum); + + if ( mJackTripMode == JackTrip::CLIENT ) + { + jacktrip.setPeerAddress(mPeerAddress); + } + + NetKS netks; + jacktrip.appendProcessPlugin(&netks); + //netks.play(); + + + //QThread::sleep(1); + jacktrip.start(); + //netks.play(); + jacktrip.wait(); + + + cout << "******** AFTER JACKTRIPTHREAD START **************" << endl; + //QThread::sleep(9999999); + + + + /* + jack_client_t* mClient; + const char* client_name = "JackThread"; + const char* server_name = NULL; + jack_options_t options = JackNoStartServer; + jack_status_t status; + + mClient = jack_client_open (client_name, options, &status, server_name); + + if (mClient == NULL) { + fprintf (stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + std::exit(1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(mClient); + fprintf (stderr, "unique name `%s' assigned\n", client_name); + } + */ + + +} diff --git a/src/JackTripThread.h b/src/JackTripThread.h new file mode 100644 index 0000000..6825386 --- /dev/null +++ b/src/JackTripThread.h @@ -0,0 +1,64 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTripThread.h + * \author Juan-Pablo Caceres + * \date September 2008 + */ + +#ifndef __JACKTRIPTHREAD_H__ +#define __JACKTRIPTHREAD_H__ + +#include + +#include "JackTrip.h" + +/** \brief Test class that runs JackTrip inside a thread + */ +class JackTripThread : public QThread +{ +public: + JackTripThread(JackTrip::jacktripModeT JacktripMode) : mJackTripMode(JacktripMode) {}; + virtual ~JackTripThread(){}; + void run(); + + void setPort(int port_num) { mPortNum = port_num; } ; + void setPeerAddress(const char* PeerHostOrIP) { mPeerAddress = PeerHostOrIP; } + +private: + JackTrip::jacktripModeT mJackTripMode; ///< JackTrip::jacktripModeT + int mPortNum; + const char* mPeerAddress; ///< Peer Address to use in jacktripModeT::CLIENT Mode +}; + + +#endif //__JACKTRIPTHREAD_H__ diff --git a/src/JackTripWorker.cpp b/src/JackTripWorker.cpp new file mode 100644 index 0000000..e20c4ba --- /dev/null +++ b/src/JackTripWorker.cpp @@ -0,0 +1,178 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTripWorker.cpp + * \author Juan-Pablo Caceres + * \date September 2008 + */ + +#include +#include + +#include +#include + +#include "JackTripWorker.h" +#include "JackTrip.h" +#include "UdpMasterListener.h" +#include "NetKS.h" +#include "LoopBack.h" + +using std::cout; using std::endl; + +//******************************************************************************* +JackTripWorker::JackTripWorker(UdpMasterListener* udpmasterlistener) : + mUdpMasterListener(NULL), + mSpawning(false), + mID(0), + mNumChans(1) +{ + /* From the QT Documentation: + QThreadPool supports executing the same QRunnable more than once + by calling tryStart(this) from within QRunnable::run(). If autoDelete is + enabled the QRunnable will be deleted when the last thread exits the + run function. Calling start() multiple times with the same QRunnable + when autoDelete is enabled creates a race condition and is not recommended. + */ + mUdpMasterListener = udpmasterlistener; + setAutoDelete(false); // stick around after calling run() + //mNetks = new NetKS; + //mNetks->play(); +} + + +//******************************************************************************* +JackTripWorker::~JackTripWorker() +{ + delete mUdpMasterListener; + +} + + +//******************************************************************************* +void JackTripWorker::setJackTrip(int id, uint32_t client_address, + uint16_t server_port, uint16_t client_port, + int num_channels) +{ + { //Start Spawning, so lock mSpawning + QMutexLocker locker(&mMutex); + mSpawning = true; + } + mID = id; + // Set the jacktrip address and ports + mClientAddress.setAddress(client_address); + mServerPort = server_port; + mClientPort = client_port; + mNumChans = num_channels; +} + + +//******************************************************************************* +void JackTripWorker::run() +{ + /* + NOTE: This is the message that qt prints when an exception is thrown: + 'Qt Concurrent has caught an exception thrown from a worker thread. + This is not supported, exceptions thrown in worker threads must be + caught before control returns to Qt Concurrent.' + */ + + // Try catching any exceptions that come from JackTrip + try + { + // Local event loop. this is necesary because QRunnables don't have their own as QThreads + QEventLoop event_loop; + + // Create and setup JackTrip Object + JackTrip jacktrip(JackTrip::CLIENT, JackTrip::UDP, mNumChans, 2); + jacktrip.setPeerAddress( mClientAddress.toString().toLatin1().data() ); + jacktrip.setBindPorts(mServerPort); + jacktrip.setPeerPorts(mClientPort-1); + + // Connect signals and slots + // ------------------------- + QObject::connect(&jacktrip, SIGNAL(signalNoUdpPacketsForSeconds()), + &jacktrip, SLOT(slotStopProcesses()), Qt::QueuedConnection); + + // Connection to terminate the local eventloop when jacktrip is done + QObject::connect(&jacktrip, SIGNAL(signalProcessesStopped()), + &event_loop, SLOT(quit()), Qt::QueuedConnection); + + // Karplus Strong String + NetKS netks; + jacktrip.appendProcessPlugin(&netks); + // Play the String + QTimer timer; + QObject::connect(&timer, SIGNAL(timeout()), &netks, SLOT(exciteString()), + Qt::QueuedConnection); + timer.start(300); + + // Start Threads and event loop + jacktrip.start(); + + { // Thread is already spawning, so release the lock + QMutexLocker locker(&mMutex); + mSpawning = false; + } + + event_loop.exec(); // Excecution will block here until exit() the QEventLoop + //-------------------------------------------------------------------------- + + // wait for jacktrip to be done before exiting the Worker Thread + jacktrip.wait(); + + } + catch ( const std::exception & e ) + { + std::cerr << "Couldn't send thread to the Pool" << endl; + std::cerr << e.what() << endl; + std::cerr << gPrintSeparator << endl; + } + + mUdpMasterListener->releasePort(mID); + { + // Thread is already spawning, so release the lock + QMutexLocker locker(&mMutex); + mSpawning = false; + } + + cout << "JackTrip ID = " << mID << " released from the THREAD POOL" << endl; + cout << gPrintSeparator << endl; +} + + +//******************************************************************************* +bool JackTripWorker::isSpawning() +{ + QMutexLocker locker(&mMutex); + return mSpawning; +} diff --git a/src/JackTripWorker.h b/src/JackTripWorker.h new file mode 100644 index 0000000..8df371e --- /dev/null +++ b/src/JackTripWorker.h @@ -0,0 +1,122 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTripWorker.h + * \author Juan-Pablo Caceres + * \date September 2008 + */ + +#ifndef __JACKTRIPWORKER_H__ +#define __JACKTRIPWORKER_H__ + +#include + +#include +#include +#include +#include +#include + +#include "jacktrip_types.h" + +class JackTrip; // forward declaration +class UdpMasterListener; // forward declaration + + +/** \brief Prototype of the worker class that will be cloned through sending threads to the + * Thread Pool + * + * This class can be send to the ThreadPool using the start() method. Each time + * it is sent, it'll became "independent" of the prototype, which means + * that the prototype state can be changed, and used to send and start + * another thread into the pool. setAutoDelete must be set to false + * in order for this to work. + */ +// Note that it is not possible to start run() as an event loop. That has to be implemented +// inside a QThread +class JackTripWorker : public QObject, public QRunnable +{ + Q_OBJECT; // QRunnable is not a QObject, so I have to inherit from QObject as well + +public: + /// \brief The class constructor + JackTripWorker(UdpMasterListener* udpmasterlistener); + + /// \brief The class destructor + virtual ~JackTripWorker(); + + /** \brief Implements the Thread Loop. + * To start the thread, call start() ( DO NOT CALL run() ). + */ + void run(); + + /** \brief Check if the Thread is Spawning + * \return true is it is spawning, false if it's already running + */ + bool isSpawning(); + + /** \brief Sets the JackTripWorker properties + * \param id ID number + * \param address + */ + void setJackTrip(int id, uint32_t client_address, + uint16_t server_port, uint16_t client_port, + int num_channels); + + +private slots: + void slotTest() + { + std::cout << "--- JackTripWorker TEST SLOT ---" << std::endl; + } + + +private: + + UdpMasterListener* mUdpMasterListener; ///< Master Listener Socket + QHostAddress mClientAddress; ///< Client Address + uint16_t mServerPort; ///< Server Ephemeral Incomming Port to use with Client + + /// Client Outgoing Port. By convention, the receving port will be mClientPort -1 + uint16_t mClientPort; + + /// Thread spawning internal lock. + /// If true, the prototype is working on creating (spawning) a new thread + volatile bool mSpawning; + QMutex mMutex; ///< Mutex to protect mSpawning + + int mID; ///< ID thread number + int mNumChans; ///< Number of Channels +}; + + +#endif //__JACKTRIPWORKER_H__ diff --git a/src/JackTripWorkerMessages.h b/src/JackTripWorkerMessages.h new file mode 100644 index 0000000..e3bef9a --- /dev/null +++ b/src/JackTripWorkerMessages.h @@ -0,0 +1,75 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTripWorkerMessages.h + * \author Juan-Pablo Caceres + * \date October 2008 + */ + +#ifndef __JACKTRIPWORKERMESSAGES_H__ +#define __JACKTRIPWORKERMESSAGES_H__ + +#include +#include + +#include + +class JackTripWorkerMessages : public QObject +{ + Q_OBJECT; + +public: + JackTripWorkerMessages() {}; + virtual ~JackTripWorkerMessages() {}; + + void play() + { + std::cout << "********** PALYING ***********************************" << std::endl; + QTimer *timer = new QTimer(this); + QObject::connect(timer, SIGNAL(timeout()), this, SLOT(slotTest()), Qt::QueuedConnection); + timer->start(300); + } + +public slots: + void slotTest() + { + std::cout << "---JackTripWorkerMessages slotTest()---" << std::endl; + } + +signals: + void signalTest(); + /// Signal to stop the event loop inside the JackTripWorker Thread + void signalStopEventLoop(); + +}; + +#endif //__JACKTRIPWORKERMESSAGES_H__ diff --git a/src/LoopBack.cpp b/src/LoopBack.cpp new file mode 100644 index 0000000..f22197b --- /dev/null +++ b/src/LoopBack.cpp @@ -0,0 +1,54 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file LoopBack.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + + +#include "LoopBack.h" +#include "jacktrip_types.h" + +#include // for memcpy + + +//******************************************************************************* +void LoopBack::compute(int nframes, float** inputs, float** outputs) +{ + for ( int i = 0; i < getNumInputs(); i++ ) { + // Everything that comes out, copy back to inputs + //memcpy(inputs[i], outputs[i], sizeof(sample_t) * nframes); + memcpy(outputs[i], inputs[i], sizeof(sample_t) * nframes); + } +} + diff --git a/src/LoopBack.h b/src/LoopBack.h new file mode 100644 index 0000000..c25de27 --- /dev/null +++ b/src/LoopBack.h @@ -0,0 +1,70 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file LoopBack.h + * \author Juan-Pablo Caceres + * \date July 2008 + */ + + +/** \brief Connect Inputs to Outputs + * + */ +#ifndef __LOOPBACK_H__ +#define __LOOPBACK_H__ + +#include "ProcessPlugin.h" + + +/** \brief This Class just copy audio from its inputs to its outputs. + * + * It can be use to do loopback without the need to externally connect channels + * in JACK. Note that if you do connect the channels in jack, you'll + * be effectively multiplying the signal by 2. + */ +class LoopBack : public ProcessPlugin +{ +public: + /// \brief The class constructor sets the number of channels to connect as loopback + LoopBack(int numchans) { mNumChannels = numchans; }; + /// \brief The class destructor + virtual ~LoopBack() {}; + + virtual int getNumInputs() { return(mNumChannels); }; + virtual int getNumOutputs() { return(mNumChannels); }; + virtual void compute(int nframes, float** inputs, float** outputs); + +private: + int mNumChannels; +}; + +#endif diff --git a/src/NetKS.h b/src/NetKS.h new file mode 100644 index 0000000..2d09c4b --- /dev/null +++ b/src/NetKS.h @@ -0,0 +1,137 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file JackTrip.h + * \author Juan-Pablo Caceres + * \date October 2008 + */ + +#ifndef __NETKS_H__ +#define __NETKS_H__ + +#include +#include + +#include + +#include "ProcessPlugin.h" + +/** \brief A simple (basic) network Karplus Strong. + * + * This plugin creates a one channel network karplus strong. + */ +class NetKS : public ProcessPlugin +{ + Q_OBJECT; + + +public: + /* + void play() + { + std::cout << "********** PALYING ***********************************" << std::endl; + QTimer *timer = new QTimer(this); + QObject::connect(timer, SIGNAL(timeout()), this, SLOT(exciteString())); + timer->start(300); + } + */ + +private slots: + + /// \brief Stlot to excite (play) the string + void exciteString() + { + std::cout << "========= EXTICING STRING ===========" << std::endl; + fbutton0 = 1.0; + //std::cout << fbutton0 << std::endl; + usleep(280000); /// \todo Define this number based on the sampling rate and buffer size + fbutton0 = 0.0; + //std::cout << fbutton0 << std::endl; + } + + //=========== FROM FAUST =================================================== + private: + float fbutton0; + float fVec0[2]; + float fRec0[2]; + int iRec1[2]; + float fVec1[2]; + public: + virtual int getNumInputs() { return 1; } + virtual int getNumOutputs() { return 1; } + static void classInit(int /*samplingFreq*/) {} + virtual void instanceInit(int samplingFreq) { + fSamplingFreq = samplingFreq; + fbutton0 = 0.0; + for (int i=0; i<2; i++) fVec0[i] = 0; + for (int i=0; i<2; i++) fRec0[i] = 0; + for (int i=0; i<2; i++) iRec1[i] = 0; + for (int i=0; i<2; i++) fVec1[i] = 0; + } + virtual void init(int samplingFreq) { + classInit(samplingFreq); + instanceInit(samplingFreq); + } + /* + virtual void buildUserInterface(UI* interface) { + interface->openVerticalBox("excitator"); + interface->addButton("play", &fbutton0); + interface->closeBox(); + } + */ + virtual void compute (int count, float** input, float** output) { + float* input0 = input[0]; + float* output0 = output[0]; + float fSlow0 = fbutton0; + for (int i=0; i 0.000000f) + fRec0[1]) - (3.333333e-03f * (fRec0[1] > 0.000000f))); + iRec1[0] = (12345 + (1103515245 * iRec1[1])); + float fTemp0 = ((4.190951e-10f * iRec1[0]) * (fRec0[0] > 0.000000f)); + float fTemp1 = input0[i]; + fVec1[0] = (fTemp1 + fTemp0); + output0[i] = (0.500000f * ((fTemp0 + fTemp1) + fVec1[1])); + // post processing + fVec1[1] = fVec1[0]; + iRec1[1] = iRec1[0]; + fRec0[1] = fRec0[0]; + fVec0[1] = fVec0[0]; + } + } + + //============================================================================ + +}; + + +#endif // __NETKS_H__ + diff --git a/src/PacketHeader.cpp b/src/PacketHeader.cpp new file mode 100644 index 0000000..1a37556 --- /dev/null +++ b/src/PacketHeader.cpp @@ -0,0 +1,265 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file PacketHeader.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#include "PacketHeader.h" +#include "JackTrip.h" + +#include +#include +#include +#include + +using std::cout; using std::endl; + + +//####################################################################### +//####################### PacketHeader ################################## +//####################################################################### +//*********************************************************************** +PacketHeader::PacketHeader(JackTrip* jacktrip) : + mSeqNumber(0), mJackTrip(jacktrip) +{} + + +//*********************************************************************** +uint64_t PacketHeader::usecTime() +{ + struct timeval tv; + gettimeofday (&tv, NULL); + return ( (tv.tv_sec * 1000000) + // seconds + (tv.tv_usec) ); // plus the microseconds. Type suseconds_t, range [-1, 1000000] +} + + + + +//####################################################################### +//####################### DefaultHeader ################################# +//####################################################################### +//*********************************************************************** +DefaultHeader::DefaultHeader(JackTrip* jacktrip) : + PacketHeader(jacktrip), mJackTrip(jacktrip) +{ + mHeader.TimeStamp = 0; + mHeader.SeqNumber = 0; + mHeader.BufferSize = 0; + mHeader.SamplingRate = 0; + mHeader.BitResolution = 0; + mHeader.NumInChannels = 0; + mHeader.NumOutChannels = 0; + //mHeader.Dummy = 0; +} + + +//*********************************************************************** +void DefaultHeader::fillHeaderCommonFromAudio() +{ + mHeader.BufferSize = mJackTrip->getBufferSizeInSamples(); + mHeader.SamplingRate = mJackTrip->getSampleRateType (); + mHeader.NumInChannels = mJackTrip->getNumInputChannels(); + mHeader.BitResolution = mJackTrip->getAudioBitResolution(); + mHeader.NumOutChannels = mJackTrip->getNumOutputChannels(); + //mHeader.SeqNumber = 0; + mHeader.TimeStamp = PacketHeader::usecTime(); + //cout << mHeader.TimeStamp << endl; + //printHeader(); +} + + +//*********************************************************************** +void DefaultHeader::checkPeerSettings(int8_t* full_packet) +{ + bool error = false; + + DefaultHeaderStruct* peer_header; + peer_header = reinterpret_cast(full_packet); + + // Check Buffer Size + if ( peer_header->BufferSize != mHeader.BufferSize ) + { + std::cerr << "ERROR: Peer Buffer Size is : " << peer_header->BufferSize << endl; + std::cerr << " Local Buffer Size is : " << mHeader.BufferSize << endl; + std::cerr << "Make sure both machines use same buffer size" << endl; + std::cerr << gPrintSeparator << endl; + error = true; + } + + // Check Sampling Rate + if ( peer_header->SamplingRate != mHeader.SamplingRate ) + { + std::cerr << "ERROR: Peer Sampling Rate is : " << + JackAudioInterface::getSampleRateFromType + ( static_cast(peer_header->SamplingRate) ) << endl; + std::cerr << " Local Sampling Rate is : " << + JackAudioInterface::getSampleRateFromType + ( static_cast(mHeader.SamplingRate) ) << endl; + std::cerr << "Make sure both machines use the same Sampling Rate" << endl; + std::cerr << gPrintSeparator << endl; + error = true; + } + + // Check Audio Bit Resolution + if ( peer_header->BitResolution != mHeader.BitResolution ) + { + std::cerr << "ERROR: Peer Audio Bit Resolution is : " + << static_cast(peer_header->BitResolution) << endl; + std::cerr << " Local Audio Bit Resolution is : " + << static_cast(mHeader.BitResolution) << endl; + std::cerr << "Make sure both machines use the same Bit Resolution" << endl; + std::cerr << gPrintSeparator << endl; + error = true; + } + + // Exit program if error + if (error) + { + //std::cerr << "Exiting program..." << endl; + //std::exit(1); + throw std::logic_error("Local and Peer Settings don't match"); + } + /// \todo Check number of channels and other parameters +} + + +//*********************************************************************** +void DefaultHeader::printHeader() const +{ + cout << "Default Packet Header:" << endl; + cout << "Buffer Size = " << static_cast(mHeader.BufferSize) << endl; + // Get the sample rate in Hz form the JackAudioInterface::samplingRateT + int sample_rate = + JackAudioInterface::getSampleRateFromType + ( static_cast(mHeader.SamplingRate) ); + cout << "Sampling Rate = " << sample_rate << endl; + cout << "Audio Bit Resolutions = " << static_cast(mHeader.BitResolution) << endl; + cout << "Number of Input Channels = " << static_cast(mHeader.NumInChannels) << endl; + cout << "Number of Output Channels = " << static_cast(mHeader.NumOutChannels) << endl; + cout << "Sequence Number = " << static_cast(mHeader.SeqNumber) << endl; + cout << "Time Stamp = " << mHeader.TimeStamp << endl; + cout << gPrintSeparator << endl; + cout << sizeof(mHeader) << endl; +} + + +//*********************************************************************** +uint16_t DefaultHeader::getPeerSequenceNumber(int8_t* full_packet) const +{ + DefaultHeaderStruct* peer_header; + peer_header = reinterpret_cast(full_packet); + return peer_header->SeqNumber; +} + + + + + + + + +//####################################################################### +//####################### JamLinkHeader ################################# +//####################################################################### +//*********************************************************************** +JamLinkHeader::JamLinkHeader(JackTrip* jacktrip) : + PacketHeader(jacktrip), mJackTrip(jacktrip) +{ + mHeader.Common = 0; + mHeader.SeqNumber = 0; + mHeader.TimeStamp = 0; +} + + +//*********************************************************************** +void JamLinkHeader::fillHeaderCommonFromAudio() +{ + // Check number of channels + int num_inchannels = mJackTrip->getNumInputChannels(); + if ( num_inchannels != 1 ) { + //std::cerr << "ERROR: JamLink only support ONE channel. Run JackTrip using only one channel" + // << endl; + //std::exit(1); + throw std::logic_error("JamLink only support ONE channel. Run JackTrip using only one channel"); + } + + // Sampling Rate + int rate_type = mJackTrip->getSampleRateType(); + if ( rate_type != JackAudioInterface::SR48 ) { + throw std::logic_error("ERROR: JamLink only support 48kHz for communication with JackTrip at the moment."); + } + + // Check Buffer Size + int buf_size = mJackTrip->getBufferSizeInSamples(); + if ( buf_size != 64 ) + { + throw std::logic_error("ERROR: JamLink only support 64 buffer size for communication with JackTrip at the moment."); + } + + mHeader.Common = (ETX_MONO | ETX_16BIT | ETX_XTND) + 64; + switch (rate_type) + { + case JackAudioInterface::SR48 : + mHeader.Common = (mHeader.Common | ETX_48KHZ); + break; + case JackAudioInterface::SR44 : + mHeader.Common = (mHeader.Common | ETX_44KHZ); + break; + case JackAudioInterface::SR32 : + mHeader.Common = (mHeader.Common | ETX_32KHZ); + break; + case JackAudioInterface::SR22 : + mHeader.Common = (mHeader.Common | ETX_22KHZ); + break; + default: + //std::cerr << "ERROR: Sample rate not supported by JamLink" << endl; + //std::exit(1); + throw std::out_of_range("Sample rate not supported by JamLink"); + break; + } +} + + + + + + +//####################################################################### +//####################### EmptyHeader ################################# +//####################################################################### +//*********************************************************************** +EmptyHeader::EmptyHeader(JackTrip* jacktrip) : + PacketHeader(jacktrip), mJackTrip(jacktrip) +{} diff --git a/src/PacketHeader.h b/src/PacketHeader.h new file mode 100644 index 0000000..73cf979 --- /dev/null +++ b/src/PacketHeader.h @@ -0,0 +1,292 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file PacketHeader.h + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#ifndef __PACKETHEADER_H__ +#define __PACKETHEADER_H__ + +#include +#include // for shared_ptr +#include + +#include "jacktrip_types.h" +#include "jacktrip_globals.h" +class JackTrip; // Forward Declaration + + +/// \brief Abstract Header Struct, Header Stucts should subclass it +struct HeaderStruct{}; + +/// \brief Default Header Struct +struct DefaultHeaderStruct : public HeaderStruct +{ +public: + // watch out for alignment... + uint64_t TimeStamp; ///< Time Stamp + uint16_t SeqNumber; ///< Sequence Number + uint16_t BufferSize; ///< Buffer Size in Samples + uint8_t SamplingRate; ///< Sampling Rate in JackAudioInterface::samplingRateT + uint8_t BitResolution; ///< Audio Bit Resolution + uint8_t NumInChannels; ///< Number of Input Channels + uint8_t NumOutChannels; ///< Number of Output Channels + //uint8_t Dummy; ///< Dummy value to byte padding alignment +}; + +//--------------------------------------------------------- +//JamLink UDP Header: +/************************************************************************/ +/* values for the UDP stream type */ +/* streamType is a 16-bit value at the head of each UDP stream */ +/* Its bit map is as follows: (b15-msb) */ +/* B15:reserved, B14:extended header, B13 Stereo, B12 not 16-bit */ +/* B11-B9: 0-48 Khz, 1-44 Khz, 2-32 Khz, 3-24 Khz, */ +/* 4-22 Khz, 5-16 Khz, 6-11 Khz, 7-8 Khz */ +/* B8-0: Samples in packet */ +/************************************************************************/ +const unsigned short ETX_RSVD = (0<<15); +const unsigned short ETX_XTND = (1<<14); +const unsigned short ETX_STEREO = (1<<13); +const unsigned short ETX_MONO = (0<<13); +const unsigned short ETX_16BIT = (0<<12); +//inline unsigned short ETX_RATE_MASK(const unsigned short a) { a&(0x7<<9); } +const unsigned short ETX_48KHZ = (0<<9); +const unsigned short ETX_44KHZ = (1<<9); +const unsigned short ETX_32KHZ = (2<<9); +const unsigned short ETX_24KHZ = (3<<9); +const unsigned short ETX_22KHZ = (4<<9); +const unsigned short ETX_16KHZ = (5<<9); +const unsigned short ETX_11KHZ = (6<<9); +const unsigned short ETX_8KHZ = (7<<9); +// able to express up to 512 SPP +//inline unsigned short ETX_SPP(const unsigned short a) { (a&0x01FF); } + +/// \brief JamLink Header Struct +struct JamLinkHeaderStuct : public HeaderStruct +{ + // watch out for alignment -- need to be on 4 byte chunks + uint16_t Common; ///< Common part of the header, 16 bit + uint16_t SeqNumber; ///< Sequence Number + uint32_t TimeStamp; ///< Time Stamp +}; + + +//####################################################################### +//####################### PacketHeader ################################## +//####################################################################### +/** \brief Base class for header type. Subclass this struct to + * create a new header. + */ +class PacketHeader +{ +public: + /// \brief The class Constructor + PacketHeader(JackTrip* jacktrip); + /// \brief The class Destructor + virtual ~PacketHeader() {}; + + /** \brief Return a time stamp in microseconds + * \return Time stamp: microseconds since midnight (0 hour), January 1, 1970 + */ + static uint64_t usecTime(); + + /// \todo Implement this using a JackTrip Method (Mediator) member instead of the + /// reference to JackAudio + virtual void fillHeaderCommonFromAudio() = 0; + + /* \brief Parse the packet header and take appropriate measures (like change settings, or + * quit the program if peer settings don't match) + */ + virtual void parseHeader() = 0; + + virtual void checkPeerSettings(int8_t* full_packet) = 0; + virtual uint16_t getPeerSequenceNumber(int8_t* full_packet) const = 0; + + /* \brief Increase sequence number for counter, a 16bit number + */ + virtual void increaseSequenceNumber() + { + mSeqNumber++; + }; + + /* \brief Returns the current sequence number + * \return 16bit Sequence number + */ + virtual uint16_t getSequenceNumber() const + { + return mSeqNumber; + } + + /* \brief Get the header size in bytes + */ + virtual int getHeaderSizeInBytes() const = 0; + + + virtual void putHeaderInPacketBaseClass(int8_t* full_packet, + const HeaderStruct& header_struct) + { + std::memcpy(full_packet, reinterpret_cast(&header_struct), + getHeaderSizeInBytes() ); + }; + + /* \brief Put the header in buffer pointed by full_packet + * \param full_packet Pointer to full packet (audio+header). Size must be + * sizeof(header part) + sizeof(audio part) + */ + virtual void putHeaderInPacket(int8_t* full_packet) = 0; + +private: + uint16_t mSeqNumber; + JackTrip* mJackTrip; ///< JackTrip mediator class +}; + + + + +//####################################################################### +//####################### DefaultHeader ################################# +//####################################################################### +/** \brief Default Header + */ +class DefaultHeader : public PacketHeader +{ +public: + /* + //----------STRUCT----------------------------------------- + /// \brief Default Header Struct + struct DefaultHeaderStruct + { + // watch out for alignment... + uint64_t TimeStamp; ///< Time Stamp + uint16_t SeqNumber; ///< Sequence Number + uint16_t BufferSize; ///< Buffer Size in Samples + uint8_t SamplingRate; ///< Sampling Rate in JackAudioInterface::samplingRateT + uint8_t NumInChannels; ///< Number of Input Channels + uint8_t NumOutChannels; ///< Number of Output Channels + // uint8_t BitResolution; ///< \todo implement this part + }; + //--------------------------------------------------------- + */ + DefaultHeader(JackTrip* jacktrip); + virtual ~DefaultHeader() {}; + virtual void fillHeaderCommonFromAudio(); + virtual void parseHeader() {}; + virtual void checkPeerSettings(int8_t* full_packet); + virtual void increaseSequenceNumber() + { + mHeader.SeqNumber++; + //std::cout << "Sequence Number = " << static_cast(mHeader.SeqNumber) << std::endl; + }; + virtual uint16_t getSequenceNumber() const + { + return mHeader.SeqNumber; + } + virtual uint16_t getPeerSequenceNumber(int8_t* full_packet) const; + virtual int getHeaderSizeInBytes() const { return sizeof(mHeader); }; + virtual void putHeaderInPacket(int8_t* full_packet) + { + putHeaderInPacketBaseClass(full_packet, mHeader); + //std::memcpy(full_packet, reinterpret_cast(&mHeader), + // getHeaderSizeInBytes() ); + }; + void printHeader() const; + +private: + //DefaultHeaderStruct mHeader; ///< Header Struct + DefaultHeaderStruct mHeader;///< Default Header Struct + JackTrip* mJackTrip; ///< JackTrip mediator class +}; + + + + +//####################################################################### +//####################### JamLinkHeader ################################# +//####################################################################### + +/** \brief JamLink Header + */ +class JamLinkHeader : public PacketHeader +{ +public: + + JamLinkHeader(JackTrip* jacktrip); + virtual ~JamLinkHeader() {}; + + virtual void fillHeaderCommonFromAudio(); + virtual void parseHeader() {}; + virtual void checkPeerSettings(int8_t* /*full_packet*/) {} + virtual uint16_t getPeerSequenceNumber(int8_t* /*full_packet*/) const { /*\todo IMPLEMENT*/ return 0; } + virtual void increaseSequenceNumber() {}; + virtual int getHeaderSizeInBytes() const { return sizeof(mHeader); }; + virtual void putHeaderInPacket(int8_t* full_packet) + { + putHeaderInPacketBaseClass(full_packet, mHeader); + }; + +private: + JamLinkHeaderStuct mHeader; ///< JamLink Header Struct + JackTrip* mJackTrip; ///< JackTrip mediator class +}; + + + +//####################################################################### +//####################### EmptyHeader ################################# +//####################################################################### + +/** \brief Empty Header to use with systems that don't include a header. + */ +class EmptyHeader : public PacketHeader +{ +public: + + EmptyHeader(JackTrip* jacktrip); + virtual ~EmptyHeader() {}; + + virtual void fillHeaderCommonFromAudio() {}; + virtual void parseHeader() {}; + virtual void checkPeerSettings(int8_t* /*full_packet*/) {} + virtual uint16_t getPeerSequenceNumber(int8_t* /*full_packet*/) const { return 0; /*\todo IMPLEMENT*/} + virtual void increaseSequenceNumber() {}; + virtual int getHeaderSizeInBytes() const { return 0; }; + virtual void putHeaderInPacket(int8_t* /*full_packet*/) {}; + +private: + JackTrip* mJackTrip; ///< JackTrip mediator class +}; + + +#endif //__PACKETHEADER_H__ diff --git a/src/ProcessPlugin.cpp b/src/ProcessPlugin.cpp new file mode 100644 index 0000000..be26a31 --- /dev/null +++ b/src/ProcessPlugin.cpp @@ -0,0 +1,31 @@ +//#include "ProcessPlugin.h" + +/* +//---------------------------------------------------------------------------- +// Jack Callbacks +//---------------------------------------------------------------------------- + +int srate(jack_nframes_t nframes, void *arg) +{ + printf("the sample rate is now %u/sec\n", nframes); + return 0; +} + +void jack_shutdown(void *arg) +{ + std::cout << "" << std::endl; + std::exit(1); +} + +int process (jack_nframes_t nframes, void *arg) +{ + for (int i = 0; i < gNumInChans; i++) { + gInChannel[i] = (float *)jack_port_get_buffer(input_ports[i], nframes); + } + for (int i = 0; i < gNumOutChans; i++) { + gOutChannel[i] = (float *)jack_port_get_buffer(output_ports[i], nframes); + } + DSP.compute(nframes, gInChannel, gOutChannel); + return 0; +} +*/ diff --git a/src/ProcessPlugin.h b/src/ProcessPlugin.h new file mode 100644 index 0000000..70533c8 --- /dev/null +++ b/src/ProcessPlugin.h @@ -0,0 +1,81 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file ProcessPlugin.h + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#ifndef __PROCESSPLUGIN_H__ +#define __PROCESSPLUGIN_H__ + +#include +#include + +/** \brief Interface for the process plugins to add to the JACK callback process in + * JackAudioInterface + * + * This class contains the same methods of the FAUST dsp class. A mydsp class can inherit from + * this class the same way it inherits from dsp. Subclass should implement all methods + * except init, which is optional for processing that are sampling rate dependent or + * that need specific initialization. + */ +class ProcessPlugin : public QObject +{ +public: + + /// \brief The Class Constructor + ProcessPlugin() {}; + /// \brief The Class Destructor + virtual ~ProcessPlugin() {}; + + /// \brief Return Number of Input Channels + virtual int getNumInputs() = 0; + /// \brief Return Number of Output Channels + virtual int getNumOutputs() = 0; + + //virtual void buildUserInterface(UI* interface) = 0; + + /** \brief Do proper Initialization of members and class instances. By default this + * initializes the Sampling Frequency. If a class instance depends on the + * sampling frequency, it should be initialize here. + */ + virtual void init(int samplingRate) { fSamplingFreq = samplingRate; }; + + /// \brief Compute process + virtual void compute(int nframes, float** inputs, float** outputs) = 0; + +protected: + int fSamplingFreq; ///< Faust Data member, Sampling Rate +}; + +#endif diff --git a/src/RingBuffer.cpp b/src/RingBuffer.cpp new file mode 100644 index 0000000..a9e746c --- /dev/null +++ b/src/RingBuffer.cpp @@ -0,0 +1,250 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file RingBuffer.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + + +#include "RingBuffer.h" + +#include +#include +#include +#include + +using std::cout; using std::endl; + + +//******************************************************************************* +RingBuffer::RingBuffer(int SlotSize, int NumSlots) : + mSlotSize(SlotSize), + mNumSlots(NumSlots), + mTotalSize(mSlotSize*mNumSlots), + mReadPosition(0), + mWritePosition(0), + mFullSlots(0), + mRingBuffer(new int8_t[mTotalSize]), + mLastReadSlot(new int8_t[mSlotSize]) +{ + //QMutexLocker locker(&mMutex); // lock the mutex + + // Verify if there's enough space to for the buffers + if ( (mRingBuffer == NULL) || (mLastReadSlot == NULL) ) { + //std::cerr << "ERROR: RingBuffer out of memory!" << endl; + //std::cerr << "Exiting program..." << endl; + //std::exit(1); + throw std::length_error("RingBuffer out of memory!"); + } + + // Set the buffers to zeros + /* + for (int i=0; i +#include +#include + +#include "jacktrip_types.h" + + +/** \brief Provides a ring-buffer (or circular-buffer) that can be written to and read from + * asynchronously (blocking) or synchronously (non-blocking). + * + * The RingBuffer is an array of \b NumSlots slots of memory + * each of which is of size \b SlotSize bytes (8-bits). Slots can be read and + * written asynchronously/synchronously by multiple threads. + */ +class RingBuffer +{ +public: + + /** \brief The class constructor + * \param SlotSize Size of one slot in bytes + * \param NumSlots Number of slots + */ + RingBuffer(int SlotSize, int NumSlots); + + /** \brief The class destructor + */ + virtual ~RingBuffer(); + + /** \brief Insert a slot into the RingBuffer from ptrToSlot. This method will block until + * there's space in the buffer. + * + * The caller is responsible to make sure sizeof(WriteSlot) = SlotSize. This + * method should be use when the caller can block against its output, like + * sending/receiving UDP packets. It shouldn't be used by audio. For that, use the + * insertSlotNonBlocking. + * \param ptrToSlot Pointer to slot to insert into the RingBuffer + */ + void insertSlotBlocking(const int8_t* ptrToSlot); + + /** \brief Read a slot from the RingBuffer into ptrToReadSlot. This method will block until + * there's space in the buffer. + * + * The caller is responsible to make sure sizeof(ptrToReadSlot) = SlotSize. This + * method should be use when the caller can block against its input, like + * sending/receiving UDP packets. It shouldn't be used by audio. For that, use the + * readSlotNonBlocking. + * \param ptrToReadSlot Pointer to read slot from the RingBuffer + */ + void readSlotBlocking(int8_t* ptrToReadSlot); + + /** \brief Same as insertSlotBlocking but non-blocking (asynchronous) + * \param ptrToSlot Pointer to slot to insert into the RingBuffer + */ + void insertSlotNonBlocking(const int8_t* ptrToSlot); + + /** \brief Same as readSlotBlocking but non-blocking (asynchronous) + * \param ptrToReadSlot Pointer to read slot from the RingBuffer + */ + void readSlotNonBlocking(int8_t* ptrToReadSlot); + + +protected: + + /** \brief Sets the memory in the Read Slot when uderrun occurs. By default, + * this sets it to 0. Override this method in a subclass for a different behavior. + * \param ptrToReadSlot Pointer to read slot from the RingBuffer + */ + virtual void setUnderrunReadSlot(int8_t* ptrToReadSlot); + + /** \brief Uses the last read slot to set the memory in the Read Slot. + * + * The last read slot is the last packet that arrived, so if no new packets are received, + * it keeps looping the same packet. + * \param ptrToReadSlot Pointer to read slot from the RingBuffer + */ + virtual void setMemoryInReadSlotWithLastReadSlot(int8_t* ptrToReadSlot); + +private: + + /// \brief Resets the ring buffer for reads under-runs non-blocking + void underrunReset(); + /// \brief Resets the ring buffer for writes over-flows non-blocking + void overflowReset(); + /// \brief Helper method to debug, prints member variables to terminal + void debugDump() const; + + const int mSlotSize; ///< The size of one slot in byes + const int mNumSlots; ///< Number of Slots + const int mTotalSize; ///< Total size of the mRingBuffer = mSlotSize*mNumSlotss + int mReadPosition; ///< Read Positions in the RingBuffer (Tail) + int mWritePosition; ///< Write Position in the RingBuffer (Head) + int mFullSlots; ///< Number of used (full) slots, in slot-size + int8_t* mRingBuffer; ///< 8-bit array of data (1-byte) + int8_t* mLastReadSlot; ///< Last slot read + + // Thread Synchronization Private Members + QMutex mMutex; ///< Mutex to protect read and write operations + QWaitCondition mBufferIsNotFull; ///< Buffer not full condition to monitor threads + QWaitCondition mBufferIsNotEmpty; ///< Buffer not empty condition to monitor threads +}; + +#endif diff --git a/src/RingBufferWavetable.h b/src/RingBufferWavetable.h new file mode 100644 index 0000000..e63dedf --- /dev/null +++ b/src/RingBufferWavetable.h @@ -0,0 +1,71 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file RingBufferWavetable.h + * \author Juan-Pablo Caceres + * \date September 2008 + */ + +#ifndef __RINGBUFFERWAVETABLE_H__ +#define __RINGBUFFERWAVETABLE_H__ + + +/** \brief Same as RingBuffer, except that it uses the Wavetable mode for + * lost or late packets. + */ +class RingBufferWavetable : public RingBuffer +{ +public: + /** \brief The class constructor + * \param SlotSize Size of one slot in bytes + * \param NumSlots Number of slots + */ + RingBufferWavetable(int SlotSize, int NumSlots) : RingBuffer(SlotSize, NumSlots) {} + + /** \brief The class destructor + */ + virtual ~RingBufferWavetable() {} + +protected: + /** \brief Sets the memory in the Read Slot when uderrun occurs. This loops as a + * wavetable in the last received packet. + * \param ptrToReadSlot Pointer to read slot from the RingBuffer + */ + virtual void setUnderrunReadSlot(int8_t* ptrToReadSlot) + { + setMemoryInReadSlotWithLastReadSlot(ptrToReadSlot); + } + +}; + + +#endif //__RINGBUFFERWAVETABLE_H__ diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 0000000..5b764da --- /dev/null +++ b/src/Settings.cpp @@ -0,0 +1,376 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file Settings.cpp + * \author Juan-Pablo Caceres + * \date July 2008 + */ + +#include "Settings.h" +#include "LoopBack.h" +#include "NetKS.h" +#include "UdpMasterListener.h" +#include "JackTripWorker.h" +#include "jacktrip_globals.h" + +#include +#include // for command line parsing +#include + +#include "ThreadPoolTest.h" + +using std::cout; using std::endl; + +int gVerboseFlag = 0; + + +//******************************************************************************* +Settings::Settings() : + mJackTrip(NULL), + mJackTripMode(JackTrip::SERVER), + mDataProtocol(JackTrip::UDP), + mNumChans(2), + mBufferQueueLength(gDefaultQueueLength), + mAudioBitResolution(JackAudioInterface::BIT16), + mPortNum(gDefaultPort), + mClientName(NULL), + mUnderrrunZero(false), + mLoopBack(false), + mJamLink(false), + mEmptyHeader(false), + mJackTripServer(false), + mRedundancy(1) +{} + +//******************************************************************************* +Settings::~Settings() +{ + stopJackTrip(); + delete mJackTrip; +} + +//******************************************************************************* +void Settings::parseInput(int argc, char** argv) +{ + // If no command arguments are given, print instructions + if(argc == 1) { + printUsage(); + std::exit(0); + } + + // Usage example at: + // http://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html#Getopt-Long-Option-Example + // options descriptor + //---------------------------------------------------------------------------- + static struct option longopts[] = { + // These options set a flag, has to be sepcified as a long option --verbose + { "verbose", no_argument, &gVerboseFlag, 1 }, + // These options don't set a flag. + { "numchannels", required_argument, NULL, 'n' }, // Number of input and output channels + { "server", no_argument, NULL, 's' }, // Run in server mode + { "client", required_argument, NULL, 'c' }, // Run in client mode, set server IP address + { "jacktripserver", no_argument, NULL, 'S' }, // Run in JamLink mode + { "pingtoserver", required_argument, NULL, 'C' }, // Run in ping to server mode, set server IP address + { "portoffset", required_argument, NULL, 'o' }, // Port Offset from 4464 + { "queue", required_argument, NULL, 'q' }, // Queue Length + { "redundancy", required_argument, NULL, 'r' }, // Redundancy + { "bitres", required_argument, NULL, 'b' }, // Audio Bit Resolution + { "zerounderrun", no_argument, NULL, 'z' }, // Use Underrun to Zeros Mode + { "loopback", no_argument, NULL, 'l' }, // Run in loopback mode + { "jamlink", no_argument, NULL, 'j' }, // Run in JamLink mode + { "emptyheader", no_argument, NULL, 'e' }, // Run in JamLink mode + { "clientname", required_argument, NULL, 'J' }, // Run in JamLink mode + { "version", no_argument, NULL, 'v' }, // Version Number + { "help", no_argument, NULL, 'h' }, // Print Help + { NULL, 0, NULL, 0 } + }; + + // Parse Command Line Arguments + //---------------------------------------------------------------------------- + /// \todo Specify mandatory arguments + int ch; + while ( (ch = getopt_long(argc, argv, "n:sc:SC:o:q:r:b:zljeJ:vh", longopts, NULL)) != -1 ) + switch (ch) { + + case 'n': // Number of input and output channels + //------------------------------------------------------- + mNumChans = atoi(optarg); + break; + case 's': // Run in server mode + //------------------------------------------------------- + mJackTripMode = JackTrip::SERVER; + break; + case 'S': // Run in jacktripserver mode + //------------------------------------------------------- + mJackTripServer = true; + break; + case 'c': // Client mode + //------------------------------------------------------- + mJackTripMode = JackTrip::CLIENT; + mPeerAddress = optarg; + break; + case 'C': // Ping to server + //------------------------------------------------------- + mJackTripMode = JackTrip::CLIENTTOPINGSERVER; + mPeerAddress = optarg; + break; + case 'o': // Port Offset + //------------------------------------------------------- + mPortNum += atoi(optarg); + break; + case 'b': + //------------------------------------------------------- + if ( atoi(optarg) == 8 ) { + mAudioBitResolution = JackAudioInterface::BIT8; } + else if ( atoi(optarg) == 16 ) { + mAudioBitResolution = JackAudioInterface::BIT16; } + else if ( atoi(optarg) == 24 ) { + mAudioBitResolution = JackAudioInterface::BIT24; } + else if ( atoi(optarg) == 32 ) { + mAudioBitResolution = JackAudioInterface::BIT32; } + else { + std::cerr << "--bitres ERROR: Wrong bit resolutions: " + << atoi(optarg) << " is not supported." << endl; + printUsage(); + std::exit(1); } + break; + case 'q': + //------------------------------------------------------- + if ( atoi(optarg) <= 0 ) { + std::cerr << "--queue ERROR: The queue has to be a positive integer" << endl; + printUsage(); + std::exit(1); } + else { + mBufferQueueLength = atoi(optarg); + } + break; + case 'r': + //------------------------------------------------------- + if ( atoi(optarg) <= 0 ) { + std::cerr << "--queue ERROR: The queue has to be a positive integer" << endl; + printUsage(); + std::exit(1); } + else { + mRedundancy = atoi(optarg); + } + break; + case 'z': // underrun to zero + //------------------------------------------------------- + mUnderrrunZero = true; + break; + case 'l': // loopback + //------------------------------------------------------- + mLoopBack = true; + break; + case 'e': // jamlink + //------------------------------------------------------- + mEmptyHeader = true; + break; + case 'j': // jamlink + //------------------------------------------------------- + mJamLink = true; + break; + case 'J': + //------------------------------------------------------- + mClientName = optarg; + break; + case 'v': + //------------------------------------------------------- + cout << "JackTrip VERSION: " << gVersion << endl; + cout << "Copyright (c) 2008-2009 Juan-Pablo Caceres, Chris Chafe." << endl; + cout << "SoundWIRE group at CCRMA, Stanford University" << endl; + cout << "" << endl; + std::exit(0); + break; + case 'h': + //------------------------------------------------------- + printUsage(); + std::exit(0); + break; + default: + //------------------------------------------------------- + printUsage(); + std::exit(0); + break; + } + + // Warn user if undefined options where entered + //---------------------------------------------------------------------------- + if (optind < argc) { + cout << gPrintSeparator << endl; + cout << "WARINING: The following entered options have no effect" << endl; + cout << " They will be ignored!" << endl; + cout << " Type jacktrip to see options." << endl; + for( ; optind < argc; optind++) { + printf("argument: %s\n", argv[optind]); + } + cout << gPrintSeparator << endl; + } +} + + +//******************************************************************************* +void Settings::printUsage() +{ + cout << "" << endl; + cout << "JackTrip: A System for High-Quality Audio Network Performance" << endl; + cout << "over the Internet" << endl; + cout << "Copyright (c) 2008-2009 Juan-Pablo Caceres, Chris Chafe." << endl; + cout << "SoundWIRE group at CCRMA, Stanford University" << endl; + cout << "VERSION: " << gVersion << endl; + cout << "-----------------------------------------------------------------------------" << endl; + cout << "" << endl; + cout << "Usage: jacktrip [-s|-c host] [options]" << endl; + cout << "" << endl; + cout << "Options: " << endl; + cout << " -s, --server Run in Server Mode" << endl; + cout << " -c, --client Run in Client Mode" << endl; + cout << " -n, --numchannels # Number of Input and Output Channels (default " + << 2 << ")" << endl; + cout << " -q, --queue # (1 or more) Queue Buffer Length, in Packet Size (default " + << gDefaultQueueLength << ")" << endl; + cout << " -r, --redundancy # (1 or more) Packet Redundancy to avoid glitches with packet losses (defaul 1)" + << endl; + cout << " -o, --portoffset # Receiving port offset from base port " << gDefaultPort << endl; + cout << " -b, --bitres # (8, 16, 24, 32) Audio Bit Rate Resolutions (default 16)" << endl; + cout << " -z, --zerounderrun Set buffer to zeros when underrun occurs (defaults to wavetable)" << endl; + cout << " -l, --loopback Run in Loop-Back Mode" << endl; + cout << " -j, --jamlink Run in JamLink Mode (Connect to a JamLink Box)" << endl; + cout << " --clientname Change default client name (default is JackTrip)" << endl; + cout << " -v, --version Prints Version Number" << endl; + cout << " -h, --help Prints this Help" << endl; + cout << "" << endl; +} + + +//******************************************************************************* +void Settings::startJackTrip() +{ + + ///\todo Change this, just here to test + if ( mJackTripServer ) { + UdpMasterListener* udpmaster = new UdpMasterListener; + udpmaster->start(); + + //---Thread Pool Test-------------------------------------------- + /* + cout << "BEFORE START" << endl; + ThreadPoolTest* thtest = new ThreadPoolTest(); + // QThreadPool takes ownership and deletes 'hello' automatically + QThreadPool::globalInstance()->start(thtest); + + cout << "AFTER START" << endl; + sleep(2); + thtest->stop(); + QThreadPool::globalInstance()->waitForDone(); + */ + //--------------------------------------------------------------- + } + + else { + + //JackTrip jacktrip(mJackTripMode, mDataProtocol, mNumChans, + // mBufferQueueLength, mAudioBitResolution); + mJackTrip = new JackTrip(mJackTripMode, mDataProtocol, mNumChans, + mBufferQueueLength, mRedundancy, mAudioBitResolution); + + // Change client name if different from default + if (mClientName != NULL) { + mJackTrip->setClientName(mClientName); + } + + // Set buffers to zero when underrun + if ( mUnderrrunZero ) { + cout << "Setting buffers to zero when underrun..." << endl; + cout << gPrintSeparator << std::endl; + mJackTrip->setUnderRunMode(JackTrip::ZEROS); + } + + // Set peer address in server mode + if ( mJackTripMode == JackTrip::CLIENT || mJackTripMode == JackTrip::CLIENTTOPINGSERVER ) { + mJackTrip->setPeerAddress(mPeerAddress.toLatin1().data()); } + + // Set Ports + cout << "SETTING ALL PORTS" << endl; + mJackTrip->setAllPorts(mPortNum); + + // Set in JamLink Mode + if ( mJamLink ) { + cout << "Running in JamLink Mode..." << endl; + cout << gPrintSeparator << std::endl; + mJackTrip->setPacketHeaderType(DataProtocol::JAMLINK); + } + + // Set in EmptyHeader Mode + if ( mEmptyHeader ) { + cout << "Running in EmptyHeader Mode..." << endl; + cout << gPrintSeparator << std::endl; + mJackTrip->setPacketHeaderType(DataProtocol::EMPTY); + } + + // Add Plugins + if ( mLoopBack ) { + cout << "Running in Loop-Back Mode..." << endl; + cout << gPrintSeparator << std::endl; + //std::tr1::shared_ptr loopback(new LoopBack(mNumChans)); + //mJackTrip->appendProcessPlugin(loopback.get()); + + LoopBack* loopback = new LoopBack(mNumChans); + mJackTrip->appendProcessPlugin(loopback); + + // ----- Test Karplus Strong ----------------------------------- + //std::tr1::shared_ptr loopback(new NetKS()); + //mJackTrip->appendProcessPlugin(loopback); + //loopback->play(); + //NetKS* netks = new NetKS; + //mJackTrip->appendProcessPlugin(netks); + //netks->play(); + // ------------------------------------------------------------- + } + + // Start JackTrip + mJackTrip->start(); + + /* + sleep(10); + cout << "Stoping JackTrip..." << endl; + mJackTrip->stop(); + */ + } +} + + +//******************************************************************************* +void Settings::stopJackTrip() +{ + mJackTrip->stop(); +} diff --git a/src/Settings.h b/src/Settings.h new file mode 100644 index 0000000..5c08808 --- /dev/null +++ b/src/Settings.h @@ -0,0 +1,84 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file Settings.h + * \author Juan-Pablo Caceres + * \date July 2008 + */ + + +#ifndef __SETTINGS_H__ +#define __SETTINGS_H__ + +#include "DataProtocol.h" +#include "JackAudioInterface.h" +#include "JackTrip.h" + +/** \brief Class to set usage options and parse settings from input + */ +class Settings +{ +public: + Settings(); + virtual ~Settings(); + + /// \brief Parses command line input + void parseInput(int argc, char** argv); + + void startJackTrip(); + void stopJackTrip(); + + /// \brief Prints usage help + void printUsage(); + + bool getLoopBack() { return mLoopBack; }; + +private: + JackTrip* mJackTrip; ///< JackTrip class + JackTrip::jacktripModeT mJackTripMode; ///< JackTrip::jacktripModeT + JackTrip::dataProtocolT mDataProtocol; ///< Data Protocol + int mNumChans; ///< Number of Channels (inputs = outputs) + int mBufferQueueLength; ///< Audio Buffer from network queue length + JackAudioInterface::audioBitResolutionT mAudioBitResolution; + QString mPeerAddress; ///< Peer Address to use in jacktripModeT::CLIENT Mode + int mPortNum; ///< Port Number + char* mClientName; + bool mUnderrrunZero; ///< Use Underrun to Zero mode + + bool mLoopBack; ///< Loop-back mode + bool mJamLink; ///< JamLink mode + bool mEmptyHeader; ///< EmptyHeader mode + bool mJackTripServer; ///< JackTrip Server mode + unsigned int mRedundancy; ///< Redundancy factor for data in the network +}; + +#endif diff --git a/src/TestRingBuffer.h b/src/TestRingBuffer.h new file mode 100644 index 0000000..136710b --- /dev/null +++ b/src/TestRingBuffer.h @@ -0,0 +1,49 @@ +#ifndef __TESTRINGBUFFER__ +#define __TESTRINGBUFFER__ + +#include "RingBuffer.h" +#include +#include + +static RingBuffer rb(2,100); + +class TestRingBufferWrite : public QThread +{ +public: + + void run() + { + int8_t* writeSlot; + writeSlot = new int8_t[2]; + writeSlot[0] = *"a"; + writeSlot[1] = *"b"; + while (true) { + //std::cout << "writing BEFORE" << std::endl; + rb.insertSlotBlocking(writeSlot); + //std::cout << "writing AFTER" << std::endl; + } + } + +}; + + +class TestRingBufferRead : public QThread +{ +public: + + void run() + { + int8_t* readSlot; + readSlot = new int8_t[2]; + while (true) { + //std::cout << "reading BEFORE" << std::endl; + rb.readSlotBlocking(readSlot); + //std::cout << "reading AFTER" << std::endl; + //std::cout << *(readSlot) << std::endl; + //std::cout << *(readSlot+1) << std::endl; + } + } +}; + +#endif + diff --git a/src/ThreadPoolTest.h b/src/ThreadPoolTest.h new file mode 100644 index 0000000..1c85653 --- /dev/null +++ b/src/ThreadPoolTest.h @@ -0,0 +1,77 @@ +/** + * \file ThreadPoolTest.h + * \author Juan-Pablo Caceres + * \date October 2008 + */ + +#ifndef __THREADPOOLTEST_H__ +#define __THREADPOOLTEST_H__ + +#include +#include +#include +#include + +#include + +#include "NetKS.h" +#include "JackTripWorkerMessages.h" + + +class ThreadPoolTest : public QObject, public QRunnable +//class ThreadPoolTest : public QThread +{ + Q_OBJECT; + +public: + ThreadPoolTest() + { + setAutoDelete(false); + } + + void run() + { + JackTripWorkerMessages jtm; + QThread testThread; + //jtm.moveToThread(&testThread); + + //QObject::connect(&jtm, SIGNAL(signalTest()), &jtm, SLOT(slotTest()), Qt::QueuedConnection); + testThread.start(); + jtm.play(); + //testThread.wait(); + + //std::cout << "--------------- BEFORE ---------------" << std::endl; + //NetKS netks; + //netks.play(); + //std::cout << "--------------- AFTER ---------------" << std::endl; + + QEventLoop loop; + //QObject::connect(this, SIGNAL(stopELoop()), &loop, SLOT(quit()), Qt::QueuedConnection); + loop.exec(); + //std::cout << "--------------- EXITING QRUNNABLE---------------" << std::endl; + /* + while (true) { + std::cout << "Hello world from thread" << std::endl; + sleep(1); + } + */ + } + + void stop() + { + std::cout << "--------------- ELOOP STOP---------------" << std::endl; + emit stopELoop(); + } + +signals: + void stopELoop(); + +private slots: + void fromServer() + { + std::cout << "--------------- SIGNAL RECEIVED ---------------" << std::endl; + } + +}; + +#endif //__THREADPOOLTEST_H__ diff --git a/src/UdpDataProtocol.cpp b/src/UdpDataProtocol.cpp new file mode 100644 index 0000000..9e2275f --- /dev/null +++ b/src/UdpDataProtocol.cpp @@ -0,0 +1,505 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file UdpDataProtocol.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include "UdpDataProtocol.h" +#include "jacktrip_globals.h" +#include "JackTrip.h" + +#include +#include +#include +#include +#include +#include // for POSIX Sockets + +using std::cout; using std::endl; + +// NOTE: It's better not to use +// using namespace std; +// because some functions (like exit()) get confused with QT functions + +// sJackMutex definition +QMutex UdpDataProtocol::sUdpMutex; + +//******************************************************************************* +UdpDataProtocol::UdpDataProtocol(JackTrip* jacktrip, const runModeT runmode, + int bind_port, int peer_port, + unsigned int udp_redundancy_factor) : +DataProtocol(jacktrip, runmode, bind_port, peer_port), +mBindPort(bind_port), mPeerPort(peer_port), +mRunMode(runmode), +mAudioPacket(NULL), mFullPacket(NULL), +mUdpRedundancyFactor(udp_redundancy_factor) +{ + if (mRunMode == RECEIVER) { + QObject::connect(this, SIGNAL(signalWatingTooLong(int)), + jacktrip, SLOT(slotUdpWatingTooLong(int)), Qt::QueuedConnection); + } +} + + +//******************************************************************************* +UdpDataProtocol::~UdpDataProtocol() +{ + delete[] mAudioPacket; + delete[] mFullPacket; + wait(); +} + + +//******************************************************************************* +void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP) +{ + mPeerAddress.setAddress(peerHostOrIP); + // check if the ip address is valid + if ( mPeerAddress.isNull() ) { + std::cerr << "ERROR: Incorrect presentation format address" << endl; + std::cerr << "'" << peerHostOrIP <<"' does not seem to be a valid IP address" << endl; + throw std::invalid_argument(""); + } + /* + else { + std::cout << "Peer Address set to: " + << mPeerAddress.toString().toStdString() << std::endl; + cout << gPrintSeparator << endl; + usleep(100); + } + */ +} + + +//******************************************************************************* +void UdpDataProtocol::bindSocket(QUdpSocket& UdpSocket) +{ + QMutexLocker locker(&sUdpMutex); + + // Creat socket descriptor + int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + + // Set local IPv4 Address + struct sockaddr_in local_addr; + ::bzero(&local_addr, sizeof(local_addr)); + local_addr.sin_family = AF_INET; //AF_INET: IPv4 Protocol + local_addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY: let the kernel decide the active address + local_addr.sin_port = htons(mBindPort); //set local port + + // Set socket to be reusable, this is platform dependent + int one = 1; +#if defined ( __LINUX__ ) + ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#endif +#if defined ( __MAC_OSX__ ) + // This option is not avialable on Linux, and without it MAC OS X + // has problems rebinding a socket + ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); +#endif + + // Bind the Socket + if ( (::bind(sock_fd, (struct sockaddr *) &local_addr, sizeof(local_addr))) < 0 ) + { throw std::runtime_error("ERROR: UDP Socket Bind Error"); } + + // To be able to use the two UDP sockets bound to the same port number, + // we connect the receiver and issue a SHUT_WR. + if (mRunMode == SENDER) { + // We use the sender as an unconnected UDP socket + UdpSocket.setSocketDescriptor(sock_fd, QUdpSocket::BoundState, + QUdpSocket::WriteOnly); + } + else if (mRunMode == RECEIVER) { + // Set peer IPv4 Address + struct sockaddr_in peer_addr; + bzero(&peer_addr, sizeof(peer_addr)); + peer_addr.sin_family = AF_INET; //AF_INET: IPv4 Protocol + peer_addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY: let the kernel decide the active address + peer_addr.sin_port = htons(mPeerPort); //set local port + // Connect the socket and issue a Write shutdown (to make it a + // reader socket only) + if ( (::inet_pton(AF_INET, mPeerAddress.toString().toLatin1().constData(), + &peer_addr.sin_addr)) < 1 ) + { throw std::runtime_error("ERROR: Invalid address presentation format"); } + if ( (::connect(sock_fd, (struct sockaddr *) &peer_addr, sizeof(peer_addr))) < 0) + { throw std::runtime_error("ERROR: Could not connect UDP socket"); } + if ( (::shutdown(sock_fd,SHUT_WR)) < 0) + { throw std::runtime_error("ERROR: Could suntdown SHUT_WR UDP socket"); } + + UdpSocket.setSocketDescriptor(sock_fd, QUdpSocket::ConnectedState, + QUdpSocket::ReadOnly); + cout << "UDP Socket Receiving in Port: " << mBindPort << endl; + cout << gPrintSeparator << endl; + } + + // OLD CODE WITHOUT POSIX FIX-------------------------------------------------- + /* + /// \todo if port is already used, try binding in a different port + QUdpSocket::BindMode bind_mode; + if (mRunMode == RECEIVER) { + bind_mode = QUdpSocket::DontShareAddress; } + else if (mRunMode == SENDER) { //Share sender socket + bind_mode = QUdpSocket::ShareAddress; } + + // QHostAddress::Any : let the kernel decide the active address + if ( !UdpSocket.bind(QHostAddress::Any, mBindPort, bind_mode) ) { + throw std::runtime_error("Could not bind UDP socket. It may be already binded."); + } + else { + if ( mRunMode == RECEIVER ) { + cout << "UDP Socket Receiving in Port: " << mBindPort << endl; + cout << gPrintSeparator << endl; + } + } + */ + // ---------------------------------------------------------------------------- +} + + +//******************************************************************************* +int UdpDataProtocol::receivePacket(QUdpSocket& UdpSocket, char* buf, const size_t n) +{ + // Block until There's something to read + while ( (UdpSocket.pendingDatagramSize() < n) && !mStopped ) { QThread::usleep(100); } + int n_bytes = UdpSocket.readDatagram(buf, n); + return n_bytes; +} + + +//******************************************************************************* +int UdpDataProtocol::sendPacket(QUdpSocket& UdpSocket, const QHostAddress& PeerAddress, + const char* buf, const size_t n) +{ + int n_bytes = UdpSocket.writeDatagram(buf, n, PeerAddress, mPeerPort); + return n_bytes; +} + + +//******************************************************************************* +void UdpDataProtocol::getPeerAddressFromFirstPacket(QUdpSocket& UdpSocket, + QHostAddress& peerHostAddress, + uint16_t& port) +{ + while ( !UdpSocket.hasPendingDatagrams() ) { + msleep(100); + } + char buf[1]; + UdpSocket.readDatagram(buf, 1, &peerHostAddress, &port); +} + + +//******************************************************************************* +void UdpDataProtocol::run() +{ + mStopped = false; + + // Creat and bind sockets + QUdpSocket UdpSocket; + bindSocket(UdpSocket); // Bind Socket + QHostAddress PeerAddress; + PeerAddress = mPeerAddress; + + // Setup Audio Packet buffer + size_t audio_packet_size = getAudioPacketSizeInBites(); + //cout << "audio_packet_size: " << audio_packet_size << endl; + mAudioPacket = new int8_t[audio_packet_size]; + std::memset(mAudioPacket, 0, audio_packet_size); // set buffer to 0 + + // Setup Full Packet buffer + int full_packet_size = mJackTrip->getPacketSizeInBytes(); + //cout << "full_packet_size: " << full_packet_size << endl; + mFullPacket = new int8_t[full_packet_size]; + std::memset(mFullPacket, 0, full_packet_size); // set buffer to 0 + + bool timeout = false; // Time out flag for packets that arrive too late + + // Put header in first packet + mJackTrip->putHeaderInPacket(mFullPacket, mAudioPacket); + + // Redundancy Variables + // (Algorithm explained at the end of this file) + // --------------------------------------------- + int full_redundant_packet_size = full_packet_size * mUdpRedundancyFactor; + int8_t* full_redundant_packet; + full_redundant_packet = new int8_t[full_redundant_packet_size]; + std::memset(full_redundant_packet, 0, full_redundant_packet_size); // Initialize to 0 + + // Set realtime priority (function in jacktrip_globals.h) + set_crossplatform_realtime_priority(); + + // Connect signals and slots for packets arriving too late notifications + QObject::connect(this, SIGNAL(signalWatingTooLong(int)), + this, SLOT(printUdpWaitedTooLong(int)), + Qt::QueuedConnection); + + switch ( mRunMode ) + { + case RECEIVER : { + //----------------------------------------------------------------------------------- + // Wait for the first packet to be ready and obtain address + // from that packet + std::cout << "Waiting for Peer..." << std::endl; + // This blocks waiting for the first packet + while ( !UdpSocket.hasPendingDatagrams() ) { QThread::msleep(100); } + int first_packet_size = UdpSocket.pendingDatagramSize(); + // The following line is the same as + // int8_t* first_packet = new int8_t[first_packet_size]; + // but avoids memory leaks + std::tr1::shared_ptr first_packet(new int8_t[first_packet_size]); + receivePacket( UdpSocket, reinterpret_cast(first_packet.get()), first_packet_size); + // Check that peer has the same audio settings + mJackTrip->checkPeerSettings(first_packet.get()); + mJackTrip->parseAudioPacket(mFullPacket, mAudioPacket); + std::cout << "Received Connection for Peer!" << std::endl; + + // Redundancy Variables + // -------------------- + // NOTE: These types need to be the same unsigned integer as the sequence + // number in the header. That way, they wrap around in the "same place" + uint16_t current_seq_num = 0; // Store current sequence number + uint16_t last_seq_num = 0; // Store last package sequence number + uint16_t newer_seq_num = 0; // Store newer sequence number + + while ( !mStopped ) + { + // Timer to report packets arriving too late + // This QT method gave me a lot of trouble, so I replaced it with my own 'waitForReady' + // that uses signals and slots and can also report with packets have not + // arrive for a longer time + //timeout = UdpSocket.waitForReadyRead(30); + timeout = waitForReady(UdpSocket, 60000); //60 seconds + + // OLD CODE WITHOUT REDUNDANCY---------------------------------------------------- + /* + // This is blocking until we get a packet... + receivePacket( UdpSocket, reinterpret_cast(mFullPacket), full_packet_size); + + mJackTrip->parseAudioPacket(mFullPacket, mAudioPacket); + + // ...so we want to send the packet to the buffer as soon as we get in from + // the socket, i.e., non-blocking + //mRingBuffer->insertSlotNonBlocking(mAudioPacket); + mJackTrip->writeAudioBuffer(mAudioPacket); + */ + //---------------------------------------------------------------------------------- + receivePacketRedundancy(UdpSocket, + full_redundant_packet, + full_redundant_packet_size, + full_packet_size, + current_seq_num, + last_seq_num, + newer_seq_num); + } + break; } + + case SENDER : { + //----------------------------------------------------------------------------------- + while ( !mStopped ) + { + // OLD CODE WITHOUT REDUNDANCY ----------------------------------------------------- + /* + // We block until there's stuff available to read + mJackTrip->readAudioBuffer( mAudioPacket ); + mJackTrip->putHeaderInPacket(mFullPacket, mAudioPacket); + // This will send the packet immediately + //int bytes_sent = sendPacket( reinterpret_cast(mFullPacket), full_packet_size); + sendPacket( UdpSocket, PeerAddress, reinterpret_cast(mFullPacket), full_packet_size); + */ + //---------------------------------------------------------------------------------- + sendPacketRedundancy(UdpSocket, + PeerAddress, + full_redundant_packet, + full_redundant_packet_size, + full_packet_size); + } + break; } + } +} + + +//******************************************************************************* +bool UdpDataProtocol::waitForReady(QUdpSocket& UdpSocket, int timeout_msec) +{ + int loop_resolution_usec = 100; // usecs to wait on each loop + int emit_resolution_usec = 10000; // 10 milliseconds + int timeout_usec = timeout_msec * 1000; + int ellaped_time_usec = 0; // Ellapsed time in milliseconds + + while ( ( !(UdpSocket.hasPendingDatagrams()) && (ellaped_time_usec <= timeout_usec) ) + && !mStopped ){ + //cout << mStopped << endl; + QThread::usleep(loop_resolution_usec); + ellaped_time_usec += loop_resolution_usec; + + if ( !(ellaped_time_usec % emit_resolution_usec) ) { + emit signalWatingTooLong(static_cast(ellaped_time_usec/1000)); + } + } + + if ( ellaped_time_usec >= timeout_usec ) + { + emit signalWatingTooLong(ellaped_time_usec/1000); + return false; + } + return true; +} + + +//******************************************************************************* +void UdpDataProtocol::printUdpWaitedTooLong(int wait_msec) +{ + int wait_time = 30; // msec + if ( !(wait_msec%wait_time) ) { + std::cerr << "UDP is waited too long (more than " << wait_time << "ms)..." << endl; + } +} + + +//******************************************************************************* +void UdpDataProtocol::receivePacketRedundancy(QUdpSocket& UdpSocket, + int8_t* full_redundant_packet, + int full_redundant_packet_size, + int full_packet_size, + uint16_t& current_seq_num, + uint16_t& last_seq_num, + uint16_t& newer_seq_num) +{ + // This is blocking until we get a packet... + receivePacket( UdpSocket, reinterpret_cast(full_redundant_packet), + full_redundant_packet_size); + + // Get Packet Sequence Number + newer_seq_num = + mJackTrip->getPeerSequenceNumber(full_redundant_packet); + current_seq_num = newer_seq_num; + + + //cout << current_seq_num << " "; + int redun_last_index = 0; + for (unsigned int i = 1; igetPeerSequenceNumber( full_redundant_packet + (i*full_packet_size) ); + //cout << current_seq_num << " "; + } + //cout << endl; + + last_seq_num = newer_seq_num; // Save last read packet + + // Send to audio all available audio packets, in order + for (int i = redun_last_index; i>=0; i--) { + memcpy(mFullPacket, + full_redundant_packet + (i*full_packet_size), + full_packet_size); + mJackTrip->parseAudioPacket(mFullPacket, mAudioPacket); + mJackTrip->writeAudioBuffer(mAudioPacket); + } +} + +//******************************************************************************* +void UdpDataProtocol::sendPacketRedundancy(QUdpSocket& UdpSocket, + QHostAddress& PeerAddress, + int8_t* full_redundant_packet, + int full_redundant_packet_size, + int full_packet_size) +{ + mJackTrip->readAudioBuffer( mAudioPacket ); + mJackTrip->putHeaderInPacket(mFullPacket, mAudioPacket); + + // Move older packets to end of array of redundant packets + std::memmove(full_redundant_packet+full_packet_size, + full_redundant_packet, + full_packet_size*(mUdpRedundancyFactor-1)); + // Copy new packet to the begining of array + std::memcpy(full_redundant_packet, + mFullPacket, full_packet_size); + + // 10% (or other number) packet lost simulation. + // Uncomment the if to activate + //--------------------------------------------------------------------------------- + //int random_integer = rand(); + //if ( random_integer > (RAND_MAX/10) ) + //{ + sendPacket( UdpSocket, PeerAddress, reinterpret_cast(full_redundant_packet), + full_redundant_packet_size); + //} + //--------------------------------------------------------------------------------- + + mJackTrip->increaseSequenceNumber(); +} + + +/* + The Redundancy Algorythmn works as follows. We send a packet that contains + a mUdpRedundancyFactor number of packets (header+audio). This big packet looks + as follows + + ---------- ------------ ----------------------------------- + | UDP[n] | | UDP[n-1] | ... | UDP[n-(mUdpRedundancyFactor-1)] | + ---------- ------------ ----------------------------------- + + Then, for the new audio buffer, we shift everything to the right and send: + + ---------- ------------ ------------------------------------- + | UDP[n+1] | | UDP[n] | ... | UDP[n-(mUdpRedundancyFactor-1)+1] | + ---------- ------------ ------------------------------------- + + etc... + + For a redundancy factor of 4, this will look as follows: + ---------- ---------- ---------- ---------- + | UDP[4] | | UDP[3] | | UDP[2] | | UDP[1] | + ---------- ---------- ---------- ---------- + + ---------- ---------- ---------- ---------- + | UDP[5] | | UDP[4] | | UDP[3] | | UDP[2] | + ---------- ---------- ---------- ---------- + + ---------- ---------- ---------- ---------- + | UDP[6] | | UDP[5] | | UDP[4] | | UDP[3] | + ---------- ---------- ---------- ---------- + + etc... + + Then, the receiving end checks if the firs packet in the list is the one it should use, + otherwise it continure reding the mUdpRedundancyFactor packets until it finds the one that + should come next (this can better perfected by just jumping until the correct packet). + If it has more than one packet that it hasn't yet received, it sends it to the soundcard + one by one. +*/ diff --git a/src/UdpDataProtocol.h b/src/UdpDataProtocol.h new file mode 100644 index 0000000..fbce562 --- /dev/null +++ b/src/UdpDataProtocol.h @@ -0,0 +1,197 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file UdpDataProtocol.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#ifndef __UDPDATAPROTOCOL_H__ +#define __UDPDATAPROTOCOL_H__ + +#include +#include +#include + +#include "DataProtocol.h" +#include "jacktrip_types.h" +#include "jacktrip_globals.h" + +/** \brief UDP implementation of DataProtocol class + * + * The class has a bind port and a peer port. The meaning of these + * depends on the runModeT. If it's a SENDER, bind port is the source port and + * peer port is the destination port for each UDP packet. If it's a RECEIVER, + * the bind port destination port (for incoming packets) and the peer port + * is the source port. + * + * The SENDER and RECEIVER socket can share the same port/address pair (for compatibility + * with the JamLink boxes). This is achieved setting + * the resusable property in the socket for address and port. You have to + * externaly check if the port is already binded if you want to avoid re-binding to the + * same port. + */ +class UdpDataProtocol : public DataProtocol +{ + Q_OBJECT; + +public: + + /** \brief The class constructor + * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator) + * \param runmode Sets the run mode, use either SENDER or RECEIVER + * \param bind_port Port number to bind for this socket (this is the receive or send port depending on the runmode) + * \param peer_port Peer port number (this is the receive or send port depending on the runmode) + * \param udp_redundancy_factor Number of redundant packets + */ + UdpDataProtocol(JackTrip* jacktrip, const runModeT runmode, + int bind_port, int peer_port, + unsigned int udp_redundancy_factor = 1); + + /** \brief The class destructor + */ + virtual ~UdpDataProtocol(); + + /** \brief Set the Peer address to connect to + * \param peerHostOrIP IPv4 number or host name + */ + void setPeerAddress(const char* peerHostOrIP); + + /** \brief Receives a packet. It blocks until a packet is received + * + * This function makes sure we recieve a complete packet + * of size n + * \param buf Buffer to store the recieved packet + * \param n size of packet to receive + * \return number of bytes read, -1 on error + */ + //virtual int receivePacket(char* buf, const size_t n); + virtual int receivePacket(QUdpSocket& UdpSocket, char* buf, const size_t n); + + /** \brief Sends a packet + * + * This function meakes sure we send a complete packet + * of size n + * \param buf Buffer to send + * \param n size of packet to receive + * \return number of bytes read, -1 on error + */ + virtual int sendPacket(QUdpSocket& UdpSocket, const QHostAddress& PeerAddress, + const char* buf, const size_t n); + + /** \brief Obtains the peer address from the first UDP packet received. This address + * is used by the SERVER mode to connect back to the client. + * \param peerHostAddress QHostAddress to store the peer address + * \param port Receiving port + */ + virtual void getPeerAddressFromFirstPacket(QUdpSocket& UdpSocket, + QHostAddress& peerHostAddress, + uint16_t& port); + + /** \brief Sets the bind port number + */ + void setBindPort(int port) + { mBindPort = port; } + + /** \brief Sets the peer port number + */ + void setPeerPort(int port) + { mPeerPort = port; } + + /** \brief Implements the Thread Loop. To start the thread, call start() + * ( DO NOT CALL run() ) + * + * This function creats and binds all the socket and start the connection loop thread. + */ + virtual void run(); + + +private slots: + void printUdpWaitedTooLong(int wait_msec); + + +signals: + + /// \brief Signals when waiting every 10 milliseconds, with the total wait on wait_msec + /// \param wait_msec Total wait in milliseconds + void signalWatingTooLong(int wait_msec); + + +private: + + /** \brief Binds the UDP socket to the available address and specified port + */ + void bindSocket(QUdpSocket& UdpSocket); + + /** \brief This function blocks until data is available for reading in the + * QUdpSocket. The function will timeout after timeout_msec microseconds. + * + * This function is intended to replace QAbstractSocket::waitForReadyRead which has + * some problems with multithreading. + * + * \return returns true if there is data available for reading; + * otherwise it returns false (if an error occurred or the operation timed out) + */ + bool waitForReady(QUdpSocket& UdpSocket, int timeout_msec); + + /** \brief Redundancy algorythm at the receiving end + */ + void receivePacketRedundancy(QUdpSocket& UdpSocket, + int8_t* full_redundant_packet, + int full_redundant_packet_size, + int full_packet_size, + uint16_t& current_seq_num, + uint16_t& last_seq_num, + uint16_t& newer_seq_num); + + /** \brief Redundancy algorythm at the sender end + */ + void sendPacketRedundancy(QUdpSocket& UdpSocket, + QHostAddress& PeerAddress, + int8_t* full_redundant_packet, + int full_redundant_packet_size, + int full_packet_size); + + int mBindPort; ///< Local Port number to Bind + int mPeerPort; ///< Peer Port number + const runModeT mRunMode; ///< Run mode, either SENDER or RECEIVER + + QHostAddress mPeerAddress; ///< The Peer Address + + int8_t* mAudioPacket; ///< Buffer to store Audio Packets + int8_t* mFullPacket; ///< Buffer to store Full Packet (audio+header) + + unsigned int mUdpRedundancyFactor; ///< Factor of redundancy + static QMutex sUdpMutex; ///< Mutex to make thread safe the binding process +}; + +#endif // __UDPDATAPROTOCOL_H__ diff --git a/src/UdpDataProtocolPOSIX.cpp.tmp b/src/UdpDataProtocolPOSIX.cpp.tmp new file mode 100644 index 0000000..fe77daa --- /dev/null +++ b/src/UdpDataProtocolPOSIX.cpp.tmp @@ -0,0 +1,145 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file UdpDataProtocol.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include "UdpDataProtocol.h" + +#include +#include +#include +#include + +// NOTE: It's better not to use +// using namespace std; +// because some functions (like exit()) get confused with QT functions + + + +//******************************************************************************* +UdpDataProtocol::UdpDataProtocol(const runModeT runmode, const char* peerHostOrIP) + : DataProtocol(runmode) +{ + setPeerIPv4Address(peerHostOrIP); + setBindSocket(); +} + + +//******************************************************************************* +void UdpDataProtocol::setBindSocket() +{ + // UDP socket creation + mSockFd = socket(AF_INET, SOCK_DGRAM, 0); + if ( mSockFd < 0 ) { + std::cerr << "ERROR: UDP Socket Error" << std::endl; + std::exit(0); + } + + // Bind local address and port + /// \todo Bind to a different port in case this one is used by a different instance + /// of the program + struct sockaddr_in LocalIPv4Addr = getLocalIPv4AddressStruct(); + int nBind = bind(mSockFd, (struct sockaddr *) &LocalIPv4Addr, sizeof(LocalIPv4Addr)); + if ( nBind < 0 ) { + std::cerr << "ERROR: UDP Socket Bind Error" << std::endl; + std::exit(0); + } + + std::cout << "Successful socket creation and port binding" << std::endl; + + //Connected UDP + struct sockaddr_in PeerIPv4Addr = getPeerIPv4AddressStruct(); + int nCon = ::connect(mSockFd, (struct sockaddr *) &PeerIPv4Addr, sizeof(PeerIPv4Addr)); + if ( nCon < 0) { + std::cerr << "ERROR: UDP Socket Connect Error" << std::endl; + std::exit(0); + } +} + + +//******************************************************************************* +// Adapted form Stevens' "Unix Network Programming", third edition +// Page 88 (readn) +size_t UdpDataProtocol::receivePacket(char* buff, size_t n) +{ + size_t nleft; + ssize_t nread; + char* ptr; + + ptr = buff; + nleft = n; + while (nleft > 0) { + if ( (nread = ::read(mSockFd, ptr, nleft)) < 0) { + if (errno == EINTR) + nread = 0; // and call read() again + else + return(-1); + } else if (nread == 0) + break; // EOF + + nleft -= nread; + ptr += nread; + } + return(n - nleft); +} + + + +//******************************************************************************* +// Adapted form Stevens' "Unix Network Programming", third edition +// Page 88 (writen) +// Write "n" bytes to a descriptor +size_t UdpDataProtocol::sendPacket(const char* buff, size_t n) +{ + size_t nleft; + ssize_t nwritten; + const char* ptr; + + ptr = buff; + nleft = n; + while (nleft > 0) { + if ( (nwritten = ::write(mSockFd, ptr, nleft)) <= 0) { + if (nwritten < 0 && errno == EINTR) + nwritten = 0; // and call write() again + else + return(-1); // error + } + + nleft -= nwritten; + ptr += nwritten; + } + return(n); +} + diff --git a/src/UdpDataProtocolPOSIX.h.tmp b/src/UdpDataProtocolPOSIX.h.tmp new file mode 100644 index 0000000..a7fb087 --- /dev/null +++ b/src/UdpDataProtocolPOSIX.h.tmp @@ -0,0 +1,92 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file UdpDataProtocol.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#ifndef __UDPDATAPROTOCOL_H__ +#define __UDPDATAROTOCOL_H__ + +#include "DataProtocol.h" + +/** \brief UDP implementation of DataProtocol class + * + * + * + */ +class UdpDataProtocol : public DataProtocol +{ +public: + + /** \brief The class constructor + * \param runmode Sets the run mode, use either SENDER or RECEIVER + * \param peerHostOrIP IPv4 number or host name + */ + UdpDataProtocol(const runModeT runmode, const char* peerHostOrIP); + + /** \brief The class destructor + */ + virtual ~UdpDataProtocol() {}; + + /** \brief Receives a packet + * + * This function makes sure we recieve a complete packet + * of size n + * \param buf Buffer to store the recieved packet + * \param n size of packet to receive + * \return number of bytes read, -1 on error + */ + virtual size_t receivePacket(char* buf, size_t n); + + /** \brief Sends a packet + * + * This function meakes sure we send a complete packet + * of size n + * \param buff Buffer to send + * \param n size of packet to receive + * \return number of bytes read, -1 on error + */ + virtual size_t sendPacket(const char* buff, size_t n); + + //virtual void run(); + + +private: + + void setBindSocket(); + + int mSockFd; ///< Socket file descriptor +}; + +#endif diff --git a/src/UdpMasterListener.cpp b/src/UdpMasterListener.cpp new file mode 100644 index 0000000..874eda4 --- /dev/null +++ b/src/UdpMasterListener.cpp @@ -0,0 +1,186 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file UdpMasterListener.cpp + * \author Juan-Pablo Caceres and Chris Chafe + * \date September 2008 + */ + +#include +#include +#include + +#include "UdpMasterListener.h" +#include "JackTripWorker.h" +#include "jacktrip_globals.h" + +using std::cout; using std::endl; + + +//******************************************************************************* +UdpMasterListener::UdpMasterListener(int server_port) : + mJTWorker(NULL), + mServerPort(server_port), + mStopped(false), + mTotalRunningThreads(0) +{ + // Register JackTripWorker with the master listener + mJTWorker = new JackTripWorker(this); + mThreadPool.setExpiryTimeout(3000); // msec (-1) = forever + // Inizialize IP addresses + for (int i = 0; i= 0) // old address is -1 + { + // redirect port and spawn listener + sendToPoolPrototype(id); + // wait until one is complete before another spawns + while (mJTWorker->isSpawning()) { QThread::msleep(1); } + mTotalRunningThreads++; + cout << "Total Running Threads: " << mTotalRunningThreads << endl; + cout << "=======================================================" << endl; + } + //cout << "ENDDDDDDDDDDDDDDDDDd === " << id << endl; + } + QThread::msleep(100); + } +} + + +//******************************************************************************* +void UdpMasterListener::sendToPoolPrototype(int id) +{ + cout << "id ID **********@@@@@@@@@@@@@@@@@@@@@************** " << id << endl; + mJTWorker->setJackTrip(id, mActiveAddress[id][0], + mBasePort+(2*id), mActiveAddress[id][1], + 1); /// \todo temp default to 1 channel + mThreadPool.start(mJTWorker, QThread::TimeCriticalPriority); //send one thread to the pool +} + + +//******************************************************************************* +void UdpMasterListener::bindUdpSocket(QUdpSocket& udpsocket, int port) +{ + // QHostAddress::Any : let the kernel decide the active address + if ( !udpsocket.bind(QHostAddress::Any, + port, QUdpSocket::DefaultForPlatform) ) { + //std::cerr << "ERROR: could not bind UDP socket" << endl; + //std::exit(1); + throw std::runtime_error("Could not bind UDP socket. It may be already binded."); + } + else { + cout << "UDP Socket Receiving in Port: " << port << endl; + } +} + + +//******************************************************************************* +// check by comparing 32-bit addresses +int UdpMasterListener::isNewAddress(uint32_t address, uint16_t port) +{ + /// \todo Add the port number in the comparison, i.e., compart IP/port pair + + bool busyAddress = false; + int id = 0; + while ( !busyAddress && (id + +#include +#include +#include +#include + +#include "jacktrip_types.h" +#include "jacktrip_globals.h" +class JackTripWorker; // forward declaration + + +/** \brief Master UDP listener on the Server. + * + * This creates a server that will listen on the well know port (the server port) and will + * spawn JackTrip threads into the Thread pool. Clients request a connection. + */ +class UdpMasterListener : public QThread +{ + Q_OBJECT; + +public: + UdpMasterListener(int server_port = gServerUdpPort); + virtual ~UdpMasterListener(); + + /** \brief Implements the Thread Loop. To start the thread, call start() + * ( DO NOT CALL run() ) + */ + void run(); + + /// \brief Stops the execution of the Thread + void stop() { mStopped = true; }; + + int releasePort(int id); + +private slots: + void testRecieve() + { + std::cout << "========= TEST RECEIVE SLOT ===========" << std::endl; + } + +signals: + void Listening(); + void ClientAddressSet(); + + +private: + /** \brief Binds a QUdpSocket. It chooses the available (active) interface. + * \param udpsocket a QUdpSocket + * \param port Port number + */ + static void bindUdpSocket(QUdpSocket& udpsocket, int port); + + /* \brief Send the JackTripWorker to the thread pool. This will run + * until it's done. We still have control over the prototype class. + * \param id Identification Number + */ + void sendToPoolPrototype(int id); + + /** \brief Check if address is already handled, if not add to array + * \param IPv4 address as a number + * \return -1 if address is busy, id number if not + */ + int isNewAddress(uint32_t address, uint16_t port); + + QUdpSocket mUdpMasterSocket; ///< The UDP socket + QHostAddress mPeerAddress; ///< The Peer Address + + JackTripWorker* mJTWorker; ///< Class that will be used as prototype + QThreadPool mThreadPool; ///< The Thread Pool + + int mServerPort; //< Server known port number + int mBasePort; + uint32_t mActiveAddress[gMaxThreads][2]; ///< Active addresses pool numbers (32 bits IPv4 numbers) + QHash mActiveAddresPortPair; + + /// Boolean stop the execution of the thread + volatile bool mStopped; + int mTotalRunningThreads; ///< Number of Threads running in the pool +}; + + +#endif //__UDPMASTERLISTENER_H__ diff --git a/src/build b/src/build new file mode 100755 index 0000000..546567d --- /dev/null +++ b/src/build @@ -0,0 +1,25 @@ +#!/bin/bash +## Created by Juan-Pablo Caceres + +# Check for Platform +platform='unknown' +unamestr=`uname` +if [[ "$unamestr" == 'Linux' ]]; then + echo "Building on Linux" + platform='linux' +elif [[ "$unamestr" == 'Darwin' ]]; then + echo "Building on Mac OS X" + platform='macosx' +fi + +# Set qmake command name +if [[ $platform == 'linux' ]]; then + QCMD=qmake-qt4 +elif [[ $platform == 'macosx' ]]; then + QCMD=qmake +fi + +# Build +$QCMD jacktrip.pro +make clean +make release diff --git a/src/jacktrip-1.0.5.diff b/src/jacktrip-1.0.5.diff new file mode 100644 index 0000000..30292b9 --- /dev/null +++ b/src/jacktrip-1.0.5.diff @@ -0,0 +1,26 @@ +Index: src/JackTripWorker.h +=================================================================== +--- src/JackTripWorker.h (revision 495) ++++ src/JackTripWorker.h (working copy) +@@ -46,6 +46,8 @@ + #include + #include + ++#include "jacktrip_types.h" ++ + class JackTrip; // forward declaration + class UdpMasterListener; // forward declaration + +Index: src/jacktrip_globals.cpp +=================================================================== +--- src/jacktrip_globals.cpp (revision 495) ++++ src/jacktrip_globals.cpp (working copy) +@@ -38,6 +38,8 @@ + #include "jacktrip_globals.h" + #include "jacktrip_types.h" + ++#include ++ + #if defined ( __LINUX__ ) + #include + #endif //__LINUX__ diff --git a/src/jacktrip.pro b/src/jacktrip.pro new file mode 100644 index 0000000..b92e69d --- /dev/null +++ b/src/jacktrip.pro @@ -0,0 +1,68 @@ +#****************************** +# Created by Juan-Pablo Caceres +#****************************** + +CONFIG += qt thread debug_and_release build_all +CONFIG(debug, debug|release) { + TARGET = jacktrip_debug + } else { + TARGET = jacktrip + } +QT -= gui +QT += network +INCLUDEPATH+=/usr/local/include +LIBS += -ljack -lm +macx { + message(MAC OS X) + CONFIG -= app_bundle + CONFIG += x86 #ppc + LIBS += -framework CoreAudio + DEFINES += __MAC_OSX__ + } +linux-g++ { + message(Linux) + QMAKE_CXXFLAGS += -g -O2 + DEFINES += __LINUX__ + } +DESTDIR = . +QMAKE_CLEAN += ./jacktrip ./jacktrip_debug +target.path = /usr/bin +INSTALLS += target + +# Input +HEADERS += DataProtocol.h \ + JackAudioInterface.h \ + JackTrip.h \ + jacktrip_globals.h \ + jacktrip_types.h \ + JackTripThread.h \ + JackTripWorker.h \ + JackTripWorkerMessages.h \ + LoopBack.h \ + NetKS.h \ + PacketHeader.h \ + ProcessPlugin.h \ + RingBuffer.h \ + RingBufferWavetable.h \ + Settings.h \ + TestRingBuffer.h \ + ThreadPoolTest.h \ + UdpDataProtocol.h \ + UdpMasterListener.h \ + jacktrip_tests.cpp +SOURCES += DataProtocol.cpp \ + JackAudioInterface.cpp \ + JackTrip.cpp \ + jacktrip_globals.cpp \ + jacktrip_main.cpp \ + jacktrip_tests.cpp \ + JackTripThread.cpp \ + JackTripWorker.cpp \ + LoopBack.cpp \ + PacketHeader.cpp \ + ProcessPlugin.cpp \ + RingBuffer.cpp \ + Settings.cpp \ + tests.cpp \ + UdpDataProtocol.cpp \ + UdpMasterListener.cpp diff --git a/src/jacktrip_globals.cpp b/src/jacktrip_globals.cpp new file mode 100644 index 0000000..df5c24d --- /dev/null +++ b/src/jacktrip_globals.cpp @@ -0,0 +1,196 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file globals.cpp + * \author Juan-Pablo Caceres + * \date August 2008 + */ + +#include "jacktrip_globals.h" +#include "jacktrip_types.h" + +#include + +#if defined ( __LINUX__ ) +#include +#endif //__LINUX__ + +#if defined ( __MAC_OSX__ ) +#include +#include + +//#include + +//#include +//#include +//#include + +//#include +//#include +//m#include +//#include +//#include + + +//#include +//#include +//#include +//#include +//#include + + + + +//#include +//#include +//#include +//#include +//#include + +#endif //__MAC_OSX__ + + +#if defined ( __MAC_OSX__ ) +//******************************************************************************* +//http://developer.apple.com/DOCUMENTATION/Darwin/Conceptual/KernelProgramming/scheduler/chapter_8_section_4.html +//http://lists.apple.com/archives/darwin-dev/2007/Sep/msg00035.html +int set_realtime(int period, int computation, int constraint) +{ + //AbsoluteTime time; + //clock_get_uptime((uint64_t *)&time); + + //uint64_t result; + //clock_get_uptime(&result); + //clock_get_system_microtime(&result,&result); + + struct thread_time_constraint_policy ttcpolicy; + int ret; + + ttcpolicy.period=period; // HZ/160 + ttcpolicy.computation=computation; // HZ/3300; + ttcpolicy.constraint=constraint; // HZ/2200; + ttcpolicy.preemptible=1; + + if ((ret=thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT)) != KERN_SUCCESS) { + fprintf(stderr, "set_realtime() failed.\n"); + return 0; + } + return 1; +} +#endif //__MAC_OSX__ + + +#if defined ( __LINUX__ ) +//******************************************************************************* +int get_fifo_priority (bool half) +{ + int min, max, priority; + min = sched_get_priority_min (SCHED_FIFO); + max = sched_get_priority_max (SCHED_FIFO); + if (half) { + priority = (max - (max - min) / 2); } + else { + priority = max; } + + //priority=min; + return priority; +} +#endif //__LINUX__ + + +#if defined ( __LINUX__ ) +//******************************************************************************* +int set_fifo_priority (bool half) +{ + struct sched_param p; + int priority; + // scheduling priority + + + if (true) // (!getuid () || !geteuid ()) + { + priority = get_fifo_priority (half); + p.sched_priority = priority; + + if (sched_setscheduler (0, SCHED_FIFO, &p) == -1) + { + fprintf (stderr, + "\ncould not activate scheduling with priority %d\n", + priority); + return -1; + } + seteuid (getuid ()); + //fprintf (stderr, + // "\nset scheduling priority to %d (SCHED_FIFO)\n", + // priority); + } + else + { + fprintf (stderr, + "\ninsufficient privileges to set scheduling priority\n"); + priority = 0; + } + return priority; +} +#endif //__LINUX__ + + +#if defined ( __LINUX__ ) +//******************************************************************************* +int set_realtime_priority (void) +{ + struct sched_param schp; + + memset (&schp, 0, sizeof (schp)); + schp.sched_priority = sched_get_priority_max (SCHED_FIFO); + if (sched_setscheduler (0, SCHED_FIFO, &schp) != 0) + { + perror ("set_scheduler"); + return -1; + } + return 0; +} +#endif //__LINUX__ + +void set_crossplatform_realtime_priority() +{ +#if defined ( __LINUX__ ) + set_fifo_priority (false); +#endif //__LINUX__ +#if defined ( __MAC_OSX__ ) + set_realtime(1250000,60000,90000); +#endif //__MAC_OSX__ +} + + diff --git a/src/jacktrip_globals.h b/src/jacktrip_globals.h new file mode 100644 index 0000000..f9d2d76 --- /dev/null +++ b/src/jacktrip_globals.h @@ -0,0 +1,126 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file globals.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#ifndef __JACKTRIP_GLOBALS_H__ +#define __JACKTRIP_GLOBALS_H__ + +#include "JackAudioInterface.h" + + +//namespace JackTrip/// \todo Add this namespace + +const char* const gVersion = "1.0.5"; ///< JackTrip version + +//******************************************************************************* +/// \name Default Values +//@{ +const int gDefaultNumInChannels = 2; +const int gDefaultNumOutChannels = 2; +const JackAudioInterface::audioBitResolutionT gDefaultBitResolutionMode = + JackAudioInterface::BIT16; +const int gDefaultQueueLength = 4; +const int gDefaultOutputQueueLength = 4; +//@} + + +//******************************************************************************* +/// \name Network related ports +//@{ +const int gDefaultPort = 4464; ///< Default JackTrip Port +//const int gInputPort_0 = 4464; ///< Input base port +//const int gOutputPort_0 = 4465; ///< Output base port +//const int gDefaultSendPort = 4464; ///< Default for to use to send packet +//@} + + +//******************************************************************************* +/// \name Separator for terminal printing +//@{ +const char* const gPrintSeparator = "---------------------------------------------------------"; +//@} + + +//******************************************************************************* +/// \name Global flags +//@{ +extern int gVerboseFlag; ///< Verbose mode flag declaration +//@} + + +//******************************************************************************* +/// \name JackAudio +//@{ +const int gJackBitResolution = 32; ///< Audio Bit Resolution of the Jack Server +//@} + + +//******************************************************************************* +/// \name Global Functions + +void set_crossplatform_realtime_priority(); + +//@{ +// Linux Specific Functions +#if defined ( __LINUX__ ) +/// \brief Returns fifo priority +int get_fifo_priority (bool half); +/// \brief Set fifo priority (if user has sufficient privileges). +int set_fifo_priority (bool half); +int set_realtime_priority (void); +#endif //__LINUX__ +//@} + +//@{ +// Mac OS X Specific Functions +#if defined ( __MAC_OSX__ ) +int set_realtime(int period, int computation, int constraint); +#endif //__MAC_OSX__ +//@} + + +//******************************************************************************* +/// \name JackTrip Server parameters +//@{ +/// Maximum Threads that can be run at the same time +const int gMaxThreads = 290; // some pthread limit around 297? + +/// Public well-known UDP port to where the clients will connect +const int gServerUdpPort = 4464; +//@} + + +#endif diff --git a/src/jacktrip_main.cpp b/src/jacktrip_main.cpp new file mode 100644 index 0000000..e0d4203 --- /dev/null +++ b/src/jacktrip_main.cpp @@ -0,0 +1,86 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file main.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ + +#include + +#include + +#include "JackAudioInterface.h" +#include "UdpDataProtocol.h" +#include "RingBuffer.h" +#include "JackTrip.h" +#include "Settings.h" +//#include "TestRingBuffer.h" +#include "LoopBack.h" +#include "PacketHeader.h" +#include "JackTripThread.h" +#include "jacktrip_tests.cpp" + +#include "jacktrip_globals.h" + +using std::cout; using std::endl; + + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + + //--------TESTS-------------------------- + //main_tests(argc, argv); // test functions + //while (true) sleep(9999); + //--------------------------------------- + + // Get Settings from user + // ---------------------- + try + { + // Get Settings from user + // ---------------------- + Settings* settings = new Settings; + settings->parseInput(argc, argv); + settings->startJackTrip(); + } + catch ( const std::exception & e ) + { + std::cerr << "ERROR:" << endl; + std::cerr << e.what() << endl; + std::cerr << "Exiting JackTrip..." << endl; + std::cerr << gPrintSeparator << endl; + return -1; + } + return app.exec(); +} diff --git a/src/jacktrip_tests.cpp b/src/jacktrip_tests.cpp new file mode 100644 index 0000000..17cddf1 --- /dev/null +++ b/src/jacktrip_tests.cpp @@ -0,0 +1,103 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file jacktrip_tests.cpp + * \author Juan-Pablo Caceres + * \date September 2008 + */ + +#include + +#include + +#include "JackTripThread.h" + +using std::cout; using std::endl; + +const int num_jacktrips = 5; +const int base_port = 4464; + + +void main_tests(int argc, char** argv); +void test_threads_server(); +void test_threads_client(const char* peer_address); + + +void main_tests(int /*argc*/, char** argv) +{ + if (argv[1][0] == 's' ) + { + test_threads_server(); + } + else if (argv[1][0] == 'c' ) + { + test_threads_client("171.64.197.209"); + } +} + + +// Test many servers running at the same time +void test_threads_server() +{ + QVector jacktrips; + jacktrips.resize(num_jacktrips); + int port_num; + for (int i = 0; i < num_jacktrips; i++) + { + port_num = base_port + i*10; + cout << "Port Number: " << port_num << endl; + jacktrips[i] = new JackTripThread(JackTrip::SERVER); + jacktrips[i]->setPort(port_num); + jacktrips[i]->start(QThread::NormalPriority); + //sleep(1); + } +} + + +// Test many servers running at the same time +void test_threads_client(const char* peer_address) +{ + QVector jacktrips; + jacktrips.resize(num_jacktrips); + int port_num; + for (int i = 0; i < num_jacktrips; i++) + { + port_num = base_port + i*10; + cout << "Port Number: " << port_num << endl; + jacktrips[i] = new JackTripThread(JackTrip::CLIENT); + jacktrips[i]->setPort(port_num); + jacktrips[i]->setPeerAddress(peer_address); + //sleep(1); + jacktrips[i]->start(QThread::NormalPriority); + //sleep(1); + } +} diff --git a/src/jacktrip_types.h b/src/jacktrip_types.h new file mode 100644 index 0000000..08bfe87 --- /dev/null +++ b/src/jacktrip_types.h @@ -0,0 +1,85 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file jacktrip_types_jacktrip.h + * \author Juan-Pablo Caceres + * \date June 2008 + */ + + +#ifndef __JACKTRIP_TYPES_H__ +#define __JACKTRIP_TYPES_H__ + +#include +#include //For QT4 types + +//------------------------------------------------------------------------------- +/** \name Audio typedefs + * + */ +//------------------------------------------------------------------------------- +//@{ +/// Audio sample type +typedef jack_default_audio_sample_t sample_t; +//@} + + +//------------------------------------------------------------------------------- +/** \name Typedefs that guaranty some specific bit length + * + * It uses the QT4 types. This can be changed in the future, keeping + * compatibility for the rest of the code. + */ +//------------------------------------------------------------------------------- +//@{ +/// Typedef for unsigned char. This type is guaranteed to be 8-bit. +typedef quint8 uint8_t; +/// Typedef for unsigned short. This type is guaranteed to be 16-bit. +typedef quint16 uint16_t; +/// Typedef for unsigned int. This type is guaranteed to be 32-bit. +typedef quint32 uint32_t; +/// \brief Typedef for unsigned long long int. This type is guaranteed to +/// be 64-bit. +//typedef quint64 uint64_t; +/// Typedef for signed char. This type is guaranteed to be 8-bit. +typedef qint8 int8_t; +/// Typedef for signed short. This type is guaranteed to be 16-bit. +typedef qint16 int16_t; +/// Typedef for signed int. This type is guaranteed to be 32-bit. +typedef qint32 int32_t; +/// \brief Typedef for long long int. This type is guaranteed to +/// be 64-bit. +//typedef qint64 int64_t; +//@} + + +#endif diff --git a/src/tests.cpp b/src/tests.cpp new file mode 100644 index 0000000..6582563 --- /dev/null +++ b/src/tests.cpp @@ -0,0 +1,122 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file tests.cpp + * \author Juan-Pablo Caceres + * \date June 2008 + */ +#include +#include +#include + + +#include "JackAudioInterface.h" +#include "UdpDataProtocol.h" +#include "RingBuffer.h" +#include "JackTrip.h" +#include "Settings.h" +#include "TestRingBuffer.h" +#include "jacktrip_globals.h" + +using namespace std; + +void tests() +{ + // Test JackTrip + //================================================================ + //JackTrip jacktrip1; + //jacktrip1.startThreads(); + + //JackTrip jacktrip2; + //jacktrip2.startThreads(); + + /* + // TestRingBuffer + //================================================================ + TestRingBufferWrite tw; + TestRingBufferRead tr; + tr.start(); + tw.start(); + */ + + /* + // Test RingBuffer + //================================================================ + RingBuffer rb(2,2); + + int8_t* writeSlot; + writeSlot = new int8_t[2]; + writeSlot[0] = *"a"; + writeSlot[1] = *"b"; + std::cout << *writeSlot << std::endl; + std::cout << writeSlot[0] << std::endl; + std::cout << writeSlot[1] << std::endl; + std::cout << *(writeSlot+1) << std::endl; + rb.writeSlot(writeSlot); + + int8_t* readSlot; + readSlot = new int8_t[2]; + rb.readSlot(readSlot); + std::cout << *(readSlot) << std::endl; + std::cout << *(readSlot+1) << std::endl; + */ + + + /* + // Test UDP Socket + //================================================================ + UdpDataProtocol udp_rec(RECEIVER, "192.168.1.4"); + UdpDataProtocol udp_send(SENDER, "192.168.1.4"); + udp_rec.start(); + udp_send.start(); + */ + + /* + // Test JackAudioInterface + //================================================================ + JackAudioInterface jack_test(4); + cout << "SR: " << jack_test.getSampleRate() << endl; + cout << "Buffer Size: " << jack_test.getBufferSize() << endl; + jack_test.setProcessCallback(process); + jack_test.startProcess(); + */ + + + while (true) + { + //cout << "SR: " << test.getSampleRate() << endl; + //cout << "Buffer Size: " << test.getBufferSize() << endl; + usleep(1000000); + //usleep(1); + } + +} -- 2.30.2