├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.toml ├── GNU-GPL-v2.0.md ├── LICENSE.md ├── README.md ├── build.rs ├── c-wrapper ├── CMakeLists.txt ├── link_rs.cpp └── link_rs.h └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | **/.DS_Store 6 | /.vscode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ableton/link"] 2 | path = ableton/link 3 | url = https://github.com/Ableton/link 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ableton-link" 3 | version = "0.1.0" 4 | authors = ["Magnus Herold "] 5 | edition = "2018" 6 | description = "Rust bindings for Ableton Link" 7 | readme = "README.md" 8 | license-file = "LICENSE.md" 9 | repository = "https://github.com/magdaddy/ableton-link-rs" 10 | keywords = ["ableton", "link"] 11 | categories = ["api-bindings"] 12 | 13 | [badges] 14 | travis-ci = { repository = "magdaddy/ableton-link-rs", branch = "master" } 15 | 16 | [dependencies] 17 | 18 | [build-dependencies] 19 | cmake = "0.1" 20 | bindgen = "0.49" -------------------------------------------------------------------------------- /GNU-GPL-v2.0.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 2, June 1991_ 5 | _Copyright © 1989, 1991 Free Software Foundation, Inc.,_ 6 | _51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_ 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | ### Preamble 12 | 13 | The licenses for most software are designed to take away your 14 | freedom to share and change it. By contrast, the GNU General Public 15 | License is intended to guarantee your freedom to share and change free 16 | software--to make sure the software is free for all its users. This 17 | General Public License applies to most of the Free Software 18 | Foundation's software and to any other program whose authors commit to 19 | using it. (Some other Free Software Foundation software is covered by 20 | the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | this service if you wish), that you receive source code or can get it 27 | if you want it, that you can change the software or use pieces of it 28 | in new free programs; and that you know you can do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid 31 | anyone to deny you these rights or to ask you to surrender the rights. 32 | These restrictions translate to certain responsibilities for you if you 33 | distribute copies of the software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether 36 | gratis or for a fee, you must give the recipients all the rights that 37 | you have. You must make sure that they, too, receive or can get the 38 | source code. And you must show them these terms so they know their 39 | rights. 40 | 41 | We protect your rights with two steps: **(1)** copyright the software, and 42 | **(2)** offer you this license which gives you legal permission to copy, 43 | distribute and/or modify the software. 44 | 45 | Also, for each author's protection and ours, we want to make certain 46 | that everyone understands that there is no warranty for this free 47 | software. If the software is modified by someone else and passed on, we 48 | want its recipients to know that what they have is not the original, so 49 | that any problems introduced by others will not reflect on the original 50 | authors' reputations. 51 | 52 | Finally, any free program is threatened constantly by software 53 | patents. We wish to avoid the danger that redistributors of a free 54 | program will individually obtain patent licenses, in effect making the 55 | program proprietary. To prevent this, we have made it clear that any 56 | patent must be licensed for everyone's free use or not licensed at all. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | **0.** This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The “Program”, below, 66 | refers to any such program or work, and a “work based on the Program” 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term “modification”.) Each licensee is addressed as “you”. 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | **1.** You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | **2.** You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | * **a)** You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | * **b)** You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | * **c)** If the modified program normally reads commands interactively 103 | when run, you must cause it, when started running for such 104 | interactive use in the most ordinary way, to print or display an 105 | announcement including an appropriate copyright notice and a 106 | notice that there is no warranty (or else, saying that you provide 107 | a warranty) and that users may redistribute the program under 108 | these conditions, and telling the user how to view a copy of this 109 | License. (Exception: if the Program itself is interactive but 110 | does not normally print such an announcement, your work based on 111 | the Program is not required to print an announcement.) 112 | 113 | These requirements apply to the modified work as a whole. If 114 | identifiable sections of that work are not derived from the Program, 115 | and can be reasonably considered independent and separate works in 116 | themselves, then this License, and its terms, do not apply to those 117 | sections when you distribute them as separate works. But when you 118 | distribute the same sections as part of a whole which is a work based 119 | on the Program, the distribution of the whole must be on the terms of 120 | this License, whose permissions for other licensees extend to the 121 | entire whole, and thus to each and every part regardless of who wrote it. 122 | 123 | Thus, it is not the intent of this section to claim rights or contest 124 | your rights to work written entirely by you; rather, the intent is to 125 | exercise the right to control the distribution of derivative or 126 | collective works based on the Program. 127 | 128 | In addition, mere aggregation of another work not based on the Program 129 | with the Program (or with a work based on the Program) on a volume of 130 | a storage or distribution medium does not bring the other work under 131 | the scope of this License. 132 | 133 | **3.** You may copy and distribute the Program (or a work based on it, 134 | under Section 2) in object code or executable form under the terms of 135 | Sections 1 and 2 above provided that you also do one of the following: 136 | 137 | * **a)** Accompany it with the complete corresponding machine-readable 138 | source code, which must be distributed under the terms of Sections 139 | 1 and 2 above on a medium customarily used for software interchange; or, 140 | * **b)** Accompany it with a written offer, valid for at least three 141 | years, to give any third party, for a charge no more than your 142 | cost of physically performing source distribution, a complete 143 | machine-readable copy of the corresponding source code, to be 144 | distributed under the terms of Sections 1 and 2 above on a medium 145 | customarily used for software interchange; or, 146 | * **c)** Accompany it with the information you received as to the offer 147 | to distribute corresponding source code. (This alternative is 148 | allowed only for noncommercial distribution and only if you 149 | received the program in object code or executable form with such 150 | an offer, in accord with Subsection b above.) 151 | 152 | The source code for a work means the preferred form of the work for 153 | making modifications to it. For an executable work, complete source 154 | code means all the source code for all modules it contains, plus any 155 | associated interface definition files, plus the scripts used to 156 | control compilation and installation of the executable. However, as a 157 | special exception, the source code distributed need not include 158 | anything that is normally distributed (in either source or binary 159 | form) with the major components (compiler, kernel, and so on) of the 160 | operating system on which the executable runs, unless that component 161 | itself accompanies the executable. 162 | 163 | If distribution of executable or object code is made by offering 164 | access to copy from a designated place, then offering equivalent 165 | access to copy the source code from the same place counts as 166 | distribution of the source code, even though third parties are not 167 | compelled to copy the source along with the object code. 168 | 169 | **4.** You may not copy, modify, sublicense, or distribute the Program 170 | except as expressly provided under this License. Any attempt 171 | otherwise to copy, modify, sublicense or distribute the Program is 172 | void, and will automatically terminate your rights under this License. 173 | However, parties who have received copies, or rights, from you under 174 | this License will not have their licenses terminated so long as such 175 | parties remain in full compliance. 176 | 177 | **5.** You are not required to accept this License, since you have not 178 | signed it. However, nothing else grants you permission to modify or 179 | distribute the Program or its derivative works. These actions are 180 | prohibited by law if you do not accept this License. Therefore, by 181 | modifying or distributing the Program (or any work based on the 182 | Program), you indicate your acceptance of this License to do so, and 183 | all its terms and conditions for copying, distributing or modifying 184 | the Program or works based on it. 185 | 186 | **6.** Each time you redistribute the Program (or any work based on the 187 | Program), the recipient automatically receives a license from the 188 | original licensor to copy, distribute or modify the Program subject to 189 | these terms and conditions. You may not impose any further 190 | restrictions on the recipients' exercise of the rights granted herein. 191 | You are not responsible for enforcing compliance by third parties to 192 | this License. 193 | 194 | **7.** If, as a consequence of a court judgment or allegation of patent 195 | infringement or for any other reason (not limited to patent issues), 196 | conditions are imposed on you (whether by court order, agreement or 197 | otherwise) that contradict the conditions of this License, they do not 198 | excuse you from the conditions of this License. If you cannot 199 | distribute so as to satisfy simultaneously your obligations under this 200 | License and any other pertinent obligations, then as a consequence you 201 | may not distribute the Program at all. For example, if a patent 202 | license would not permit royalty-free redistribution of the Program by 203 | all those who receive copies directly or indirectly through you, then 204 | the only way you could satisfy both it and this License would be to 205 | refrain entirely from distribution of the Program. 206 | 207 | If any portion of this section is held invalid or unenforceable under 208 | any particular circumstance, the balance of the section is intended to 209 | apply and the section as a whole is intended to apply in other 210 | circumstances. 211 | 212 | It is not the purpose of this section to induce you to infringe any 213 | patents or other property right claims or to contest validity of any 214 | such claims; this section has the sole purpose of protecting the 215 | integrity of the free software distribution system, which is 216 | implemented by public license practices. Many people have made 217 | generous contributions to the wide range of software distributed 218 | through that system in reliance on consistent application of that 219 | system; it is up to the author/donor to decide if he or she is willing 220 | to distribute software through any other system and a licensee cannot 221 | impose that choice. 222 | 223 | This section is intended to make thoroughly clear what is believed to 224 | be a consequence of the rest of this License. 225 | 226 | **8.** If the distribution and/or use of the Program is restricted in 227 | certain countries either by patents or by copyrighted interfaces, the 228 | original copyright holder who places the Program under this License 229 | may add an explicit geographical distribution limitation excluding 230 | those countries, so that distribution is permitted only in or among 231 | countries not thus excluded. In such case, this License incorporates 232 | the limitation as if written in the body of this License. 233 | 234 | **9.** The Free Software Foundation may publish revised and/or new versions 235 | of the General Public License from time to time. Such new versions will 236 | be similar in spirit to the present version, but may differ in detail to 237 | address new problems or concerns. 238 | 239 | Each version is given a distinguishing version number. If the Program 240 | specifies a version number of this License which applies to it and “any 241 | later version”, you have the option of following the terms and conditions 242 | either of that version or of any later version published by the Free 243 | Software Foundation. If the Program does not specify a version number of 244 | this License, you may choose any version ever published by the Free Software 245 | Foundation. 246 | 247 | **10.** If you wish to incorporate parts of the Program into other free 248 | programs whose distribution conditions are different, write to the author 249 | to ask for permission. For software which is copyrighted by the Free 250 | Software Foundation, write to the Free Software Foundation; we sometimes 251 | make exceptions for this. Our decision will be guided by the two goals 252 | of preserving the free status of all derivatives of our free software and 253 | of promoting the sharing and reuse of software generally. 254 | 255 | ### NO WARRANTY 256 | 257 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 258 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 259 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 260 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 261 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 262 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 263 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 264 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 265 | REPAIR OR CORRECTION. 266 | 267 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 268 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 269 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 270 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 271 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 272 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 273 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 274 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 275 | POSSIBILITY OF SUCH DAMAGES. 276 | 277 | END OF TERMS AND CONDITIONS 278 | 279 | ### How to Apply These Terms to Your New Programs 280 | 281 | If you develop a new program, and you want it to be of the greatest 282 | possible use to the public, the best way to achieve this is to make it 283 | free software which everyone can redistribute and change under these terms. 284 | 285 | To do so, attach the following notices to the program. It is safest 286 | to attach them to the start of each source file to most effectively 287 | convey the exclusion of warranty; and each file should have at least 288 | the “copyright” line and a pointer to where the full notice is found. 289 | 290 | 291 | Copyright (C) 292 | 293 | This program is free software; you can redistribute it and/or modify 294 | it under the terms of the GNU General Public License as published by 295 | the Free Software Foundation; either version 2 of the License, or 296 | (at your option) any later version. 297 | 298 | This program is distributed in the hope that it will be useful, 299 | but WITHOUT ANY WARRANTY; without even the implied warranty of 300 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 301 | GNU General Public License for more details. 302 | 303 | You should have received a copy of the GNU General Public License along 304 | with this program; if not, write to the Free Software Foundation, Inc., 305 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 306 | 307 | Also add information on how to contact you by electronic and paper mail. 308 | 309 | If the program is interactive, make it output a short notice like this 310 | when it starts in an interactive mode: 311 | 312 | Gnomovision version 69, Copyright (C) year name of author 313 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 314 | This is free software, and you are welcome to redistribute it 315 | under certain conditions; type `show c' for details. 316 | 317 | The hypothetical commands `show w` and `show c` should show the appropriate 318 | parts of the General Public License. Of course, the commands you use may 319 | be called something other than `show w` and `show c`; they could even be 320 | mouse-clicks or menu items--whatever suits your program. 321 | 322 | You should also get your employer (if you work as a programmer) or your 323 | school, if any, to sign a “copyright disclaimer” for the program, if 324 | necessary. Here is a sample; alter the names: 325 | 326 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 327 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 328 | 329 | , 1 April 1989 330 | Ty Coon, President of Vice 331 | 332 | This General Public License does not permit incorporating your program into 333 | proprietary programs. If your program is a subroutine library, you may 334 | consider it more useful to permit linking proprietary applications with the 335 | library. If this is what you want to do, use the GNU Lesser General 336 | Public License instead of this License. 337 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Copyright 2019 Magnus Herold. 4 | 5 | Ableton Link is dual licensed under GPLv2+ and a proprietary license. 6 | 7 | This means that this wrapper is automatically under the GPLv2+ as well. A copy of the license is distributed with the source code. 8 | 9 | If you would like to incorporate Link into a proprietary software application, please contact link-devs@ableton.com. 10 | 11 | In that case, this wrapper can be used under the MIT license: 12 | 13 | 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: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | 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. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/magdaddy/ableton-link-rs.svg?branch=master)](https://travis-ci.com/magdaddy/ableton-link-rs) 2 | [![docs](https://docs.rs/ableton-link/badge.svg)](https://docs.rs/ableton-link) 3 | 4 | # ableton-link-rs 5 | 6 | A wrapper for the [Ableton Link](https://github.com/Ableton/link) library, a technology that synchronizes musical beat, 7 | tempo, and phase across multiple applications running on one or more devices. Applications on devices connected to a 8 | local network discover each other automatically and form a musical session in which each participant can perform 9 | independently: anyone can start or stop while still staying in time. Anyone can change the tempo, the others will 10 | follow. Anyone can join or leave without disrupting the session. 11 | 12 | For further details, see [Ableton Link](https://github.com/Ableton/link). 13 | 14 | ## Remarks 15 | 16 | This is my first shot at wrapping a C++ library for Rust. Also, I'm pretty much a C/C++ noob. So expect some rough edges. 17 | 18 | I'm not totally sure about the all the design choices, especially the `with_app/audio_session_state`- methods. If you see a better way, PR's are welcome. 19 | 20 | This has only been tested on macOS and seems to compile on Linux as well. Don't know about Windows or others... 21 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use cmake::Config; 2 | use bindgen::builder; 3 | 4 | #[cfg(target_os = "macos")] 5 | fn link_cpp() { 6 | // IMPORTANT!!! otherwise linker errors, apparently only on macOS 7 | println!("cargo:rustc-link-lib=c++"); 8 | } 9 | 10 | #[cfg(not(target_os = "macos"))] 11 | fn link_cpp() { 12 | println!("cargo:rustc-link-lib=stdc++"); 13 | } 14 | 15 | fn main() { 16 | // cmake 17 | // Builds the project in the directory located in `libfoo`, installing it 18 | // into $OUT_DIR 19 | let dst = Config::new("c-wrapper") 20 | // .cxxflag("-fno-rtti") 21 | // .no_build_target(true) 22 | .build_target("linkrs") 23 | .build(); 24 | let builddir = dst.join("build"); 25 | println!("cargo:rustc-link-search=native={}", builddir.display()); 26 | println!("cargo:rustc-link-lib=static=linkrs"); 27 | link_cpp(); 28 | 29 | // bindgen 30 | let bindings = builder() 31 | .header("c-wrapper/link_rs.h") 32 | .whitelist_function("Link_.*") 33 | .whitelist_function("SessionState_.*") 34 | .whitelist_function("Clock_.*") 35 | .generate() 36 | .expect("generate bindings"); 37 | let outfile = dst.join("link_rs.rs"); 38 | bindings.write_to_file(outfile).expect("write bindings to file"); 39 | 40 | } -------------------------------------------------------------------------------- /c-wrapper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9.1) 2 | 3 | project(link-rs) 4 | 5 | include(../ableton/link/AbletonLinkConfig.cmake) 6 | 7 | add_library(linkrs STATIC link_rs.cpp) 8 | target_link_libraries(linkrs Ableton::Link) 9 | -------------------------------------------------------------------------------- /c-wrapper/link_rs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "link_rs.h" 5 | 6 | 7 | // Link 8 | 9 | WLink* Link_create(double bpm) { 10 | return reinterpret_cast(new ableton::Link(bpm)); 11 | } 12 | 13 | void Link_destroy(WLink* lp) { delete reinterpret_cast(lp); } 14 | 15 | bool Link_isEnabled(WLink* lp) { 16 | return reinterpret_cast(lp)->isEnabled(); 17 | } 18 | 19 | void Link_enable(WLink* lp, bool enable) { 20 | reinterpret_cast(lp)->enable(enable); 21 | } 22 | 23 | bool Link_isStartStopSyncEnabled(WLink* lp) { 24 | return reinterpret_cast(lp)->isStartStopSyncEnabled(); 25 | } 26 | 27 | void Link_enableStartStopSync(WLink* lp, bool enable) { 28 | reinterpret_cast(lp)->enableStartStopSync(enable); 29 | } 30 | 31 | size_t Link_numPeers(WLink* lp) { 32 | return reinterpret_cast(lp)->numPeers(); 33 | } 34 | 35 | void Link_setNumPeersCallback(WLink* lp, void (*callback)(size_t)) { 36 | reinterpret_cast(lp)->setNumPeersCallback(callback); 37 | } 38 | 39 | void Link_setTempoCallback(WLink* lp, void (*callback)(double)) { 40 | reinterpret_cast(lp)->setTempoCallback(callback); 41 | } 42 | 43 | void Link_setStartStopCallback(WLink* lp, void (*callback)(bool)) { 44 | reinterpret_cast(lp)->setStartStopCallback(callback); 45 | } 46 | 47 | WClock* Link_clock(WLink* lp) { 48 | auto c = reinterpret_cast(lp)->clock(); 49 | auto cp = new ableton::Link::Clock(c); 50 | return reinterpret_cast(cp); 51 | } 52 | 53 | void Link_withAudioSessionState(WLink* lp, RustClosurePtr cp, void* closure_data) { 54 | auto ss = reinterpret_cast(lp)->captureAudioSessionState(); 55 | cp(closure_data, reinterpret_cast(&ss)); 56 | } 57 | 58 | void Link_commitAudioSessionState(WLink* lp, WSessionState* ssp) { 59 | auto ss = *reinterpret_cast(ssp); 60 | reinterpret_cast(lp)->commitAudioSessionState(ss); 61 | } 62 | 63 | WSessionState* Link_captureAppSessionState(WLink* lp) { 64 | auto sss = reinterpret_cast(lp)->captureAppSessionState(); 65 | auto ssh = new ableton::Link::SessionState(sss); 66 | return reinterpret_cast(ssh); 67 | } 68 | 69 | void Link_withAppSessionState(WLink* lp, RustClosurePtr cp, void* closure_data) { 70 | auto ss = reinterpret_cast(lp)->captureAppSessionState(); 71 | cp(closure_data, reinterpret_cast(&ss)); 72 | } 73 | 74 | void Link_commitAppSessionState(WLink* lp, WSessionState* ssp) { 75 | auto ss = *reinterpret_cast(ssp); 76 | reinterpret_cast(lp)->commitAppSessionState(ss); 77 | } 78 | 79 | // SessionState 80 | 81 | void SessionState_destroy(WSessionState* ssp) { 82 | delete reinterpret_cast(ssp); 83 | } 84 | 85 | double SessionState_tempo(WSessionState* ssp) { 86 | return reinterpret_cast(ssp)->tempo(); 87 | } 88 | 89 | void SessionState_setTempo(WSessionState* ssp, double bpm, int64_t atTime) { 90 | std::chrono::microseconds t(atTime); 91 | reinterpret_cast(ssp)->setTempo(bpm, t); 92 | } 93 | 94 | double SessionState_beatAtTime(WSessionState* ssp, int64_t time, double quantum) { 95 | auto asp = reinterpret_cast(ssp); 96 | std::chrono::microseconds t(time); 97 | return asp->beatAtTime(t, quantum); 98 | } 99 | 100 | double SessionState_phaseAtTime(WSessionState* ssp, int64_t time, double quantum) { 101 | auto asp = reinterpret_cast(ssp); 102 | std::chrono::microseconds t(time); 103 | return asp->phaseAtTime(t, quantum); 104 | } 105 | 106 | int64_t SessionState_timeAtBeat(WSessionState* ssp, double beat, double quantum) { 107 | auto asp = reinterpret_cast(ssp); 108 | auto micros = asp->timeAtBeat(beat, quantum); 109 | return micros.count(); 110 | } 111 | 112 | void SessionState_requestBeatAtTime(WSessionState* ssp, double beat, int64_t time, double quantum) { 113 | auto asp = reinterpret_cast(ssp); 114 | std::chrono::microseconds t(time); 115 | asp->requestBeatAtTime(beat, t, quantum); 116 | } 117 | 118 | void SessionState_forceBeatAtTime(WSessionState* ssp, double beat, int64_t time, double quantum) { 119 | auto asp = reinterpret_cast(ssp); 120 | std::chrono::microseconds t(time); 121 | asp->forceBeatAtTime(beat, t, quantum); 122 | } 123 | 124 | void SessionState_setIsPlaying(WSessionState* ssp, bool isPlaying, int64_t time) { 125 | auto asp = reinterpret_cast(ssp); 126 | std::chrono::microseconds t(time); 127 | asp->setIsPlaying(isPlaying, t); 128 | } 129 | 130 | bool SessionState_isPlaying(WSessionState* ssp) { 131 | return reinterpret_cast(ssp)->isPlaying(); 132 | } 133 | 134 | int64_t SessionState_timeForIsPlaying(WSessionState* ssp) { 135 | return reinterpret_cast(ssp)->timeForIsPlaying().count(); 136 | } 137 | 138 | void SessionState_requestBeatAtStartPlayingTime(WSessionState* ssp, double beat, double quantum) { 139 | auto asp = reinterpret_cast(ssp); 140 | asp->requestBeatAtStartPlayingTime(beat, quantum); 141 | } 142 | 143 | void SessionState_setIsPlayingAndRequestBeatAtTime(WSessionState* ssp, bool isPlaying, int64_t time, double beat, double quantum) { 144 | auto asp = reinterpret_cast(ssp); 145 | std::chrono::microseconds t(time); 146 | asp->setIsPlayingAndRequestBeatAtTime(isPlaying, t, beat, quantum); 147 | } 148 | 149 | // Clock 150 | 151 | void Clock_destroy(WClock* cp) { 152 | delete reinterpret_cast(cp); 153 | } 154 | 155 | #if defined(__APPLE__) 156 | int64_t Clock_ticksToMicros(WClock* cp, uint64_t ticks) { 157 | return reinterpret_cast(cp)->ticksToMicros(ticks).count(); 158 | } 159 | 160 | uint64_t Clock_microsToTicks(WClock* cp, int64_t micros) { 161 | std::chrono::microseconds t(micros); 162 | return reinterpret_cast(cp)->microsToTicks(t); 163 | } 164 | 165 | uint64_t Clock_ticks(WClock* cp) { 166 | return reinterpret_cast(cp)->ticks(); 167 | } 168 | #endif 169 | 170 | int64_t Clock_micros(WClock* cp) { 171 | return reinterpret_cast(cp)->micros().count(); 172 | } 173 | 174 | -------------------------------------------------------------------------------- /c-wrapper/link_rs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | typedef struct WLink WLink; 10 | typedef struct WSessionState WSessionState; 11 | typedef struct WClock WClock; 12 | typedef void (*RustClosurePtr)(void*, WSessionState*); 13 | 14 | // Link 15 | 16 | WLink* Link_create(double bpm); 17 | void Link_destroy(WLink* lp); 18 | 19 | bool Link_isEnabled(WLink* link); 20 | void Link_enable(WLink* lp, bool enable); 21 | 22 | bool Link_isStartStopSyncEnabled(WLink* lp); 23 | void Link_enableStartStopSync(WLink* lp, bool enable); 24 | 25 | size_t Link_numPeers(WLink* lp); 26 | 27 | void Link_setNumPeersCallback(WLink* lp, void (*callback)(size_t)); 28 | void Link_setTempoCallback(WLink* lp, void (*callback)(double)); 29 | void Link_setStartStopCallback(WLink* lp, void (*callback)(bool)); 30 | 31 | WClock* Link_clock(WLink* lp); 32 | 33 | void Link_withAudioSessionState(WLink* lp, RustClosurePtr cp, void* closure_data); 34 | void Link_commitAudioSessionState(WLink* lp, WSessionState* ssp); 35 | 36 | // WSessionState* Link_captureAppSessionState(WLink* lp); 37 | 38 | void Link_withAppSessionState(WLink* lp, RustClosurePtr cp, void* closure_data); 39 | void Link_commitAppSessionState(WLink* lp, WSessionState* ssp); 40 | 41 | // SessionState 42 | 43 | // void SessionState_destroy(WSessionState* ssp); 44 | 45 | double SessionState_tempo(WSessionState* sp); 46 | void SessionState_setTempo(WSessionState* ssp, double bpm, int64_t atTime); 47 | 48 | double SessionState_beatAtTime(WSessionState* ssp, int64_t time, double quantum); 49 | double SessionState_phaseAtTime(WSessionState* ssp, int64_t time, double quantum); 50 | int64_t SessionState_timeAtBeat(WSessionState* sp, double beat, double quantum); 51 | void SessionState_requestBeatAtTime(WSessionState* ssp, double beat, int64_t time, double quantum); 52 | void SessionState_forceBeatAtTime(WSessionState* ssp, double beat, int64_t time, double quantum); 53 | 54 | void SessionState_setIsPlaying(WSessionState* ssp, bool isPlaying, int64_t time); 55 | bool SessionState_isPlaying(WSessionState* sp); 56 | 57 | int64_t SessionState_timeForIsPlaying(WSessionState* ssp); 58 | void SessionState_requestBeatAtStartPlayingTime(WSessionState* ssp, double beat, double quantum); 59 | void SessionState_setIsPlayingAndRequestBeatAtTime(WSessionState* ssp, bool isPlaying, int64_t time, double beat, double quantum); 60 | 61 | // Clock 62 | 63 | void Clock_destroy(WClock* cp); 64 | 65 | #if defined(__APPLE__) 66 | int64_t Clock_ticksToMicros(WClock* cp, uint64_t ticks); 67 | uint64_t Clock_microsToTicks(WClock* cp, int64_t micros); 68 | 69 | uint64_t Clock_ticks(WClock* cp); 70 | #endif 71 | 72 | int64_t Clock_micros(WClock* cp); 73 | 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An overview of Link concepts can be found at 2 | //! [http://ableton.github.io/link](http://ableton.github.io/link). 3 | //! 4 | //! Then see the doc of [Link](struct.Link.html) and 5 | //! [SessionState](struct.SessionState.html). 6 | //! Most of it is directly taken from the original Link docs. 7 | //! 8 | //! All i64 time values are in microseconds and should be used with the 9 | //! [Clock](struct.Clock.html). 10 | 11 | #[allow(non_camel_case_types)] 12 | #[allow(non_snake_case)] 13 | mod sys { 14 | include!(concat!(env!("OUT_DIR"), "/link_rs.rs")); 15 | } 16 | 17 | use sys::*; 18 | use std::os::raw::c_void; 19 | 20 | /// # Represents a participant in a Link session. 21 | /// 22 | /// Each Link instance has its own session state which 23 | /// represents a beat timeline and a transport start/stop state. The 24 | /// timeline starts running from beat 0 at the initial tempo when 25 | /// constructed. The timeline always advances at a speed defined by 26 | /// its current tempo, even if transport is stopped. Synchronizing to the 27 | /// transport start/stop state of Link is optional for every peer. 28 | /// The transport start/stop state is only shared with other peers when 29 | /// start/stop synchronization is enabled. 30 | /// 31 | /// A Link instance is initially disabled after construction, which 32 | /// means that it will not communicate on the network. Once enabled, 33 | /// a Link instance initiates network communication in an effort to 34 | /// discover other peers. When peers are discovered, they immediately 35 | /// become part of a shared Link session. 36 | /// 37 | /// Each method of the Link type documents its thread-safety and 38 | /// realtime-safety properties. When a method is marked thread-safe, 39 | /// it means it is safe to call from multiple threads 40 | /// concurrently. When a method is marked realtime-safe, it means that 41 | /// it does not block and is appropriate for use in the thread that 42 | /// performs audio IO. 43 | /// 44 | /// Link provides one session state capture/commit method pair for use 45 | /// in the audio thread and one for all other application contexts. In 46 | /// general, modifying the session state should be done in the audio 47 | /// thread for the most accurate timing results. The ability to modify 48 | /// the session state from application threads should only be used in 49 | /// cases where an application's audio thread is not actively running 50 | /// or if it doesn't generate audio at all. Modifying the Link session 51 | /// state from both the audio thread and an application thread 52 | /// concurrently is not advised and will potentially lead to unexpected 53 | /// behavior. 54 | pub struct Link { 55 | wlink: *mut WLink, 56 | } 57 | 58 | impl Drop for Link { 59 | fn drop(&mut self) { 60 | unsafe { Link_destroy(self.wlink) } 61 | // println!("Link destroyed!") 62 | } 63 | } 64 | 65 | impl Link { 66 | /// Construct with an initial tempo. 67 | pub fn new(bpm: f64) -> Link { 68 | Link { wlink: unsafe { Link_create(bpm) } } 69 | } 70 | 71 | /// Is Link currently enabled? 72 | /// * Thread-safe: yes 73 | /// * Realtime-safe: yes 74 | pub fn is_enabled(&self) -> bool { 75 | unsafe { Link_isEnabled(self.wlink) } 76 | } 77 | 78 | /// Enable/disable Link. 79 | /// * Thread-safe: yes 80 | /// * Realtime-safe: no 81 | pub fn enable(&mut self, enable: bool) { 82 | unsafe { Link_enable(self.wlink, enable) } 83 | } 84 | 85 | /// Is start/stop synchronization enabled? 86 | /// * Thread-safe: yes 87 | /// * Realtime-safe: no 88 | pub fn is_start_stop_sync_enabled(&self) -> bool { 89 | unsafe { Link_isStartStopSyncEnabled(self.wlink) } 90 | } 91 | 92 | /// Enable start/stop synchronization. 93 | /// * Thread-safe: yes 94 | /// * Realtime-safe: no 95 | pub fn enable_start_stop_sync(&mut self, enable: bool) { 96 | unsafe { Link_enableStartStopSync(self.wlink, enable) } 97 | } 98 | 99 | /// How many peers are currently connected in a Link session? 100 | /// * Thread-safe: yes 101 | /// * Realtime-safe: yes 102 | pub fn num_peers(&self) -> usize { 103 | unsafe { Link_numPeers(self.wlink) } 104 | } 105 | 106 | /// Register a callback to be notified when the number of 107 | /// peers in the Link session changes. 108 | /// * Thread-safe: yes 109 | /// * Realtime-safe: no 110 | /// 111 | /// The callback is invoked on a Link-managed thread. 112 | pub fn set_num_peers_callback(&mut self, callback: extern fn(usize)) { 113 | unsafe { 114 | let cb = callback as unsafe extern fn(usize); 115 | Link_setNumPeersCallback(self.wlink, Some(cb)); 116 | } 117 | } 118 | 119 | /// Register a callback to be notified when the session tempo changes. 120 | /// * Thread-safe: yes 121 | /// * Realtime-safe: no 122 | /// 123 | /// The callback is invoked on a Link-managed thread. 124 | pub fn set_tempo_callback(&mut self, callback: extern fn(f64)) { 125 | unsafe { 126 | let cb = callback as unsafe extern fn(f64); 127 | Link_setTempoCallback(self.wlink, Some(cb)); 128 | } 129 | } 130 | 131 | /// Register a callback to be notified when the state of 132 | /// start/stop isPlaying changes. 133 | /// * Thread-safe: yes 134 | /// * Realtime-safe: no 135 | /// 136 | /// The callback is invoked on a Link-managed thread. 137 | pub fn set_start_stop_callback(&mut self, callback: extern fn(bool)) { 138 | unsafe { 139 | let cb = callback as unsafe extern fn(bool); 140 | Link_setStartStopCallback(self.wlink, Some(cb)); 141 | } 142 | } 143 | 144 | /// The clock used by Link. 145 | /// * Thread-safe: yes 146 | /// * Realtime-safe: yes 147 | /// 148 | /// The Clock type is a platform-dependent 149 | /// representation of the system clock. It exposes a `ticks()` method 150 | /// that returns the current ticks of the system clock as well as 151 | /// `micros()`, which is a normalized representation of the current system 152 | /// time in std::chrono::microseconds. It also provides conversion 153 | /// functions `ticksToMicros()` and `microsToTicks()` to faciliate 154 | /// converting between these units. 155 | pub fn clock(&self) -> Clock { 156 | Clock { wc: unsafe { Link_clock(self.wlink) } } 157 | } 158 | 159 | // Capture the current Link Session State from the audio thread. 160 | // * Thread-safe: no 161 | // * Realtime-safe: yes 162 | // 163 | // This method should ONLY be called in the audio thread 164 | // and must not be accessed from any other threads. The returned 165 | // object stores a snapshot of the current Link Session State, so it 166 | // should be captured and used in a local scope. Storing the 167 | // Session State for later use in a different context is not advised 168 | // because it will provide an outdated view. 169 | // fn capture_audio_session_state(&self) -> SessionState { 170 | // unimplemented!() 171 | // } 172 | 173 | /// Capture the current Link Session State from the audio thread. 174 | /// * Thread-safe: no 175 | /// * Realtime-safe: yes 176 | /// 177 | /// This method should ONLY be called in the audio thread 178 | /// and must not be accessed from any other threads. The closure 179 | /// passes a snapshot of the current Link Session State, it 180 | /// should only be used in the local scope. Storing the 181 | /// Session State for later use in a different context is not advised 182 | /// because it will provide an outdated view. 183 | pub fn with_audio_session_state(&self, f: F) 184 | where F: FnMut(SessionState) 185 | { 186 | let user_data = &f as *const _ as *mut c_void; 187 | unsafe { 188 | Link_withAudioSessionState(self.wlink, Some(closure_wrapper::), user_data); 189 | } 190 | 191 | extern fn closure_wrapper(closure: *mut c_void, wss: *mut WSessionState) 192 | where F: FnMut(SessionState) 193 | { 194 | let opt_closure = closure as *mut Option; 195 | unsafe { 196 | let mut fnx = (*opt_closure).take().unwrap(); 197 | let ss = SessionState { wss }; 198 | fnx(ss); 199 | } 200 | } 201 | } 202 | 203 | /// Commit the given Session State to the Link session from the audio thread. 204 | /// * Thread-safe: no 205 | /// * Realtime-safe: yes 206 | /// 207 | /// This method should ONLY be called in the audio 208 | /// thread. The given Session State will replace the current Link 209 | /// state. Modifications will be communicated to other peers in the 210 | /// session. 211 | pub fn commit_audio_session_state(&mut self, ss: SessionState) { 212 | unsafe { Link_commitAudioSessionState(self.wlink, ss.wss) } 213 | } 214 | 215 | // Capture the current Link Session State from an application thread. 216 | // * Thread-safe: yes 217 | // * Realtime-safe: no 218 | // 219 | // Provides a mechanism for capturing the Link Session 220 | // State from an application thread (other than the audio thread). 221 | // The returned Session State stores a snapshot of the current Link 222 | // state, so it should be captured and used in a local scope. 223 | // Storing the it for later use in a different context is not 224 | // advised because it will provide an outdated view. 225 | // pub fn capture_app_session_state(&self) -> SessionState { 226 | // let wss = unsafe { Link_captureAppSessionState(self.wlink) }; 227 | // SessionState { wss } 228 | // } 229 | 230 | /// Capture the current Link Session State from an application thread. 231 | /// * Thread-safe: yes 232 | /// * Realtime-safe: no 233 | /// 234 | /// Provides a mechanism for capturing the Link Session 235 | /// State from an application thread (other than the audio thread). 236 | /// The closure passes a Session State that stores a snapshot of the current Link 237 | /// state, it should only be used in the local scope. 238 | /// Storing it for later use in a different context is not 239 | /// advised because it will provide an outdated view. 240 | pub fn with_app_session_state(&self, f: F) 241 | where F: FnMut(SessionState) 242 | { 243 | let user_data = &f as *const _ as *mut c_void; 244 | unsafe { 245 | Link_withAppSessionState(self.wlink, Some(closure_wrapper::), user_data); 246 | } 247 | 248 | extern fn closure_wrapper(closure: *mut c_void, wss: *mut WSessionState) 249 | where F: FnMut(SessionState) 250 | { 251 | let opt_closure = closure as *mut Option; 252 | unsafe { 253 | let mut fnx = (*opt_closure).take().unwrap(); 254 | let ss = SessionState { wss }; 255 | fnx(ss); 256 | } 257 | } 258 | } 259 | 260 | /// Commit the given Session State to the Link session from an 261 | /// application thread. 262 | /// * Thread-safe: yes 263 | /// * Realtime-safe: no 264 | /// 265 | /// The given Session State will replace the current Link 266 | /// Session State. Modifications of the Session State will be 267 | /// communicated to other peers in the session. 268 | pub fn commit_app_session_state(&mut self, ss: SessionState) { 269 | unsafe { Link_commitAppSessionState(self.wlink, ss.wss) } 270 | } 271 | } 272 | 273 | /// # Representation of a timeline and the start/stop state 274 | /// 275 | /// A SessionState object is intended for use in a local scope within 276 | /// a single thread - none of its methods are thread-safe. All of its methods are 277 | /// non-blocking, so it is safe to use from a realtime thread. 278 | /// It provides functions to observe and manipulate the timeline and start/stop 279 | /// state. 280 | /// 281 | /// The timeline is a representation of a mapping between time and beats for varying 282 | /// quanta. 283 | /// 284 | /// The start/stop state represents the user intention to start or stop transport at 285 | /// a specific time. Start stop synchronization is an optional feature that allows to 286 | /// share the user request to start or stop transport between a subgroup of peers in 287 | /// a Link session. When observing a change of start/stop state, audio playback of a 288 | /// peer should be started or stopped the same way it would have happened if the user 289 | /// had requested that change at the according time locally. The start/stop state can 290 | /// only be changed by the user. This means that the current local start/stop state 291 | /// persists when joining or leaving a Link session. After joining a Link session 292 | /// start/stop change requests will be communicated to all connected peers. 293 | pub struct SessionState { 294 | wss: *mut WSessionState, 295 | } 296 | 297 | // impl Drop for SessionState { 298 | // fn drop(&mut self) { 299 | // unsafe { SessionState_destroy(self.wss) } 300 | // } 301 | // } 302 | 303 | impl SessionState { 304 | /// The tempo of the timeline, in bpm. 305 | pub fn tempo(&self) -> f64 { 306 | unsafe { SessionState_tempo(self.wss) } 307 | } 308 | 309 | /// Set the timeline tempo to the given bpm value, taking 310 | /// effect at the given time. 311 | pub fn set_tempo(&mut self, bpm: f64, at_time: i64) { 312 | unsafe { SessionState_setTempo(self.wss, bpm, at_time) } 313 | } 314 | 315 | /// Get the beat value corresponding to the given time 316 | /// for the given quantum. 317 | /// 318 | /// The magnitude of the resulting beat value is 319 | /// unique to this Link instance, but its phase with respect to 320 | /// the provided quantum is shared among all session 321 | /// peers. For non-negative beat values, the following 322 | /// property holds: fmod(beatAtTime(t, q), q) == phaseAtTime(t, q) 323 | pub fn beat_at_time(&self, time: i64, quantum: f64) -> f64 { 324 | unsafe { SessionState_beatAtTime(self.wss, time, quantum) } 325 | } 326 | 327 | /// Get the session phase at the given time for the given quantum. 328 | /// 329 | /// The result is in the interval [0, quantum). The 330 | /// result is equivalent to fmod(beatAtTime(t, q), q) for 331 | /// non-negative beat values. This method is convenient if the 332 | /// client is only interested in the phase and not the beat 333 | /// magnitude. Also, unlike fmod, it handles negative beat values 334 | /// correctly. 335 | pub fn phase_at_time(&self, time: i64, quantum: f64) -> f64 { 336 | unsafe { SessionState_phaseAtTime(self.wss, time, quantum) } 337 | } 338 | 339 | /// Get the time at which the given beat occurs for the 340 | /// given quantum. 341 | /// 342 | /// The inverse of beatAtTime, assuming a constant 343 | /// tempo. beatAtTime(timeAtBeat(b, q), q) === b. 344 | pub fn time_at_beat(&self, beat: f64, quantum: f64) -> i64 { 345 | unsafe { SessionState_timeAtBeat(self.wss, beat, quantum) } 346 | } 347 | 348 | /// Attempt to map the given beat to the given time in the 349 | /// context of the given quantum. 350 | /// 351 | /// This method behaves differently depending on the 352 | /// state of the session. If no other peers are connected, 353 | /// then this instance is in a session by itself and is free to 354 | /// re-map the beat/time relationship whenever it pleases. In this 355 | /// case, beatAtTime(time, quantum) == beat after this method has 356 | /// been called. 357 | /// 358 | /// If there are other peers in the session, this instance 359 | /// should not abruptly re-map the beat/time relationship in the 360 | /// session because that would lead to beat discontinuities among 361 | /// the other peers. In this case, the given beat will be mapped 362 | /// to the next time value greater than the given time with the 363 | /// same phase as the given beat. 364 | /// 365 | /// This method is specifically designed to enable the concept of 366 | /// "quantized launch" in client applications. If there are no other 367 | /// peers in the session, then an event (such as starting 368 | /// transport) happens immediately when it is requested. If there 369 | /// are other peers, however, we wait until the next time at which 370 | /// the session phase matches the phase of the event, thereby 371 | /// executing the event in-phase with the other peers in the 372 | /// session. The client only needs to invoke this method to 373 | /// achieve this behavior and should not need to explicitly check 374 | /// the number of peers. 375 | pub fn request_beat_at_time(&mut self, beat: f64, time: i64, quantum: f64) { 376 | unsafe { SessionState_requestBeatAtTime(self.wss, beat, time, quantum) } 377 | } 378 | 379 | /// Rudely re-map the beat/time relationship for all peers 380 | /// in a session. 381 | /// 382 | /// DANGER: This method should only be needed in 383 | /// certain special circumstances. Most applications should not 384 | /// use it. It is very similar to requestBeatAtTime except that it 385 | /// does not fall back to the quantizing behavior when it is in a 386 | /// session with other peers. Calling this method will 387 | /// unconditionally map the given beat to the given time and 388 | /// broadcast the result to the session. This is very anti-social 389 | /// behavior and should be avoided. 390 | /// 391 | /// One of the few legitimate uses of this method is to 392 | /// synchronize a Link session with an external clock source. By 393 | /// periodically forcing the beat/time mapping according to an 394 | /// external clock source, a peer can effectively bridge that 395 | /// clock into a Link session. Much care must be taken at the 396 | /// application layer when implementing such a feature so that 397 | /// users do not accidentally disrupt Link sessions that they may 398 | /// join. 399 | pub fn force_beat_at_time(&mut self, beat: f64, time: i64, quantum: f64) { 400 | unsafe { SessionState_forceBeatAtTime(self.wss, beat, time, quantum) } 401 | } 402 | 403 | /// Set if transport should be playing or stopped, taking effect 404 | /// at the given time. 405 | pub fn set_is_playing(&mut self, is_playing: bool, time: i64) { 406 | unsafe { SessionState_setIsPlaying(self.wss, is_playing, time) } 407 | } 408 | 409 | /// Is transport playing? 410 | pub fn is_playing(&self) -> bool { 411 | unsafe { SessionState_isPlaying(self.wss) } 412 | } 413 | 414 | /// Get the time at which a transport start/stop occurs. 415 | pub fn time_for_is_playing(&self) -> i64 { 416 | unsafe { SessionState_timeForIsPlaying(self.wss) } 417 | } 418 | 419 | /// Convenience function to attempt to map the given beat to the time 420 | /// when transport is starting to play in context of the given quantum. 421 | /// This function evaluates to a no-op if isPlaying() equals false. 422 | pub fn request_beat_at_start_playing_time(&mut self, beat: f64, quantum: f64) { 423 | unsafe { SessionState_requestBeatAtStartPlayingTime(self.wss, beat, quantum) } 424 | } 425 | 426 | /// Convenience function to start or stop transport at a given time and 427 | /// attempt to map the given beat to this time in context of the given quantum. 428 | pub fn set_is_playing_and_request_beat_at_time(&mut self, 429 | is_playing: bool, time: i64, beat: f64, quantum: f64) { 430 | 431 | unsafe { SessionState_setIsPlayingAndRequestBeatAtTime(self.wss, 432 | is_playing, time, beat, quantum) } 433 | } 434 | } 435 | 436 | pub struct Clock { 437 | wc: *mut WClock, 438 | } 439 | 440 | impl Drop for Clock{ 441 | fn drop(&mut self) { 442 | unsafe { Clock_destroy(self.wc) } 443 | } 444 | } 445 | 446 | impl Clock { 447 | #[cfg(target_os = "macos")] 448 | pub fn ticks_to_micros(&self, ticks: u64) -> i64 { 449 | unsafe { Clock_ticksToMicros(self.wc, ticks) } 450 | } 451 | 452 | #[cfg(target_os = "macos")] 453 | pub fn micros_to_ticks(&self, micros: i64) -> u64 { 454 | unsafe { Clock_microsToTicks(self.wc, micros) } 455 | } 456 | 457 | #[cfg(target_os = "macos")] 458 | pub fn ticks(&self) -> u64 { 459 | unsafe { Clock_ticks(self.wc) } 460 | } 461 | 462 | pub fn micros(&self) -> i64 { 463 | unsafe { Clock_micros(self.wc) } 464 | } 465 | } 466 | 467 | #[cfg(test)] 468 | mod tests { 469 | #[test] 470 | fn it_works() { 471 | assert_eq!(2 + 2, 4); 472 | } 473 | } 474 | --------------------------------------------------------------------------------