pax_global_header00006660000000000000000000000064137741223260014521gustar00rootroot0000000000000052 comment=3e1abd83bceacb18c9b220c0f10757c9f96f29b8 ocaml-bjack-0.1.6/000077500000000000000000000000001377412232600136705ustar00rootroot00000000000000ocaml-bjack-0.1.6/.github/000077500000000000000000000000001377412232600152305ustar00rootroot00000000000000ocaml-bjack-0.1.6/.github/workflows/000077500000000000000000000000001377412232600172655ustar00rootroot00000000000000ocaml-bjack-0.1.6/.github/workflows/main.yml000066400000000000000000000005171377412232600207370ustar00rootroot00000000000000name: Build on: [push] jobs: build: runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: [ubuntu-20.04, macos-latest] steps: - uses: actions/checkout@v1 - uses: avsm/setup-ocaml@master - run: opam pin add -n . - run: opam depext -yt bjack - run: opam install -t . ocaml-bjack-0.1.6/.gitignore000066400000000000000000000000631377412232600156570ustar00rootroot00000000000000*~ _build *.byte *.native _tests .merlin *.install ocaml-bjack-0.1.6/.ocamlformat000066400000000000000000000003201377412232600161700ustar00rootroot00000000000000profile = conventional break-separators = after space-around-lists = false doc-comments = before match-indent = 2 match-indent-nested = always parens-ite exp-grouping = preserve module-item-spacing = compact ocaml-bjack-0.1.6/CHANGES000066400000000000000000000015571377412232600146730ustar00rootroot000000000000000.1.6 (2020-01-02) ====== * Switch to dune! 0.1.5 (2015-07-29) ===== * Fix some warnings and deprecated calls. 0.1.4 (17-12-2013) ===== * Rename errno to errnum in order to build on Hurd (thanks Pino Toscano). * Updated configure. 0.1.3 (11-10-2009) ===== * Added support for --enable-debugging configure option * Added NO_CUSTOM to build in standard mode. * Added prefix to main compilation variables if passed to configure. * Makefile now honnors LIBDIRS variable for linking against libraries located in other places than then standard ones. * configure.ac: Use LIBS instead of LDFLAGS when redefining LIBS 0.1.2 (17-02-2009) ===== * Fix invalid strlen call leading to segfaults (#219). Thanks to MiiJaySung for reporting, testing and fixing this ! 0.1.1 (23/06/2008) ===== * Fix typo in caml_bjack_open_byte(s) 0.1.0 (10/05/2008) ===== * Initial release ocaml-bjack-0.1.6/COPYING000066400000000000000000000655731377412232600147430ustar00rootroot00000000000000The software distributed in this tarball is licenced under the GNU Lesser General Public License, with a special exception: As a special exception to the GNU Library General Public License, you may link, statically or dynamically, a "work that uses the Library" with a publicly distributed version of the Library to produce an executable file containing portions of the Library, and distribute that executable file under terms of your choice, without any of the additional requirements listed in clause 6 of the GNU Library General Public License. By "a publicly distributed version of the Library", we mean either the unmodified Library as distributed by The Savonet Team, or a modified version of the Library that is distributed under the conditions defined in clause 3 of the GNU Library General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Library General Public License. The GNU Lesser General Public Licence text is: <---------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ocaml-bjack-0.1.6/README.md000066400000000000000000000012011377412232600151410ustar00rootroot00000000000000ocaml-bjack This package contains an OCaml blocking API for the jack audio connection kit. Please read the COPYING file before using this software. Prerequisites: ============== - ocaml - jack audio connection kit - dune >= 2.0 Compilation: ============ ``` $ dune build ``` This should build both the native and the byte-code version of the extension library. Installation: ============= Via `opam`: ``` $ opam install mad ``` Via `dune` (for developers): ``` $ dune install ``` Author: ======= This author of this software may be contacted by electronic mail at the following address: savonet-users@lists.sourceforge.net. ocaml-bjack-0.1.6/TIPS000066400000000000000000000010131377412232600143650ustar00rootroot00000000000000******** * TIPS * ******** * To use jack without a soundcard, tweaking the -w parameter of dummy driver of jack can be useful. For example: jackd -d dummy -r 44100 -P -p 2048 -w 20000 * To have alsa output rerouted to jack, use the following .asoundrc: -------- pcm.jack { type jack playback_ports { 0 alsa_pcm:playback_1 1 alsa_pcm:playback_2 } capture_ports { 0 alsa_pcm:capture_1 1 alsa_pcm:capture_2 } } pcm.!default { type plug slave { pcm "jack" } } --------- ocaml-bjack-0.1.6/bjack.opam000066400000000000000000000013731377412232600156240ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "0.1.6" synopsis: "Bindings for the Jack library which provides functions for linking audio programs" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "LGPL-2.1" homepage: "https://github.com/savonet/ocaml-bjack" bug-reports: "https://github.com/savonet/ocaml-bjack/issues" depends: [ "dune" {>= "2.0"} "dune-configurator" "conf-bjack" ] build: [ ["dune" "subst"] {pinned} [ "dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test} "@doc" {with-doc} ] ] dev-repo: "git+https://github.com/savonet/ocaml-bjack.git" ocaml-bjack-0.1.6/dune-project000066400000000000000000000007041377412232600162130ustar00rootroot00000000000000(lang dune 2.0) (version 0.1.6) (name bjack) (source (github savonet/ocaml-bjack)) (license LGPL-2.1) (authors "The Savonet Team ") (maintainers "The Savonet Team ") (generate_opam_files true) (package (name bjack) (synopsis "Bindings for the Jack library which provides functions for linking audio programs") (depends (dune (>= 2.0)) dune-configurator conf-bjack) ) ocaml-bjack-0.1.6/src/000077500000000000000000000000001377412232600144575ustar00rootroot00000000000000ocaml-bjack-0.1.6/src/bjack.ml000066400000000000000000000201551377412232600160660ustar00rootroot00000000000000(* * Copyright 2007-2008 Romain Beauxis * * This file is part of ocaml-bjack. * * ocaml-bjack is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ocaml-bjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ocaml-bjack; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception to the GNU Library General Public License, you may * link, statically or dynamically, a "work that uses the Library" with a publicly * distributed version of the Library to produce an executable file containing * portions of the Library, and distribute that executable file under terms of * your choice, without any of the additional requirements listed in clause 6 * of the GNU Library General Public License. * By "a publicly distributed version of the Library", we mean either the unmodified * Library as distributed by The Savonet Team, or a modified version of the Library that is * distributed under the conditions defined in clause 3 of the GNU Library General * Public License. This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU Library General Public License. * *) (* @author Romain Beauxis *) type t exception Open exception Bytes_per_output_frame_invalid exception Bytes_per_input_frame_invalid exception Too_many_output_channels exception Port_name_output_channel_mismatch exception Port_not_found exception Too_many_input_channels exception Port_name_input_channel_mismatch let _ = Callback.register_exception "bio2jack_exn_open" Open; Callback.register_exception "bio2jack_exn_bytes_per_output_frame_invalid" Bytes_per_output_frame_invalid; Callback.register_exception "bio2jack_exn_bytes_per_input_frame_invalid" Bytes_per_input_frame_invalid; Callback.register_exception "bio2jack_exn_too_many_output_channels" Too_many_output_channels; Callback.register_exception "bio2jack_exn_port_name_output_channel_mismatch" Port_name_output_channel_mismatch; Callback.register_exception "bio2jack_exn_port_not_found" Port_not_found; Callback.register_exception "bio2jack_exn_too_many_input_channels" Too_many_input_channels; Callback.register_exception "bio2jack_exn_port_name_input_channel_mismatch" Port_name_input_channel_mismatch external private_value_int : string -> int = "caml_bjack_priv_value_int" type converter = | Best_quality | Medium_quality | Fastest | Zero_order_hold | Linear let int_of_converter c = let f = private_value_int in match c with | Best_quality -> f "SRC_SINC_BEST_QUALITY" | Medium_quality -> f "SRC_SINC_MEDIUM_QUALITY" | Fastest -> f "SRC_SINC_FASTEST" | Zero_order_hold -> f "SRC_ZERO_ORDER_HOLD" | Linear -> f "SRC_LINEAR" external set_conversion_function : int -> unit = "caml_bjack_set_conversion_function" let set_conversion_function c = set_conversion_function (int_of_converter c) type port_flag = Input | Output | Physical | Monitor | Terminal let int_of_port_flag flag = let f = private_value_int in match flag with | Input -> f "JackPortIsInput" | Output -> f "JackPortIsOutput" | Physical -> f "JackPortIsPhysical" | Monitor -> f "JackPortCanMonitor" | Terminal -> f "JackPortIsTerminal" let iport_flags flags = List.fold_left (fun x y -> x lor int_of_port_flag y) 0 flags external open_t : int -> int -> string -> string -> int -> int -> int -> int -> t = "caml_bjack_open_byte" "caml_bjack_open" let open_t ?(ringbuffer_size = 4096) ?(server_name = "") ~rate ~bits_per_sample ~input_channels ~output_channels ~flags ~client_name () = let flags = iport_flags flags in open_t bits_per_sample rate client_name server_name input_channels output_channels flags ringbuffer_size external close : t -> unit = "caml_bjack_close" external write : t -> string -> int = "caml_bjack_write" external read : t -> int -> string = "caml_bjack_read" external reset : t -> unit = "caml_bjack_reset" type position_unit = Bytes | Milliseconds type position_type = Played | Written_to_jack | Written let int_of_position_unit t = let f = private_value_int in match t with Bytes -> f "BYTES" | Milliseconds -> f "MILLISECONDS" let int_of_position_type t = let f = private_value_int in match t with | Played -> f "PLAYED" | Written_to_jack -> f "WRITTEN_TO_JACK" | Written -> f "WRITTEN" external get_position : t -> int -> int -> int = "caml_bjack_get_position" let get_position ~position_unit ~position_type device = get_position device (int_of_position_unit position_unit) (int_of_position_type position_type) external set_position : t -> int -> int -> unit = "caml_bjack_set_position" let set_position ~position_unit device position = set_position device (int_of_position_unit position_unit) position external get_output_latency : t -> int = "caml_bjack_get_output_latency" external get_input_latency : t -> int = "caml_bjack_get_input_latency" type playing_state = Playing | Paused | Stopped | Closed | Reset let int_of_playing_state s = let f = private_value_int in match s with | Playing -> f "PLAYING" | Paused -> f "PAUSED" | Stopped -> f "STOPPED" | Closed -> f "CLOSED" | Reset -> f "RESET" let playing_state_of_int n = let f = private_value_int in match n with | s when s = f "PLAYING" -> Playing | s when s = f "PAUSED" -> Paused | s when s = f "STOPPED" -> Stopped | s when s = f "CLOSED" -> Closed | s when s = f "RESET" -> Reset | _ -> raise Not_found external set_state : t -> int -> unit = "caml_bjack_set_state" let set_state device state = set_state device (int_of_playing_state state) external get_state : t -> int = "caml_bjack_set_state" let get_state device = playing_state_of_int (get_state device) external get_max_output_buffered_bytes : t -> int = "caml_bjack_get_max_output_buffered_bytes" external get_max_input_buffered_bytes : t -> int = "caml_bjack_get_max_input_buffered_bytes" external get_jack_buffered_bytes : t -> int = "caml_bjack_get_jack_buffered_bytes" type volume = Linear | Decibel let int_of_volume v = let f = private_value_int in match (v : volume) with Linear -> f "linear" | Decibel -> f "dbAttenuation" let volume_of_int n : volume = let f = private_value_int in match n with | e when e = f "linear" -> Linear | e when e = f "dbAttenuation" -> Decibel | _ -> raise Not_found external set_volume_effect_type : t -> int -> int = "caml_bjack_set_volume_effect_type" let set_volume_effect_type device volume = volume_of_int (set_volume_effect_type device (int_of_volume volume)) external set_all_volume : t -> int -> unit = "caml_bjack_set_all_volume" external set_channel_volume : t -> int -> int -> unit = "caml_bjack_set_channel_volume" let set_channel_volume ~channel ~device volume = set_channel_volume device channel volume external get_channel_volume : t -> int -> int = "caml_bjack_get_channel_volume" external get_output_bytes_per_second : t -> int = "caml_bjack_get_output_bytes_per_second" external get_input_bytes_per_second : t -> int = "caml_bjack_get_input_bytes_per_second" external get_bytes_stored : t -> int = "caml_bjack_get_bytes_stored" external get_bytes_free_space : t -> int = "caml_bjack_get_bytes_free_space" external get_bytes_used_space : t -> int = "caml_bjack_get_bytes_used_space" external get_bytes_per_output_frame : t -> int = "caml_bjack_get_bytes_per_output_frame" external get_bytes_per_input_frame : t -> int = "caml_bjack_get_bytes_per_input_frame" external get_num_output_channels : t -> int = "caml_bjack_get_num_output_channels" external get_num_input_channels : t -> int = "caml_bjack_get_num_input_channels" external get_sample_rate : t -> int = "caml_bjack_get_sample_rate" ocaml-bjack-0.1.6/src/bjack.mli000066400000000000000000000104301377412232600162320ustar00rootroot00000000000000(* * Copyright 2007-2008 Romain Beauxis * * This file is part of ocaml-bjack. * * ocaml-bjack is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ocaml-bjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ocaml-bjack; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception to the GNU Library General Public License, you may * link, statically or dynamically, a "work that uses the Library" with a publicly * distributed version of the Library to produce an executable file containing * portions of the Library, and distribute that executable file under terms of * your choice, without any of the additional requirements listed in clause 6 * of the GNU Library General Public License. * By "a publicly distributed version of the Library", we mean either the unmodified * Library as distributed by The Savonet Team, or a modified version of the Library that is * distributed under the conditions defined in clause 3 of the GNU Library General * Public License. This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU Library General Public License. * *) (* @author Romain Beauxis *) (** Ocaml blocking API to jack audio connection kit *) (** {2 Types and exceptions } *) (** Type for a Bjack device *) type t (** Various possible samplerate converters *) type converter = | Best_quality | Medium_quality | Fastest | Zero_order_hold | Linear exception Open exception Bytes_per_output_frame_invalid exception Bytes_per_input_frame_invalid exception Too_many_output_channels exception Port_name_output_channel_mismatch exception Port_not_found exception Too_many_input_channels exception Port_name_input_channel_mismatch (** Set conversion function *) val set_conversion_function : converter -> unit (** Various jack port options *) type port_flag = Input | Output | Physical | Monitor | Terminal (** {2 Blocking API } *) (** Open a Bjack device *) val open_t : ?ringbuffer_size:int -> ?server_name:string -> rate:int -> bits_per_sample:int -> input_channels:int -> output_channels:int -> flags:port_flag list -> client_name:string -> unit -> t (** Close a Bjack device *) val close : t -> unit (** Write to a device. * Raises [Too_many_output_channels] if there are no output channels available on the device *) val write : t -> string -> int (** Read from a device. * Raises [Too_many_input_channels] if there are no input channels available on the device *) val read : t -> int -> string (** Reset a Bjack device *) val reset : t -> unit (** {2 Parameters and informations } *) type position_unit = Bytes | Milliseconds type position_type = Played | Written_to_jack | Written val get_position : position_unit:position_unit -> position_type:position_type -> t -> int val set_position : position_unit:position_unit -> t -> int -> unit val get_output_latency : t -> int val get_input_latency : t -> int type playing_state = Playing | Paused | Stopped | Closed | Reset val set_state : t -> playing_state -> unit val get_state : t -> playing_state val get_max_output_buffered_bytes : t -> int val get_max_input_buffered_bytes : t -> int val get_jack_buffered_bytes : t -> int type volume = Linear | Decibel val set_volume_effect_type : t -> volume -> volume val set_all_volume : t -> int -> unit val set_channel_volume : channel:int -> device:t -> int -> unit val get_channel_volume : t -> int -> int val get_output_bytes_per_second : t -> int val get_input_bytes_per_second : t -> int val get_bytes_stored : t -> int val get_bytes_free_space : t -> int val get_bytes_used_space : t -> int val get_bytes_per_output_frame : t -> int val get_bytes_per_input_frame : t -> int val get_num_output_channels : t -> int val get_num_input_channels : t -> int val get_sample_rate : t -> int ocaml-bjack-0.1.6/src/config/000077500000000000000000000000001377412232600157245ustar00rootroot00000000000000ocaml-bjack-0.1.6/src/config/discover.ml000066400000000000000000000012241377412232600200730ustar00rootroot00000000000000module C = Configurator.V1 let () = C.main ~name:"bjack-pkg-config" (fun c -> let default : C.Pkg_config.package_conf = { libs = ["-ljack"; "-lsamplerate"]; cflags = [] } in let conf = match C.Pkg_config.get c with | None -> default | Some pc -> ( match C.Pkg_config.query_expr_err pc ~package:"jack samplerate" ~expr:"jack samplerate" with | Error msg -> failwith msg | Ok deps -> deps) in C.Flags.write_sexp "c_flags.sexp" conf.cflags; C.Flags.write_sexp "c_library_flags.sexp" conf.libs) ocaml-bjack-0.1.6/src/config/dune000066400000000000000000000000751377412232600166040ustar00rootroot00000000000000(executable (name discover) (libraries dune.configurator)) ocaml-bjack-0.1.6/src/dune000066400000000000000000000005151377412232600153360ustar00rootroot00000000000000(library (name bjack) (public_name bjack) (synopsis "OCaml bindings for bjack") (foreign_stubs (language c) (names jack_stubs jack_wrapper) (flags (:include c_flags.sexp))) (c_library_flags (:include c_library_flags.sexp))) (rule (targets c_flags.sexp c_library_flags.sexp) (action (run ./config/discover.exe))) ocaml-bjack-0.1.6/src/jack_stubs.c000066400000000000000000000271071377412232600167620ustar00rootroot00000000000000/* * Copyright 2007-2008 Romain Beauxis * * This file is part of ocaml-bjack. * * ocaml-bjack is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ocaml-bjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ocaml-bjack; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception to the GNU Library General Public License, you may * link, statically or dynamically, a "work that uses the Library" with a * publicly distributed version of the Library to produce an executable file * containing portions of the Library, and distribute that executable file under * terms of your choice, without any of the additional requirements listed in * clause 6 of the GNU Library General Public License. By "a publicly * distributed version of the Library", we mean either the unmodified Library as * distributed by The Savonet Team, or a modified version of the Library that is * distributed under the conditions defined in clause 3 of the GNU Library * General Public License. This exception does not however invalidate any other * reasons why the executable file might be covered by the GNU Library General * Public License. * */ /* * Ocaml blocking API to the jack audio connection kit. * * @author Romain Beauxis */ #include "jack_wrapper.h" #include #include #include #include #include #include #include #include #include static value caml_bjack_handle_error(int errnum) { switch (errnum) { case ERR_OPENING_JACK: caml_raise_constant(*caml_named_value("bio2jack_exn_open")); case ERR_BYTES_PER_OUTPUT_FRAME_INVALID: caml_raise_constant( *caml_named_value("bio2jack_exn_bytes_per_output_frame_invalid")); case ERR_BYTES_PER_INPUT_FRAME_INVALID: caml_raise_constant( *caml_named_value("bio2jack_exn_bytes_per_input_frame_invalid")); case ERR_TOO_MANY_OUTPUT_CHANNELS: caml_raise_constant( *caml_named_value("bio2jack_exn_too_many_output_channels")); case ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH: caml_raise_constant( *caml_named_value("bio2jack_exn_port_name_output_channel_mismatch")); case ERR_PORT_NOT_FOUND: caml_raise_constant(*caml_named_value("bio2jack_exn_port_not_found")); case ERR_TOO_MANY_INPUT_CHANNELS: caml_raise_constant( *caml_named_value("bio2jack_exn_too_many_input_channels")); case ERR_PORT_NAME_INPUT_CHANNEL_MISMATCH: caml_raise_constant( *caml_named_value("bio2jack_exn_port_name_input_channel_mismatch")); default: caml_failwith("Failed to open device: Unknown error"); } } CAMLprim value caml_bjack_priv_value_int(value name) { CAMLparam1(name); char *s = String_val(name); if (!strcmp(s, "PLAYED")) CAMLreturn(Val_int(PLAYED)); if (!strcmp(s, "WRITTEN_TO_JACK")) CAMLreturn(Val_int(WRITTEN_TO_JACK)); if (!strcmp(s, "WRITTEN")) CAMLreturn(Val_int(WRITTEN)); /* Values from samplerate.h */ if (!strcmp(s, "SRC_SINC_BEST_QUALITY")) CAMLreturn(Val_int(SRC_SINC_BEST_QUALITY)); if (!strcmp(s, "SRC_SINC_MEDIUM_QUALITY")) CAMLreturn(Val_int(SRC_SINC_MEDIUM_QUALITY)); if (!strcmp(s, "SRC_SINC_FASTEST")) CAMLreturn(Val_int(SRC_SINC_FASTEST)); if (!strcmp(s, "SRC_ZERO_ORDER_HOLD")) CAMLreturn(Val_int(SRC_ZERO_ORDER_HOLD)); if (!strcmp(s, "SRC_LINEAR")) CAMLreturn(Val_int(SRC_LINEAR)); /* Values from jack/types.h */ if (!strcmp(s, "JackPortIsInput")) CAMLreturn(Val_int(JackPortIsInput)); if (!strcmp(s, "JackPortIsOutput")) CAMLreturn(Val_int(JackPortIsOutput)); if (!strcmp(s, "JackPortIsPhysical")) CAMLreturn(Val_int(JackPortIsPhysical)); if (!strcmp(s, "JackPortCanMonitor")) CAMLreturn(Val_int(JackPortCanMonitor)); if (!strcmp(s, "JackPortIsTerminal")) CAMLreturn(Val_int(JackPortIsTerminal)); caml_failwith("Invalid value"); } #define Bjack_drv_val(v) (*((jack_driver_t **)Data_custom_val(v))) static void finalize_bjack_drv(value d) { jack_driver_t *drv = Bjack_drv_val(d); JACK_Close(drv); free(drv); } static struct custom_operations bjack_drv_ops = { "ocaml_bjack_drv", finalize_bjack_drv, custom_compare_default, custom_hash_default, custom_serialize_default, custom_deserialize_default}; CAMLprim value caml_bjack_open(value bit_per_sample, value rate, value name, value server, value input_channels, value output_channels, value _jack_port_flags, value size) { CAMLparam2(name, server); CAMLlocal1(driver); jack_driver_t *drv = JACK_CreateDriver(); if (drv == NULL) caml_failwith("drv_malloc"); unsigned long r = Unsigned_long_val(rate); int jack_ports_flags = Int_val(_jack_port_flags); int errnum = JACK_Open(drv, Int_val(bit_per_sample), &r, String_val(name), String_val(server), Int_val(input_channels), Int_val(output_channels), jack_ports_flags, Int_val(size)); if (errnum != ERR_SUCCESS) caml_bjack_handle_error(errnum); driver = caml_alloc_custom(&bjack_drv_ops, sizeof(jack_driver_t *), 1, 0); Bjack_drv_val(driver) = drv; CAMLreturn(driver); } CAMLprim value caml_bjack_open_byte(value *argv, int argc) { return caml_bjack_open(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); } CAMLprim value caml_bjack_close(value device) { CAMLparam1(device); jack_driver_t *drv = Bjack_drv_val(device); int errnum = JACK_Close(drv); if (errnum != ERR_SUCCESS) caml_bjack_handle_error(errnum); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_reset(value device) { CAMLparam1(device); JACK_Reset(Bjack_drv_val(device)); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_write(value device, value data) { CAMLparam2(device, data); int n = caml_string_length(data); jack_driver_t *drv = Bjack_drv_val(device); long ret; char *buf = malloc(n); memcpy(buf, String_val(data), n); if (drv->num_output_channels > 0) { caml_enter_blocking_section(); ret = JACK_Write(drv, (unsigned char *)buf, n); caml_leave_blocking_section(); } else { caml_raise_constant( *caml_named_value("bio2jack_exn_too_many_output_channels")); } if (ret < 0) caml_failwith("jack_write"); free(buf); CAMLreturn(Val_long(ret)); } CAMLprim value caml_bjack_read(value device, value len) { CAMLparam2(device, len); CAMLlocal1(ans); int n = Int_val(len); char *buf = malloc(n); jack_driver_t *drv = Bjack_drv_val(device); long ret; if (drv->num_input_channels > 0) { caml_enter_blocking_section(); ret = JACK_Read(drv, (unsigned char *)buf, n); caml_leave_blocking_section(); } else { caml_raise_constant( *caml_named_value("bio2jack_exn_too_many_input_channels")); } if (ret < 0) caml_failwith("jack_read"); ans = caml_alloc_string(ret); memcpy(String_val(ans), buf, ret); free(buf); CAMLreturn(ans); } CAMLprim value caml_bjack_get_position(value d, value pos_type, value type) { CAMLparam3(d, pos_type, type); CAMLreturn(Val_long( JACK_GetPosition(Bjack_drv_val(d), Int_val(pos_type), Int_val(type)))); } CAMLprim value caml_bjack_set_position(value d, value pos_type, value position) { CAMLparam3(d, pos_type, position); JACK_SetPosition(Bjack_drv_val(d), Int_val(pos_type), Int_val(position)); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_get_output_latency(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetJackOutputLatency(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_input_latency(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetJackInputLatency(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_set_state(value d, value state) { CAMLparam2(d, state); int ret = JACK_SetState(Bjack_drv_val(d), Int_val(state)); if (ret != 0) caml_failwith("state"); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_get_state(value d) { CAMLparam1(d); CAMLreturn(Val_int(JACK_GetState(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_max_output_buffered_bytes(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetMaxOutputBufferedBytes(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_max_input_buffered_bytes(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetMaxInputBufferedBytes(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_jack_buffered_bytes(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetJackBufferedBytes(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_set_volume_effect_type(value d, value type) { CAMLparam2(d, type); CAMLreturn( Val_int(JACK_SetVolumeEffectType(Bjack_drv_val(d), Int_val(type)))); } CAMLprim value caml_bjack_set_all_volume(value d, value volume) { CAMLparam2(d, volume); int ret = JACK_SetAllVolume(Bjack_drv_val(d), Unsigned_int_val(volume)); if (ret != 0) caml_failwith("volume"); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_set_channel_volume(value d, value channel, value volume) { CAMLparam3(d, channel, volume); int ret = JACK_SetVolumeForChannel( Bjack_drv_val(d), Unsigned_int_val(channel), Unsigned_int_val(volume)); if (ret != 0) caml_failwith("volume"); CAMLreturn(Val_unit); } CAMLprim value caml_bjack_get_channel_volume(value d, value channel) { CAMLparam2(d, channel); unsigned int volume; JACK_GetVolumeForChannel(Bjack_drv_val(d), Unsigned_int_val(channel), &volume); CAMLreturn(Val_long(volume)); } CAMLprim value caml_bjack_get_output_bytes_per_second(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetOutputBytesPerSecond(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_input_bytes_per_second(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetInputBytesPerSecond(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_bytes_stored(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetBytesStored(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_bytes_free_space(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetBytesFreeSpace(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_bytes_used_space(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetBytesUsedSpace(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_bytes_per_output_frame(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetBytesPerOutputFrame(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_bytes_per_input_frame(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetBytesPerInputFrame(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_num_input_channels(value d) { CAMLparam1(d); CAMLreturn(Val_int(JACK_GetNumInputChannels(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_num_output_channels(value d) { CAMLparam1(d); CAMLreturn(Val_int(JACK_GetNumOutputChannels(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_get_sample_rate(value d) { CAMLparam1(d); CAMLreturn(Val_long(JACK_GetSampleRate(Bjack_drv_val(d)))); } CAMLprim value caml_bjack_set_conversion_function(value n) { CAMLparam1(n); JACK_SetSampleRateConversionFunction(Int_val(n)); CAMLreturn(Val_unit); } ocaml-bjack-0.1.6/src/jack_wrapper.c000066400000000000000000001717401377412232600173050ustar00rootroot00000000000000/* * Copyright 2003-2006 Chris Morgan * Copyright 2007-2008 Romain Beauxis * * This file is part of ocaml-bjack. It uses code from the bio2jack project. * * ocaml-bjack is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ocaml-bjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ocaml-taglib; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception to the GNU Library General Public License, you may * link, statically or dynamically, a "work that uses the Library" with a * publicly distributed version of the Library to produce an executable file * containing portions of the Library, and distribute that executable file under * terms of your choice, without any of the additional requirements listed in * clause 6 of the GNU Library General Public License. By "a publicly * distributed version of the Library", we mean either the unmodified Library as * distributed by The Savonet Team, or a modified version of the Library that is * distributed under the conditions defined in clause 3 of the GNU Library * General Public License. This exception does not however invalidate any other * reasons why the executable file might be covered by the GNU Library General * Public License. * */ #include #include #include #include #include #include "jack_wrapper.h" /* enable/disable TRACING through the JACK_Callback() function */ /* this can sometimes be too much information */ #define TRACE_CALLBACK 0 /* set to 1 for verbose output */ #define VERBOSE_OUTPUT 0 /* set to 1 to enable debug messages */ #define DEBUG_OUTPUT 0 /* set to 1 to enable tracing */ #define TRACE_ENABLE 0 /* set to 1 to enable the function timers */ #define TIMER_ENABLE 0 /* set to 1 to enable tracing of getDriver() and releaseDriver() */ #define TRACE_getReleaseDevice 0 #define ENABLE_WARNINGS 0 #define OUTFILE stderr #if TIMER_ENABLE /* This seemingly construct makes timing arbitrary functions really easy all you have to do is place a 'TIMER("start\n")' at the beginning and a 'TIMER("stop\n")' at the end of any function and this does the rest (naturally you can place any printf-compliant text you like in the argument along with the associated values). */ static struct timeval timer_now; #define TIMER(format, args...) \ gettimeofday(&timer_now, 0); \ fprintf(OUTFILE, "%ld.%06ld: %s::%s(%d) " format, timer_now.tv_sec, \ timer_now.tv_usec, __FILE__, __FUNCTION__, __LINE__, ##args) #else #define TIMER(...) #endif #if TRACE_ENABLE #define TRACE(format, args...) \ fprintf(OUTFILE, "%s::%s(%d) " format, __FILE__, __FUNCTION__, __LINE__, \ ##args); \ fflush(OUTFILE); #else #define TRACE(...) #endif #if DEBUG_OUTPUT #define DEBUG(format, args...) \ fprintf(OUTFILE, "%s::%s(%d) " format, __FILE__, __FUNCTION__, __LINE__, \ ##args); \ fflush(OUTFILE); #else #define DEBUG(...) #endif #if TRACE_CALLBACK #define CALLBACK_TRACE(format, args...) \ fprintf(OUTFILE, "%s::%s(%d) " format, __FILE__, __FUNCTION__, __LINE__, \ ##args); \ fflush(OUTFILE); #else #define CALLBACK_TRACE(...) #endif #if ENABLE_WARNINGS #define WARN(format, args...) \ fprintf(OUTFILE, "WARN: %s::%s(%d) " format, __FILE__, __FUNCTION__, \ __LINE__, ##args); \ fflush(OUTFILE); #else #define WARN(...) #endif #define ERR(format, args...) \ fprintf(OUTFILE, "ERR: %s::%s(%d) " format, __FILE__, __FUNCTION__, \ __LINE__, ##args); \ fflush(OUTFILE); #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) < (b)) ? (b) : (a)) /* Which SRC converter function we should use when doing sample rate conversion. Default to the fastest of the 'good quality' set. */ static int preferred_src_converter = SRC_SINC_FASTEST; /* enable/disable code that allows us to close a device without actually closing * the jack device */ /* this works around the issue where jack doesn't always close devices by the * time the close function call returns */ #define JACK_CLOSE_HACK 1 typedef jack_default_audio_sample_t sample_t; typedef jack_nframes_t nframes_t; #if JACK_CLOSE_HACK static void JACK_CloseDevice(jack_driver_t *drv, bool close_client); #else static void JACK_CloseDevice(jack_driver_t *drv); #endif /* Prototypes */ static int JACK_OpenDevice(jack_driver_t *drv); static void JACK_CleanupDriver(jack_driver_t *drv); /* Return the difference between two timeval structures in terms of milliseconds */ long TimeValDifference(struct timeval *start, struct timeval *end) { double long ms; /* milliseconds value */ ms = end->tv_sec - start->tv_sec; /* compute seconds difference */ ms *= (double)1000; /* convert to milliseconds */ ms += (double)(end->tv_usec - start->tv_usec) / (double)1000; /* add on microseconds difference */ return (long)ms; } /* get a device and lock the devices mutex */ /* */ /* also attempt to reconnect to jack since this function is called from */ /* most other ocaml-bjack functions it provides a good point to attempt * reconnection */ /* */ /* Ok, I know this looks complicated and it kind of is. The point is that when you're trying to trace mutexes it's more important to know *who* called us than just that we were called. This uses from pre-processor trickery so that the fprintf is actually placed in the function making the getDriver call. Thus, the __FUNCTION__ and __LINE__ macros will actually reference our caller, rather than getDriver. The reason the fprintf call is passes as a parameter is because this macro has to still return a jack_driver_t* and we want to log both before *and* after the getDriver call for easier detection of blocked calls. */ // Beurk (toots) #if TRACE_getReleaseDevice #define getDriver(x) \ _getDriver(x, fprintf(OUTFILE, "%s::%s(%d) getting driver %s\n", __FILE__, \ __FUNCTION__, __LINE__, x->client_name)); \ TRACE("got driver %s\n", x->client_name); jack_driver_t *_getDriver(jack_driver_t *drv, int ignored) { fflush(OUTFILE); #else jack_driver_t *getDriver(jack_driver_t *drv) { #endif if (pthread_mutex_lock(&drv->mutex) != 0) ERR("lock returned an error\n"); /* should we try to restart the jack server? */ if (drv->jackd_died && drv->client == 0) { struct timeval now; gettimeofday(&now, 0); /* wait 250ms before trying again */ if (TimeValDifference(&drv->last_reconnect_attempt, &now) >= 250) { JACK_OpenDevice(drv); drv->last_reconnect_attempt = now; } } return drv; } /* release a device's mutex */ /* */ /* This macro is similar to the one for getDriver above, only simpler since we only really need to know when the lock was release for the sake of debugging. */ #if TRACE_getReleaseDevice #define releaseDriver(x) \ TRACE("releasing driver %s\n", x->client_name); \ _releaseDriver(x); void _releaseDriver(jack_driver_t *drv) #else void releaseDriver(jack_driver_t *drv) #endif { if (pthread_mutex_unlock(&drv->mutex) != 0) ERR("lock returned an error\n"); } /* Return a string corresponding to the input state */ char *DEBUGSTATE(enum status_enum state) { if (state == PLAYING) return "PLAYING"; else if (state == PAUSED) return "PAUSED"; else if (state == STOPPED) return "STOPPED"; else if (state == CLOSED) return "CLOSED"; else if (state == RESET) return "RESET"; else return "unknown state"; } #define SAMPLE_MAX_16BIT 32768.0f #define SAMPLE_MAX_8BIT 255.0f /* floating point volume routine */ /* volume should be a value between 0.0 and 1.0 */ static void float_volume_effect(sample_t *buf, unsigned long nsamples, float volume, int skip) { if (volume < 0) volume = 0; if (volume > 1.0) volume = 1.0; while (nsamples--) { *buf = (*buf) * volume; buf += skip; } } /* place one channel into a multi-channel stream */ static inline void mux(sample_t *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip) { /* ALERT: signed sign-extension portability !!! */ while (nsamples--) { *dst = *src; dst += dst_skip; src++; } } /* pull one channel out of a multi-channel stream */ static void demux(sample_t *dst, sample_t *src, unsigned long nsamples, unsigned long src_skip) { /* ALERT: signed sign-extension portability !!! */ while (nsamples--) { *dst = *src; dst++; src += src_skip; } } /* convert from 16 bit to floating point */ static inline void sample_move_short_float(sample_t *dst, short *src, unsigned long nsamples) { /* ALERT: signed sign-extension portability !!! */ unsigned long i; for (i = 0; i < nsamples; i++) dst[i] = (sample_t)(src[i]) / SAMPLE_MAX_16BIT; } /* convert from floating point to 16 bit */ static inline void sample_move_float_short(short *dst, sample_t *src, unsigned long nsamples) { /* ALERT: signed sign-extension portability !!! */ unsigned long i; for (i = 0; i < nsamples; i++) dst[i] = (short)((src[i]) * SAMPLE_MAX_16BIT); } /* convert from 8 bit to floating point */ static inline void sample_move_char_float(sample_t *dst, unsigned char *src, unsigned long nsamples) { /* ALERT: signed sign-extension portability !!! */ unsigned long i; for (i = 0; i < nsamples; i++) dst[i] = (sample_t)(src[i]) / SAMPLE_MAX_8BIT; } /* convert from floating point to 8 bit */ static inline void sample_move_float_char(unsigned char *dst, sample_t *src, unsigned long nsamples) { /* ALERT: signed sign-extension portability !!! */ unsigned long i; for (i = 0; i < nsamples; i++) dst[i] = (unsigned char)((src[i]) * SAMPLE_MAX_8BIT); } /* fill dst buffer with nsamples worth of silence */ static void inline sample_silence_float(sample_t *dst, unsigned long nsamples) { /* ALERT: signed sign-extension portability !!! */ while (nsamples--) { *dst = 0; dst++; } } static bool inline ensure_buffer_size(char **buffer, unsigned long *cur_size, unsigned long needed_size) { DEBUG("current size = %lu, needed size = %lu\n", *cur_size, needed_size); if (*cur_size >= needed_size) return TRUE; DEBUG("reallocing\n"); char *tmp = realloc(*buffer, needed_size); if (tmp) { *cur_size = needed_size; *buffer = tmp; return TRUE; } DEBUG("reallocing failed\n"); return FALSE; } /****************************************************************** * JACK_callback * * every time the jack server wants something from us it calls this * function, so we either deliver it some sound to play or deliver it nothing * to play */ static int JACK_callback(nframes_t nframes, void *arg) { jack_driver_t *drv = (jack_driver_t *)arg; unsigned int i; int src_error = 0; TIMER("start\n"); gettimeofday(&drv->previousTime, 0); /* record the current time */ CALLBACK_TRACE("nframes %ld, sizeof(sample_t) == %d\n", (long)nframes, sizeof(sample_t)); if (!drv->client) ERR("client is closed, this is weird...\n"); sample_t *out_buffer[MAX_OUTPUT_PORTS]; /* retrieve the buffers for the output ports */ for (i = 0; i < drv->num_output_channels; i++) out_buffer[i] = (sample_t *)jack_port_get_buffer(drv->output_port[i], nframes); sample_t *in_buffer[MAX_INPUT_PORTS]; /* retrieve the buffers for the input ports */ for (i = 0; i < drv->num_input_channels; i++) in_buffer[i] = (sample_t *)jack_port_get_buffer(drv->input_port[i], nframes); /* handle playing state */ if (drv->state == PLAYING) { /* handle playback data, if any */ if (drv->num_output_channels > 0) { unsigned long jackFramesAvailable = nframes; /* frames we have left to write to jack */ unsigned long numFramesToWrite; /* num frames we are writing */ size_t inputBytesAvailable = jack_ringbuffer_read_space(drv->pPlayPtr); unsigned long inputFramesAvailable; /* frames we have available */ inputFramesAvailable = inputBytesAvailable / drv->bytes_per_jack_output_frame; size_t jackBytesAvailable = jackFramesAvailable * drv->bytes_per_jack_output_frame; long read = 0; CALLBACK_TRACE( "playing... jackFramesAvailable = %ld inputFramesAvailable = %ld\n", jackFramesAvailable, inputFramesAvailable); #if JACK_CLOSE_HACK if (drv->in_use == FALSE) { /* output silence if nothing is being outputted */ for (i = 0; i < drv->num_output_channels; i++) sample_silence_float(out_buffer[i], nframes); return -1; } #endif /* make sure our buffer is large enough for the data we are writing */ /* ie. callback_buffer2_size < (bytes we already wrote + bytes we are * going to write in this loop) */ if (!ensure_buffer_size(&drv->callback_buffer2, &drv->callback_buffer2_size, jackBytesAvailable)) { ERR("allocated %lu bytes, need %lu bytes\n", drv->callback_buffer2_size, jackBytesAvailable); return -1; } /* do sample rate conversion if needed & requested */ if (drv->output_src && drv->output_sample_rate_ratio != 1.0) { long bytes_needed_write = nframes * drv->bytes_per_jack_output_frame; /* make a very good guess at how many raw bytes we'll need to satisfy * jack's request after conversion */ long bytes_needed_read = min(inputBytesAvailable, (double)(bytes_needed_write + drv->output_sample_rate_ratio * drv->bytes_per_jack_output_frame) / drv->output_sample_rate_ratio); DEBUG("guessing that we need %ld bytes in and %ld out for rate " "conversion ratio = %f\n", bytes_needed_read, bytes_needed_write, drv->output_sample_rate_ratio); if (!ensure_buffer_size(&drv->callback_buffer1, &drv->callback_buffer1_size, bytes_needed_read)) { ERR("could not realloc callback_buffer2!\n"); return 1; } if (!ensure_buffer_size(&drv->callback_buffer2, &drv->callback_buffer2_size, bytes_needed_write)) { ERR("could not realloc callback_buffer2!\n"); return 1; } if (jackFramesAvailable && inputBytesAvailable > 0) { /* read in the data, but don't move the read pointer until we know how * much SRC used */ jack_ringbuffer_peek(drv->pPlayPtr, drv->callback_buffer1, bytes_needed_read); SRC_DATA srcdata; srcdata.data_in = (sample_t *)drv->callback_buffer1; srcdata.input_frames = bytes_needed_read / drv->bytes_per_jack_output_frame; srcdata.src_ratio = drv->output_sample_rate_ratio; srcdata.data_out = (sample_t *)drv->callback_buffer2; srcdata.output_frames = nframes; srcdata.end_of_input = 0; // it's a stream, it never ends DEBUG("input_frames = %ld, output_frames = %ld\n", srcdata.input_frames, srcdata.output_frames); /* convert the sample rate */ src_error = src_process(drv->output_src, &srcdata); DEBUG("used = %ld, generated = %ld, error = %d: %s.\n", srcdata.input_frames_used, srcdata.output_frames_gen, src_error, src_strerror(src_error)); if (src_error == 0) { /* now we can move the read pointer */ jack_ringbuffer_read_advance(drv->pPlayPtr, srcdata.input_frames_used * drv->bytes_per_jack_output_frame); /* add on what we wrote */ read = srcdata.input_frames_used * drv->bytes_per_output_frame; jackFramesAvailable -= srcdata.output_frames_gen; /* take away what was used */ } } } else /* no resampling needed or requested */ { /* read as much data from the buffer as is available */ if (jackFramesAvailable && inputBytesAvailable > 0) { /* write as many bytes as we have space remaining, or as much as we * have data to write */ numFramesToWrite = min(jackFramesAvailable, inputFramesAvailable); jack_ringbuffer_read(drv->pPlayPtr, drv->callback_buffer2, jackBytesAvailable); /* add on what we wrote */ read = numFramesToWrite * drv->bytes_per_output_frame; jackFramesAvailable -= numFramesToWrite; /* take away what was written */ } } drv->written_client_bytes += read; drv->played_client_bytes += drv->clientBytesInJack; /* move forward by the previous bytes we wrote since those must have finished by now */ drv->clientBytesInJack = read; /* record the input bytes we wrote to jack */ /* see if we still have jackBytesLeft here, if we do that means that we ran out of wave data to play and had a buffer underrun, fill in the rest of the space with zero bytes so at least there is silence */ if (jackFramesAvailable) { WARN("buffer underrun of %ld frames\n", jackFramesAvailable); for (i = 0; i < drv->num_output_channels; i++) sample_silence_float(out_buffer[i] + (nframes - jackFramesAvailable), jackFramesAvailable); } /* if we aren't converting or we are converting and src_error == 0 then we * should */ /* apply volume and demux */ if (!(drv->output_src && drv->output_sample_rate_ratio != 1.0) || (src_error == 0)) { /* apply volume */ for (i = 0; i < drv->num_output_channels; i++) { if (drv->volumeEffectType == dbAttenuation) { /* assume the volume setting is dB of attenuation, a volume of 0 */ /* is 0dB attenuation */ float volume = powf(10.0, -((float)drv->volume[i]) / 20.0); float_volume_effect((sample_t *)drv->callback_buffer2 + i, (nframes - jackFramesAvailable), volume, drv->num_output_channels); } else { float_volume_effect((sample_t *)drv->callback_buffer2 + i, (nframes - jackFramesAvailable), ((float)drv->volume[i] / 100.0), drv->num_output_channels); } } /* demux the stream: we skip over the number of samples we have output * channels as the channel data */ /* is encoded like chan1,chan2,chan3,chan1,chan2,chan3... */ for (i = 0; i < drv->num_output_channels; i++) { demux(out_buffer[i], (sample_t *)drv->callback_buffer2 + i, (nframes - jackFramesAvailable), drv->num_output_channels); } } } /* handle record data, if any */ if (drv->num_input_channels > 0) { long jack_bytes = nframes * drv->bytes_per_jack_input_frame; /* how many bytes jack is feeding us */ if (!ensure_buffer_size(&drv->callback_buffer1, &drv->callback_buffer1_size, jack_bytes)) { ERR("allocated %lu bytes, need %lu bytes\n", drv->callback_buffer1_size, jack_bytes); return -1; } /* mux the invividual channels into one stream */ for (i = 0; i < drv->num_input_channels; i++) { mux((sample_t *)drv->callback_buffer1 + i, in_buffer[i], nframes, drv->num_input_channels); } /* do sample rate conversion if needed & requested */ if (drv->input_src && drv->input_sample_rate_ratio != 1.0) { /* make a very good guess at how many raw bytes we'll need to read all * the data jack gave us */ long bytes_needed_write = (double)(jack_bytes + drv->input_sample_rate_ratio * drv->bytes_per_jack_input_frame) * drv->input_sample_rate_ratio; DEBUG("guessing that we need %ld bytes in and %ld out for rate " "conversion ratio = %f\n", nframes * drv->bytes_per_jack_input_frame, bytes_needed_write, drv->input_sample_rate_ratio); if (!ensure_buffer_size(&drv->callback_buffer2, &drv->callback_buffer2_size, bytes_needed_write)) { ERR("could not realloc callback_buffer2!\n"); return 1; } SRC_DATA srcdata; srcdata.data_in = (sample_t *)drv->callback_buffer1; srcdata.input_frames = nframes; srcdata.src_ratio = drv->input_sample_rate_ratio; srcdata.data_out = (sample_t *)drv->callback_buffer2; srcdata.output_frames = drv->callback_buffer2_size / drv->bytes_per_jack_input_frame; srcdata.end_of_input = 0; // it's a stream, it never ends DEBUG("input_frames = %ld, output_frames = %ld\n", srcdata.input_frames, srcdata.output_frames); /* convert the sample rate */ src_error = src_process(drv->input_src, &srcdata); DEBUG("used = %ld, generated = %ld, error = %d: %s.\n", srcdata.input_frames_used, srcdata.output_frames_gen, src_error, src_strerror(src_error)); if (src_error == 0) { long write_space = jack_ringbuffer_write_space(drv->pRecPtr); long bytes_used = srcdata.output_frames_gen * drv->bytes_per_jack_input_frame; /* if there isn't enough room, do nothing. */ if (write_space < bytes_used) { /* hey, we warn about underruns, we might as well warn about * overruns as well */ WARN("buffer overrun of %ld bytes\n", jack_bytes - write_space); } else { jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer2, bytes_used); } } } else /* no resampling needed */ { long write_space = jack_ringbuffer_write_space(drv->pRecPtr); /* if there isn't enough room, do nothing. */ if (write_space < jack_bytes) { WARN("buffer overrun of %ld bytes\n", jack_bytes - write_space); } else { jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer1, jack_bytes); } } } } else if (drv->state == PAUSED || drv->state == STOPPED || drv->state == CLOSED || drv->state == RESET) { CALLBACK_TRACE("%s, outputting silence\n", DEBUGSTATE(drv->state)); /* output silence if nothing is being outputted */ for (i = 0; i < drv->num_output_channels; i++) sample_silence_float(out_buffer[i], nframes); /* if we were told to reset then zero out some variables */ /* and transition to STOPPED */ if (drv->state == RESET) { drv->written_client_bytes = 0; drv->played_client_bytes = 0; /* number of the clients bytes that jack has played */ drv->client_bytes = 0; /* bytes that the client wrote to use */ drv->clientBytesInJack = 0; /* number of input bytes in jack(not necessary the number of bytes written to jack) */ drv->position_byte_offset = 0; if (drv->pPlayPtr) jack_ringbuffer_reset(drv->pPlayPtr); if (drv->pRecPtr) jack_ringbuffer_reset(drv->pRecPtr); drv->state = STOPPED; /* transition to STOPPED */ } } CALLBACK_TRACE("done\n"); TIMER("finish\n"); return 0; } /****************************************************************** * JACK_bufsize * * Called whenever the jack server changes the the max number * of frames passed to JACK_callback */ static int JACK_bufsize(nframes_t nframes, void *arg) { jack_driver_t *drv = (jack_driver_t *)arg; TRACE("the maximum buffer size is now %lu frames\n", (long)nframes); drv->jack_buffer_size = nframes; return 0; } /****************************************************************** * JACK_srate */ int JACK_srate(nframes_t nframes, void *arg) { jack_driver_t *drv = (jack_driver_t *)arg; drv->jack_sample_rate = (long)nframes; /* make sure to recalculate the ratios needed for proper sample rate * conversion */ drv->output_sample_rate_ratio = (double)drv->jack_sample_rate / (double)drv->client_sample_rate; if (drv->output_src) src_set_ratio(drv->output_src, drv->output_sample_rate_ratio); drv->input_sample_rate_ratio = (double)drv->client_sample_rate / (double)drv->jack_sample_rate; if (drv->input_src) src_set_ratio(drv->input_src, drv->input_sample_rate_ratio); TRACE("the sample rate is now %lu/sec\n", (long)nframes); return 0; } /****************************************************************** * JACK_shutdown * * if this is called then jack shut down... handle this appropriately */ void JACK_shutdown(void *arg) { jack_driver_t *drv = (jack_driver_t *)arg; int n = strlen(drv->client_name) + 1; char *client_name = malloc(n); if (client_name == NULL) { ERR("Couldn't allocate %d bytes\n", n); return; } strcpy(client_name, drv->client_name); n = strlen(drv->server_name) + 1; char *server_name = malloc(n); if (server_name == NULL) { ERR("Couldn't allocate %d bytes\n", n); return; } strcpy(server_name, drv->server_name); TRACE("\n"); getDriver(drv); drv->client = 0; /* reset client */ drv->jackd_died = TRUE; TRACE("jack shutdown, setting client to 0 and jackd_died to true, closing " "device\n"); #if JACK_CLOSE_HACK JACK_CloseDevice(drv, TRUE); #else JACK_CloseDevice(drv); #endif TRACE("trying to reconnect right now\n"); drv->client_name = client_name; drv->server_name = server_name; /* lets see if we can't reestablish the connection */ if (JACK_OpenDevice(drv) != ERR_SUCCESS) { ERR("unable to reconnect with jack\n"); free(client_name); free(server_name); } releaseDriver(drv); } /****************************************************************** * JACK_Error * * Callback for jack errors */ static void JACK_Error(const char *desc) { ERR("%s\n", desc); } /****************************************************************** * JACK_OpenDevice * * RETURNS: ERR_SUCCESS upon success */ static int JACK_OpenDevice(jack_driver_t *drv) { unsigned int i; int failed = 0; int options = JackNoStartServer | JackUseExactName; TRACE("creating jack client and setting up callbacks\n"); #if JACK_CLOSE_HACK /* see if this device is already open */ if (drv->client) { /* if this device is already in use then it is bad for us to be in here */ if (drv->in_use) return ERR_OPENING_JACK; TRACE("using existing client\n"); drv->in_use = TRUE; return ERR_SUCCESS; } #endif /* set up an error handler */ jack_set_error_function(JACK_Error); /* try to become a client of the JACK server */ TRACE("client name '%s'\n", drv->client_name); TRACE("server name '%s'\n", drv->server_name); if (strcmp(drv->server_name, "") != 0) options = JackServerName | options; if ((drv->client = jack_client_open(drv->client_name, options, NULL, drv->server_name)) == 0) { /* try once more */ TRACE("trying once more to jack_client_open"); if ((drv->client = jack_client_open(drv->client_name, options, NULL, drv->server_name)) == 0) { ERR("jack server not running?\n"); return ERR_OPENING_JACK; } } TRACE("setting up jack callbacks\n"); /* JACK server to call `JACK_callback()' whenever there is work to be done. */ jack_set_process_callback(drv->client, JACK_callback, drv); /* setup a buffer size callback */ jack_set_buffer_size_callback(drv->client, JACK_bufsize, drv); /* tell the JACK server to call `srate()' whenever the sample rate of the system changes. */ jack_set_sample_rate_callback(drv->client, JACK_srate, drv); /* tell the JACK server to call `jack_shutdown()' if it ever shuts down, either entirely, or if it just decides to stop calling us. */ jack_on_shutdown(drv->client, JACK_shutdown, drv); /* display the current sample rate. once the client is activated (see below), you should rely on your own sample rate callback (see above) for this value. */ drv->jack_sample_rate = jack_get_sample_rate(drv->client); drv->output_sample_rate_ratio = (double)drv->jack_sample_rate / (double)drv->client_sample_rate; drv->input_sample_rate_ratio = (double)drv->client_sample_rate / (double)drv->jack_sample_rate; TRACE("client sample rate: %lu, jack sample rate: %lu, output ratio = %f, " "input ratio = %f\n", drv->client_sample_rate, drv->jack_sample_rate, drv->output_sample_rate_ratio, drv->input_sample_rate_ratio); drv->jack_buffer_size = jack_get_buffer_size(drv->client); /* create the output ports */ TRACE("creating output ports\n"); for (i = 0; i < drv->num_output_channels; i++) { char portname[32]; sprintf(portname, "out_%d", i); TRACE("port %d is named '%s'\n", i, portname); /* NOTE: Yes, this is supposed to be JackPortIsOutput since this is an * output */ /* port FROM ocaml-bjack */ drv->output_port[i] = jack_port_register( drv->client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } /* create the input ports */ TRACE("creating input ports\n"); for (i = 0; i < drv->num_input_channels; i++) { char portname[32]; sprintf(portname, "in_%d", i); TRACE("port %d is named '%s'\n", i, portname); /* NOTE: Yes, this is supposed to be JackPortIsInput since this is an input */ /* port TO ocaml-bjack */ drv->input_port[i] = jack_port_register( drv->client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); } #if JACK_CLOSE_HACK drv->in_use = TRUE; #endif /* tell the JACK server that we are ready to roll */ TRACE("calling jack_activate()\n"); if (jack_activate(drv->client)) { ERR("cannot activate client\n"); return ERR_OPENING_JACK; } /* if( drv->num_input_channels > 0 ) */ /* if something failed we need to shut the client down and return 0 */ if (failed) { TRACE("failed, closing and returning error\n"); #if JACK_CLOSE_HACK JACK_CloseDevice(drv, TRUE); #else JACK_CloseDevice(drv); #endif return ERR_OPENING_JACK; } TRACE("success\n"); drv->jackd_died = FALSE; /* clear out this flag so we don't keep attempting to restart things */ drv->state = PLAYING; /* clients seem to behave much better with this on from the start, especially when recording */ return ERR_SUCCESS; /* return success */ } /****************************************************************** * JACK_CloseDevice * * Close the connection to the server cleanly. * If close_client is TRUE we close the client for this device instead of * just marking the device as in_use(JACK_CLOSE_HACK only) */ #if JACK_CLOSE_HACK static void JACK_CloseDevice(jack_driver_t *drv, bool close_client) #else static void JACK_CloseDevice(jack_driver_t *drv) #endif { #if JACK_CLOSE_HACK if (close_client) { #endif TRACE("closing the jack client thread\n"); if (drv->client) { TRACE("after jack_deactivate()\n"); int errorCode = jack_client_close(drv->client); if (errorCode) ERR("jack_client_close() failed returning an error code of %d\n", errorCode); } /* reset client */ drv->client = 0; JACK_CleanupDriver(drv); drv->state = RESET; #if JACK_CLOSE_HACK } else { TRACE("setting in_use to FALSE\n"); drv->in_use = FALSE; if (!drv->client) { TRACE("critical error, closing a device that has no client\n"); } } #endif } /**************************************/ /* External interface functions below */ /**************************************/ /* Clear out any buffered data, stop playing, zero out some variables */ void JACK_Reset(jack_driver_t *drv) { getDriver(drv); /* NOTE: we use the RESET state so we don't need to worry about clearing out */ /* variables that the callback modifies while the callback is running */ /* we set the state to RESET and the callback clears the variables out for us */ drv->state = RESET; /* tell the callback that we are to reset, the callback will transition this to STOPPED */ releaseDriver(drv); } /* * open the audio device for writing to * * if client is non-zero and in_use is FALSE then just set in_use to TRUE * * return value is zero upon success, non-zero upon failure */ int JACK_Open(jack_driver_t *drv, unsigned int bits_per_channel, unsigned long *rate, char *client_name, char *server_name, unsigned int input_channels, unsigned int output_channels, unsigned long jack_port_flags, int ringbuffer_size) { int retval; int n; if (input_channels < 1 && output_channels < 1) { ERR("no input OR output channels, nothing to do\n"); return ERR_OPENING_JACK; } switch (bits_per_channel) { case 8: case 16: break; default: ERR("invalid bits_per_channel\n"); return ERR_OPENING_JACK; } if (drv->allocated == TRUE) { ERR("Device already opened\n"); return ERR_OPENING_JACK; } getDriver(drv); TRACE("bits_per_channel=%d rate=%ld, input_channels=%d, output_channels=%d\n", bits_per_channel, *rate, input_channels, output_channels); if (output_channels > MAX_OUTPUT_PORTS) { ERR("output_channels == %d, MAX_OUTPUT_PORTS == %d\n", output_channels, MAX_OUTPUT_PORTS); releaseDriver(drv); return ERR_TOO_MANY_OUTPUT_CHANNELS; } if (input_channels > MAX_INPUT_PORTS) { ERR("input_channels == %d, MAX_INPUT_PORTS == %d\n", input_channels, MAX_INPUT_PORTS); releaseDriver(drv); return ERR_TOO_MANY_INPUT_CHANNELS; } drv->jack_output_port_flags = jack_port_flags | JackPortIsInput; /* port must be input(ie we can put data into it), so mask this in */ drv->jack_input_port_flags = jack_port_flags | JackPortIsOutput; /* port must be output(ie we can get data from it), so mask this in */ /* initialize some variables */ drv->in_use = FALSE; drv->state = RESET; /* drv->jack_sample_rate is set by JACK_OpenDevice() */ drv->client_sample_rate = *rate; drv->bits_per_channel = bits_per_channel; drv->num_input_channels = input_channels; drv->num_output_channels = output_channels; n = strlen(client_name) + 1; if (n > jack_client_name_size()) { ERR("client_name length (%d) is greater than maximal possible length: %d\n", n, jack_client_name_size()); return ERR_OPENING_JACK; } drv->client_name = malloc(n); if (drv->client_name == NULL) { ERR("Couldn't allocate %d bytes\n", n); return ERR_OPENING_JACK; } strcpy(drv->client_name, client_name); n = strlen(server_name) + 1; drv->server_name = malloc(n); if (drv->server_name == NULL) { ERR("Couldn't allocate %d bytes\n", n); return ERR_OPENING_JACK; } strcpy(drv->server_name, server_name); drv->bytes_per_input_frame = (drv->bits_per_channel * drv->num_input_channels) / 8; drv->bytes_per_output_frame = (drv->bits_per_channel * drv->num_output_channels) / 8; drv->bytes_per_jack_output_frame = sizeof(sample_t) * drv->num_output_channels; drv->bytes_per_jack_input_frame = sizeof(sample_t) * drv->num_input_channels; if (drv->num_output_channels > 0) { drv->pPlayPtr = jack_ringbuffer_create(drv->num_output_channels * drv->bytes_per_jack_output_frame * ringbuffer_size); } if (drv->num_input_channels > 0) { drv->pRecPtr = jack_ringbuffer_create(drv->num_input_channels * drv->bytes_per_jack_input_frame * ringbuffer_size); } DEBUG("bytes_per_output_frame == %ld\n", drv->bytes_per_output_frame); DEBUG("bytes_per_input_frame == %ld\n", drv->bytes_per_input_frame); DEBUG("bytes_per_jack_output_frame == %ld\n", drv->bytes_per_jack_output_frame); DEBUG("bytes_per_jack_input_frame == %ld\n", drv->bytes_per_jack_input_frame); /* go and open up the device */ retval = JACK_OpenDevice(drv); if (retval != ERR_SUCCESS) { TRACE("error opening jack device\n"); releaseDriver(drv); return retval; } else { TRACE("succeeded opening jack device\n"); } int error; if (drv->num_output_channels > 0) { drv->output_src = src_new(preferred_src_converter, drv->num_output_channels, &error); if (error != 0) { src_delete(drv->output_src); drv->output_src = 0; ERR("Could not created SRC object for output stream %d: %s\n", error, src_strerror(error)); } } if (drv->num_input_channels > 0) { drv->input_src = src_new(preferred_src_converter, drv->num_input_channels, &error); if (error != 0) { src_delete(drv->input_src); drv->input_src = 0; ERR("Could not created SRC object for input stream %d: %s\n", error, src_strerror(error)); } } drv->allocated = TRUE; /* record that we opened this device */ DEBUG("sizeof(sample_t) == %d\n", sizeof(sample_t)); int periodSize = jack_get_buffer_size(drv->client); int periods = 0; jack_latency_range_t range; /* FIXME: maybe we should keep different latency values for input vs output? */ if (drv->num_output_channels > 0) { jack_port_get_latency_range(drv->output_port[0], JackPlaybackLatency, &range); periods = range.max / periodSize; drv->latencyMS = periodSize * periods * 1000 / (drv->jack_sample_rate * (drv->bits_per_channel / 8 * drv->num_output_channels)); } else if (drv->num_input_channels > 0) { jack_port_get_latency_range(drv->input_port[0], JackCaptureLatency, &range); periods = range.max / periodSize; drv->latencyMS = periodSize * periods * 1000 / (drv->jack_sample_rate * (drv->bits_per_channel / 8 * drv->num_input_channels)); } TRACE("drv->latencyMS == %ldms\n", drv->latencyMS); releaseDriver(drv); return ERR_SUCCESS; /* success */ } /* Close the jack device */ // FIXME: add error handling in here at some point... /* NOTE: return 0 for success, non-zero for failure */ int JACK_Close(jack_driver_t *drv) { getDriver(drv); TRACE("client %s)\n", drv->client_name); #if JACK_CLOSE_HACK JACK_CloseDevice(drv, TRUE); #else JACK_CloseDevice(drv); #endif drv->state = RESET; /* free buffer memory */ drv->callback_buffer1_size = 0; if (drv->callback_buffer1) free(drv->callback_buffer1); drv->callback_buffer1 = 0; drv->callback_buffer2_size = 0; if (drv->callback_buffer2) free(drv->callback_buffer2); drv->callback_buffer2 = 0; drv->rw_buffer1_size = 0; if (drv->rw_buffer1) free(drv->rw_buffer1); drv->rw_buffer1 = 0; if (drv->pPlayPtr) jack_ringbuffer_free(drv->pPlayPtr); drv->pPlayPtr = 0; if (drv->pRecPtr) jack_ringbuffer_free(drv->pRecPtr); drv->pRecPtr = 0; /* free the SRC objects */ if (drv->output_src) src_delete(drv->output_src); drv->output_src = 0; if (drv->input_src) src_delete(drv->input_src); drv->input_src = 0; drv->allocated = FALSE; /* release this device */ if (drv->client_name) free(drv->client_name); drv->client_name = NULL; if (drv->server_name) free(drv->server_name); drv->server_name = NULL; releaseDriver(drv); return 0; } /* If we haven't already taken in the max allowed data then create a wave header */ /* to package the audio data and attach the wave header to the end of the */ /* linked list of wave headers */ /* These wave headers will be peeled off as they are played by the callback * routine */ /* Return value is the number of bytes written */ /* NOTE: this function takes the length of data to be written bytes */ long JACK_Write(jack_driver_t *drv, unsigned char *data, unsigned long bytes) { getDriver(drv); if (drv->in_use != TRUE) { ERR("Device not connected to jack!\n"); return -1; } long frames_free, frames; TIMER("start\n"); TRACE("client %d, bytes == %ld\n", drv->client_name, bytes); /* check and see that we have enough space for this audio */ frames_free = jack_ringbuffer_write_space(drv->pPlayPtr) / drv->bytes_per_jack_output_frame; frames = bytes / drv->bytes_per_output_frame; TRACE("frames free == %ld, bytes = %lu\n", frames_free, bytes); TRACE("state = '%s'\n", DEBUGSTATE(drv->state)); /* if we are currently STOPPED we should start playing now... do this before the check for bytes == 0 since some clients like to write 0 bytes the first time out */ if (drv->state == STOPPED) { TRACE("currently STOPPED, transitioning to PLAYING\n"); drv->state = PLAYING; } /* handle the case where the user calls this routine with 0 bytes */ if (bytes == 0 || frames_free < 1) { TRACE("no room left\n"); TIMER("finish (nothing to do, buffer is full)\n"); releaseDriver(drv); return 0; /* indicate that we couldn't write any bytes */ } frames = min(frames, frames_free); long jack_bytes = frames * drv->bytes_per_jack_output_frame; if (!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes)) { ERR("couldn't allocate enough space for the buffer\n"); releaseDriver(drv); return 0; } /* adjust bytes to be how many client bytes we're actually writing */ bytes = frames * drv->bytes_per_output_frame; /* convert from client samples to jack samples we have to tell it how many samples there are, which is frames * channels */ switch (drv->bits_per_channel) { case 8: sample_move_char_float((sample_t *)drv->rw_buffer1, (unsigned char *)data, frames * drv->num_output_channels); break; case 16: sample_move_short_float((sample_t *)drv->rw_buffer1, (short *)data, frames * drv->num_output_channels); break; } DEBUG("ringbuffer read space = %d, write space = %d\n", jack_ringbuffer_read_space(drv->pPlayPtr), jack_ringbuffer_write_space(drv->pPlayPtr)); jack_ringbuffer_write(drv->pPlayPtr, drv->rw_buffer1, jack_bytes); DEBUG("wrote %lu bytes, %lu jack_bytes\n", bytes, jack_bytes); DEBUG("ringbuffer read space = %d, write space = %d\n", jack_ringbuffer_read_space(drv->pPlayPtr), jack_ringbuffer_write_space(drv->pPlayPtr)); drv->client_bytes += bytes; /* update client_bytes */ TIMER("finish\n"); DEBUG("returning bytes written of %ld\n", bytes); releaseDriver(drv); return bytes; /* return the number of bytes we wrote out */ } long JACK_Read(jack_driver_t *drv, unsigned char *data, unsigned long bytes) { getDriver(drv); if (drv->in_use != TRUE) { ERR("Device not connected to jack!\n"); return -1; } long frames_available, frames; TIMER("start\n"); TRACE("client %s, bytes == %ld\n", drv->client_name, bytes); /* find out if there's any data to read */ frames_available = jack_ringbuffer_read_space(drv->pRecPtr) / drv->bytes_per_jack_input_frame; frames = bytes / drv->bytes_per_input_frame; DEBUG("frames available = %ld, bytes = %lu\n", frames_available, bytes); TRACE("state = '%s'\n", DEBUGSTATE(drv->state)); /* if we are currently STOPPED we should start recording now... */ if (drv->state == STOPPED) { TRACE("currently STOPPED, transitioning to PLAYING\n"); drv->state = PLAYING; } /* handle the case where the user calls this routine with 0 bytes */ if (bytes == 0 || frames_available < 1) { TRACE("no bytes in buffer\n"); TIMER("finish (nothing to do)\n"); releaseDriver(drv); return 0; } frames = min(frames, frames_available); long jack_bytes = frames * drv->bytes_per_jack_input_frame; if (!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes)) { ERR("couldn't allocate enough space for the buffer\n"); releaseDriver(drv); return 0; } DEBUG("ringbuffer read space = %d, write space = %d\n", jack_ringbuffer_read_space(drv->pRecPtr), jack_ringbuffer_write_space(drv->pRecPtr)); jack_ringbuffer_read(drv->pRecPtr, drv->rw_buffer1, frames * drv->bytes_per_jack_input_frame); DEBUG("ringbuffer read space = %d, write space = %d\n", jack_ringbuffer_read_space(drv->pRecPtr), jack_ringbuffer_write_space(drv->pRecPtr)); int i; for (i = 0; i < drv->num_output_channels; i++) { /* apply volume to the floating value */ if (drv->volumeEffectType == dbAttenuation) { /* assume the volume setting is dB of attenuation, a volume of 0 */ /* is 0dB attenuation */ float volume = powf(10.0, -((float)drv->volume[i]) / 20.0); float_volume_effect((sample_t *)drv->rw_buffer1 + i, frames, volume, drv->num_output_channels); } else { float_volume_effect((sample_t *)drv->rw_buffer1 + i, frames, ((float)drv->volume[i] / 100.0), drv->num_output_channels); } } /* convert from jack samples to client samples we have to tell it how many samples there are, which is frames * channels */ switch (drv->bits_per_channel) { case 8: sample_move_float_char((unsigned char *)data, (sample_t *)drv->rw_buffer1, frames * drv->num_input_channels); break; case 16: sample_move_float_short((short *)data, (sample_t *)drv->rw_buffer1, frames * drv->num_input_channels); break; } TIMER("finish\n"); long read_bytes = frames * drv->bytes_per_input_frame; DEBUG("returning bytes read of %ld\n", bytes); releaseDriver(drv); return read_bytes; } /* return ERR_SUCCESS for success */ int JACK_SetVolumeForChannel(jack_driver_t *drv, unsigned int channel, unsigned int volume) { getDriver(drv); /* TODO?: maybe we should have different volume levels for input & output */ /* ensure that we have the channel we are setting volume for */ if (channel > (drv->num_output_channels - 1)) { releaseDriver(drv); return 1; } if (volume > 100) volume = 100; /* check for values in excess of max */ drv->volume[channel] = volume; releaseDriver(drv); return ERR_SUCCESS; } /* Set the volume */ /* return 0 for success */ /* NOTE: we check for invalid volume values */ int JACK_SetAllVolume(jack_driver_t *drv, unsigned int volume) { unsigned int i; TRACE("client %s, setting volume of %d\n", drv->client_name, volume); for (i = 0; i < drv->num_output_channels; i++) { if (JACK_SetVolumeForChannel(drv, i, volume) != ERR_SUCCESS) { return 1; } } return ERR_SUCCESS; } /* Return the current volume in the inputted pointers */ /* NOTE: we check for null pointers being passed in just in case */ void JACK_GetVolumeForChannel(jack_driver_t *drv, unsigned int channel, unsigned int *volume) { /* ensure that we have the channel we are getting volume for */ if (channel > (drv->num_output_channels - 1)) { ERR("asking for channel index %d but we only have %ld channels\n", channel, drv->num_output_channels); return; } if (volume) *volume = drv->volume[channel]; #if VERBOSE_OUTPUT if (volume) { TRACE("client %s, returning volume of %d for channel %d\n", drv->client_name, *volume, channel); } else { TRACE("volume is null, can't dereference it\n"); } #endif } /* linear means 0 volume is silence, 100 is full volume */ /* dbAttenuation means 0 volume is 0dB attenuation */ /* ocaml-bjack defaults to linear */ enum JACK_VOLUME_TYPE JACK_SetVolumeEffectType(jack_driver_t *drv, enum JACK_VOLUME_TYPE type) { enum JACK_VOLUME_TYPE retval; getDriver(drv); TRACE("setting type of '%s'\n", (type == dbAttenuation ? "dbAttenuation" : "linear")); retval = drv->volumeEffectType; drv->volumeEffectType = type; releaseDriver(drv); return retval; } /* Controls the state of the playback(playing, paused, ...) */ int JACK_SetState(jack_driver_t *drv, enum status_enum state) { getDriver(drv); switch (state) { case PAUSED: drv->state = PAUSED; break; case PLAYING: drv->state = PLAYING; break; case STOPPED: drv->state = STOPPED; break; default: TRACE("unknown state of %d\n", state); } TRACE("%s\n", DEBUGSTATE(drv->state)); releaseDriver(drv); return 0; } /* Retrieve the current state of the device */ enum status_enum JACK_GetState(jack_driver_t *drv) { enum status_enum return_val; return_val = drv->state; TRACE("client %s, returning current state of %s\n", drv->client_name, DEBUGSTATE(return_val)); return return_val; } /* Retrieve the number of bytes per second we are outputting */ unsigned long JACK_GetOutputBytesPerSecond(jack_driver_t *drv) { return drv->bytes_per_output_frame * drv->client_sample_rate; } /* Retrieve the number of input bytes(from jack) per second we are outputting to the user of ocaml-bjack */ unsigned long JACK_GetInputBytesPerSecond(jack_driver_t *drv) { return drv->bytes_per_input_frame * drv->client_sample_rate; } /* An approximation of how many bytes we have to send out to jack */ /* that is computed as if we were sending jack a continuous stream of */ /* bytes rather than chunks during discrete callbacks. */ /* Return the number of bytes we have buffered thus far for output */ /* NOTE: convert from output bytes to input bytes in here */ unsigned long JACK_GetBytesStored(jack_driver_t *drv) { getDriver(drv); if (drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) { releaseDriver(drv); return 0; } /* leave at least one frame in the buffer at all times to prevent underruns */ long return_val = jack_ringbuffer_read_space(drv->pPlayPtr) - drv->jack_buffer_size; if (return_val <= 0) { return_val = 0; } else { /* adjust from jack bytes to client bytes */ return_val = return_val / drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame; } releaseDriver(drv); return return_val; } /* Return the number of bytes we can write to the device */ unsigned long JACK_GetBytesFreeSpace(jack_driver_t *drv) { getDriver(drv); if (drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) { releaseDriver(drv); return 0; } /* leave at least one frame in the buffer at all times to prevent underruns */ long return_val = jack_ringbuffer_write_space(drv->pPlayPtr) - drv->jack_buffer_size; if (return_val <= 0) { return_val = 0; } else { /* adjust from jack bytes to client bytes */ return_val = return_val / drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame; } releaseDriver(drv); if (return_val < 0) return_val = 0; return return_val; } /* bytes of space used in the input buffer */ unsigned long JACK_GetBytesUsedSpace(jack_driver_t *drv) { long return_val; if (drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0) { return_val = 0; } else { /* adjust from jack bytes to client bytes */ return_val = jack_ringbuffer_read_space(drv->pRecPtr) / drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame; } if (return_val < 0) return_val = 0; return return_val; } /* Get the current position of the driver, either in bytes or */ /* in milliseconds */ /* NOTE: this is position relative to input bytes, output bytes may differ greatly due to input vs. output channel count */ long JACK_GetPosition(jack_driver_t *drv, enum pos_enum position, int type) { long return_val = 0; struct timeval now; long elapsedMS; double sec2msFactor = 1000; #if TRACE_ENABLE char *type_str = "UNKNOWN type"; #endif /* if we are reset we should return a position of 0 */ if (drv->state == RESET) { TRACE("we are currently RESET, returning 0\n"); return 0; } if (type == WRITTEN) { #if TRACE_ENABLE type_str = "WRITTEN"; #endif return_val = drv->client_bytes; } else if (type == WRITTEN_TO_JACK) { #if TRACE_ENABLE type_str = "WRITTEN_TO_JACK"; #endif return_val = drv->written_client_bytes; } else if (type == PLAYED) /* account for the elapsed time for the played_bytes */ { #if TRACE_ENABLE type_str = "PLAYED"; #endif return_val = drv->played_client_bytes; gettimeofday(&now, 0); elapsedMS = TimeValDifference( &drv->previousTime, &now); /* find the elapsed milliseconds since last JACK_Callback() */ TRACE("elapsedMS since last callback is '%ld'\n", elapsedMS); /* account for the bytes played since the last JACK_Callback() */ /* NOTE: [Xms * (Bytes/Sec)] * (1 sec/1,000ms) */ /* NOTE: don't do any compensation if no data has been sent to jack since * the last callback */ /* as this would result a bogus computed result */ if (drv->clientBytesInJack != 0) { return_val += (long)((double)elapsedMS * ((double)JACK_GetOutputBytesPerSecond(drv) / sec2msFactor)); } else { TRACE("clientBytesInJack == 0\n"); } } /* add on the offset */ return_val += drv->position_byte_offset; /* convert byte position to milliseconds value if necessary */ if (position == MILLISECONDS) { if (JACK_GetOutputBytesPerSecond(drv) != 0) { return_val = (long)(((double)return_val / (double)JACK_GetOutputBytesPerSecond(drv)) * (double)sec2msFactor); } else { return_val = 0; } } #if TRACE_ENABLE TRACE("drv->client %s, type(%s), return_val = %ld\n", drv->client_name, type_str, return_val); #endif return return_val; } // Set position always applies to written bytes // NOTE: we must apply this instantly because if we pass this as a message // to the callback we risk the user sending us audio data in the mean time // and there is no need to send this as a message, we don't modify any // internal variables void JACK_SetPosition(jack_driver_t *drv, enum pos_enum position, long value) { getDriver(drv); double sec2msFactor = 1000; #if TRACE_ENABLE long input_value = value; #endif /* convert the incoming value from milliseconds into bytes */ if (position == MILLISECONDS) { value = (long)(((double)value * (double)JACK_GetOutputBytesPerSecond(drv)) / sec2msFactor); } /* ensure that if the user asks for the position */ /* they will at this instant get the correct position */ drv->position_byte_offset = value - drv->client_bytes; TRACE("client %s input_value of %ld %s, new value of %ld, setting " "position_byte_offset to %ld\n", drv->client_name, input_value, (position == MILLISECONDS) ? "ms" : "bytes", value, drv->position_byte_offset); releaseDriver(drv); } /* Return the number of bytes per frame, or (output_channels * bits_per_channel) * / 8 */ unsigned long JACK_GetBytesPerOutputFrame(jack_driver_t *drv) { long return_val = drv->bytes_per_output_frame; TRACE("client %s, return_val = %ld\n", drv->client_name, return_val); return return_val; } /* Return the number of bytes per frame, or (input_channels * bits_per_channel) * / 8 */ unsigned long JACK_GetBytesPerInputFrame(jack_driver_t *drv) { return drv->bytes_per_input_frame; } /* Return the number of output bytes we buffer max */ long JACK_GetMaxOutputBufferedBytes(jack_driver_t *drv) { long return_val; if (drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) return_val = 0; /* adjust from jack bytes to client bytes */ return_val = (jack_ringbuffer_read_space(drv->pPlayPtr) + jack_ringbuffer_write_space(drv->pPlayPtr)) / drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame; TRACE("return_val = %ld\n", return_val); return return_val; } /* Return the number of input bytes we buffer max */ long JACK_GetMaxInputBufferedBytes(jack_driver_t *drv) { long return_val; if (drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0) return_val = 0; /* adjust from jack bytes to client bytes */ return_val = (jack_ringbuffer_read_space(drv->pRecPtr) + jack_ringbuffer_write_space(drv->pRecPtr)) / drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame; TRACE("return_val = %ld\n", return_val); return return_val; } /* Get the number of output channels */ int JACK_GetNumOutputChannels(jack_driver_t *drv) { int return_val = drv->num_output_channels; TRACE("getting num_output_channels of %d\n", return_val); return return_val; } /* Get the number of input channels */ int JACK_GetNumInputChannels(jack_driver_t *drv) { int return_val = drv->num_input_channels; TRACE("getting num_input_channels of %d\n", return_val); return return_val; } /* Get the number of samples per second, the sample rate */ long JACK_GetSampleRate(jack_driver_t *drv) { int return_val = drv->client_sample_rate; TRACE("getting sample_rate of %d\n", return_val); return return_val; } void JACK_CleanupDriver(jack_driver_t *drv) { TRACE("\n"); /* things that need to be reset both in JACK_Init & JACK_CloseDevice */ drv->client = 0; drv->in_use = FALSE; drv->state = CLOSED; drv->jack_sample_rate = 0; drv->output_sample_rate_ratio = 1.0; drv->input_sample_rate_ratio = 1.0; drv->jackd_died = FALSE; gettimeofday(&drv->previousTime, 0); /* record the current time */ gettimeofday(&drv->last_reconnect_attempt, 0); } /* Initialize the jack porting library to a clean state */ jack_driver_t *JACK_CreateDriver(void) { jack_driver_t *drv = malloc(sizeof(jack_driver_t)); if (drv == NULL) return NULL; memset(drv, 0, sizeof(jack_driver_t)); int y; pthread_mutex_init(&drv->mutex, NULL); drv->volumeEffectType = linear; for (y = 0; y < MAX_OUTPUT_PORTS; y++) /* make all volume 100% as a default */ drv->volume[y] = 100; JACK_CleanupDriver(drv); drv->state = RESET; drv->client_name = NULL; drv->server_name = NULL; return drv; } /* Get the latency, in frames, of jack */ long JACK_GetJackOutputLatency(jack_driver_t *drv) { long return_val = 0; jack_latency_range_t range; if (drv->client && drv->num_output_channels) { jack_port_get_latency_range(drv->output_port[0], JackPlaybackLatency, &range); return_val = range.max; } TRACE("got latency of %ld frames\n", return_val); return return_val; } /* Get the latency, in frames, of jack */ long JACK_GetJackInputLatency(jack_driver_t *drv) { long return_val = 0; jack_latency_range_t range; if (drv->client && drv->num_input_channels) { jack_port_get_latency_range(drv->input_port[0], JackCaptureLatency, &range); return_val = range.max; } TRACE("got latency of %ld frames\n", return_val); return return_val; } /* bytes that jack requests during each callback */ unsigned long JACK_GetJackBufferedBytes(jack_driver_t *drv) { long return_val; if (drv->bytes_per_jack_output_frame == 0) { return_val = 0; } else { /* adjust from jack bytes to client bytes */ return_val = drv->jack_buffer_size / drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame * drv->num_output_channels; } return return_val; } /* FIXME: put the filename of the resample library header file with the decoders * in here */ /* consider mapping them in the ocaml-bjack.h header file since its useless to * the user unless */ /* they can figure out wtf the settings on */ void JACK_SetSampleRateConversionFunction(int converter) { preferred_src_converter = converter; } ocaml-bjack-0.1.6/src/jack_wrapper.h000066400000000000000000000266501377412232600173110ustar00rootroot00000000000000/* * Copyright 2003-2006 Chris Morgan * Copyright 2007-2008 Romain Beauxis * * This file is part of ocaml-bjack. It uses code from the bio2jack project. * * ocaml-bjack is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ocaml-bjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ocaml-taglib; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * As a special exception to the GNU Library General Public License, you may * link, statically or dynamically, a "work that uses the Library" with a * publicly distributed version of the Library to produce an executable file * containing portions of the Library, and distribute that executable file under * terms of your choice, without any of the additional requirements listed in * clause 6 of the GNU Library General Public License. By "a publicly * distributed version of the Library", we mean either the unmodified Library as * distributed by The Savonet Team, or a modified version of the Library that is * distributed under the conditions defined in clause 3 of the GNU Library * General Public License. This exception does not however invalidate any other * reasons why the executable file might be covered by the GNU Library General * Public License. * */ #ifndef _H_JACK_OUT_H #define _H_JACK_OUT_H #include #include #include #include #ifdef __cplusplus extern "C" { #else #define bool long #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #define ERR_SUCCESS 0 #define ERR_OPENING_JACK 1 #define ERR_BYTES_PER_OUTPUT_FRAME_INVALID 3 #define ERR_BYTES_PER_INPUT_FRAME_INVALID 4 #define ERR_TOO_MANY_OUTPUT_CHANNELS 5 #define ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH 6 #define ERR_PORT_NOT_FOUND 7 #define ERR_TOO_MANY_INPUT_CHANNELS 8 #define ERR_PORT_NAME_INPUT_CHANNEL_MISMATCH 9 enum status_enum { PLAYING, PAUSED, STOPPED, CLOSED, RESET }; enum pos_enum { BYTES, MILLISECONDS }; #define MAX_OUTPUT_PORTS 10 #define MAX_INPUT_PORTS 10 /* linear means 0 volume is silence, 100 is full volume */ /* dbAttenuation means 0 volume is 0dB attenuation */ /* ocaml-bjack defaults to linear */ /* Note: volume controls only effect output channels for now */ enum JACK_VOLUME_TYPE { linear, dbAttenuation }; typedef struct jack_driver_s { bool allocated; /* whether or not this device has been allocated */ long jack_sample_rate; /* jack samples(frames) per second */ long client_sample_rate; /* client samples(frames) per second */ double output_sample_rate_ratio; /* ratio between jack's output rate & ours */ double input_sample_rate_ratio; /* ratio between our input rate & jack's */ unsigned long num_input_channels; /* number of input channels(1 is mono, 2 stereo etc..) */ unsigned long num_output_channels; /* number of output channels(1 is mono, 2 stereo etc..) */ unsigned long bits_per_channel; /* number of bits per channel (only 8 & 16 are currently supported) */ unsigned long bytes_per_output_frame; /* (num_output_channels * bits_per_channel) / 8 */ unsigned long bytes_per_input_frame; /* (num_input_channels * bits_per_channel) / 8 */ unsigned long bytes_per_jack_output_frame; /* (num_output_channels * bits_per_channel) / 8 */ unsigned long bytes_per_jack_input_frame; /* (num_input_channels * bits_per_channel) / 8 */ unsigned long latencyMS; /* latency in ms between writing and actual audio output of the written data */ long clientBytesInJack; /* number of INPUT bytes(from the client of ocaml-bjack) we wrote to jack(not necessary the number of bytes we wrote to jack) */ long jack_buffer_size; /* size of the buffer jack will pass in to the process callback */ unsigned long callback_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */ char *callback_buffer1; unsigned long callback_buffer2_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */ char *callback_buffer2; unsigned long rw_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_(Read|Write) */ char *rw_buffer1; struct timeval previousTime; /* time of last JACK_Callback() write to jack, allows for MS accurate bytes played */ unsigned long written_client_bytes; /* input bytes we wrote to jack, not necessarily actual bytes we wrote to jack due to channel and other conversion */ unsigned long played_client_bytes; /* input bytes that jack has played */ unsigned long client_bytes; /* total bytes written by the client of ocaml-bjack via JACK_Write() */ jack_port_t *output_port[MAX_OUTPUT_PORTS]; /* output ports */ jack_port_t *input_port[MAX_OUTPUT_PORTS]; /* input ports */ jack_client_t *client; /* pointer to jack client */ char *client_name; /* Jack server where we should connect to. */ char *server_name; unsigned long jack_output_port_flags; /* flags to be passed to jack when opening the output ports */ unsigned long jack_input_port_flags; /* flags to be passed to jack when opening the output ports */ jack_ringbuffer_t *pPlayPtr; /* the playback ringbuffer */ jack_ringbuffer_t *pRecPtr; /* the recording ringbuffer */ SRC_STATE *output_src; /* SRC object for the output stream */ SRC_STATE *input_src; /* SRC object for the output stream */ enum status_enum state; /* one of PLAYING, PAUSED, STOPPED, CLOSED, RESET etc */ unsigned int volume[MAX_OUTPUT_PORTS]; /* percentage of sample value to preserve, 100 would be no attenuation */ enum JACK_VOLUME_TYPE volumeEffectType; /* linear or dbAttenuation, if dbAttenuation volume is the number of dBs of attenuation to apply, 0 volume being no attenuation, full volume */ long position_byte_offset; /* an offset that we will apply to returned position queries to achieve */ /* the position that the user of the driver desires set */ bool in_use; /* true if this device is currently in use */ pthread_mutex_t mutex; /* mutex to lock this specific device */ /* variables used for trying to restart the connection to jack */ bool jackd_died; /* true if jackd has died and we should try to restart it */ struct timeval last_reconnect_attempt; } jack_driver_t; #define PLAYED \ 1 /* played out of the speakers(estimated value but should be close */ #define WRITTEN_TO_JACK 2 /* amount written out to jack */ #define WRITTEN 3 /* amount written to the ocaml-bjack device */ /**********************/ /* External functions */ jack_driver_t *JACK_CreateDriver(void); /* This functions allocated memory. It should be freed by the programmer. */ void JACK_SetSampleRateConversionFunction( int converter); /* which SRC converter function should be used for the next Open()d device */ int JACK_Open(jack_driver_t *drv, unsigned int bits_per_channel, unsigned long *rate, char *client_name, char *server_name, unsigned int input_channels, unsigned int output_channels, unsigned long jack_port_flags, int rb_size); int JACK_Close(jack_driver_t *drv); /* return 0 for success */ void JACK_Reset(jack_driver_t *drv); /* free all buffered data and reset several values in the device */ long JACK_Write(jack_driver_t *drv, unsigned char *data, unsigned long bytes); /* returns the number of bytes written, -1 for fatal errors */ long JACK_Read(jack_driver_t *drv, unsigned char *data, unsigned long bytes); /* returns the number of bytes read, -1 for fatal errors */ /* state setting values */ /* set/get the written/played/buffered value based on a byte or millisecond * input value */ long JACK_GetPosition(jack_driver_t *drv, enum pos_enum position, int type); void JACK_SetPosition(jack_driver_t *drv, enum pos_enum position, long value); long JACK_GetJackLatency(jack_driver_t *drv); /* deprectated, you probably want JACK_GetJackOutputLatency */ long JACK_GetJackOutputLatency( jack_driver_t *drv); /* return the output latency in frames */ long JACK_GetJackInputLatency( jack_driver_t *drv); /* return the input latency in frames */ int JACK_SetState(jack_driver_t *drv, enum status_enum state); /* playing, paused, stopped */ enum status_enum JACK_GetState(jack_driver_t *drv); long JACK_GetMaxOutputBufferedBytes(jack_driver_t *drv); long JACK_GetMaxInputBufferedBytes(jack_driver_t *drv); /* bytes that jack requests during each callback */ unsigned long JACK_GetJackBufferedBytes(jack_driver_t *drv); /* Properties of the jack driver */ enum JACK_VOLUME_TYPE JACK_SetVolumeEffectType(jack_driver_t *drv, enum JACK_VOLUME_TYPE type); int JACK_SetAllVolume(jack_driver_t *drv, unsigned int volume); /* returns 0 on success */ int JACK_SetVolumeForChannel(jack_driver_t *drv, unsigned int channel, unsigned int volume); void JACK_GetVolumeForChannel(jack_driver_t *drv, unsigned int channel, unsigned int *volume); unsigned long JACK_GetOutputBytesPerSecond( jack_driver_t *drv); /* bytes_per_output_frame * sample_rate */ unsigned long JACK_GetInputBytesPerSecond( jack_driver_t *drv); /* bytes_per_input_frame * sample_rate */ unsigned long JACK_GetBytesStored( jack_driver_t *drv); /* bytes currently buffered in the output buffer */ unsigned long JACK_GetBytesFreeSpace( jack_driver_t *drv); /* bytes of free space in the output buffer */ unsigned long JACK_GetBytesUsedSpace( jack_driver_t *drv); /* bytes of space used in the input buffer */ unsigned long JACK_GetBytesPerOutputFrame(jack_driver_t *drv); unsigned long JACK_GetBytesPerInputFrame(jack_driver_t *drv); /* Note: these will probably be removed in a future release */ int JACK_GetNumInputChannels(jack_driver_t *drv); int JACK_GetNumOutputChannels(jack_driver_t *drv); long JACK_GetSampleRate(jack_driver_t *drv); /* samples per second */ #ifdef __cplusplus } #endif #endif /* #ifndef JACK_OUT_H */