diff options
-rw-r--r-- | AUTHORS | 14 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | Makefile.am | 36 | ||||
-rw-r--r-- | TODO | 20 | ||||
-rw-r--r-- | alsa_driver.c | 1226 | ||||
-rwxr-xr-x | autogen.sh | 6 | ||||
-rw-r--r-- | client.c | 1206 | ||||
-rw-r--r-- | configure.in | 64 | ||||
-rwxr-xr-x | conftest | bin | 0 -> 6650 bytes | |||
-rw-r--r-- | conftest.c | 1 | ||||
-rw-r--r-- | doc/Makefile | 62 | ||||
-rw-r--r-- | doc/compiling.m4 | 4 | ||||
-rw-r--r-- | doc/configuring.m4 | 4 | ||||
-rw-r--r-- | doc/contributors.m4 | 4 | ||||
-rw-r--r-- | doc/download.m4 | 4 | ||||
-rw-r--r-- | doc/faq.m4 | 4 | ||||
-rw-r--r-- | doc/features.m4 | 4 | ||||
-rw-r--r-- | doc/header.html | 54 | ||||
-rw-r--r-- | doc/helping.m4 | 4 | ||||
-rw-r--r-- | doc/index.m4 | 7 | ||||
-rw-r--r-- | doc/intro.m4 | 4 | ||||
-rw-r--r-- | doc/issues.m4 | 85 | ||||
-rw-r--r-- | doc/links.m4 | 4 | ||||
-rw-r--r-- | doc/mailinglist.m4 | 4 | ||||
-rw-r--r-- | doc/manual.m4 | 4 | ||||
-rw-r--r-- | doc/news.m4 | 4 | ||||
-rw-r--r-- | doc/requirements.m4 | 4 | ||||
-rw-r--r-- | doc/todo.m4 | 4 | ||||
-rw-r--r-- | doc/trailer.html | 23 | ||||
-rw-r--r-- | driver.c | 238 | ||||
-rw-r--r-- | engine.c | 2250 | ||||
-rw-r--r-- | fltk_client.cc | 98 | ||||
-rw-r--r-- | generic_hw.c | 57 | ||||
-rw-r--r-- | hammerfall.c | 293 | ||||
-rw-r--r-- | jack.pc.in | 11 | ||||
-rw-r--r-- | jack/Makefile.am | 4 | ||||
-rw-r--r-- | jack/alsa_driver.h | 159 | ||||
-rw-r--r-- | jack/driver.h | 136 | ||||
-rw-r--r-- | jack/engine.h | 87 | ||||
-rw-r--r-- | jack/error.h | 8 | ||||
-rw-r--r-- | jack/generic.h | 7 | ||||
-rw-r--r-- | jack/hammerfall.h | 17 | ||||
-rw-r--r-- | jack/hardware.h | 27 | ||||
-rw-r--r-- | jack/internal.h | 215 | ||||
-rw-r--r-- | jack/jack.h | 228 | ||||
-rw-r--r-- | jack/memops.h | 89 | ||||
-rw-r--r-- | jack/pool.h | 29 | ||||
-rw-r--r-- | jack/port.h | 89 | ||||
-rw-r--r-- | jack/types.h | 69 | ||||
-rw-r--r-- | jackd.c | 157 | ||||
-rw-r--r-- | memops.c | 257 | ||||
-rw-r--r-- | monitor_client.c | 23 | ||||
-rw-r--r-- | pool.c | 31 | ||||
-rw-r--r-- | port.h | 51 | ||||
-rw-r--r-- | simple_client.c | 83 |
55 files changed, 7913 insertions, 0 deletions
@@ -0,0 +1,14 @@ +JACK was inspired by and partially designed during discussions on the +Linux Audio Developers mailing list. Particularly significant +contributions to those discussions came from (in alphabetical order): + + Paul Davis + David Olofson + Benno Sennoner + Kai Vehamen + +Many other members of LAD contributed ideas to JACK. + +Paul Davis was the principal author of the JACK API and of the sample +implementation contained here. + @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..ee2a6a7 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,36 @@ +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in \ + stamp-h.in \ + config.guess mkinstalldirs \ + missing install-sh config.sub ltconfig \ + ltmain.sh acinclude.m4 + +SUBDIRS = jack doc + +EXTRA_PROGRAMS = jack_fltk_client + +bin_PROGRAMS = jackd jack_simple_client jack_monitor_client @XTRA@ + +jackd_SOURCES = jackd.c +jackd_LDFLAGS = -L. -ljack -lltdl -lpthread + +jack_simple_client_SOURCES = simple_client.c +jack_simple_client_LDFLAGS = -L. -ljack -lltdl -lpthread + +jack_monitor_client_SOURCES = monitor_client.c +jack_monitor_client_LDFLAGS = -L. -ljack -lltdl -lpthread + +jack_fltk_client_SOURCES = fltk_client.cc +jack_fltk_client_LDFLAGS = -L. -L/usr/X11R6/lib -lfltk -lX11 -lXext -ljack -lltdl -lpthread + +lib_LTLIBRARIES = libjack.la jack_alsa.la + +libjack_la_SOURCES = engine.c client.c pool.c driver.c + +jack_alsa_la_LDFLAGS = -module +jack_alsa_la_SOURCES = alsa_driver.c hammerfall.c generic_hw.c memops.c +jack_alsa_la_LIBADD = -lasound + +EXTRA_DIST = jack + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = jack.pc @@ -0,0 +1,20 @@ +TODO + +fix graph sorting to use same approach as in ARDOUR::Session for + routes + +figure out how to have pools of buffers for ports by type +figure out how to use the same buffer over and over when possible + +passing args to drivers and clients? +getting callbacks and args from dynamically loaded clients? +how do dynamically loaded clients (un)register ports, activate, etc. +pool based malloc for rt client-local mem allocation + +TO THINK ABOUT: + +multiple port buffer shm segments (i.e. dynamically + increase the total number of ports in the system) + + + diff --git a/alsa_driver.c b/alsa_driver.c new file mode 100644 index 0000000..688c552 --- /dev/null +++ b/alsa_driver.c @@ -0,0 +1,1226 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <math.h> +#include <stdio.h> +#include <memory.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <asm/msr.h> +#include <glib.h> +#include <stdarg.h> + +#include <jack/alsa_driver.h> +#include <jack/types.h> +#include <jack/internal.h> +#include <jack/engine.h> +#include <jack/hammerfall.h> +#include <jack/generic.h> + +static int config_max_level = 0; +static int config_min_level = 0; + +static unsigned long current_usecs () { + unsigned long now; + rdtscl (now); + return now / 450; +} + +static void +alsa_driver_release_channel_dependent_memory (alsa_driver_t *driver) + +{ + if (driver->playback_addr) { + free (driver->playback_addr); + driver->playback_addr = 0; + } + + if (driver->capture_addr) { + free (driver->capture_addr); + driver->capture_addr = 0; + } + + if (driver->silent) { + free (driver->silent); + driver->silent = 0; + } + + if (driver->input_monitor_requests) { + free (driver->input_monitor_requests); + driver->input_monitor_requests = 0; + } +} + +static int +alsa_driver_check_capabilities (alsa_driver_t *driver) + +{ + return 0; +} + +static int +alsa_driver_check_card_type (alsa_driver_t *driver) + +{ + int err; + snd_ctl_card_info_t *card_info; + + snd_ctl_card_info_alloca (&card_info); + + if ((err = snd_ctl_open (&driver->ctl_handle, driver->alsa_name, 0)) < 0) { + jack_error ("control open \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + + if ((err = snd_ctl_card_info(driver->ctl_handle, card_info)) < 0) { + jack_error ("control hardware info \"%s\" (%s)", driver->alsa_name, snd_strerror (err)); + snd_ctl_close (driver->ctl_handle); + return -1; + } + + driver->alsa_driver = strdup(snd_ctl_card_info_get_driver (card_info)); + + return alsa_driver_check_capabilities (driver); +} + +static int +alsa_driver_hammerfall_hardware (alsa_driver_t *driver) + +{ + driver->hw = jack_alsa_hammerfall_hw_new (driver); + return 0; +} + +static int +alsa_driver_generic_hardware (alsa_driver_t *driver) + +{ + driver->hw = jack_alsa_generic_hw_new (driver); + return 0; +} + +static int +alsa_driver_hw_specific (alsa_driver_t *driver) + +{ + int err; + + if (!strcmp(driver->alsa_driver, "RME9652")) { + if ((err = alsa_driver_hammerfall_hardware (driver)) != 0) { + return err; + } + } else { + if ((err = alsa_driver_generic_hardware (driver)) != 0) { + return err; + } + } + + if (driver->hw->capabilities & Cap_HardwareMonitoring) { + driver->has_hw_monitoring = TRUE; + } else { + driver->has_hw_monitoring = FALSE; + } + + /* XXX need to ensure that this is really FALSE */ + + driver->hw_monitoring = FALSE; + + if (driver->hw->capabilities & Cap_ClockLockReporting) { + driver->has_clock_sync_reporting = TRUE; + } else { + driver->has_clock_sync_reporting = FALSE; + } + + return 0; +} + +static void +alsa_driver_setup_io_function_pointers (alsa_driver_t *driver) + +{ + switch (driver->sample_bytes) { + case 2: + if (driver->interleaved) { + driver->channel_copy = memcpy_interleave_d16_s16; + } else { + driver->channel_copy = memcpy_fake; + } + + driver->write_via_copy = sample_move_d16_sS; + driver->read_via_copy = sample_move_dS_s16; + break; + + case 4: + if (driver->interleaved) { + driver->channel_copy = memcpy_interleave_d32_s32; + } else { + driver->channel_copy = memcpy_fake; + } + + driver->write_via_copy = sample_move_d32u24_sS; + driver->read_via_copy = sample_move_dS_s32u24; + + break; + } +} + +static int +alsa_driver_configure_stream (alsa_driver_t *driver, + const char *stream_name, + snd_pcm_t *handle, + snd_pcm_hw_params_t *hw_params, + snd_pcm_sw_params_t *sw_params, + unsigned long *nchns) +{ + int err; + + if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) { + jack_error ("ALSA: no playback configurations available"); + return -1; + } + + if ((err = snd_pcm_hw_params_set_periods_integer (handle, hw_params)) < 0) { + jack_error ("ALSA: cannot restrict period size to integral value."); + return -1; + } + + if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0) { + if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { + jack_error ("ALSA: mmap-based access is not possible for the %s " + "stream of this audio interface", stream_name); + return -1; + } + } + + if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S32_LE)) < 0) { + if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { + jack_error ("Sorry. The audio interface \"%s\"" + "doesn't support either of the two hardware sample formats that ardour can use.", + driver->alsa_name); + return -1; + } + } + + if ((err = snd_pcm_hw_params_set_rate (handle, hw_params, driver->frame_rate, 0)) < 0) { + jack_error ("ALSA: cannot set sample/frame rate to %u for %s", driver->frame_rate, stream_name); + return -1; + } + + *nchns = snd_pcm_hw_params_get_channels_max (hw_params); + + if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, *nchns)) < 0) { + jack_error ("ALSA: cannot set channel count to %u for %s", *nchns, stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_periods (handle, hw_params, 2, 0)) < 0) { + jack_error ("ALSA: cannot set fragment count minimum to 2 for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_period_size (handle, hw_params, driver->frames_per_cycle, 0)) < 0) { + jack_error ("ALSA: cannot set fragment length to %u for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_buffer_size (handle, hw_params, 2 * driver->frames_per_cycle)) < 0) { + jack_error ("ALSA: cannot set buffer length to %u for %s", 2 * driver->frames_per_cycle, stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) { + jack_error ("ALSA: cannot set hardware parameters for %s", stream_name); + return -1; + } + + snd_pcm_sw_params_current (handle, sw_params); + + if ((err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, ~0U)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold (handle, sw_params, ~0U)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_silence_threshold (handle, sw_params, 0)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_silence_size (handle, sw_params, driver->frames_per_cycle * driver->nfragments)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, driver->frames_per_cycle)) < 0) { + jack_error ("ALSA: cannot set avail min for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) { + jack_error ("ALSA: cannot set software parameters for %s", stream_name); + return -1; + } + + return 0; +} + +static int +alsa_driver_set_parameters (alsa_driver_t *driver, nframes_t frames_per_cycle, nframes_t rate) + +{ + int p_noninterleaved; + int c_noninterleaved; + snd_pcm_format_t c_format, p_format; + int dir; + unsigned int p_period_size, c_period_size; + unsigned int p_nfragments, c_nfragments; + channel_t chn; + + driver->frame_rate = rate; + driver->frames_per_cycle = frames_per_cycle; + + if (alsa_driver_configure_stream (driver, "capture", + driver->capture_handle, + driver->capture_hw_params, + driver->capture_sw_params, + &driver->capture_nchannels)) { + jack_error ("ALSA-MCD: cannot configure capture channel"); + return -1; + } + + if (alsa_driver_configure_stream (driver, "playback", + driver->playback_handle, + driver->playback_hw_params, + driver->playback_sw_params, + &driver->playback_nchannels)) { + jack_error ("ALSA-MCD: cannot configure playback channel"); + return -1; + } + + /* check the fragment size, since thats non-negotiable */ + + p_period_size = snd_pcm_hw_params_get_period_size (driver->playback_hw_params, &dir); + c_period_size = snd_pcm_hw_params_get_period_size (driver->capture_hw_params, &dir); + + if (c_period_size != driver->frames_per_cycle || p_period_size != driver->frames_per_cycle) { + jack_error ("ALSA I/O: requested an interrupt every %u frames but got %uc%up frames", + driver->frames_per_cycle, c_period_size, p_period_size); + return -1; + } + + p_nfragments = snd_pcm_hw_params_get_periods (driver->playback_hw_params, &dir); + c_nfragments = snd_pcm_hw_params_get_periods (driver->capture_hw_params, &dir); + + if (p_nfragments != c_nfragments) { + jack_error ("ALSA I/O: different period counts for playback and capture!"); + return -1; + } + + driver->nfragments = c_nfragments; + driver->buffer_frames = driver->frames_per_cycle * driver->nfragments; + + /* Check that we are using the same sample format on both streams */ + + p_format = (snd_pcm_format_t) snd_pcm_hw_params_get_format (driver->playback_hw_params); + c_format = (snd_pcm_format_t) snd_pcm_hw_params_get_format (driver->capture_hw_params); + + if (p_format != c_format) { + jack_error ("Sorry. The audio interface \"%s\"" + "doesn't support the same sample format for capture and playback." + "Ardour cannot use this hardware.", driver->alsa_name); + return -1; + } + + driver->sample_format = p_format; + driver->sample_bytes = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->bytes_per_cycle = driver->sample_bytes * driver->frames_per_cycle; + + switch (driver->sample_format) { + case SND_PCM_FORMAT_S32_LE: + + /* XXX must handle the n-bits of 24-in-32 problems here */ + + if (config_max_level) { + driver->max_level = config_max_level; + } else { + driver->max_level = INT_MAX; + } + + if (config_min_level) { + driver->min_level = config_min_level; + } else { + driver->min_level = INT_MIN; + } + break; + + case SND_PCM_FORMAT_S16_LE: + + if (config_max_level) { + driver->max_level = config_max_level; + } else { + driver->max_level = SHRT_MAX; + } + + if (config_min_level) { + driver->min_level = config_min_level; + } else { + driver->min_level = SHRT_MIN; + } + break; + + default: + jack_error ("programming error: unhandled format type"); + exit (1); + } + + /* check interleave setup */ + + p_noninterleaved = (snd_pcm_hw_params_get_access (driver->playback_hw_params) == SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + c_noninterleaved = (snd_pcm_hw_params_get_access (driver->capture_hw_params) == SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + + if (c_noninterleaved != p_noninterleaved) { + jack_error ("ALSA: the playback and capture components of this audio interface differ " + "in their use of channel interleaving. Ardour cannot use this h/w."); + return -1; + } + + driver->interleaved = !c_noninterleaved; + + if (driver->interleaved) { + driver->interleave_unit = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->playback_interleave_skip = driver->interleave_unit * driver->playback_nchannels; + driver->capture_interleave_skip = driver->interleave_unit * driver->capture_nchannels; + } else { + driver->interleave_unit = 0; /* NOT USED */ + driver->playback_interleave_skip = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->capture_interleave_skip = driver->playback_interleave_skip; + } + + if (driver->playback_nchannels > driver->capture_nchannels) { + driver->max_nchannels = driver->playback_nchannels; + driver->user_nchannels = driver->capture_nchannels; + } else { + driver->max_nchannels = driver->capture_nchannels; + driver->user_nchannels = driver->playback_nchannels; + } + + alsa_driver_setup_io_function_pointers (driver); + + /* Allocate and initialize structures that rely on + the channels counts. + */ + + driver->playback_addr = (char **) malloc (sizeof (char *) * driver->playback_nchannels); + driver->capture_addr = (char **) malloc (sizeof (char *) * driver->capture_nchannels); + + memset (driver->playback_addr, 0, sizeof (char *) * driver->playback_nchannels); + memset (driver->capture_addr, 0, sizeof (char *) * driver->capture_nchannels); + + driver->silent = (unsigned long *) malloc (sizeof (unsigned long) * driver->playback_nchannels); + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + driver->silent[chn] = 0; + } + + driver->input_monitor_requests = (unsigned long *) malloc (sizeof (unsigned long) * driver->max_nchannels); + memset (driver->input_monitor_requests, 0, sizeof (unsigned long) * driver->max_nchannels); + + driver->clock_sync_data = (ClockSyncStatus *) malloc (sizeof (ClockSyncStatus) * + driver->capture_nchannels > driver->playback_nchannels ? + driver->capture_nchannels : driver->playback_nchannels); + + /* set up the bit pattern that is used to record which + channels require action on every cycle. any bits that are + not set after the engine's process() call indicate channels + that potentially need to be silenced. + + XXX this is limited to <wordsize> channels. Use a bitset + type instead. + */ + + driver->channel_done_bits = 0; + for (chn = 0; chn < driver->playback_nchannels; chn++) { + driver->channel_done_bits |= (1<<chn); + } + + driver->period_interval = (unsigned long) floor ((((float) driver->frames_per_cycle) / driver->frame_rate) * 1000.0); + + if (driver->engine) { + driver->engine->set_buffer_size (driver->engine, driver->frames_per_cycle); + } + + return 0; +} + +static int +alsa_driver_reset_parameters (alsa_driver_t *driver, nframes_t frames_per_cycle, nframes_t rate) +{ + /* XXX unregister old ports ? */ + alsa_driver_release_channel_dependent_memory (driver); + return alsa_driver_set_parameters (driver, frames_per_cycle, rate); +} + +static int +alsa_driver_get_channel_addresses (alsa_driver_t *driver, + snd_pcm_uframes_t *capture_avail, + snd_pcm_uframes_t *playback_avail, + snd_pcm_uframes_t *capture_offset, + snd_pcm_uframes_t *playback_offset) + +{ + unsigned long err; + channel_t chn; + + if (capture_avail) { + if ((err = snd_pcm_mmap_begin (driver->capture_handle, &driver->capture_areas, + (snd_pcm_uframes_t *) capture_offset, + (snd_pcm_uframes_t *) capture_avail)) < 0) { + jack_error ("ALSA-HW: %s: mmap areas info error", driver->alsa_name); + return -1; + } + + for (chn = 0; chn < driver->capture_nchannels; chn++) { + const snd_pcm_channel_area_t *a = &driver->capture_areas[chn]; + driver->capture_addr[chn] = (char *) a->addr + ((a->first + a->step * *capture_offset) / 8); + } + } + + if (playback_avail) { + if ((err = snd_pcm_mmap_begin (driver->playback_handle, &driver->playback_areas, + (snd_pcm_uframes_t *) playback_offset, + (snd_pcm_uframes_t *) playback_avail)) < 0) { + jack_error ("ALSA-HW: %s: mmap areas info error ", driver->alsa_name); + return -1; + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + const snd_pcm_channel_area_t *a = &driver->playback_areas[chn]; + driver->playback_addr[chn] = (char *) a->addr + ((a->first + a->step * *playback_offset) / 8); + } + } + + return 0; +} + +static int +alsa_driver_audio_start (alsa_driver_t *driver) + +{ + int err; + snd_pcm_uframes_t poffset, pavail; + channel_t chn; + + if ((err = snd_pcm_prepare (driver->playback_handle)) < 0) { + jack_error ("ALSA-HW: prepare error for playback on \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_prepare (driver->capture_handle)) < 0) { + jack_error ("ALSA-HW: prepare error for capture on \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + } + + if (driver->hw_monitoring) { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + + /* fill playback buffer with zeroes, and mark + all fragments as having data. + */ + + pavail = snd_pcm_avail_update (driver->playback_handle); + + if (pavail != driver->buffer_frames) { + jack_error ("ALSA-HW: full buffer not available at start"); + return -1; + } + + if (alsa_driver_get_channel_addresses (driver, 0, &pavail, 0, &poffset)) { + return -1; + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + alsa_driver_silence_on_channel (driver, chn, driver->buffer_frames); + } + + snd_pcm_mmap_commit (driver->playback_handle, poffset, driver->buffer_frames); + + if ((err = snd_pcm_start (driver->playback_handle)) < 0) { + jack_error ("could not start playback (%s)", snd_strerror (err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_start (driver->capture_handle)) < 0) { + jack_error ("could not start capture (%s)", snd_strerror (err)); + return -1; + } + } + + if (driver->hw_monitoring && (driver->input_monitor_mask || driver->all_monitor_in)) { + if (driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } + + snd_pcm_poll_descriptors (driver->playback_handle, &driver->pfd, 1); + driver->pfd.events = POLLOUT | POLLERR; + + return 0; +} + +static int +alsa_driver_audio_stop (alsa_driver_t *driver) + +{ + int err; + + if ((err = snd_pcm_drop (driver->playback_handle)) < 0) { + jack_error ("ALSA I/O: channel flush for playback failed (%s)", snd_strerror (err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_drop (driver->capture_handle)) < 0) { + jack_error ("ALSA I/O: channel flush for capture failed (%s)", snd_strerror (err)); + return -1; + } + } + + driver->hw->set_input_monitor_mask (driver->hw, 0); + + return 0; +} + +static int +alsa_driver_xrun_recovery (alsa_driver_t *driver) + +{ + snd_pcm_sframes_t capture_delay; + int err; + + if ((err = snd_pcm_delay (driver->capture_handle, &capture_delay))) { + jack_error ("ALSA I/O: cannot determine capture delay (%s)", snd_strerror (err)); + exit (1); + } + + fprintf (stderr, "ALSA I/O: xrun of %lu frames, (%.3f msecs)\n", capture_delay, + ((float) capture_delay / (float) driver->frame_rate) * 1000.0); + +#if ENGINE + if (!engine->xrun_recoverable ()) { + /* don't report an error here, its distracting */ + return -1; + } +#endif + + if (alsa_driver_audio_stop (driver) || alsa_driver_audio_start (driver)) { + return -1; + + } + + return 0; +} + +static void +alsa_driver_silence_untouched_channels (alsa_driver_t *driver, nframes_t nframes) + +{ + channel_t chn; + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if ((driver->channels_not_done & (1<<chn))) { + if (driver->silent[chn] < driver->buffer_frames) { + alsa_driver_silence_on_channel (driver, chn, nframes); + driver->silent[chn] += nframes; + } + } + } +} + +void +alsa_driver_set_clock_sync_status (alsa_driver_t *driver, channel_t chn, ClockSyncStatus status) + +{ + driver->clock_sync_data[chn] = status; + jack_driver_clock_sync_notify ((jack_driver_t *) driver, chn, status); +} + +static int under_gdb = FALSE; + +static int +alsa_driver_wait (alsa_driver_t *driver) + +{ + snd_pcm_sframes_t avail = 0; + snd_pcm_sframes_t contiguous = 0; + snd_pcm_sframes_t capture_avail = 0; + snd_pcm_sframes_t playback_avail = 0; + snd_pcm_uframes_t capture_offset = 0; + snd_pcm_uframes_t playback_offset = 0; + int xrun_detected; + channel_t chn; + GSList *node; + sample_t *buffer; + + again: + if (poll (&driver->pfd, 1, 1000) < 0) { + if (errno == EINTR) { + printf ("poll interrupt\n"); + // this happens mostly when run + // under gdb, or when exiting due to a signal + if (under_gdb) { + goto again; + } + return 1; + } + + jack_error ("ALSA::Device: poll call failed (%s)", strerror (errno)); + return -1; + } + + driver->time_at_interrupt = current_usecs(); + + if (driver->pfd.revents & POLLERR) { + jack_error ("ALSA-MCD: poll reports error."); + return -1; + } + + if (driver->pfd.revents == 0) { + // timed out, such as when the device is paused + return 0; + } + + xrun_detected = FALSE; + + if ((capture_avail = snd_pcm_avail_update (driver->capture_handle)) < 0) { + if (capture_avail == -EPIPE) { + xrun_detected = TRUE; + } else { + jack_error ("unknown ALSA avail_update return value (%u)", capture_avail); + } + } + + if ((playback_avail = snd_pcm_avail_update (driver->playback_handle)) < 0) { + if (playback_avail == -EPIPE) { + xrun_detected = TRUE; + } else { + jack_error ("unknown ALSA avail_update return value (%u)", playback_avail); + } + } + + if (xrun_detected) { + if (alsa_driver_xrun_recovery (driver)) { + return -1; + } else { + return 0; + } + } + + avail = capture_avail < playback_avail ? capture_avail : playback_avail; + + while (avail) { + + capture_avail = (avail > driver->frames_per_cycle) ? driver->frames_per_cycle : avail; + playback_avail = (avail > driver->frames_per_cycle) ? driver->frames_per_cycle : avail; + + if (alsa_driver_get_channel_addresses (driver, + (snd_pcm_uframes_t *) &capture_avail, + (snd_pcm_uframes_t *) &playback_avail, + &capture_offset, &playback_offset) < 0) { + return -1; + } + + contiguous = capture_avail < playback_avail ? capture_avail : playback_avail; + + /* XXX possible race condition here with silence_pending */ + + /* XXX this design is wrong. cf. ardour/audioengine *** FIX ME *** */ + + if (driver->silence_pending) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if (driver->silence_pending & (1<<chn)) { + alsa_driver_silence_on_channel (driver, chn, contiguous); + } + } + driver->silence_pending = 0; + } + + driver->channels_not_done = driver->channel_done_bits; + + if ((driver->hw->input_monitor_mask != driver->input_monitor_mask) && + driver->hw_monitoring && !driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + + /* XXX race condition on engine ptr */ + + if (driver->engine && driver->engine->process (driver->engine, contiguous)) { + jack_error ("ALSA I/O: engine processing error - stopping."); + return -1; + } + + /* now move data from ports to channels */ + + for (chn = 0, node = driver->playback_ports; node; node = g_slist_next (node), chn++) { + + jack_port_t *port = (jack_port_t *) node->data; + + /* optimize needless data copying away */ + + if (port->connections == 0) { + continue; + } + + buffer = (sample_t *) jack_port_get_buffer (port, contiguous); + alsa_driver_write_to_channel (driver, chn, buffer, contiguous, 0, 1.0); + } + + /* Now handle input monitoring */ + + if (!driver->hw_monitoring) { + if (driver->all_monitor_in) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + alsa_driver_copy_channel (driver, chn, chn, contiguous); + } + } else if (driver->input_monitor_mask) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if (driver->input_monitor_mask & (1<<chn)) { + alsa_driver_copy_channel (driver, chn, chn, contiguous); + } + } + } + } + + if (driver->channels_not_done) { + alsa_driver_silence_untouched_channels (driver, contiguous); + } + + snd_pcm_mmap_commit (driver->capture_handle, capture_offset, contiguous); + snd_pcm_mmap_commit (driver->playback_handle, playback_offset, contiguous); + + avail -= contiguous; + } + + return 0; +} + +static int +alsa_driver_process (nframes_t nframes, void *arg) + +{ + alsa_driver_t *driver = (alsa_driver_t *) arg; + channel_t chn; + jack_port_t *port; + GSList *node; + + for (chn = 0, node = driver->capture_ports; node; node = g_slist_next (node), chn++) { + + port = (jack_port_t *) node->data; + + if (port->connections == 0) { + continue; + } + + alsa_driver_read_from_channel (driver, chn, port->shared->buffer, nframes, 0); + } + + return 0; +} + +static void +alsa_driver_port_monitor_handler (jack_port_id_t port_id, int onoff, void *arg) +{ + alsa_driver_t *driver = (alsa_driver_t *) arg; + jack_port_shared_t *port; + int channel; + + port = &driver->engine->control->ports[port_id]; + sscanf (port->name, "%*s%*s%*s%d", &channel); + driver->request_monitor_input ((jack_driver_t *) driver, channel, onoff); +} + +static void +alsa_driver_attach (alsa_driver_t *driver, jack_engine_t *engine) + +{ + char buf[32]; + channel_t chn; + jack_port_t *port; + + driver->engine = engine; + + driver->engine->set_buffer_size (engine, driver->frames_per_cycle); + driver->engine->set_sample_rate (engine, driver->frame_rate); + + /* Now become a client of the engine */ + + if ((driver->client = jack_driver_become_client ("ALSA I/O")) == NULL) { + jack_error ("ALSA: cannot become client"); + return; + } + + jack_set_process_callback (driver->client, alsa_driver_process, driver); + jack_set_port_monitor_callback (driver->client, alsa_driver_port_monitor_handler, driver); + + for (chn = 0; chn < driver->capture_nchannels; chn++) { + snprintf (buf, sizeof(buf) - 1, "Input %lu", chn+1); + port = jack_port_register (driver->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput|JackPortIsPhysical|JackPortCanMonitor, 0); + if (port == 0) { + jack_error ("ALSA: cannot register port for %s", buf); + break; + } + driver->capture_ports = g_slist_append (driver->capture_ports, port); + printf ("registered %s\n", port->shared->name); + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + snprintf (buf, sizeof(buf) - 1, "Output %lu", chn+1); + port = jack_port_register (driver->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput|JackPortIsPhysical, 0); + if (port == 0) { + jack_error ("ALSA: cannot register port for %s", buf); + break; + } + driver->playback_ports = g_slist_append (driver->playback_ports, port); + printf ("registered %s\n", port->shared->name); + } + + printf ("ports registered, starting client\n"); + + jack_activate (driver->client); +} + +static void +alsa_driver_detach (alsa_driver_t *driver, jack_engine_t *engine) + +{ + GSList *node; + + for (node = driver->capture_ports; node; node = g_slist_next (node)) { + jack_port_unregister (driver->client, ((jack_port_t *) node->data)); + } + + g_slist_free (driver->capture_ports); + driver->capture_ports = 0; + + for (node = driver->playback_ports; node; node = g_slist_next (node)) { + jack_port_unregister (driver->client, ((jack_port_t *) node->data)); + } + + g_slist_free (driver->playback_ports); + driver->playback_ports = 0; + + driver->engine = 0; +} + +static int +alsa_driver_change_sample_clock (alsa_driver_t *driver, SampleClockMode mode) + +{ + return driver->hw->change_sample_clock (driver->hw, mode); +} + +static void +alsa_driver_mark_channel_silent (alsa_driver_t *driver, unsigned long chn) +{ + driver->silence_pending |= (1<<chn); +} + +static void +alsa_driver_request_monitor_input (alsa_driver_t *driver, unsigned long chn, int yn) + +{ + int changed; + + if (chn >= driver->max_nchannels) { + return; + } + + changed = FALSE; + + if (yn) { + if (++driver->input_monitor_requests[chn] == 1) { + if (!(driver->input_monitor_mask & (1<<chn))) { + driver->input_monitor_mask |= (1<<chn); + changed = TRUE; + } + } + } else { + if (driver->input_monitor_requests[chn] && --driver->input_monitor_requests[chn] == 0) { + if (driver->input_monitor_mask & (1<<chn)) { + driver->input_monitor_mask &= ~(1<<chn); + changed = TRUE; + } + } + } + + if (changed) { + if (!driver->hw_monitoring && !yn) { + alsa_driver_mark_channel_silent (driver, chn); + } + + /* Tell anyone who cares about the state of input monitoring */ + + jack_driver_input_monitor_notify ((jack_driver_t *) driver, chn, yn); + } +} + +static void +alsa_driver_request_all_monitor_input (alsa_driver_t *driver, int yn) + +{ + if (driver->hw_monitoring) { + if (yn) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } + + driver->all_monitor_in = yn; +} + +static void +alsa_driver_set_hw_monitoring (alsa_driver_t *driver, int yn) + +{ + if (yn) { + driver->hw_monitoring = TRUE; + + if (driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } else { + driver->hw_monitoring = FALSE; + driver->hw->set_input_monitor_mask (driver->hw, 0); + } +} + +static nframes_t +alsa_driver_frames_since_cycle_start (alsa_driver_t *driver) +{ + return (nframes_t) ((driver->frame_rate / 1000000.0) * ((float) (current_usecs() - driver->time_at_interrupt))); +} + +static ClockSyncStatus +alsa_driver_clock_sync_status (channel_t chn) + +{ + return Lock; +} + +static void +alsa_driver_delete (alsa_driver_t *driver) + +{ + if (driver->capture_handle) { + snd_pcm_close (driver->capture_handle); + driver->capture_handle = 0; + } + + if (driver->playback_handle) { + snd_pcm_close (driver->playback_handle); + driver->capture_handle = 0; + } + + if (driver->capture_hw_params) { + snd_pcm_hw_params_free (driver->capture_hw_params); + driver->capture_hw_params = 0; + } + + if (driver->playback_hw_params) { + snd_pcm_hw_params_free (driver->playback_hw_params); + driver->playback_hw_params = 0; + } + + if (driver->capture_sw_params) { + snd_pcm_sw_params_free (driver->capture_sw_params); + driver->capture_sw_params = 0; + } + + if (driver->playback_sw_params) { + snd_pcm_sw_params_free (driver->playback_sw_params); + driver->playback_sw_params = 0; + } + + if (driver->hw) { + driver->hw->release (driver->hw); + driver->hw = 0; + } + free(driver->alsa_name); + free(driver->alsa_driver); + + alsa_driver_release_channel_dependent_memory (driver); + jack_driver_release ((jack_driver_t *) driver); + free (driver); +} + +static jack_driver_t * +alsa_driver_new (char *name, char *alsa_device, + nframes_t frames_per_cycle, + nframes_t rate) +{ + int err; + + alsa_driver_t *driver; + + printf ("creating alsa driver ... %s|%lu|%lu\n", alsa_device, frames_per_cycle, rate); + + driver = (alsa_driver_t *) calloc (1, sizeof (alsa_driver_t)); + + jack_driver_init ((jack_driver_t *) driver); + + driver->attach = (JackDriverAttachFunction) alsa_driver_attach; + driver->detach = (JackDriverDetachFunction) alsa_driver_detach; + driver->wait = (JackDriverWaitFunction) alsa_driver_wait; + + driver->audio_stop = (JackDriverAudioStopFunction) alsa_driver_audio_stop; + driver->audio_start = (JackDriverAudioStartFunction) alsa_driver_audio_start; + driver->set_hw_monitoring = (JackDriverSetHwMonitoringFunction) alsa_driver_set_hw_monitoring ; + driver->reset_parameters = (JackDriverResetParametersFunction) alsa_driver_reset_parameters; + driver->mark_channel_silent = (JackDriverMarkChannelSilentFunction) alsa_driver_mark_channel_silent; + driver->request_monitor_input = (JackDriverRequestMonitorInputFunction) alsa_driver_request_monitor_input; + driver->request_all_monitor_input = (JackDriverRequestAllMonitorInputFunction) alsa_driver_request_all_monitor_input; + driver->frames_since_cycle_start = (JackDriverFramesSinceCycleStartFunction) alsa_driver_frames_since_cycle_start; + driver->clock_sync_status = (JackDriverClockSyncStatusFunction) alsa_driver_clock_sync_status; + driver->change_sample_clock = (JackDriverChangeSampleClockFunction) alsa_driver_change_sample_clock; + + driver->ctl_handle = 0; + driver->hw = 0; + driver->capture_and_playback_not_synced = FALSE; + driver->nfragments = 0; + driver->max_nchannels = 0; + driver->user_nchannels = 0; + driver->playback_nchannels = 0; + driver->capture_nchannels = 0; + driver->playback_addr = 0; + driver->capture_addr = 0; + driver->silence_pending = 0; + driver->silent = 0; + driver->input_monitor_requests = 0; + driver->all_monitor_in = FALSE; + + driver->clock_mode = ClockMaster; /* XXX is it? */ + driver->input_monitor_mask = 0; /* XXX is it? */ + + driver->capture_ports = 0; + driver->playback_ports = 0; + + if ((err = snd_pcm_open (&driver->playback_handle, alsa_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + jack_error ("ALSA-MCD: Cannot open PCM device %s/%s", name, alsa_device); + free (driver); + return 0; + } + + driver->alsa_name = strdup (alsa_device); + + if ((err = snd_pcm_open (&driver->capture_handle, alsa_device, SND_PCM_STREAM_CAPTURE, 0)) < 0) { + jack_error ("ALSA-MCD: Cannot open PCM device %s", name); + free (driver); + return 0; + } + + if (alsa_driver_check_card_type (driver)) { + free (driver); + return 0; + } + + driver->playback_hw_params = 0; + driver->capture_hw_params = 0; + driver->playback_sw_params = 0; + driver->capture_hw_params = 0; + + if ((err = snd_pcm_hw_params_malloc (&driver->playback_hw_params)) < 0) { + jack_error ("ALSA: could no allocate playback hw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_hw_params_malloc (&driver->capture_hw_params)) < 0) { + jack_error ("ALSA: could no allocate capture hw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_sw_params_malloc (&driver->playback_sw_params)) < 0) { + jack_error ("ALSA: could no allocate playback sw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_sw_params_malloc (&driver->capture_sw_params)) < 0) { + jack_error ("ALSA: could no allocate capture sw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if (alsa_driver_set_parameters (driver, frames_per_cycle, rate)) { + alsa_driver_delete (driver); + return 0; + } + + if (snd_pcm_link (driver->capture_handle, driver->playback_handle) != 0) { + driver->capture_and_playback_not_synced = TRUE; + } else { + driver->capture_and_playback_not_synced = FALSE; + } + + alsa_driver_hw_specific (driver); + + return (jack_driver_t *) driver; +} + +/* PLUGIN INTERFACE */ + +jack_driver_t * +driver_initialize (va_list ap) +{ + nframes_t srate; + nframes_t frames_per_interrupt; + char *pcm_name; + + pcm_name = va_arg (ap, char *); + frames_per_interrupt = va_arg (ap, nframes_t); + srate = va_arg (ap, nframes_t); + + return alsa_driver_new ("ALSA I/O", pcm_name, frames_per_interrupt, srate); +} + +void +driver_finish (jack_driver_t *driver) +{ + alsa_driver_delete ((alsa_driver_t *) driver); +} + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..f763969 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +libtoolize --force && aclocal $ACLOCAL_FLAGS && automake --add-missing --foreign && autoconf + +exit 0 + diff --git a/client.c b/client.c new file mode 100644 index 0000000..eb1a1d2 --- /dev/null +++ b/client.c @@ -0,0 +1,1206 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <sys/socket.h> +#include <sys/un.h> +#include <pthread.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/poll.h> +#include <stdio.h> +#include <asm/msr.h> + +#include <jack/jack.h> +#include <jack/internal.h> +#include <jack/engine.h> +#include <jack/pool.h> + +static pthread_mutex_t client_lock; +static pthread_cond_t client_ready; +static void *jack_zero_filled_buffer = 0; + +struct _jack_client { + + jack_control_t *engine; + jack_client_control_t *control; + struct pollfd *pollfd; + int pollmax; + int graph_next_fd; + int request_fd; + GSList *port_segments; + GSList *ports; + pthread_t thread; + char fifo_prefix[FIFO_NAME_SIZE+1]; + char thread_ok : 1; + char first_active : 1; +}; + +#define event_fd pollfd[0].fd +#define graph_wait_fd pollfd[1].fd + +typedef struct { + int status; + struct _jack_client *client; + const char *client_name; +} client_info; + +jack_client_t * +jack_client_alloc () + +{ + jack_client_t *client; + + client = (jack_client_t *) malloc (sizeof (jack_client_t)); + client->pollfd = (struct pollfd *) malloc (sizeof (struct pollfd) * 2); + client->pollmax = 2; + + client->request_fd = -1; + client->event_fd = -1; + client->graph_wait_fd = -1; + client->graph_next_fd = -1; + client->port_segments = NULL; + client->ports = NULL; + client->engine = NULL; + client->control = 0; + client->thread_ok = FALSE; + client->first_active = TRUE; + + return client; +} + +static jack_port_shared_t * +jack_port_shared_by_id (jack_client_t *client, jack_port_id_t id) + +{ + return &client->engine->ports[id]; +} + +static jack_port_t * +jack_port_by_id (jack_client_t *client, jack_port_id_t id) + +{ + GSList *node; + + for (node = client->ports; node; node = g_slist_next (node)) { + if (((jack_port_t *) node->data)->shared->id == id) { + return (jack_port_t *) node->data; + } + } + + return NULL; +} + +static jack_port_id_t +jack_port_id_by_name (jack_client_t *client, const char *port_name) + +{ + jack_port_id_t id, limit; + jack_port_shared_t *port; + + limit = client->engine->port_max; + port = &client->engine->ports[0]; + + for (id = 0; id < limit; id++) { + if (port[id].in_use && strcmp (port[id].name, port_name) == 0) { + return port[id].id; + } + } + + return NoPort; +} + +static void +jack_client_invalidate_port_buffers (jack_client_t *client) + +{ + GSList *node; + jack_port_t *port; + + /* This releases all local memory owned by input ports + and sets the buffer pointer to NULL. This will cause + jack_port_get_buffer() to reallocate space for the + buffer on the next call (if there is one). + */ + + for (node = client->ports; node; node = g_slist_next (node)) { + port = (jack_port_t *) node->data; + + if (port->shared->flags & JackPortIsInput) { + /* XXX release buffer */ + port->shared->buffer = NULL; + } + } +} + +int +jack_client_handle_port_connection (jack_client_t *client, jack_event_t *event) + +{ + jack_port_t *control_port; + jack_port_shared_t *shared; + GSList *node; + + switch (event->type) { + case PortConnected: + shared = jack_port_shared_by_id (client, event->y.other_id); + control_port = jack_port_by_id (client, event->x.self_id); + control_port->connections = g_slist_prepend (control_port->connections, shared); + printf ("%s connected to %s\n", control_port->shared->name, shared->name); + break; + + case PortDisconnected: + shared = jack_port_shared_by_id (client, event->y.other_id); + control_port = jack_port_by_id (client, event->x.self_id); + + for (node = control_port->connections; node; node = g_slist_next (node)) { + if (((jack_port_shared_t *) node->data) == shared) { + control_port->connections = g_slist_remove_link (control_port->connections, node); + g_slist_free_1 (node); + break; + } + } + printf ("%s DIS-connected and %s\n", control_port->shared->name, shared->name); + break; + + default: + /* impossible */ + break; + } + + return 0; +} + +static int +jack_handle_reorder (jack_client_t *client, jack_event_t *event) +{ + char path[FIFO_NAME_SIZE+1]; + + if (client->graph_wait_fd >= 0) { + close (client->graph_wait_fd); + client->graph_wait_fd = -1; + } + + if (client->graph_next_fd >= 0) { + close (client->graph_next_fd); + client->graph_next_fd = -1; + } + + sprintf (path, "%s-%lu", client->fifo_prefix, event->x.n); + + if ((client->graph_wait_fd = open (path, O_RDONLY)) <= 0) { + jack_error ("cannot open specified fifo [%s] for reading (%s)", path, strerror (errno)); + return -1; + } + + sprintf (path, "%s-%lu", client->fifo_prefix, event->x.n+1); + + if ((client->graph_next_fd = open (path, O_WRONLY)) < 0) { + jack_error ("cannot open specified fifo [%s] for writing (%s)", path, strerror (errno)); + return -1; + } + + return 0; +} + +static int +server_connect (int which) + +{ + int fd; + struct sockaddr_un addr; + + if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + jack_error ("cannot create client socket (%s)", strerror (errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + g_snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "/tmp/jack_%d", which); + + if (connect (fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { + jack_error ("cannot connect to jack server", strerror (errno)); + close (fd); + return -1; + } + + return fd; +} + +static int +server_event_connect (jack_client_t *client) + +{ + int fd; + struct sockaddr_un addr; + jack_client_connect_ack_request_t req; + jack_client_connect_ack_result_t res; + + if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + jack_error ("cannot create client event socket (%s)", strerror (errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + g_snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "/tmp/jack_ack_0"); + + if (connect (fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { + jack_error ("cannot connect to jack server for events", strerror (errno)); + close (fd); + return -1; + } + + req.client_id = client->control->id; + + if (write (fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot write event connect request to server (%s)", strerror (errno)); + close (fd); + return -1; + } + + if (read (fd, &res, sizeof (res)) != sizeof (res)) { + jack_error ("cannot read event connect result from server (%s)", strerror (errno)); + close (fd); + return -1; + } + + if (res.status != 0) { + close (fd); + return -1; + } + + return fd; +} + +jack_client_t * +jack_client_new (const char *client_name) + +{ + int req_fd = -1; + int ev_fd = -1; + void *addr; + jack_client_connect_request_t req; + jack_client_connect_result_t res; + jack_port_segment_info_t *si; + jack_client_t *client; + int client_shm_id; + int control_shm_id; + int port_segment_shm_id; + int n; + + if (strlen (client_name) > sizeof (req.name) - 1) { + jack_error ("\"%s\" is too long to be used as a JACK client name.\n" + "Please use %lu characters or less.", + sizeof (req.name) - 1); + return NULL; + } + + if ((req_fd = server_connect (0)) < 0) { + jack_error ("cannot connect to default JACK server"); + return NULL; + } + + req.type = ClientOutOfProcess; + strncpy (req.name, client_name, sizeof (req.name) - 1); + + if (write (req_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send request to jack server (%s)", strerror (errno)); + close (req_fd); + return NULL; + } + + if ((n = read (req_fd, &res, sizeof (res))) != sizeof (res)) { + jack_error ("cannot read response from jack server (%s)", strerror (errno)); + close (req_fd); + return NULL; + } + + if (res.status) { + close (req_fd); + jack_error ("could not attach as client"); + return NULL; + } + + client = jack_client_alloc (); + + strcpy (client->fifo_prefix, res.fifo_prefix); + client->request_fd = req_fd; + + client->pollfd[0].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; + client->pollfd[1].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; + + /* Lookup, attach and register the port/buffer segments in use + right now. + */ + + if ((port_segment_shm_id = shmget (res.port_segment_key, 0, 0)) < 0) { + jack_error ("cannot determine shared memory segment for port segment key 0x%x", res.port_segment_key); + goto fail; + } + + if ((addr = shmat (port_segment_shm_id, res.port_segment_address, 0)) == (void *) -1) { + jack_error ("cannot attached port segment shared memory at 0x%", res.port_segment_address); + goto fail; + } + + si = (jack_port_segment_info_t *) malloc (sizeof (jack_port_segment_info_t)); + si->shm_key = res.port_segment_key; + si->address = addr; + + /* the first chunk of the first port segment is always set by the engine + to be a conveniently-sized, zero-filled lump of memory. + */ + + if (client->port_segments == NULL) { + jack_zero_filled_buffer = si->address; + } + + client->port_segments = g_slist_prepend (client->port_segments, si); + + /* attach the engine control/info block */ + + if ((control_shm_id = shmget (res.control_key, 0, 0)) < 0) { + jack_error ("cannot determine shared memory segment for control key 0x%x", res.control_key); + goto fail; + } + + if ((addr = shmat (control_shm_id, 0, 0)) == (void *) -1) { + jack_error ("cannot attached engine control shared memory segment"); + goto fail; + } + + client->engine = (jack_control_t *) addr; + + /* now attach the client control block */ + + if ((client_shm_id = shmget (res.client_key, 0, 0)) < 0) { + jack_error ("cannot determine shared memory segment for client key 0x%x", res.client_key); + goto fail; + } + + if ((addr = shmat (client_shm_id, 0, 0)) == (void *) -1) { + jack_error ("cannot attached client control shared memory segment"); + goto fail; + } + + client->control = (jack_client_control_t *) addr; + + if ((ev_fd = server_event_connect (client)) < 0) { + jack_error ("cannot connect to server for event stream (%s)", strerror (errno)); + goto fail; + } + + client->event_fd = ev_fd; + + return client; + + fail: + if (client->engine) { + shmdt (client->engine); + } + if (client->control) { + shmdt ((char *) client->control); + } + if (req_fd >= 0) { + close (req_fd); + } + if (ev_fd >= 0) { + close (ev_fd); + } + + return 0; +} + +static void * +jack_client_thread (void *arg) + +{ + jack_client_t *client = (jack_client_t *) arg; + jack_client_control_t *control = client->control; + jack_event_t event; + char status = 0; + char c; + int err = 0; + + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + pthread_mutex_lock (&client_lock); + client->thread_ok = TRUE; + pthread_cond_signal (&client_ready); + pthread_mutex_unlock (&client_lock); + + while (err == 0) { + + if (poll (client->pollfd, client->pollmax, 1000) < 0) { + if (errno == EINTR) { + printf ("poll interrupted\n"); + continue; + } + jack_error ("poll failed in client (%s)", strerror (errno)); + status = -1; + break; + } + + if (client->pollfd[0].revents & ~POLLIN) { + jack_error ("engine has shut down socket; thread exiting"); + pthread_exit (0); + } + + if (client->pollfd[0].revents & POLLIN) { + + /* server has sent us an event. process the event and reply */ + + if (read (client->event_fd, &event, sizeof (event)) != sizeof (event)) { + jack_error ("cannot read server event (%s)", strerror (errno)); + err++; + break; + } + + status = 0; + + switch (event.type) { + case PortRegistered: + if (control->port_register) { + control->port_register (event.x.port_id, TRUE, control->port_register_arg); + } + break; + + case PortUnregistered: + if (control->port_register) { + control->port_register (event.x.port_id, FALSE, control->port_register_arg); + } + break; + + case GraphReordered: + status = jack_handle_reorder (client, &event); + break; + + case PortConnected: + case PortDisconnected: + status = jack_client_handle_port_connection (client, &event); + break; + + case BufferSizeChange: + jack_client_invalidate_port_buffers (client); + + if (control->bufsize) { + status = control->bufsize (control->nframes, control->bufsize_arg); + } + break; + + case SampleRateChange: + if (control->srate) { + status = control->srate (control->nframes, control->srate_arg); + } + break; + + case NewPortBufferSegment: + break; + + case PortMonitor: + if (control->port_monitor) { + control->port_monitor (event.x.port_id, TRUE, control->port_monitor_arg); + } + break; + + case PortUnMonitor: + if (control->port_monitor) { + control->port_monitor (event.x.port_id, FALSE, control->port_monitor_arg); + } + break; + + } + + if (write (client->event_fd, &status, sizeof (status)) != sizeof (status)) { + jack_error ("cannot send event response to engine (%s)", strerror (errno)); + err++; + break; + } + + } + + if (client->pollfd[1].revents & POLLIN) { + + /* the previous stage of the graph has told us to + process(). + */ + + control->state = JACK_CLIENT_STATE_TRIGGERED; + + if (read (client->graph_wait_fd, &c, sizeof (c)) != sizeof (c)) { + jack_error ("cannot clean up byte from inter-client pipe (%s)", strerror (errno)); + err++; + break; + } + + status = control->process (control->nframes, control->process_arg); + + if (!status) { + control->state = JACK_CLIENT_STATE_FINISHED; + } + + /* this may fail. if it does, the engine will discover + that for itcontrol due a cycle timeout, which is about + the best we can do without a lot of mostly wasted + effort. + */ + + write (client->graph_next_fd, &c, 1); + } + } + + return (void *) err; +} + +static int +jack_start_thread (jack_client_t *client) + +{ + pthread_attr_t *attributes = 0; + + if (client->engine->real_time) { + + /* Get the client thread to run as an RT-FIFO + scheduled thread of appropriate priority. + */ + + struct sched_param rt_param; + + attributes = (pthread_attr_t *) malloc (sizeof (pthread_attr_t)); + + pthread_attr_init (attributes); + + if (pthread_attr_setschedpolicy (attributes, SCHED_FIFO)) { + jack_error ("cannot set FIFO scheduling class for RT thread"); + return -1; + } + + if (pthread_attr_setscope (attributes, PTHREAD_SCOPE_SYSTEM)) { + jack_error ("Cannot set scheduling scope for RT thread"); + return -1; + } + + memset (&rt_param, 0, sizeof (rt_param)); + rt_param.sched_priority = client->engine->client_priority; + + if (pthread_attr_setschedparam (attributes, &rt_param)) { + jack_error ("Cannot set scheduling priority for RT thread (%s)", strerror (errno)); + return -1; + } + } + + if (pthread_create (&client->thread, attributes, jack_client_thread, client)) { + return -1; + } + return 0; +} + +int +jack_activate (jack_client_t *client) + +{ + jack_request_t req; + + if (client->control->type == ClientOutOfProcess && client->first_active) { + + pthread_mutex_init (&client_lock, NULL); + pthread_cond_init (&client_ready, NULL); + + pthread_mutex_lock (&client_lock); + + if (jack_start_thread (client)) { + pthread_mutex_unlock (&client_lock); + return -1; + } + + pthread_cond_wait (&client_ready, &client_lock); + pthread_mutex_unlock (&client_lock); + + if (!client->thread_ok) { + jack_error ("could not start client thread"); + return -1; + } + + client->first_active = FALSE; + } + + req.type = ActivateClient; + req.x.client_id = client->control->id; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send activate client request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read activate client result from server (%s)", strerror (errno)); + return -1; + } + + return req.status; +} + +int +jack_deactivate (jack_client_t *client) + +{ + jack_request_t req; + + req.type = DeactivateClient; + req.x.client_id = client->control->id; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send activate client request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read activate client result from server (%s)", strerror (errno)); + return -1; + } + + return req.status; +} + +int +jack_client_close (jack_client_t *client) + +{ + GSList *node; + jack_request_t req; + void *status; + + req.type = DropClient; + req.x.client_id = client->control->id; + + /* stop the thread */ + + pthread_cancel (client->thread); + pthread_join (client->thread, &status); + + shmdt ((char *) client->control); + shmdt (client->engine); + + for (node = client->port_segments; node; node = g_slist_next (node)) { + jack_port_segment_info_t *si; + si = (jack_port_segment_info_t *) node->data; + shmdt (si->address); + free (si); + } + + g_slist_free (client->port_segments); + g_slist_free (client->ports); + + if (client->graph_wait_fd) { + close (client->graph_wait_fd); + } + + if (client->graph_next_fd) { + close (client->graph_next_fd); + } + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send drop client request to server"); + req.status = -1; + } + + close (client->event_fd); + close (client->request_fd); + + free (client->pollfd); + free (client); + + return req.status; +} + +int +jack_load_client (const char *client_name, const char *path_to_so) + +{ + int fd; + jack_client_connect_request_t req; + jack_client_connect_result_t res; + + if ((fd = server_connect (0)) < 0) { + jack_error ("cannot connect to jack server"); + return 0; + } + + req.type = ClientDynamic; + + strncpy (req.name, client_name, sizeof (req.name) - 1); + req.name[sizeof(req.name)-1] = '\0'; + strncpy (req.object_path, path_to_so, sizeof (req.name) - 1); + req.object_path[sizeof(req.object_path)-1] = '\0'; + + if (write (fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send request to jack server (%s)", strerror (errno)); + close (fd); + return 0; + } + + if (read (fd, &res, sizeof (res)) != sizeof (res)) { + jack_error ("cannot read response from jack server (%s)", strerror (errno)); + close (fd); + return 0; + } + + close (fd); + return res.status; +} + +jack_client_t * +jack_driver_become_client (const char *client_name) + + +{ + int fd; + jack_client_connect_request_t req; + jack_client_connect_result_t res; + jack_client_t *client = 0; + + if ((fd = server_connect (0)) < 0) { + jack_error ("cannot connect to jack server"); + return 0; + } + + req.type = ClientDriver; + strncpy (req.name, client_name, sizeof (req.name) - 1); + req.name[sizeof(req.name)-1] = '\0'; + + if (write (fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send request to jack server (%s)", strerror (errno)); + close (fd); + return 0; + } + + if (read (fd, &res, sizeof (res)) != sizeof (res)) { + jack_error ("cannot read response from jack server (%s)", strerror (errno)); + close (fd); + return 0; + } + + if (res.status) { + return 0; + } + + client = jack_client_alloc (); + + client->request_fd = fd; + client->control = res.client_control; + client->engine = res.engine_control; + + /* allow the engine to act on the client's behalf + when dealing with in-process clients. + */ + + client->control->private_internal_client = client; + + return client; +} + +unsigned long jack_get_buffer_size (jack_client_t *client) + +{ + return client->engine->buffer_size; +} + +unsigned long jack_get_sample_rate (jack_client_t *client) + +{ + return client->engine->sample_rate; +} + +static jack_port_t * +jack_port_new (jack_port_id_t port_id, jack_control_t *control) + +{ + jack_port_t *port; + jack_port_shared_t *shared; + + shared = &control->ports[port_id]; + + port = (jack_port_t *) malloc (sizeof (jack_port_t)); + + port->shared = shared; + port->connections = 0; + port->tied = NULL; + + return port; +} + +jack_port_t * +jack_port_register (jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size) +{ + jack_request_t req; + jack_port_t *port = 0; + + /* before we get started, check a few basics */ + + if (flags & JackPortCanMonitor) { + if (client->control->port_monitor == NULL) { + jack_error ("you cannot register ports with PortCanMonitor " + "without a port monitor callback"); + return NULL; + } + } + + req.type = RegisterPort; + + strcpy ((char *) req.x.port_info.name, (const char *) client->control->name); + strcat ((char *) req.x.port_info.name, ":"); + strcat ((char *) req.x.port_info.name, port_name); + + strncpy (req.x.port_info.type, port_type, sizeof (req.x.port_info.type) - 1); + req.x.port_info.flags = flags; + req.x.port_info.buffer_size = buffer_size; + req.x.port_info.client_id = client->control->id; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send port registration request to server"); + return 0; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read port registration result from server"); + return 0; + } + + if (req.status != 0) { + return NULL; + } + + port = jack_port_new (req.x.port_info.port_id, client->engine); + client->ports = g_slist_prepend (client->ports, port); + + return port; +} + +int +jack_port_unregister (jack_client_t *client, jack_port_t *port) + +{ + jack_request_t req; + + req.type = UnRegisterPort; + req.x.port_info.port_id = port->shared->id; + req.x.port_info.client_id = client->control->id; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send port registration request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read port registration result from server"); + return -1; + } + + return req.status; +} + +int +jack_port_connect (jack_client_t *client, const char *source_port, const char *destination_port) + +{ + jack_request_t req; + + req.type = ConnectPorts; + + strncpy (req.x.connect.source_port, source_port, sizeof (req.x.connect.source_port) - 1); + req.x.connect.source_port[sizeof(req.x.connect.source_port) - 1] = '\0'; + strncpy (req.x.connect.destination_port, destination_port, sizeof (req.x.connect.destination_port) - 1); + req.x.connect.destination_port[sizeof(req.x.connect.destination_port) - 1] = '\0'; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send port connection request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read port connection result from server"); + return -1; + } + + return req.status; +} + +int +jack_port_disconnect (jack_client_t *client, const char *source_port, const char *destination_port) + +{ + jack_request_t req; + + req.type = DisconnectPorts; + + strncpy (req.x.connect.source_port, source_port, sizeof (req.x.connect.source_port) - 1); + req.x.connect.source_port[sizeof(req.x.connect.source_port) - 1] = '\0'; + strncpy (req.x.connect.destination_port, destination_port, sizeof (req.x.connect.destination_port) - 1); + req.x.connect.destination_port[sizeof(req.x.connect.destination_port) - 1] = '\0'; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send port connection request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read port connection result from server"); + return -1; + } + + return req.status; +} + +int +jack_engine_takeover_timebase (jack_client_t *client) + +{ + jack_request_t req; + + req.type = SetTimeBaseClient; + req.x.client_id = client->control->id; + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send set time base request to server"); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read set time base result from server"); + return -1; + } + + return req.status; +} + +void +jack_update_time (jack_client_t *client, nframes_t time) + +{ + client->control->frame_time = time; +} + +void +jack_set_error_function (void (*func) (const char *, ...)) +{ + jack_error = func; +} + +void * +jack_port_get_buffer (jack_port_t *port, nframes_t nframes) + +{ + GSList *node, *next; + + /* Output port. The buffer was assigned by the engine + when the port was registered. + */ + + if (port->shared->flags & JackPortIsOutput) { + if (port->tied) { + return jack_port_get_buffer (port->tied, nframes); + } + return port->shared->buffer; + } + + /* Input port. + */ + + if ((node = port->connections) == NULL) { + + /* no connections; return a zero-filled buffer */ + + return jack_zero_filled_buffer; + } + + if ((next = g_slist_next (node)) == NULL) { + + /* one connection: use zero-copy mode - just pass + the buffer of the connected (output) port. + */ + + return ((jack_port_shared_t *) node->data)->buffer; + } + + /* multiple connections. use a local buffer and mixdown + the incoming data to that buffer. we have already + established the existence of a mixdown function + during the connection process. + */ + + if (port->shared->buffer == NULL) { + port->shared->buffer = jack_pool_alloc + (port->shared->type_info.buffer_scale_factor * sizeof (sample_t) * nframes); + } + + port->shared->type_info.mixdown (port, nframes); + + return port->shared->buffer; +} + +int +jack_port_tie (jack_port_t *dst, jack_port_t *src) + +{ + if (dst->shared->client_id != src->shared->client_id) { + jack_error ("cannot tie ports not owned by the same client"); + return -1; + } + + if (dst->shared->flags & JackPortIsOutput) { + jack_error ("cannot tie an input port"); + return -1; + } + + dst->own_buffer = dst->shared->buffer; + dst->tied = src; + return 0; +} + +int +jack_port_untie (jack_port_t *port) + +{ + if (port->tied == NULL) { + jack_error ("port \"%s\" is not tied", port->shared->name); + return -1; + } + port->shared->buffer = port->own_buffer; + port->tied = NULL; + return 0; +} + +int +jack_set_process_callback (jack_client_t *client, JackProcessCallback callback, void *arg) + +{ + if (client->control->active) { + return -1; + } + client->control->process_arg = arg; + client->control->process = callback; + return 0; +} + +int +jack_set_buffer_size_callback (jack_client_t *client, JackBufferSizeCallback callback, void *arg) + +{ + if (client->control->active) { + return -1; + } + client->control->bufsize_arg = arg; + client->control->bufsize = callback; + + /* Now invoke it */ + + callback (client->engine->buffer_size, arg); + + return 0; +} + +int +jack_set_sample_rate_callback (jack_client_t *client, JackSampleRateCallback callback, void *arg) + +{ + if (client->control->active) { + return -1; + } + client->control->srate_arg = arg; + client->control->srate = callback; + + /* Now invoke it */ + + callback (client->engine->sample_rate, arg); + + return 0; +} + +int +jack_set_port_registration_callback(jack_client_t *client, JackPortRegistrationCallback callback, void *arg) + +{ + if (client->control->active) { + return -1; + } + client->control->port_register_arg = arg; + client->control->port_register = callback; + return 0; +} + +int +jack_set_port_monitor_callback (jack_client_t *client, JackPortMonitorCallback callback, void *arg) + +{ + if (client->control->active) { + return -1; + } + client->control->port_monitor_arg = arg; + client->control->port_monitor = callback; + return 0; +} + +int +jack_get_process_start_fd (jack_client_t *client) +{ + /* once this has been called, the client thread + does not sleep on the graph wait fd. + */ + + client->pollmax = 1; + return client->graph_wait_fd; + +} + +int +jack_get_process_done_fd (jack_client_t *client) +{ + return client->graph_next_fd; +} + +int +jack_port_request_monitor (jack_client_t *client, const char *port_name, int onoff) + +{ + jack_request_t req; + int n; + + req.type = (onoff ? RequestPortMonitor : RequestPortUnMonitor); + req.x.port_info.port_id = jack_port_id_by_name (client, port_name); + + if (write (client->request_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot send request to jack server (%s)", strerror (errno)); + return -1; + } + + if ((n = read (client->request_fd, &req, sizeof (req))) != sizeof (req)) { + jack_error ("cannot read response from jack server (%s)", strerror (errno)); + return -1; + } + + return req.status; +} + diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..9fd0cb7 --- /dev/null +++ b/configure.in @@ -0,0 +1,64 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(client.c) + +AC_CONFIG_AUX_DIR(.) + +JACK_MAJOR_VERSION=0 +JACK_MINOR_VERSION=2 +JACK_MICRO_VERSION=4 + +BETA= + +AC_SUBST(JACK_MAJOR_VERSION) +AC_SUBST(JACK_MINOR_VERSION) +AC_SUBST(JACK_MICRO_VERSION) + +JACK_SO_VERSION=${JACK_MAJOR_VERSION}:${JACK_MINOR_VERSION}:${JACK_MICRO_VERSION}${BETA} +JACK_VERSION=$JACK_MAJOR_VERSION.$JACK_MINOR_VERSION.${JACK_MICRO_VERSION}${BETA} +JACK_RELEASE=$JACK_MAJOR_VERSION-$JACK_MINOR_VERSION-${JACK_MICRO_VERSION}${BETA} + +AC_SUBST(JACK_SO_VERSION) +AC_SUBST(JACK_VERSION) +AC_SUBST(JACK_RELEASE) + +AM_INIT_AUTOMAKE(jack,${JACK_VERSION}) + +CFLAGS="-g -Wall -I.. -D_REENTRANT" +OPT_CFLAGS="-D_REENTRANT -O6 -Wall -fomit-frame-pointer -ffast-math -fstrength-reduce -funroll-loops -fmove-all-movables" + +AM_PATH_GLIB(1.0.0,,[AC_MSG_ERROR([*** JACK requires glib, but it doesn't appear to be installed])]) + +CFLAGS="$CFLAGS $GLIB_CFLAGS" +CXXFLAGS="$CFLAGS" +LIBS="$LIBS $GLIB_LIBS" + +AC_ARG_ENABLE(optimize, + [ --enable-optimize ask the compiler for its best optimizations.], + [ if test "x$enable_optimize" != "xno" ; then CFLAGS="$CFLAGS $OPT_CFLAGS" ; fi ]) + +XTRA="" + +AC_ARG_ENABLE(fltk-client, + [ --enable-fltk-client build the FLTK test client.]) + +if test "x$enable_fltk_client" != "no" ; then + AC_CHECK_LIB(fltk,main, + [ XTRA="$XTRA jack_fltk_client" ], + [ AC_MSG_ERROR([*** you can't build the FLTK client without the FLTK library])], + [ -L/usr/X11R6/lib -lX11 -lXext ]) +fi + +AC_SUBST(XTRA) + +AC_PROG_CXX +AC_PROG_LD +AM_PROG_LIBTOOL + +dnl check for the libraries we need + +AC_OUTPUT(Makefile + jack.pc + jack/Makefile) + + + diff --git a/conftest b/conftest Binary files differnew file mode 100755 index 0000000..39e41ff --- /dev/null +++ b/conftest diff --git a/conftest.c b/conftest.c new file mode 100644 index 0000000..cb8c7a5 --- /dev/null +++ b/conftest.c @@ -0,0 +1 @@ +static int dummy; diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..37fbfe3 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,62 @@ +WEBSITE = ardour.sourceforge.net + +HTML-FILES := index.html \ + issues.html \ + manual.html \ + features.html \ + mailinglist.html \ + contributors.html \ + intro.html \ + download.html \ + index.html \ + news.html \ + links.html \ + requirements.html \ + configuring.html \ + helping.html \ + compiling.html \ + todo.html + +user = $(shell whoami) +fullname = $(shell awk -F: '/$(user)/ { print $$5}' /etc/passwd) + +%.html: %.m4 header.html trailer.html + m4 -P -E -I. $< > $@ + sed -e "s/@LASTMOD@/`date`/" -e "s/@USER@/$(fullname)/" \ + < $@ > $@.XXX && mv $@.XXX $@ + +.PHONY: manual +.PHONY: upload + +html: $(HTML-FILES) + +upload: build-updir + cd updir ; \ + if [ "`ls`" ] ; then \ + (echo "cd /home/groups/j/ja/jackit/htdocs && tar -zxvf - ; exit"; tar cf - *.html | gzip) | \ + ssh \$(user)@shell.sourceforge.net; \ + cd .. ; \ + touch last-upload ; \ + fi + +upload-images: build-updir + (echo "cd /home/groups/j/ja/jackit/htdocs && tar -zxvf - ; exit"; tar cf - *.png | gzip) | \ + ssh \$(user)@shell.sourceforge.net; \ + +build-updir: + if [ ! -d updir ] ; then mkdir updir ; else rm -rf updir/* ; fi ; \ + if [ ! -f last-upload ] ; then touch --date="Jan 1st 1970" last-upload; fi ; \ + uptime=`ls -l --full-time last-upload | awk '{printf ("%s %s %s %s %s\n", $$6, $$7, $$8, $$9, $$10)}'`; \ + tar -chf - --newer="$$uptime" \ + --exclude=updir \ + --exclude="*.m4" \ + --exclude=Makefile \ + --exclude=CVS \ + --exclude=manual \ + --exclude=header.html \ + --exclude=trailer.html \ + --exclude=last-upload . | (cd updir; tar xf - ) ; \ + +clean: + rm -f $(HTML-FILES) + diff --git a/doc/compiling.m4 b/doc/compiling.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/compiling.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/configuring.m4 b/doc/configuring.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/configuring.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/contributors.m4 b/doc/contributors.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/contributors.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/download.m4 b/doc/download.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/download.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/faq.m4 b/doc/faq.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/faq.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/features.m4 b/doc/features.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/features.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/header.html b/doc/header.html new file mode 100644 index 0000000..5f52aac --- /dev/null +++ b/doc/header.html @@ -0,0 +1,54 @@ +<!-- jackit standard header --> + +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <META NAME="GENERATOR" CONTENT="Emacs 20.2"> + <TITLE>Ardour</TITLE> +</HEAD> +<BODY> + +<table border=0 cellspacing=10 cellpadding=0> +<tr valign="top"> + +<!-- Sidebar --> +<td> + +<a href=http://ardour.sourceforge.net/> +<img border=0 src="jack2.png" alt="JACK audio connection kit"></a> +</p> +</font> + +<p align="center"> +<ul> + <li><a href="http://sourceforge.net/project/?group_id=2218">Project Page</a> + <li><a href="intro.html">Introduction</a> + <li><a href="features.html">Features</a> + <li><a href="requirements.html">Requirements</a> + <p></p> + <li><a href="news.html">News and History</a> + <p></p> + <li><a href="download.html">Download</a> + <li><a href="compiling.html">Compiling</a> + <li><a href="configuring.html">Configuring</a> + <p></p> + <li><a href="manual.html">Manual</a> + <li><a href="faq.html">FAQ</a> + <p></p> + <li><a href="helping.html">Getting Involved</a> + <li><a href="todo.html">To Do List</a> + <li><a href="mailinglist.html">Mailing Lists</a> + <li><a href="links.html">Links</a> + <li><a href="contributors.html">Contributors</a> +</ul> +</p> +<p> +Web: <a href=http://ardour.sourceforge.net/>http://jackit.sourceforge.net/</a><br> +Email: (MEMBER-ONLY) <a href=mailto:jackit@lists.sourceforge.net>jackit@lists.sourceforge.net</a> +</p> +</td> + +<td> +<!-- Main body --> +<div class=mainbody> + +<!-- Standard header ends --------------------------------------------------> diff --git a/doc/helping.m4 b/doc/helping.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/helping.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/index.m4 b/doc/index.m4 new file mode 100644 index 0000000..32d14e6 --- /dev/null +++ b/doc/index.m4 @@ -0,0 +1,7 @@ +<html> + +m4_include(`header.html') + +<h2>About JACK</h2> + +m4_include(`trailer.html') diff --git a/doc/intro.m4 b/doc/intro.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/intro.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/issues.m4 b/doc/issues.m4 new file mode 100644 index 0000000..c4080d4 --- /dev/null +++ b/doc/issues.m4 @@ -0,0 +1,85 @@ +<html> + +m4_include(`header.html') + +<h2>Issues to consider when porting programs to JACK</h2> + +<h4>Sample bit width assumptions</h4> + +A lot existing Linux audio software tends to assume that audio samples +are 8 or 16 bits wide, and uses <code>short</code> to store them. This +does not work with JACK, where all sample data, regardless of the +original data format in which it was obtained (e.g. from disk), is +stored as a floating point value normalized to the range -1.0 to +1.0. + +<h4>Channel interleaving assumptions</h4> + +Almost all existing Linux audio software assumes that when delivering +a sample stream with more than one channel, the samples should be +interleaved. This does not work with JACK, where all sample streams +are mono. + +<h4>Block-on-write or block-on-read assumptions</h4> + +Quite a lot of existing Linux audio software tends to be structured +around the blocking behaviour of a call to write(2) or read(2) when +the file descriptor concerned refers to the audio interface. They +often have this structure: + +<verbatim> + + // Playback + + while (1) { + get_sample_date_from_somewhere (buf); + write (audiofd, buf, bufsize); + } + + // Capture + + while (1) { + read (audiofd, buf, bufsize); + put_sample_data_somewhere (buf); + } + +</verbatim> + +These structures don't work with JACK, which is entirely callback +driven and moves audio data by copying it to and from memory +locations, not files. Instead, its necessary to define a +<code>process()</code> callback which does this: + +<verbatim> + + // playback + + int + process (nframes_t nframes) + { + get_nframes_of_data_from_somewhere_without_blocking (buf); + sample_t *addr = jack_port_get_buffer (playback_port); + memcpy (addr, buf, nframes * sizeof (sample_t)); + } + + // capture + + int + process (nframes_t nframes) + { + sample_t *addr = jack_port_get_buffer (capture_port); + memcpy (buf, addr, nframes * sizeof (sample_t)); + put_nframes_of_data_somewhere_without_blocking (buf); + } + +</verbatim> + +The code in the <code>process()</code> function should not under +(almost) any circumstances block: that is, it may not read/write data +from/to a file, it may not call malloc(), it may not use +pthread_mutex_lock(), and it should generally avoid system calls. the +<code>process()</code> callback will be executed when the JACK server +decides it should be, and it cannot be used to time other parts of the +program. + +m4_include(`trailer.html') + diff --git a/doc/links.m4 b/doc/links.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/links.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/mailinglist.m4 b/doc/mailinglist.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/mailinglist.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/manual.m4 b/doc/manual.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/manual.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/news.m4 b/doc/news.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/news.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/requirements.m4 b/doc/requirements.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/requirements.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/todo.m4 b/doc/todo.m4 new file mode 100644 index 0000000..c1525b3 --- /dev/null +++ b/doc/todo.m4 @@ -0,0 +1,4 @@ +<html> + +m4_include(`header.html') +m4_include(`trailer.html') diff --git a/doc/trailer.html b/doc/trailer.html new file mode 100644 index 0000000..f447800 --- /dev/null +++ b/doc/trailer.html @@ -0,0 +1,23 @@ +<!-- Standard trailer begins -----------------------------------------------> + +<p class="lastmod" align="right"> +<br> +<br> +<small> +<i>Last modified: @LASTMOD@ by @USER@</i> +<br><a href="http://www.anybrowser.org/campaign/">[Best Viewed with Any Browser]</a> +</small> +</p> + +</div> +</font> + +</td></tr> +</table> + +<A href="http://sourceforge.net"> +<IMG src="http://sourceforge.net/sflogo.php?group_id=2218&type=1" width="88" height="31" border="0"></A> +</td> + +</BODY> +</HTML> diff --git a/driver.c b/driver.c new file mode 100644 index 0000000..8860543 --- /dev/null +++ b/driver.c @@ -0,0 +1,238 @@ +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <jack/driver.h> +#include <jack/internal.h> +#include <jack/error.h> + +static int dummy_attach (jack_driver_t *drv, jack_engine_t *eng) { return 0; } +static int dummy_detach (jack_driver_t *drv, jack_engine_t *eng) { return 0; } +static int dummy_wait (jack_driver_t *drv) { return 0; } +static nframes_t dummy_frames_since_cycle_start (jack_driver_t *drv) { return 0; } +static ClockSyncStatus dummy_clock_sync_status (jack_driver_t *drv, channel_t chn) { return ClockMaster; } +static int dummy_audio_stop (jack_driver_t *drv) { return 0; } +static int dummy_audio_start (jack_driver_t *drv) { return 0;; } +static void dummy_set_hw_monitoring (jack_driver_t *drv, int yn) { return; } +static int dummy_change_sample_clock (jack_driver_t *drv, SampleClockMode mode) { return 0; } +static int dummy_reset_parameters (jack_driver_t *drv, nframes_t frames_per_cycle, nframes_t rate) { return 0; } +static void dummy_mark_channel_silent (jack_driver_t *drv, unsigned long chn) { return; } +static void dummy_request_monitor_input (jack_driver_t *drv, unsigned long chn, int yn) { return ; } +static void dummy_request_all_monitor_input (jack_driver_t *drv, int yn) { return; } + +int +jack_driver_monitoring_input (jack_driver_t *driver, channel_t chn) +{ + return chn != NoChannel && (driver->all_monitor_in || (driver->input_monitor_mask & (1<<chn))); +} + +void +jack_driver_init (jack_driver_t *driver) + +{ + memset (driver, 0, sizeof (*driver)); + + driver->input_monitor_mask = 0; + + driver->attach = dummy_attach; + driver->detach = dummy_detach; + driver->wait = dummy_wait; + driver->frames_since_cycle_start = dummy_frames_since_cycle_start; + driver->clock_sync_status = dummy_clock_sync_status; + driver->audio_stop = dummy_audio_stop; + driver->audio_start = dummy_audio_start; + driver->set_hw_monitoring = dummy_set_hw_monitoring ; + driver->change_sample_clock = dummy_change_sample_clock; + driver->reset_parameters = dummy_reset_parameters; + driver->mark_channel_silent = dummy_mark_channel_silent; + driver->request_monitor_input = dummy_request_monitor_input; + driver->request_all_monitor_input = dummy_request_all_monitor_input; + driver->monitoring_input = jack_driver_monitoring_input; + driver->engine = 0; + + pthread_mutex_init (&driver->clock_sync_lock, 0); + driver->clock_sync_listeners = 0; + + pthread_mutex_init (&driver->input_monitor_lock, 0); + driver->input_monitor_listeners = 0; +} + +void +jack_driver_release (jack_driver_t *driver) + +{ + GSList *node; + + for (node = driver->clock_sync_listeners; node; node = g_slist_next (node)) { + free (node->data); + } + g_slist_free (driver->clock_sync_listeners); + + for (node = driver->input_monitor_listeners; node; node = g_slist_next (node)) { + free (node->data); + } + g_slist_free (driver->input_monitor_listeners); +} + +int +jack_driver_listen_for_clock_sync_status (jack_driver_t *driver, + ClockSyncListenerFunction func, + void *arg) +{ + ClockSyncListener *csl; + + csl = (ClockSyncListener *) malloc (sizeof (ClockSyncListener)); + csl->function = func; + csl->arg = arg; + csl->id = driver->next_clock_sync_listener_id++; + + pthread_mutex_lock (&driver->clock_sync_lock); + driver->clock_sync_listeners = g_slist_prepend (driver->clock_sync_listeners, csl); + pthread_mutex_unlock (&driver->clock_sync_lock); + return csl->id; +} + +int +jack_driver_stop_listening_to_clock_sync_status (jack_driver_t *driver, int which) + +{ + GSList *node; + int ret = -1; + pthread_mutex_lock (&driver->clock_sync_lock); + for (node = driver->clock_sync_listeners; node; node = g_slist_next (node)) { + if (((ClockSyncListener *) node->data)->id == which) { + driver->clock_sync_listeners = g_slist_remove_link (driver->clock_sync_listeners, node); + free (node->data); + g_slist_free_1 (node); + ret = 0; + break; + } + } + pthread_mutex_unlock (&driver->clock_sync_lock); + return ret; +} + +int +jack_driver_listen_for_input_monitor_status (jack_driver_t *driver, + InputMonitorListenerFunction func, + void *arg) +{ + InputMonitorListener *iml; + + iml = (InputMonitorListener *) malloc (sizeof (InputMonitorListener)); + iml->function = func; + iml->arg = arg; + iml->id = driver->next_input_monitor_listener_id++; + + pthread_mutex_lock (&driver->input_monitor_lock); + driver->input_monitor_listeners = g_slist_prepend (driver->input_monitor_listeners, iml); + pthread_mutex_unlock (&driver->input_monitor_lock); + return iml->id; +} + +int +jack_driver_stop_listening_to_input_monitor_status (jack_driver_t *driver, int which) + +{ + GSList *node; + int ret = -1; + + pthread_mutex_lock (&driver->input_monitor_lock); + for (node = driver->input_monitor_listeners; node; node = g_slist_next (node)) { + if (((InputMonitorListener *) node->data)->id == which) { + driver->input_monitor_listeners = g_slist_remove_link (driver->input_monitor_listeners, node); + free (node->data); + g_slist_free_1 (node); + ret = 0; + break; + } + } + pthread_mutex_unlock (&driver->input_monitor_lock); + return ret; +} + +void jack_driver_clock_sync_notify (jack_driver_t *driver, channel_t chn, ClockSyncStatus status) +{ + GSList *node; + + pthread_mutex_lock (&driver->input_monitor_lock); + for (node = driver->input_monitor_listeners; node; node = g_slist_next (node)) { + ClockSyncListener *csl = (ClockSyncListener *) node->data; + csl->function (chn, status, csl->arg); + } + pthread_mutex_unlock (&driver->input_monitor_lock); + +} + +void jack_driver_input_monitor_notify (jack_driver_t *driver, channel_t chn, int status) +{ + GSList *node; + + pthread_mutex_lock (&driver->input_monitor_lock); + for (node = driver->input_monitor_listeners; node; node = g_slist_next (node)) { + InputMonitorListener *iml = (InputMonitorListener *) node->data; + iml->function (chn, status, iml->arg); + } + pthread_mutex_unlock (&driver->input_monitor_lock); +} + + +jack_driver_t * +jack_driver_load (const char *path_to_so, ...) + +{ + va_list ap; + const char *errstr; + dlhandle handle; + jack_driver_t *driver; + jack_driver_t *(*initialize)(va_list); + void (*finish)(jack_driver_t *); + + va_start (ap, path_to_so); + handle = dlopen (path_to_so, RTLD_NOW|RTLD_GLOBAL); + + if (handle == 0) { + if ((errstr = dlerror ()) != 0) { + jack_error ("can't load \"%s\": %s", path_to_so, errstr); + } else { + jack_error ("bizarre error loading driver shared object %s", path_to_so); + } + va_end (ap); + return 0; + } + + initialize = dlsym (handle, "driver_initialize"); + + if ((errstr = dlerror ()) != 0) { + jack_error ("no initialize function in shared object %s\n", path_to_so); + dlclose (handle); + va_end (ap); + return 0; + } + + finish = dlsym (handle, "driver_finish"); + + if ((errstr = dlerror ()) != 0) { + jack_error ("no finish function in in shared driver object %s", path_to_so); + dlclose (handle); + va_end (ap); + return 0; + } + + if ((driver = initialize (ap)) != 0) { + driver->handle = handle; + driver->finish = finish; + } + + va_end (ap); + + return driver; + +} + +void +jack_driver_unload (jack_driver_t *driver) +{ + driver->finish (driver); + dlclose (driver->handle); +} diff --git a/engine.c b/engine.c new file mode 100644 index 0000000..4bfa112 --- /dev/null +++ b/engine.c @@ -0,0 +1,2250 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <unistd.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/shm.h> +#include <stdio.h> +#include <stdarg.h> +#include <sys/ipc.h> +#include <signal.h> +#include <sys/types.h> +#include <string.h> +#include <limits.h> +#include <sys/mman.h> + +#include <asm/msr.h> + +#include <jack/internal.h> +#include <jack/engine.h> +#include <jack/driver.h> + +typedef struct { + + jack_port_internal_t *source; + jack_port_internal_t *destination; + +} jack_connection_internal_t; + +typedef struct _jack_client_internal { + + jack_client_control_t *control; + + int request_fd; + int event_fd; + int subgraph_start_fd; + int subgraph_wait_fd; + GSList *ports; /* protected by engine->graph_lock */ + int shm_id; + int shm_key; + unsigned long rank; + struct _jack_client_internal *next_client; /* not a linked list! */ + dlhandle handle; + +} jack_client_internal_t; + +static int jack_port_assign_buffer (jack_engine_t *, jack_port_internal_t *); +static jack_port_internal_t *jack_get_port_by_name (jack_engine_t *, const char *name); + +static void jack_client_delete (jack_engine_t *, jack_client_internal_t *); +static void jack_remove_client (jack_engine_t *engine, jack_client_internal_t *client); + +static jack_client_internal_t *jack_client_internal_new (jack_engine_t *engine, int fd, jack_client_connect_request_t *); +static jack_client_internal_t *jack_client_internal_by_id (jack_engine_t *engine, jack_client_id_t id); + +static void jack_sort_graph (jack_engine_t *engine, int take_lock); +static int jack_rechain_graph (jack_engine_t *engine, int take_lock); +static int jack_get_fifo_fd (jack_engine_t *engine, int which_fifo); +static int jack_create_fifo (jack_engine_t *engine, int which_fifo); +static int jack_port_do_connect (jack_engine_t *engine, const char *source_port, const char *destination_port); +static int jack_port_do_disconnect (jack_engine_t *engine, const char *source_port, const char *destination_port); + +static int jack_port_do_unregister (jack_engine_t *engine, jack_request_t *); +static int jack_port_do_register (jack_engine_t *engine, jack_request_t *); +static void jack_port_release (jack_engine_t *engine, jack_port_internal_t *); +static void jack_port_clear_connections (jack_engine_t *engine, jack_port_internal_t *port); +static int jack_port_disconnect_internal (jack_engine_t *engine, jack_port_internal_t *src, + jack_port_internal_t *dst, int sort_graph); + +static void jack_port_registration_notify (jack_engine_t *, jack_port_id_t, int); +static int jack_send_connection_notification (jack_engine_t *, jack_client_id_t, jack_port_id_t, jack_port_id_t, int); +static int jack_deliver_event (jack_engine_t *, jack_client_internal_t *, jack_event_t *); + +static void jack_audio_port_mixdown (jack_port_t *port, nframes_t nframes); + +/* This is a disgusting kludge to work around issues with shmat. + A more robust solution is needed. +*/ + +static char *top_end_of_unmapped_memory = (char *) (1048576 * 1536); /* 1.5GB */ +static char *low_end_of_unmapped_memory = (char *) (1048576 * 1024); /* 1GB */ + +static char * +fixed_shmat (int shmid, char *shmaddr, int shmflg, size_t size) +{ + char *addr; + char *attempt; + + if (shmaddr != 0) { + return shmat (shmid, shmaddr, shmflg); + } + + attempt = (char *) (top_end_of_unmapped_memory - size); + + while (attempt > low_end_of_unmapped_memory) { + if ((addr = (char *) shmat (shmid, attempt, shmflg|SHM_RND)) != (char *) -1) { + top_end_of_unmapped_memory = addr; + return addr; + } + attempt -= size; + } + return (char *) -1; +} + +jack_port_type_info_t builtin_port_types[] = { + { JACK_DEFAULT_AUDIO_TYPE, jack_audio_port_mixdown, 1 }, + { 0, NULL } +}; + +static inline int +jack_client_is_inprocess (jack_client_internal_t *client) +{ + return (client->control->type == ClientDynamic) || (client->control->type == ClientDriver); +} + +static void +default_jack_error (const char *fmt, ...) + +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); +} + +void (*jack_error)(const char *fmt, ...) = &default_jack_error; + +static +void shm_destroy (int status, void *arg) + +{ + int shm_id = (int) arg; + shmctl (shm_id, IPC_RMID, 0); +} + +static +void unlink_path (int status, void *arg) +{ + char *path = (char *) arg; + unlink (path); + free (arg); +} + +static int +make_sockets (int fd[2]) +{ + struct sockaddr_un addr; + int i; + + /* First, the master server socket */ + + if ((fd[0] = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + jack_error ("cannot create server socket (%s)", strerror (errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + for (i = 0; i < 999; i++) { + snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "/tmp/jack_%d", i); + if (access (addr.sun_path, F_OK) != 0) { + break; + } + } + + if (i == 999) { + jack_error ("all possible server socket names in use!!!"); + close (fd[0]); + return -1; + } + + on_exit (unlink_path, (void *) strdup (addr.sun_path)); + + if (bind (fd[0], (struct sockaddr *) &addr, sizeof (addr)) < 0) { + jack_error ("cannot bind server to socket (%s)", strerror (errno)); + close (fd[0]); + return -1; + } + + if (listen (fd[0], 1) < 0) { + jack_error ("cannot enable listen on server socket (%s)", strerror (errno)); + close (fd[0]); + return -1; + } + + /* Now the client/server event ack server socket */ + + if ((fd[1] = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + jack_error ("cannot create event ACK socket (%s)", strerror (errno)); + close (fd[0]); + return -1; + } + + addr.sun_family = AF_UNIX; + for (i = 0; i < 999; i++) { + snprintf (addr.sun_path, sizeof (addr.sun_path) - 1, "/tmp/jack_ack_%d", i); + if (access (addr.sun_path, F_OK) != 0) { + break; + } + } + + if (i == 999) { + jack_error ("all possible server ACK socket names in use!!!"); + close (fd[0]); + close (fd[1]); + return -1; + } + + on_exit (unlink_path, (void *) strdup (addr.sun_path)); + + if (bind (fd[1], (struct sockaddr *) &addr, sizeof (addr)) < 0) { + jack_error ("cannot bind server to socket (%s)", strerror (errno)); + close (fd[0]); + close (fd[1]); + return -1; + } + + if (listen (fd[1], 1) < 0) { + jack_error ("cannot enable listen on server socket (%s)", strerror (errno)); + close (fd[0]); + close (fd[1]); + return -1; + } + + return 0; +} + +static void +jack_cleanup_clients (jack_engine_t *engine) + +{ + jack_client_control_t *ctl; + jack_client_internal_t *client; + GSList *node; + GSList *remove = 0; + static int x = 0; + + x++; + + pthread_mutex_lock (&engine->graph_lock); + for (node = engine->clients; node; node = g_slist_next (node)) { + + client = (jack_client_internal_t *) node->data; + ctl = client->control; + + if (ctl->state > JACK_CLIENT_STATE_NOT_TRIGGERED) { + remove = g_slist_prepend (remove, node->data); + printf ("%d: removing failed client %s\n", x, client->control->name); + } + } + pthread_mutex_unlock (&engine->graph_lock); + + if (remove) { + for (node = remove; node; node = g_slist_next (node)) { + jack_remove_client (engine, (jack_client_internal_t *) node->data); + } + g_slist_free (remove); + } +} + +static int +jack_add_port_segment (jack_engine_t *engine, unsigned long nports) + +{ + jack_port_segment_info_t *si; + key_t key; + int id; + char *addr; + int offset; + size_t size; + size_t step; + + key = random(); + size = nports * sizeof (sample_t) * engine->control->buffer_size; + + if ((id = shmget (key, size, IPC_CREAT|0666)) < 0) { + jack_error ("cannot create new port segment of %d bytes, key = 0x%x (%s)", size, key, strerror (errno)); + return -1; + } + + if ((addr = fixed_shmat (id, 0, 0, size)) == (char *) -1) { + jack_error ("cannot attach new port segment (%s)", strerror (errno)); + shmctl (id, IPC_RMID, 0); + return -1; + } + + on_exit (shm_destroy, (void *) id); + + si = (jack_port_segment_info_t *) malloc (sizeof (jack_port_segment_info_t)); + si->shm_key = key; + si->address = addr; + + engine->port_segments = g_slist_prepend (engine->port_segments, si); + engine->port_segment_key = key; /* XXX fix me */ + engine->port_segment_address = addr; /* XXX fix me */ + + pthread_mutex_lock (&engine->buffer_lock); + + offset = 0; + + step = engine->control->buffer_size * sizeof (sample_t); + + while (offset < size) { + jack_port_buffer_info_t *bi; + + bi = (jack_port_buffer_info_t *) malloc (sizeof (jack_port_buffer_info_t)); + bi->shm_key = key; + bi->offset = offset; + + /* we append because we want the list to be in memory-address order */ + + engine->port_buffer_freelist = g_slist_append (engine->port_buffer_freelist, bi); + + offset += step; + } + + /* convert the first chunk of the segment into a zero-filled area */ + + if (engine->silent_buffer == 0) { + engine->silent_buffer = (jack_port_buffer_info_t *) engine->port_buffer_freelist->data; + + engine->port_buffer_freelist = g_slist_remove_link (engine->port_buffer_freelist, engine->port_buffer_freelist); + + memset (engine->port_segment_address + engine->silent_buffer->offset, 0, + sizeof (sample_t) * engine->control->buffer_size); + } + + pthread_mutex_unlock (&engine->buffer_lock); + + /* XXX notify all clients of new segment */ + + return 0; +} + +static int +jack_set_buffer_size (jack_engine_t *engine, nframes_t nframes) +{ + /* XXX this is not really right, since it only works for + audio ports. + */ + + engine->control->buffer_size = nframes; + jack_add_port_segment (engine, engine->control->port_max); + return 0; +} + +static int +jack_set_sample_rate (jack_engine_t *engine, nframes_t nframes) + +{ + engine->control->sample_rate = nframes; + return 0; +} + +static int +jack_process (jack_engine_t *engine, nframes_t nframes) +{ + int err = 0; + jack_client_internal_t *client; + jack_client_control_t *ctl; + GSList *node; + struct pollfd pollfd[1]; + char c; + +// unsigned long then, now; +// rdtscl (then); + + if (pthread_mutex_trylock (&engine->graph_lock) != 0) { + return 0; + } + + for (node = engine->clients; node; node = g_slist_next (node)) { + ctl = ((jack_client_internal_t *) node->data)->control; + ctl->state = JACK_CLIENT_STATE_NOT_TRIGGERED; + ctl->nframes = nframes; + } + + if (engine->timebase_client) { + engine->control->frame_time = engine->timebase_client->control->frame_time; + } + + for (node = engine->clients; err == 0 && node; ) { + + client = (jack_client_internal_t *) node->data; + + if (!client->control->active) { + node = g_slist_next (node); + continue; + } + + ctl = client->control; + + if (jack_client_is_inprocess (client)) { + + /* in-process client ("plugin") */ + + if (ctl->process (nframes, ctl->process_arg) == 0) { + ctl->state = JACK_CLIENT_STATE_FINISHED; + } else { + jack_error ("in-process client %s failed", client->control->name); + ctl->state = JACK_CLIENT_STATE_TRIGGERED; + err++; + break; + } + + node = g_slist_next (node); + + } else { + + /* out of process subgraph */ + + if (write (client->subgraph_start_fd, &c, sizeof (c)) != sizeof (c)) { + jack_error ("cannot initiate graph processing (%s)", strerror (errno)); + err++; + break; + } + + /* now wait for the result. use poll instead of read so that we + can timeout effectively. + */ + + pollfd[0].fd = client->subgraph_wait_fd; + pollfd[0].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; + + if (poll (pollfd, 1, engine->driver->period_interval) < 0) { + jack_error ("engine cannot poll for graph completion (%s)", strerror (errno)); + err++; + break; + } + + if (pollfd[0].revents == 0) { + jack_error ("subgraph starting at %s timed out (state = %d)", + client->control->name, client->control->state); + err++; + break; + } else if (pollfd[0].revents & ~POLLIN) { + jack_error ("error/hangup on graph wait fd"); + err++; + break; + } else { + if (read (client->subgraph_wait_fd, &c, sizeof (c)) != sizeof (c)) { + jack_error ("cannot clean up byte from graph wait fd (%s)", strerror (errno)); + err++; + break; + } + } + + /* Move to next in-process client (or end of client list) */ + + while (node) { + if (jack_client_is_inprocess (((jack_client_internal_t *) node->data))) { + break; + } + node = g_slist_next (node); + } + } + } + + pthread_mutex_unlock (&engine->graph_lock); + + if (err) { + jack_cleanup_clients (engine); + } + +// rdtscl (now); +// printf ("engine cycle time: %.6f usecs\n", ((float) (now - then)) / 450.00f); + return 0; +} + +static int +jack_load_client (jack_engine_t *engine, jack_client_internal_t *client, const char *path_to_so) +{ + const char *errstr; + dlhandle handle; + + handle = dlopen (path_to_so, RTLD_NOW|RTLD_GLOBAL); + + if (handle == 0) { + if ((errstr = dlerror ()) != 0) { + jack_error ("can't load \"%s\": %s", path_to_so, errstr); + } else { + jack_error ("bizarre error loading driver shared object %s", path_to_so); + } + return -1; + } + + client->handle = handle; + +#if 0 + initialize = dlsym (handle, "client_initialize"); + + if ((errstr = dlerror ()) != 0) { + jack_error ("no initialize function in shared object %s\n", path_to_so); + dlclose (handle); + return -1; + } + + finish = dlsym (handle, "client_finish"); + + if ((errstr = dlerror ()) != 0) { + jack_error ("no finish function in in shared driver object %s", path_to_so); + dlclose (handle); + return -1; + } +#endif + + return 0; + +} + +static void +jack_client_unload (jack_client_internal_t *client) +{ + if (client->handle) { +// client->finish (client); + dlclose (client->handle); + } +} + +static int +handle_new_client (jack_engine_t *engine, int client_fd) + +{ + jack_client_internal_t *client; + jack_client_connect_request_t req; + jack_client_connect_result_t res; + + if (read (client_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read connection request from client"); + return -1; + } + + res.status = 0; + + if ((client = jack_client_internal_new (engine, client_fd, &req)) == 0) { + jack_error ("cannot create new client object"); + return -1; + } + + printf ("new client: %s, type %d @ %p\n", client->control->name, req.type, client->control); + + res.status = 0; + res.client_key = client->shm_key; + res.control_key = engine->control_key; + res.port_segment_key = engine->port_segment_key; + res.port_segment_address = engine->port_segment_address; + res.realtime = engine->control->real_time; + res.realtime_priority = engine->rtpriority - 1; + + if (jack_client_is_inprocess (client)) { + + res.client_control = client->control; + res.engine_control = engine->control; + + } else { + strcpy (res.fifo_prefix, engine->fifo_prefix); + } + + res.status = 0; + + if (write (client->request_fd, &res, sizeof (res)) != sizeof (res)) { + jack_error ("cannot write connection response to client"); + jack_client_delete (engine, client); + return -1; + } + + if (res.status) { + return res.status; + } + + pthread_mutex_lock (&engine->graph_lock); + engine->clients = g_slist_prepend (engine->clients, client); + pthread_mutex_unlock (&engine->graph_lock); + + if (client->control->type != ClientDynamic) { + if (engine->pfd_max >= engine->pfd_size) { + engine->pfd = (struct pollfd *) realloc (engine->pfd, sizeof (struct pollfd) * engine->pfd_size + 16); + engine->pfd_size += 16; + } + + engine->pfd[engine->pfd_max].fd = client->request_fd; + engine->pfd[engine->pfd_max].events = POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL; + engine->pfd_max++; + } + + + return 0; +} + +static int +handle_client_ack_connection (jack_engine_t *engine, int client_fd) + +{ + jack_client_internal_t *client; + jack_client_connect_ack_request_t req; + jack_client_connect_ack_result_t res; + + if (read (client_fd, &req, sizeof (req)) != sizeof (req)) { + jack_error ("cannot read ACK connection request from client"); + return -1; + } + + if ((client = jack_client_internal_by_id (engine, req.client_id)) == NULL) { + jack_error ("unknown client ID in ACK connection request"); + return -1; + } + + fprintf (stderr, "client %s is on event fd %d\n", client->control->name, client_fd); + + client->event_fd = client_fd; + + res.status = 0; + + if (write (client->event_fd, &res, sizeof (res)) != sizeof (res)) { + jack_error ("cannot write ACK connection response to client"); + return -1; + } + + return 0; +} + +static int +jack_client_drop (jack_engine_t *engine, jack_client_id_t id) + +{ + jack_client_internal_t *client; + + if ((client = jack_client_internal_by_id (engine, id)) == 0) { + jack_error ("unknown client ID in DropClient request"); + return -1; + } + + jack_remove_client (engine, client); + return 0; +} + +#if 0 +static int +jack_client_has_connections (jack_client_internal_t *client) + +{ + GSList *node; + + for (node = client->ports; node; node = g_slist_next (node)) { + if (((jack_port_internal_t *) node->data)->connections) { + return TRUE; + } + } + + return FALSE; +} +#endif + +static int +jack_client_activate (jack_engine_t *engine, jack_client_id_t id) + +{ + jack_client_internal_t *client; + GSList *node; + int ret = -1; + + pthread_mutex_lock (&engine->graph_lock); + + for (node = engine->clients; node; node = g_slist_next (node)) { + + if (((jack_client_internal_t *) node->data)->control->id == id) { + + client = (jack_client_internal_t *) node->data; + + if (!jack_client_is_inprocess (client)) { + jack_create_fifo (engine, ++engine->external_client_cnt); + } + + client->control->active = TRUE; + + jack_rechain_graph (engine, FALSE); + + ret = 0; + break; + } + } + + pthread_mutex_unlock (&engine->graph_lock); + return ret; +} + +static int +jack_client_do_deactivate (jack_engine_t *engine, jack_client_internal_t *client) + +{ + /* called must hold engine->graph_lock and must have checked for and/or + cleared all connections held by client. + */ + + client->control->active = FALSE; + + if (!jack_client_is_inprocess (client)) { + engine->external_client_cnt--; + } + + jack_sort_graph (engine, FALSE); + return 0; +} + +static void +jack_client_disconnect (jack_engine_t *engine, jack_client_internal_t *client) + +{ + GSList *node; + jack_port_internal_t *port; + + /* call tree **** MUST HOLD *** engine->graph_lock */ + + for (node = client->ports; node; node = g_slist_next (node)) { + port = (jack_port_internal_t *) node->data; + jack_port_clear_connections (engine, port); + jack_port_release (engine, port); + } + + g_slist_free (client->ports); + client->ports = 0; +} + +static int +jack_client_deactivate (jack_engine_t *engine, jack_client_id_t id, int to_wait) + +{ + GSList *node; + int ret = -1; + + pthread_mutex_lock (&engine->graph_lock); + + printf ("trying ... deactivating client id %d\n", id); + + for (node = engine->clients; node; node = g_slist_next (node)) { + + jack_client_internal_t *client = (jack_client_internal_t *) node->data; + + if (client->control->id == id) { + + printf ("deactivating client id %d\n", id); + + if (client == engine->timebase_client) { + engine->timebase_client = 0; + engine->control->frame_time = 0; + } + + jack_client_disconnect (engine, client); + ret = jack_client_do_deactivate (engine, node->data); + break; + } + } + + pthread_mutex_unlock (&engine->graph_lock); + + return ret; +} + +static int +jack_set_timebase (jack_engine_t *engine, jack_client_id_t client) +{ + int ret = -1; + + pthread_mutex_lock (&engine->graph_lock); + + if ((engine->timebase_client = jack_client_internal_by_id (engine, client)) != 0) { + engine->control->frame_time = engine->timebase_client->control->frame_time; + ret = 0; + } + pthread_mutex_unlock (&engine->graph_lock); + return ret; +} + +static int +handle_client_jack_error (jack_engine_t *engine, int fd) + +{ + jack_client_internal_t *client = 0; + GSList *node; + + pthread_mutex_lock (&engine->graph_lock); + + for (node = engine->clients; node; node = g_slist_next (node)) { + if (((jack_client_internal_t *) node->data)->request_fd == fd) { + client = (jack_client_internal_t *) node->data; + break; + } + } + + pthread_mutex_unlock (&engine->graph_lock); + + if (client == 0) { + jack_error ("i/o error on unknown client fd %d", fd); + return -1; + } + + jack_remove_client (engine, client); + return 0; +} + +static int +jack_client_port_monitor (jack_engine_t *engine, jack_port_id_t port_id, int onoff) + +{ + jack_port_shared_t *port; + jack_client_internal_t *client = NULL; + jack_event_t event; + + if (port_id < 0 || port_id >= engine->port_max) { + jack_error ("illegal port ID in port monitor request"); + return -1; + } + + port = &engine->control->ports[port_id]; + + if (!(port->flags & JackPortCanMonitor)) { + jack_error ("port monitor request made on a port (%s) that doesn't support monitoring", + port->name); + return -1; + } + + pthread_mutex_lock (&engine->graph_lock); + if ((client = jack_client_internal_by_id (engine, port->client_id)) == NULL) { + jack_error ("unknown client owns port %d!!", port_id); + pthread_mutex_unlock (&engine->graph_lock); + return -1; + } + pthread_mutex_unlock (&engine->graph_lock); + + event.type = (onoff ? PortMonitor : PortUnMonitor); + event.x.port_id = port_id; + + return jack_deliver_event (engine, client, &event); +} + +static int +handle_client_io (jack_engine_t *engine, int fd) + +{ + jack_request_t req; + jack_client_internal_t *client = 0; + int reply_fd; + GSList *node; + + pthread_mutex_lock (&engine->graph_lock); + + for (node = engine->clients; node; node = g_slist_next (node)) { + if (((jack_client_internal_t *) node->data)->request_fd == fd) { + client = (jack_client_internal_t *) node->data; + break; + } + } + + pthread_mutex_unlock (&engine->graph_lock); + + if (client == 0) { + jack_error ("client input on unknown fd %d!", fd); + return -1; + } + + if (read (client->request_fd, &req, sizeof (req)) < sizeof (req)) { + jack_error ("cannot read request from client"); + jack_remove_client (engine, client); + return -1; + } + + reply_fd = client->request_fd; + + switch (req.type) { + case RegisterPort: + req.status = jack_port_do_register (engine, &req); + break; + + case UnRegisterPort: + req.status = jack_port_do_unregister (engine, &req); + break; + + case ConnectPorts: + req.status = jack_port_do_connect (engine, req.x.connect.source_port, req.x.connect.destination_port); + break; + + case DisconnectPorts: + req.status = jack_port_do_disconnect (engine, req.x.connect.source_port, req.x.connect.destination_port); + break; + + case DropClient: + req.status = jack_client_drop (engine, req.x.client_id); + reply_fd = -1; + break; + + case ActivateClient: + req.status = jack_client_activate (engine, req.x.client_id); + break; + + case DeactivateClient: + req.status = jack_client_deactivate (engine, req.x.client_id, TRUE); + break; + + case SetTimeBaseClient: + req.status = jack_set_timebase (engine, req.x.client_id); + break; + + case RequestPortMonitor: + req.status = jack_client_port_monitor (engine, req.x.port_info.port_id, TRUE); + break; + + case RequestPortUnMonitor: + req.status = jack_client_port_monitor (engine, req.x.port_info.port_id, FALSE); + break; + } + + if (reply_fd >= 0) { + if (write (reply_fd, &req, sizeof (req)) < sizeof (req)) { + jack_error ("cannot write request result to client"); + return -1; + } + } + + return 0; +} + +static void * +jack_server_thread (void *arg) + +{ + jack_engine_t *engine = (jack_engine_t *) arg; + struct sockaddr_un client_addr; + socklen_t client_addrlen; + struct pollfd *pfd; + int client_socket; + int done = 0; + int i; + int max; + + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + engine->pfd[0].fd = engine->fds[0]; + engine->pfd[0].events = POLLIN|POLLERR; + engine->pfd[1].fd = engine->fds[1]; + engine->pfd[1].events = POLLIN|POLLERR; + engine->pfd_max = 2; + + while (!done) { + + /* XXX race here with new external clients + causing engine->pfd to be reallocated. + I don't know how to solve this + short of copying the entire + contents of the pfd struct. Ick. + */ + + max = engine->pfd_max; + pfd = engine->pfd; + + if (poll (pfd, max, 10000) < 0) { + if (errno == EINTR) { + continue; + } + jack_error ("poll failed (%s)", strerror (errno)); + break; + } + + /* check the master server socket */ + + if (pfd[0].revents & POLLERR) { + jack_error ("error on server socket"); + break; + } + + if (pfd[0].revents & POLLIN) { + + memset (&client_addr, 0, sizeof (client_addr)); + client_addrlen = sizeof (client_addr); + + if ((client_socket = accept (engine->fds[0], (struct sockaddr *) &client_addr, &client_addrlen)) < 0) { + jack_error ("cannot accept new connection (%s)", strerror (errno)); + } else if (handle_new_client (engine, client_socket) < 0) { + jack_error ("cannot complete new client connection process"); + close (client_socket); + } + } + + /* check the ACK server socket */ + + if (pfd[1].revents & POLLERR) { + jack_error ("error on server ACK socket"); + break; + } + + if (pfd[1].revents & POLLIN) { + + memset (&client_addr, 0, sizeof (client_addr)); + client_addrlen = sizeof (client_addr); + + if ((client_socket = accept (engine->fds[1], (struct sockaddr *) &client_addr, &client_addrlen)) < 0) { + jack_error ("cannot accept new ACK connection (%s)", strerror (errno)); + } else if (handle_client_ack_connection (engine, client_socket)) { + jack_error ("cannot complete client ACK connection process"); + close (client_socket); + } + } + + /* check each client socket */ + + for (i = 2; i < max; i++) { + if (pfd[i].fd < 0) { + continue; + } + + if (pfd[i].revents & ~POLLIN) { + handle_client_jack_error (engine, pfd[i].fd); + } else if (pfd[i].revents & POLLIN) { + if (handle_client_io (engine, pfd[i].fd)) { + jack_error ("bad hci\n"); + } + } + } + } + + return 0; +} + +static void +jack_start_server (jack_engine_t *engine) + +{ + pthread_create (&engine->server_thread, 0, &jack_server_thread, engine); + pthread_detach (engine->server_thread); +} + +jack_engine_t * +jack_engine_new (int realtime, int rtpriority) +{ + jack_engine_t *engine; + size_t control_size; + void *addr; + int i; + + engine = (jack_engine_t *) malloc (sizeof (jack_engine_t)); + + engine->driver = 0; + engine->process = jack_process; + engine->set_sample_rate = jack_set_sample_rate; + engine->set_buffer_size = jack_set_buffer_size; + + engine->next_client_id = 1; + engine->timebase_client = 0; + engine->port_max = 128; + engine->rtpriority = rtpriority; + engine->silent_buffer = 0; + engine->getthehelloutathere = FALSE; + + pthread_mutex_init (&engine->graph_lock, 0); + pthread_mutex_init (&engine->buffer_lock, 0); + pthread_mutex_init (&engine->port_lock, 0); + + engine->clients = 0; + + engine->port_segments = 0; + engine->port_buffer_freelist = 0; + + engine->pfd_size = 16; + engine->pfd_max = 0; + engine->pfd = (struct pollfd *) malloc (sizeof (struct pollfd) * engine->pfd_size); + + engine->fifo_size = 16; + engine->fifo = (int *) malloc (sizeof (int) * engine->fifo_size); + for (i = 0; i < engine->fifo_size; i++) { + engine->fifo[i] = -1; + } + + /* Build a linked list of known port types. We use a list so that + we can easily manage other data types without messing with + reallocation of arrays, etc. + */ + + engine->port_types = NULL; + for (i = 0; builtin_port_types[i].type_name; i++) { + engine->port_types = g_slist_append (engine->port_types, &builtin_port_types[i]); + } + + engine->external_client_cnt = 0; + + srandom (time ((time_t *) 0)); + + engine->control_key = random(); + control_size = sizeof (jack_control_t) + (sizeof (jack_port_shared_t) * engine->port_max); + + if ((engine->control_shm_id = shmget (engine->control_key, control_size, IPC_CREAT|0644)) < 0) { + jack_error ("cannot create engine control shared memory segment (%s)", strerror (errno)); + return 0; + } + + if ((addr = fixed_shmat (engine->control_shm_id, 0, 0, control_size)) == (void *) -1) { + jack_error ("cannot attach control shared memory segment (%s)", strerror (errno)); + shmctl (engine->control_shm_id, IPC_RMID, 0); + return 0; + } + + on_exit (shm_destroy, (void *) engine->control_shm_id); + + engine->control = (jack_control_t *) addr; + + /* Mark all ports as available */ + + for (i = 0; i < engine->port_max; i++) { + engine->control->ports[i].in_use = 0; + engine->control->ports[i].id = i; + } + + /* allocate internal port structures so that we can keep + track of port connections. + */ + + engine->internal_ports = (jack_port_internal_t *) malloc (sizeof (jack_port_internal_t) * engine->port_max); + + for (i = 0; i < engine->port_max; i++) { + engine->internal_ports[i].connections = 0; + } + + if (make_sockets (engine->fds) < 0) { + jack_error ("cannot create server sockets"); + return 0; + } + + engine->control->port_max = engine->port_max; + engine->control->real_time = realtime; + engine->control->client_priority = engine->rtpriority - 1; + + engine->control->sample_rate = 0; + engine->control->buffer_size = 0; + engine->control->frame_time = 0; + + sprintf (engine->fifo_prefix, "/tmp/jack_fifo_%d", getpid()); + + jack_create_fifo (engine, 0); + jack_start_server (engine); + + return engine; +} + +static int +jack_become_real_time (pthread_t thread, int priority) + +{ + struct sched_param rtparam; + int x; + + memset (&rtparam, 0, sizeof (rtparam)); + rtparam.sched_priority = priority; + + if ((x = pthread_setschedparam (thread, SCHED_FIFO, &rtparam)) != 0) { + jack_error ("cannot set thread to real-time priority (FIFO/%d) (%d: %s)", rtparam.sched_priority, x, strerror (errno)); + } + + if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0) { + jack_error ("cannot lock down memory for RT thread (%s)", strerror (errno)); + } + + return 0; +} + +void +cancel_cleanup1 (void *arg) + +{ + jack_engine_t *engine = (jack_engine_t *) arg; + printf ("audio thread cancelled or finished\n"); + engine->driver->audio_stop (engine->driver); +} + +void +cancel_cleanup2 (int status, void *arg) + +{ + jack_engine_t *engine = (jack_engine_t *) arg; + engine->driver->audio_stop (engine->driver); + engine->driver->finish (engine->driver); +} + +static void * +jack_audio_thread (void *arg) + +{ + jack_engine_t *engine = (jack_engine_t *) arg; + jack_driver_t *driver = engine->driver; + unsigned long start, end; + + if (engine->control->real_time) { + jack_become_real_time (pthread_self(), engine->rtpriority); + } + + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + on_exit (cancel_cleanup2, engine); + + if (driver->audio_start (driver)) { + jack_error ("cannot start driver"); + pthread_exit (0); + } + + while (1) { + start = end; + if (driver->wait (driver)) { + break; + } + rdtscl (end); +// printf ("driver cycle time: %.6f usecs\n", ((float) (end - start)) / 450.00f); + } + + pthread_exit (0); +} + +int +jack_run (jack_engine_t *engine) + +{ + if (engine->driver == 0) { + jack_error ("engine driver not set; cannot start"); + return -1; + } + return pthread_create (&engine->audio_thread, 0, jack_audio_thread, engine); +} +int +jack_wait (jack_engine_t *engine) + +{ + void *ret = 0; + int err; + + if ((err = pthread_join (engine->audio_thread, &ret)) != 0) { + switch (err) { + case EINVAL: + jack_error ("cannot join with audio thread (thread detached, or another thread is waiting)"); + break; + case ESRCH: + jack_error ("cannot join with audio thread (thread no longer exists)"); + break; + case EDEADLK: + jack_error ("programming error: jack_wait() called by audio thread"); + break; + default: + jack_error ("cannot join with audio thread (%s)", strerror (errno)); + } + } + return (int) ret; +} + +int +jack_engine_delete (jack_engine_t *engine) + +{ + pthread_cancel (engine->audio_thread); + return 0; +} + +static jack_client_internal_t * +jack_client_internal_new (jack_engine_t *engine, int fd, jack_client_connect_request_t *req) + +{ + jack_client_internal_t *client; + key_t shm_key = 0; + int shm_id = 0; + void *addr = 0; + + switch (req->type) { + case ClientDynamic: + case ClientDriver: + break; + + case ClientOutOfProcess: + + shm_key = random(); + + if ((shm_id = shmget (shm_key, sizeof (jack_client_control_t), IPC_CREAT|0666)) < 0) { + jack_error ("cannot create client control block"); + return 0; + } + + if ((addr = fixed_shmat (shm_id, 0, 0, sizeof (jack_client_control_t))) == (void *) -1) { + jack_error ("cannot attach new client control block"); + shmctl (shm_id, IPC_RMID, 0); + return 0; + } + + break; + } + + client = (jack_client_internal_t *) malloc (sizeof (jack_client_internal_t)); + + client->request_fd = fd; + client->event_fd = -1; + client->ports = 0; + client->rank = UINT_MAX; + client->next_client = NULL; + client->handle = NULL; + + if (req->type != ClientOutOfProcess) { + + client->control = (jack_client_control_t *) malloc (sizeof (jack_client_control_t)); + + } else { + + client->shm_id = shm_id; + client->shm_key = shm_key; + client->control = (jack_client_control_t *) addr; + } + + client->control->type = req->type; + client->control->active = FALSE; + client->control->dead = FALSE; + client->control->id = engine->next_client_id++; + strcpy ((char *) client->control->name, req->name); + + client->control->process = NULL; + client->control->process_arg = NULL; + client->control->bufsize = NULL; + client->control->bufsize_arg = NULL; + client->control->srate = NULL; + client->control->srate_arg = NULL; + client->control->port_register = NULL; + client->control->port_register_arg = NULL; + client->control->port_monitor = NULL; + client->control->port_monitor_arg = NULL; + + if (req->type == ClientDynamic) { + if (jack_load_client (engine, client, req->object_path)) { + jack_error ("cannot dynamically load client from \"%s\"", req->object_path); + jack_client_delete (engine, client); + return 0; + } + } + + return client; +} + +static void +jack_port_clear_connections (jack_engine_t *engine, jack_port_internal_t *port) +{ + GSList *node, *next; + + for (node = port->connections; node; ) { + next = g_slist_next (node); + jack_port_disconnect_internal (engine, + ((jack_connection_internal_t *) node->data)->source, + ((jack_connection_internal_t *) node->data)->destination, + FALSE); + node = next; + } + + g_slist_free (port->connections); + port->connections = 0; +} + + +static void +jack_remove_client (jack_engine_t *engine, jack_client_internal_t *client) +{ + GSList *node; + int i; + + printf ("removing client %s\n", client->control->name); + + pthread_mutex_lock (&engine->graph_lock); + + client->control->dead = TRUE; + + if (client == engine->timebase_client) { + engine->timebase_client = 0; + engine->control->frame_time = 0; + } + + jack_client_disconnect (engine, client); + jack_client_do_deactivate (engine, client); + + for (node = engine->clients; node; node = g_slist_next (node)) { + if (((jack_client_internal_t *) node->data)->control->id == client->control->id) { + engine->clients = g_slist_remove_link (engine->clients, node); + g_slist_free_1 (node); + break; + } + } + + /* rearrange the pollfd array so that things work right the + next time we go into poll(2). + */ + + for (i = 0; i < engine->pfd_max; i++) { + if (engine->pfd[i].fd == client->request_fd) { + if (i+1 < engine->pfd_max) { + memcpy (&engine->pfd[i], &engine->pfd[i+1], sizeof (struct pollfd) * (engine->pfd_max - i)); + } + engine->pfd_max--; + } + } + + close (client->event_fd); + close (client->request_fd); + + jack_client_delete (engine, client); + + pthread_mutex_unlock (&engine->graph_lock); +} + +static void +jack_client_delete (jack_engine_t *engine, jack_client_internal_t *client) + +{ + jack_client_disconnect (engine, client); + + if (jack_client_is_inprocess (client)) { + jack_client_unload (client); + free ((char *) client->control); + } else { + shmdt ((void *) client->control); + } + + free (client); +} + +jack_client_internal_t * +jack_client_by_name (jack_engine_t *engine, const char *name) + +{ + jack_client_internal_t *client = NULL; + GSList *node; + + pthread_mutex_lock (&engine->graph_lock); + + for (node = engine->clients; node; node = g_slist_next (node)) { + if (strcmp ((const char *) ((jack_client_internal_t *) node->data)->control->name, name) == 0) { + client = (jack_client_internal_t *) node->data; + break; + } + } + + pthread_mutex_unlock (&engine->graph_lock); + return client; +} + +jack_client_internal_t * +jack_client_internal_by_id (jack_engine_t *engine, jack_client_id_t id) + +{ + jack_client_internal_t *client = NULL; + GSList *node; + + /* call tree ***MUST HOLD*** engine->graph_lock */ + + for (node = engine->clients; node; node = g_slist_next (node)) { + if (((jack_client_internal_t *) node->data)->control->id == id) { + client = (jack_client_internal_t *) node->data; + break; + } + } + + return client; +} + +static int +jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, jack_event_t *event) +{ + char status; + + if (client->control->dead) { + return 0; + } + + if (jack_client_is_inprocess (client)) { + + switch (event->type) { + case PortConnected: + case PortDisconnected: + jack_client_handle_port_connection (client->control->private_internal_client, event); + break; + + case GraphReordered: + jack_error ("reorder event delivered to internal client!"); + break; + + case BufferSizeChange: + if (client->control->bufsize) { + client->control->bufsize (event->x.n, client->control->bufsize_arg); + } + break; + + case SampleRateChange: + if (client->control->srate) { + client->control->srate (event->x.n, client->control->bufsize_arg); + } + break; + + case PortMonitor: + if (client->control->port_monitor) { + client->control->port_monitor (event->x.port_id, TRUE, client->control->port_monitor_arg); + } + break; + + case PortUnMonitor: + if (client->control->port_monitor) { + client->control->port_monitor (event->x.port_id, FALSE, client->control->port_monitor_arg); + } + break; + + default: + /* internal clients don't need to know */ + break; + } + + } else { + if (write (client->event_fd, event, sizeof (*event)) != sizeof (*event)) { + jack_error ("cannot send event to client [%s] (%s)", client->control->name, strerror (errno)); + return -1; + } + + if (read (client->event_fd, &status, sizeof (status)) != sizeof (status)) { + jack_error ("cannot read event response from client [%s] (%s)", client->control->name, strerror (errno)); + return -1; + } + } + + return 0; +} + +int +jack_client_set_order (jack_engine_t *engine, jack_client_internal_t *client) + +{ + jack_event_t event; + + event.type = GraphReordered; + event.x.n = client->rank; + + return jack_deliver_event (engine, client, &event); +} + +int +jack_rechain_graph (jack_engine_t *engine, int take_lock) + +{ + GSList *node, *next; + unsigned long n; + int err = 0; + int set; + jack_client_internal_t *client, *subgraph_client, *next_client; + + if (take_lock) { + pthread_mutex_lock (&engine->graph_lock); + } + + /* We're going to try to avoid reconnecting clients that + don't need to be reconnected. This is slightly tricky, + but worth it for performance reasons. + */ + + subgraph_client = 0; + + if ((node = engine->clients) == 0) { + goto done; + } + + client = (jack_client_internal_t *) node->data; + if ((next = g_slist_next (node)) == NULL) { + next_client = 0; + } else { + next_client = (jack_client_internal_t *) next->data; + } + n = 0; + + do { + if (client->rank != n || client->next_client != next_client) { + client->rank = n; + client->next_client = next_client; + set = TRUE; + } else { + set = FALSE; + } + + if (jack_client_is_inprocess (client)) { + if (subgraph_client) { + subgraph_client->subgraph_wait_fd = jack_get_fifo_fd (engine, n); + } + + subgraph_client = 0; + + } else { + + if (subgraph_client == 0) { + subgraph_client = client; + subgraph_client->subgraph_start_fd = jack_get_fifo_fd (engine, n); + } + + if (set) { + jack_client_set_order (engine, client); + } + + n++; + } + + if (next == 0) { + break; + } + + node = next; + client = (jack_client_internal_t *) node->data; + + if ((next = g_slist_next (node)) == 0) { + next_client = 0; + } else { + next_client = (jack_client_internal_t *) next->data; + } + + } while (1); + + if (subgraph_client) { + subgraph_client->subgraph_wait_fd = jack_get_fifo_fd (engine, n); + } + + done: + if (take_lock) { + pthread_mutex_unlock (&engine->graph_lock); + } + + return err; +} + +int +jack_client_connected (jack_client_internal_t *a, jack_client_internal_t *b) + +{ + GSList *pnode, *cnode; + int ret = 0; + + /* Check every port on the first client for a connection to + the second client. + + Return -1 if a should execute before b + Return 1 if a should execute after b + Return 0 if they are not connected (ie. it doesn't matter) + + If there is a feedback loop between a and b, the result + is undefined (the first connected port will be used to + determine the result). This is "OK", since there is no + correct execution order in that case. + */ + + for (pnode = a->ports; pnode; pnode = g_slist_next (pnode)) { + jack_port_internal_t *port; + + port = (jack_port_internal_t *) pnode->data; + + for (cnode = port->connections; cnode; cnode = g_slist_next (cnode)) { + jack_connection_internal_t *c; + + c = (jack_connection_internal_t *) cnode->data; + + if (c->source->shared->client_id == b->control->id) { + + /* b is the source, so a should be + executed *after* b. + */ + + ret = 1; + break; + + } else if (c->source->shared->client_id == a->control->id) { + + /* a is the source, so a should be + executed *before* b + */ + + ret = -1; + break; + } + } + + if (ret) { + break; + } + } + + return ret; +} + +static void +jack_sort_graph (jack_engine_t *engine, int take_lock) + +{ + if (take_lock) { + pthread_mutex_lock (&engine->graph_lock); + } + + engine->clients = g_slist_sort (engine->clients, (GCompareFunc) jack_client_connected); + jack_rechain_graph (engine, FALSE); + + if (take_lock) { + pthread_mutex_unlock (&engine->graph_lock); + } +} + +static int +jack_port_do_connect (jack_engine_t *engine, + const char *source_port, + const char *destination_port) +{ + jack_connection_internal_t *connection; + jack_port_internal_t *srcport, *dstport; + jack_port_id_t src_id, dst_id; + + fprintf (stderr, "trying to connect %s and %s\n", source_port, destination_port); + + if ((srcport = jack_get_port_by_name (engine, source_port)) == 0) { + jack_error ("unknown source port in attempted connection [%s]", source_port); + return -1; + } + + if ((dstport = jack_get_port_by_name (engine, destination_port)) == 0) { + jack_error ("unknown destination port in attempted connection [%s]", destination_port); + return -1; + } + + if ((dstport->shared->flags & JackPortIsInput) == 0) { + jack_error ("destination port in attempted connection is not an input port"); + return -1; + } + + if ((srcport->shared->flags & JackPortIsOutput) == 0) { + jack_error ("source port in attempted connection is not an output port"); + return -1; + } + + if (strcmp (srcport->shared->type_info.type_name, + dstport->shared->type_info.type_name) != 0) { + jack_error ("ports used in attemped connection are not of the same data type"); + return -1; + } + + connection = (jack_connection_internal_t *) malloc (sizeof (jack_connection_internal_t)); + + connection->source = srcport; + connection->destination = dstport; + + src_id = srcport->shared->id; + dst_id = dstport->shared->id; + + pthread_mutex_lock (&engine->graph_lock); + + if (dstport->shared->type_info.mixdown == NULL && dstport->connections) { + jack_error ("cannot make multiple connections to a port of type [%s]", dstport->shared->type_info.type_name); + free (connection); + } else { + dstport->connections = g_slist_prepend (dstport->connections, connection); + srcport->connections = g_slist_prepend (srcport->connections, connection); + + jack_sort_graph (engine, FALSE); + + jack_send_connection_notification (engine, srcport->shared->client_id, src_id, dst_id, TRUE); + jack_send_connection_notification (engine, dstport->shared->client_id, dst_id, src_id, TRUE); + } + + pthread_mutex_unlock (&engine->graph_lock); + + return 0; +} + +int +jack_port_disconnect_internal (jack_engine_t *engine, + jack_port_internal_t *srcport, + jack_port_internal_t *dstport, + int sort_graph) + +{ + GSList *node; + jack_connection_internal_t *connect; + int ret = -1; + jack_port_id_t src_id, dst_id; + + /* call tree **** MUST HOLD **** engine->graph_lock. */ + + printf ("disconnecting %s and %s\n", srcport->shared->name, dstport->shared->name); + + for (node = srcport->connections; node; node = g_slist_next (node)) { + + connect = (jack_connection_internal_t *) node->data; + + if (connect->source == srcport && connect->destination == dstport) { + + srcport->connections = g_slist_remove (srcport->connections, connect); + dstport->connections = g_slist_remove (dstport->connections, connect); + + src_id = srcport->shared->id; + dst_id = dstport->shared->id; + + jack_send_connection_notification (engine, srcport->shared->client_id, src_id, dst_id, FALSE); + jack_send_connection_notification (engine, dstport->shared->client_id, dst_id, src_id, FALSE); + + free (connect); + ret = 0; + break; + } + } + + if (sort_graph) { + jack_sort_graph (engine, FALSE); + } + + return ret; +} + +static int +jack_port_do_disconnect (jack_engine_t *engine, + const char *source_port, + const char *destination_port) +{ + jack_port_internal_t *srcport, *dstport; + int ret = -1; + + if ((srcport = jack_get_port_by_name (engine, source_port)) == 0) { + jack_error ("unknown source port in attempted connection [%s]", source_port); + return -1; + } + + if ((dstport = jack_get_port_by_name (engine, destination_port)) == 0) { + jack_error ("unknown destination port in attempted connection [%s]", destination_port); + return -1; + } + + pthread_mutex_lock (&engine->graph_lock); + + ret = jack_port_disconnect_internal (engine, srcport, dstport, TRUE); + + pthread_mutex_unlock (&engine->graph_lock); + return ret; +} + +static int +jack_create_fifo (jack_engine_t *engine, int which_fifo) + +{ + char path[FIFO_NAME_SIZE+1]; + + sprintf (path, "%s-%d", engine->fifo_prefix, which_fifo); + + if (mknod (path, 0666|S_IFIFO, 0) < 0) { + if (errno != EEXIST) { + jack_error ("cannot create inter-client FIFO [%s] (%s)", path, strerror (errno)); + return -1; + } + + } else { + on_exit (unlink_path, strdup (path)); + } + + jack_get_fifo_fd (engine, which_fifo); + + return 0; +} + +static int +jack_get_fifo_fd (jack_engine_t *engine, int which_fifo) + +{ + char path[FIFO_NAME_SIZE+1]; + + sprintf (path, "%s-%d", engine->fifo_prefix, which_fifo); + + if (which_fifo >= engine->fifo_size) { + int i; + + engine->fifo = (int *) realloc (engine->fifo, sizeof (int) * engine->fifo_size + 16); + for (i = engine->fifo_size; i < engine->fifo_size + 16; i++) { + engine->fifo[i] = -1; + } + engine->fifo_size += 16; + } + + if (engine->fifo[which_fifo] < 0) { + if ((engine->fifo[which_fifo] = open (path, O_RDWR|O_CREAT, 0666)) < 0) { + jack_error ("cannot open fifo [%s] (%s)", path, strerror (errno)); + return -1; + } + } + + return engine->fifo[which_fifo]; +} + +int +jack_use_driver (jack_engine_t *engine, jack_driver_t *driver) + +{ + if (driver) { + driver->detach (driver, engine); + engine->driver = 0; + } + + if (driver->attach (driver, engine)) { + return -1; + } + + engine->driver = driver; + return 0; +} + +/* PORT RELATED FUNCTIONS */ + + +jack_port_id_t +jack_get_free_port (jack_engine_t *engine) + +{ + jack_port_id_t i; + + pthread_mutex_lock (&engine->port_lock); + + for (i = 0; i < engine->port_max; i++) { + if (engine->control->ports[i].in_use == 0) { + engine->control->ports[i].in_use = 1; + break; + } + } + + pthread_mutex_unlock (&engine->port_lock); + + if (i == engine->port_max) { + return NoPort; + } + + return i; +} + +static void +jack_port_release (jack_engine_t *engine, jack_port_internal_t *port) + +{ + /* XXX add the buffer used by the port back the (correct) freelist */ + + pthread_mutex_lock (&engine->port_lock); + port->shared->in_use = 0; + pthread_mutex_unlock (&engine->port_lock); +} + +jack_port_internal_t * +jack_get_port_internal_by_name (jack_engine_t *engine, const char *name) +{ + jack_port_id_t id; + + pthread_mutex_lock (&engine->port_lock); + + for (id = 0; id < engine->port_max; id++) { + if (strcmp (engine->control->ports[id].name, name) == 0) { + break; + } + } + + pthread_mutex_unlock (&engine->port_lock); + + if (id != engine->port_max) { + return &engine->internal_ports[id]; + } else { + return NULL; + } +} + +int +jack_port_do_register (jack_engine_t *engine, jack_request_t *req) + +{ + GSList *node; + jack_port_id_t port_id; + jack_port_shared_t *shared; + jack_port_internal_t *port; + jack_client_internal_t *client; + jack_port_type_info_t *type_info; + + pthread_mutex_lock (&engine->graph_lock); + if ((client = jack_client_internal_by_id (engine, req->x.port_info.client_id)) == 0) { + jack_error ("unknown client id in port registration request"); + return -1; + } + pthread_mutex_unlock (&engine->graph_lock); + + if ((port_id = jack_get_free_port (engine)) == NoPort) { + jack_error ("no ports available!"); + return -1; + } + + shared = &engine->control->ports[port_id]; + + strcpy (shared->name, req->x.port_info.name); + + shared->client_id = req->x.port_info.client_id; + shared->flags = req->x.port_info.flags; + shared->locked = 0; + shared->buffer_size = req->x.port_info.buffer_size; + + port = &engine->internal_ports[port_id]; + + port->shared = shared; + port->connections = 0; + + type_info = NULL; + + for (node = engine->port_types; node; node = g_slist_next (node)) { + + if (strcmp (req->x.port_info.type, ((jack_port_type_info_t *) node->data)->type_name) == 0) { + type_info = (jack_port_type_info_t *) node->data; + break; + } + } + + if (type_info == NULL) { + + /* not a builtin type, so allocate a new type_info structure, + and fill it appropriately. + */ + + type_info = (jack_port_type_info_t *) malloc (sizeof (jack_port_type_info_t)); + + type_info->type_name = strdup (req->x.port_info.type); + type_info->mixdown = NULL; /* we have no idea how to mix this */ + type_info->buffer_scale_factor = -1; /* use specified port buffer size */ + + engine->port_types = g_slist_prepend (engine->port_types, type_info); + } + + + memcpy (&port->shared->type_info, type_info, sizeof (jack_port_type_info_t)); + + if (jack_port_assign_buffer (engine, port)) { + jack_error ("cannot assign buffer for port"); + return -1; + } + + pthread_mutex_lock (&engine->graph_lock); + client->ports = g_slist_prepend (client->ports, port); + jack_port_registration_notify (engine, port_id, TRUE); + pthread_mutex_unlock (&engine->graph_lock); + + req->x.port_info.port_id = port_id; + + return 0; +} + +int +jack_port_do_unregister (jack_engine_t *engine, jack_request_t *req) + + +{ + jack_client_internal_t *client; + jack_port_shared_t *shared; + jack_port_internal_t *port; + + if (req->x.port_info.port_id < 0 || req->x.port_info.port_id > engine->port_max) { + jack_error ("invalid port ID %d in unregister request\n", req->x.port_info.port_id); + return -1; + } + + shared = &engine->control->ports[req->x.port_info.port_id]; + + pthread_mutex_lock (&engine->graph_lock); + if ((client = jack_client_internal_by_id (engine, shared->client_id)) == NULL) { + jack_error ("unknown client id in port registration request"); + return -1; + } + pthread_mutex_unlock (&engine->graph_lock); + + port = &engine->internal_ports[req->x.port_info.port_id]; + + jack_port_release (engine, &engine->internal_ports[req->x.port_info.port_id]); + + pthread_mutex_lock (&engine->graph_lock); + client->ports = g_slist_remove (client->ports, port); + jack_port_registration_notify (engine, req->x.port_info.port_id, FALSE); + pthread_mutex_unlock (&engine->graph_lock); + + return 0; +} + +void +jack_port_registration_notify (jack_engine_t *engine, jack_port_id_t port_id, int yn) + +{ + jack_event_t event; + jack_client_internal_t *client; + GSList *node; + + event.type = (yn ? PortRegistered : PortUnregistered); + event.x.port_id = port_id; + + for (node = engine->clients; node; node = g_slist_next (node)) { + + client = (jack_client_internal_t *) node->data; + + if (!client->control->active) { + continue; + } + + if (client->control->port_register) { + if (jack_deliver_event (engine, client, &event)) { + jack_error ("cannot send port registration notification to %s (%s)", + client->control->name, strerror (errno)); + } + } + } +} + +int +jack_port_assign_buffer (jack_engine_t *engine, jack_port_internal_t *port) +{ + GSList *node; + jack_port_segment_info_t *psi; + jack_port_buffer_info_t *bi; + + port->shared->buffer = NULL; + + if (port->shared->flags & JackPortIsInput) { + return 0; + } + + pthread_mutex_lock (&engine->buffer_lock); + + if (engine->port_buffer_freelist == NULL) { + jack_error ("no more buffers available!"); + goto out; + } + + bi = (jack_port_buffer_info_t *) engine->port_buffer_freelist->data; + + for (node = engine->port_segments; node; node = g_slist_next (node)) { + + psi = (jack_port_segment_info_t *) node->data; + + if (bi->shm_key == psi->shm_key) { + port->shared->buffer = psi->address + bi->offset; + break; + } + } + + if (port->shared->buffer) { + engine->port_buffer_freelist = g_slist_remove (engine->port_buffer_freelist, bi); + } else { + jack_error ("port segment info for 0x%x:%d not found!", bi->shm_key, bi->offset); + } + + out: + pthread_mutex_unlock (&engine->buffer_lock); + + if (port->shared->buffer == NULL) { + return -1; + } else { + return 0; + } +} + +static jack_port_internal_t * +jack_get_port_by_name (jack_engine_t *engine, const char *name) + +{ + jack_port_id_t id; + + /* Note the potential race on "in_use". Other design + elements prevent this from being a problem. + */ + + for (id = 0; id < engine->port_max; id++) { + if (engine->control->ports[id].in_use && strcmp (engine->control->ports[id].name, name) == 0) { + return &engine->internal_ports[id]; + } + } + + return NULL; +} + +static int +jack_send_connection_notification (jack_engine_t *engine, jack_client_id_t client_id, + jack_port_id_t self_id, jack_port_id_t other_id, int connected) + +{ + jack_client_internal_t *client; + jack_event_t event; + + if ((client = jack_client_internal_by_id (engine, client_id)) == 0) { + jack_error ("no such client %d during connection notification", client_id); + return -1; + } + + fprintf (stderr, "sending connection to client %s\n", client->control->name); + + event.type = (connected ? PortConnected : PortDisconnected); + event.x.self_id = self_id; + event.y.other_id = other_id; + + if (jack_deliver_event (engine, client, &event)) { + jack_error ("cannot send port connection notification to client %s (%s)", + client->control->name, strerror (errno)); + return -1; + } + + return 0; +} + +static void +jack_audio_port_mixdown (jack_port_t *port, nframes_t nframes) +{ + GSList *node; + jack_port_shared_t *input; + nframes_t n; + sample_t *dst, *src; + + /* by the time we've called this, we've already established + the existence of more than 1 connection to this input port. + */ + + node = port->connections; + input = (jack_port_shared_t *) node->data; + memcpy (port->shared->buffer, input->buffer, sizeof (sample_t) * nframes); + + for (node = g_slist_next (node); node; node = g_slist_next (node)) { + input = (jack_port_shared_t *) node->data; + n = nframes; + dst = port->shared->buffer; + src = input->buffer; + while (--n) { + *dst++ += *src++; + } + } +} + + diff --git a/fltk_client.cc b/fltk_client.cc new file mode 100644 index 0000000..9c7b5a4 --- /dev/null +++ b/fltk_client.cc @@ -0,0 +1,98 @@ +#include <stdio.h> +#include <errno.h> +#include <unistd.h> + +extern "C" +{ +#include <jack/jack.h> +} + +#include <FL/Fl.H> +#include <FL/Fl_Window.H> +#include <FL/Fl_Slider.H> + +jack_port_t *my_input_port; +jack_port_t *my_output_port; + +float gain = 0.0; /* slider starts out with zero gain */ + +int +process (nframes_t nframes, void *arg) + +{ + sample_t *out = (sample_t *) jack_port_get_buffer (my_output_port, nframes); + sample_t *in = (sample_t *) jack_port_get_buffer (my_input_port, nframes); + + while (nframes--) + out[nframes] = in[nframes] * gain; + + return 0; +} + +int +bufsize (nframes_t nframes, void *arg) + +{ + printf ("the maximum buffer size is now %lu\n", nframes); + return 0; +} + +int +srate (nframes_t nframes, void *arg) + +{ + printf ("the sample rate is now %lu/sec\n", nframes); + return 0; +} + +void callback(Fl_Slider* s) +{ + gain = s->value(); +} + +int +main (int argc, char *argv[]) + +{ + Fl_Window w(0,0,100,120); + Fl_Slider s(10,10,20,100); + w.show(); + s.callback((Fl_Callback*) callback); + + jack_client_t *client; + + if ((client = jack_client_new ("fltktest")) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_set_process_callback (client, process, 0); + jack_set_buffer_size_callback (client, bufsize, 0); + jack_set_sample_rate_callback (client, srate, 0); + + printf ("engine sample rate: %lu\n", jack_get_sample_rate (client)); + + my_input_port = jack_port_register (client, "myinput", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + my_output_port = jack_port_register (client, "myoutput", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + } + + printf ("client activated\n"); + + if (jack_port_connect (client, "ALSA I/O:Input 1", my_input_port->shared->name)) { + fprintf (stderr, "cannot connect input ports\n"); + } + + if (jack_port_connect (client, my_output_port->shared->name, "ALSA I/O:Output 1")) { + fprintf (stderr, "cannot connect output ports\n"); + } + + Fl::run(); + + printf ("done sleeping, now closing...\n"); + jack_client_close (client); + exit (0); +} + diff --git a/generic_hw.c b/generic_hw.c new file mode 100644 index 0000000..75287cd --- /dev/null +++ b/generic_hw.c @@ -0,0 +1,57 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <jack/hardware.h> +#include <jack/alsa_driver.h> + +static int generic_set_input_monitor_mask (jack_hardware_t *hw, unsigned long mask) +{ + return -1; +} + +static int generic_change_sample_clock (jack_hardware_t *hw, SampleClockMode mode) +{ + return -1; +} + +void +jack_alsa_generic_hw_release (jack_hardware_t *hw) + +{ + return; +} + +jack_hardware_t * +jack_alsa_generic_hw_new (alsa_driver_t *driver) + +{ + jack_hardware_t *hw; + + hw = (jack_hardware_t *) malloc (sizeof (jack_hardware_t)); + + hw->capabilities = 0; + hw->input_monitor_mask = 0; + + hw->set_input_monitor_mask = generic_set_input_monitor_mask; + hw->change_sample_clock = generic_change_sample_clock; + hw->release = jack_alsa_generic_hw_release; + + return hw; +} diff --git a/hammerfall.c b/hammerfall.c new file mode 100644 index 0000000..9efa7cd --- /dev/null +++ b/hammerfall.c @@ -0,0 +1,293 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <jack/hardware.h> +#include <jack/alsa_driver.h> +#include <jack/hammerfall.h> +#include <jack/error.h> + +static void +set_control_id (snd_ctl_elem_id_t *ctl, const char *name) +{ + snd_ctl_elem_id_set_name (ctl, name); + snd_ctl_elem_id_set_numid (ctl, 0); + snd_ctl_elem_id_set_interface (ctl, SND_CTL_ELEM_IFACE_PCM); + snd_ctl_elem_id_set_device (ctl, 0); + snd_ctl_elem_id_set_subdevice (ctl, 0); + snd_ctl_elem_id_set_index (ctl, 0); +} + +static void +hammerfall_broadcast_channel_status_change (hammerfall_t *h, int lock, int sync, channel_t lowchn, channel_t highchn) + +{ + channel_t chn; + ClockSyncStatus status = 0; + + if (lock) { + status |= Lock; + } else { + status |= NoLock; + } + + if (sync) { + status |= Sync; + } else { + status |= NoSync; + } + + for (chn = lowchn; chn < highchn; chn++) { + alsa_driver_set_clock_sync_status (h->driver, chn, status); + } +} + +static void +hammerfall_check_sync_state (hammerfall_t *h, int val, int adat_id) + +{ + int lock; + int sync; + + /* S/PDIF channel is always locked and synced, but we only + need tell people once that this is TRUE. + + XXX - maybe need to make sure that the rate matches our + idea of the current rate ? + */ + + if (!h->said_that_spdif_is_fine) { + ClockSyncStatus status; + + status = Lock|Sync; + + /* XXX broken! fix for hammerfall light ! */ + + alsa_driver_set_clock_sync_status (h->driver, 24, status); + alsa_driver_set_clock_sync_status (h->driver, 25, status); + + h->said_that_spdif_is_fine = TRUE; + } + + lock = (val & 0x1) ? TRUE : FALSE; + sync = (val & 0x2) ? TRUE : FALSE; + + if (h->lock_status[adat_id] != lock || + h->sync_status[adat_id] != sync) { + hammerfall_broadcast_channel_status_change (h, lock, sync, adat_id*8, (adat_id*8)+8); + } + + h->lock_status[adat_id] = lock; + h->sync_status[adat_id] = sync; +} + +static void +hammerfall_check_sync (hammerfall_t *h, snd_ctl_elem_value_t *ctl) + +{ + const char *name; + int val; + snd_ctl_elem_id_t *ctl_id; + + snd_ctl_elem_id_alloca (&ctl_id); + snd_ctl_elem_value_get_id (ctl, ctl_id); + + name = snd_ctl_elem_id_get_name (ctl_id); + + if (strcmp (name, "ADAT1 Sync Check") == 0) { + val = snd_ctl_elem_value_get_enumerated (ctl, 0); + hammerfall_check_sync_state (h, val, 0); + } else if (strcmp (name, "ADAT2 Sync Check") == 0) { + val = snd_ctl_elem_value_get_enumerated (ctl, 0); + hammerfall_check_sync_state (h, val, 1); + } else if (strcmp (name, "ADAT3 Sync Check") == 0) { + val = snd_ctl_elem_value_get_enumerated (ctl, 0); + hammerfall_check_sync_state (h, val, 2); + } else { + jack_error ("Hammerfall: unknown control \"%s\"", name); + } +} + +static int +hammerfall_set_input_monitor_mask (jack_hardware_t *hw, unsigned long mask) +{ + hammerfall_t *h = (hammerfall_t *) hw->private; + snd_ctl_elem_value_t *ctl; + snd_ctl_elem_id_t *ctl_id; + int err; + int i; + + snd_ctl_elem_value_alloca (&ctl); + snd_ctl_elem_id_alloca (&ctl_id); + set_control_id (ctl_id, "Channels Thru"); + snd_ctl_elem_value_set_id (ctl, ctl_id); + + for (i = 0; i < 26; i++) { + snd_ctl_elem_value_set_integer (ctl, i, (mask & (1<<i)) ? 1 : 0); + } + + if ((err = snd_ctl_elem_write (h->driver->ctl_handle, ctl)) != 0) { + jack_error ("ALSA/Hammerfall: cannot set input monitoring (%s)", snd_strerror (err)); + return -1; + } + + hw->input_monitor_mask = mask; + + return 0; +} + +static int +hammerfall_change_sample_clock (jack_hardware_t *hw, SampleClockMode mode) +{ + hammerfall_t *h = (hammerfall_t *) hw->private; + snd_ctl_elem_value_t *ctl; + snd_ctl_elem_id_t *ctl_id; + int err; + + snd_ctl_elem_value_alloca (&ctl); + snd_ctl_elem_id_alloca (&ctl_id); + set_control_id (ctl_id, "Sync Mode"); + snd_ctl_elem_value_set_id (ctl, ctl_id); + + switch (mode) { + case AutoSync: + snd_ctl_elem_value_set_enumerated (ctl, 0, 0); + break; + case ClockMaster: + snd_ctl_elem_value_set_enumerated (ctl, 0, 1); + break; + case WordClock: + snd_ctl_elem_value_set_enumerated (ctl, 0, 2); + break; + } + + if ((err = snd_ctl_elem_write (h->driver->ctl_handle, ctl)) < 0) { + jack_error ("ALSA-Hammerfall: cannot set clock mode"); + } + + return 0; +} + +static void +hammerfall_release (jack_hardware_t *hw) + +{ + hammerfall_t *h = (hammerfall_t *) hw->private; + void *status; + + if (h == 0) { + return; + } + + pthread_cancel (h->monitor_thread); + pthread_join (h->monitor_thread, &status); + + free (h); +} + +static void * +hammerfall_monitor_controls (void *arg) + +{ + jack_hardware_t *hw = (jack_hardware_t *) arg; + hammerfall_t *h = (hammerfall_t *) hw->private; + snd_ctl_elem_id_t *switch_id[3]; + snd_ctl_elem_value_t *sw[3]; + + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + snd_ctl_elem_id_malloc (&switch_id[0]); + snd_ctl_elem_id_malloc (&switch_id[1]); + snd_ctl_elem_id_malloc (&switch_id[2]); + + snd_ctl_elem_value_malloc (&sw[0]); + snd_ctl_elem_value_malloc (&sw[1]); + snd_ctl_elem_value_malloc (&sw[2]); + + set_control_id (switch_id[0], "ADAT1 Sync Check"); + set_control_id (switch_id[1], "ADAT2 Sync Check"); + set_control_id (switch_id[2], "ADAT3 Sync Check"); + + snd_ctl_elem_value_set_id (sw[0], switch_id[0]); + snd_ctl_elem_value_set_id (sw[1], switch_id[1]); + snd_ctl_elem_value_set_id (sw[2], switch_id[2]); + + while (1) { + if (snd_ctl_elem_read (h->driver->ctl_handle, sw[0])) { + jack_error ("cannot read control switch 0 ..."); + } + hammerfall_check_sync (h, sw[0]); + + if (snd_ctl_elem_read (h->driver->ctl_handle, sw[1])) { + jack_error ("cannot read control switch 0 ..."); + } + hammerfall_check_sync (h, sw[1]); + + if (snd_ctl_elem_read (h->driver->ctl_handle, sw[2])) { + jack_error ("cannot read control switch 0 ..."); + } + hammerfall_check_sync (h, sw[2]); + + if (nanosleep (&h->monitor_interval, 0)) { + break; + } + } + + pthread_exit (0); +} + +jack_hardware_t * +jack_alsa_hammerfall_hw_new (alsa_driver_t *driver) + +{ + jack_hardware_t *hw; + hammerfall_t *h; + + hw = (jack_hardware_t *) malloc (sizeof (jack_hardware_t)); + + hw->capabilities = Cap_HardwareMonitoring|Cap_AutoSync|Cap_WordClock|Cap_ClockMaster|Cap_ClockLockReporting; + hw->input_monitor_mask = 0; + hw->private = 0; + + hw->set_input_monitor_mask = hammerfall_set_input_monitor_mask; + hw->change_sample_clock = hammerfall_change_sample_clock; + hw->release = hammerfall_release; + + h = (hammerfall_t *) malloc (sizeof (hammerfall_t)); + + h->lock_status[0] = FALSE; + h->sync_status[0] = FALSE; + h->lock_status[1] = FALSE; + h->sync_status[1] = FALSE; + h->lock_status[2] = FALSE; + h->sync_status[2] = FALSE; + h->said_that_spdif_is_fine = FALSE; + h->driver = driver; + + h->monitor_interval.tv_sec = 1; + h->monitor_interval.tv_nsec = 0; + + hw->private = h; + + if (pthread_create (&h->monitor_thread, 0, hammerfall_monitor_controls, hw)) { + jack_error ("ALSA/Hammerfall: cannot create sync monitor thread"); + } + + return hw; +} diff --git a/jack.pc.in b/jack.pc.in new file mode 100644 index 0000000..f3a624e --- /dev/null +++ b/jack.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: jack +Description: the Jack Audio Connection Kit: a low-latency synchronous callback-based media server +Requires: glib >= 1.0.0 +Version: @JACK_VERSION@ +Libs: -L${libdir} -ljack +Cflags: -I${includedir} diff --git a/jack/Makefile.am b/jack/Makefile.am new file mode 100644 index 0000000..d2acbbf --- /dev/null +++ b/jack/Makefile.am @@ -0,0 +1,4 @@ +MAINTAINERCLEANFILES = Makefile.in + +libjackincludedir = $(includedir)/jack +libjackinclude_HEADERS = $(wildcard *.h) diff --git a/jack/alsa_driver.h b/jack/alsa_driver.h new file mode 100644 index 0000000..a8bb759 --- /dev/null +++ b/jack/alsa_driver.h @@ -0,0 +1,159 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_alsa_driver_h__ +#define __jack_alsa_driver_h__ + +#include <sys/asoundlib.h> + +#include <jack/types.h> +#include <jack/hardware.h> +#include <jack/driver.h> +#include <jack/memops.h> +#include <jack/jack.h> + +typedef void (*ReadCopyFunction) (sample_t *dst, char *src, + unsigned long src_bytes, + unsigned long src_skip_bytes); +typedef void (*WriteCopyFunction) (char *dst, sample_t *src, + unsigned long src_bytes, + unsigned long dst_skip_bytes, + gain_t gain); +typedef void (*CopyCopyFunction) (char *dst, char *src, + unsigned long src_bytes, + unsigned long dst_skip_bytes, + unsigned long src_skip_byte); + +typedef struct { + + JACK_DRIVER_DECL + + char **playback_addr; + char **capture_addr; + const snd_pcm_channel_area_t *capture_areas; + const snd_pcm_channel_area_t *playback_areas; + unsigned long time_at_interrupt; + struct pollfd pfd; + unsigned long interleave_unit; + unsigned long capture_interleave_skip; + unsigned long playback_interleave_skip; + unsigned long max_nchannels; + unsigned long user_nchannels; + unsigned long playback_nchannels; + unsigned long capture_nchannels; + unsigned long sample_bytes; + unsigned long *silent; + char *alsa_name; + char *alsa_driver; + snd_pcm_uframes_t buffer_frames; + unsigned long bytes_per_cycle; /* per channel */ + unsigned long channels_not_done; + unsigned long channel_done_bits; + snd_pcm_format_t sample_format; + float max_sample_val; + unsigned long nfragments; + int max_level; + int min_level; + unsigned long last_mask; + unsigned long silence_pending; + snd_ctl_t *ctl_handle; + snd_pcm_t *playback_handle; + snd_pcm_t *capture_handle; + unsigned long *input_monitor_requests; + snd_pcm_hw_params_t *playback_hw_params; + snd_pcm_sw_params_t *playback_sw_params; + snd_pcm_hw_params_t *capture_hw_params; + snd_pcm_sw_params_t *capture_sw_params; + jack_hardware_t *hw; + ClockSyncStatus *clock_sync_data; + jack_client_t *client; + GSList *capture_ports; + GSList *playback_ports; + + char capture_and_playback_not_synced : 1; + char interleaved : 1; + + ReadCopyFunction read_via_copy; + WriteCopyFunction write_via_copy; + CopyCopyFunction channel_copy; + +} alsa_driver_t; + +static __inline__ void alsa_driver_mark_channel_done (alsa_driver_t *driver, channel_t chn) { + driver->channels_not_done &= ~(1<<chn); + driver->silent[chn] = 0; +} + +static __inline__ void alsa_driver_silence_on_channel (alsa_driver_t *driver, channel_t chn, nframes_t nframes) { + if (driver->interleaved) { + memset_interleave + (driver->playback_addr[chn], + 0, nframes * driver->sample_bytes, + driver->interleave_unit, + driver->playback_interleave_skip); + } else { + memset (driver->playback_addr[chn], 0, nframes * driver->sample_bytes); + } + alsa_driver_mark_channel_done (driver,chn); +} + +static __inline__ void alsa_driver_read_from_channel (alsa_driver_t *driver, + channel_t channel, sample_t *buf, + nframes_t nsamples, + unsigned long offset) +{ + driver->read_via_copy (buf, + driver->capture_addr[channel] + offset, + nsamples, + driver->capture_interleave_skip); +} + +static __inline__ void alsa_driver_write_to_channel (alsa_driver_t *driver, + channel_t channel, + sample_t *buf, + nframes_t nsamples, + unsigned long offset, + gain_t gain) +{ + driver->write_via_copy (driver->playback_addr[channel] + offset, + buf, + nsamples, + driver->playback_interleave_skip, + gain); + alsa_driver_mark_channel_done (driver, channel); +} + +static __inline__ void alsa_driver_copy_channel (alsa_driver_t *driver, + channel_t input_channel, + channel_t output_channel, + nframes_t nsamples) { + + driver->channel_copy (driver->playback_addr[output_channel], + driver->capture_addr[input_channel], + nsamples * driver->sample_bytes, + driver->playback_interleave_skip, + driver->capture_interleave_skip); + alsa_driver_mark_channel_done (driver, output_channel); +} + +void alsa_driver_set_clock_sync_status (alsa_driver_t *driver, channel_t chn, ClockSyncStatus status); + + +#endif /* __jack_alsa_driver_h__ */ diff --git a/jack/driver.h b/jack/driver.h new file mode 100644 index 0000000..2294d03 --- /dev/null +++ b/jack/driver.h @@ -0,0 +1,136 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_driver_h__ +#define __jack_driver_h__ + +#include <glib.h> +#include <ltdl.h> +#include <pthread.h> +#include <jack/types.h> + +typedef void (*ClockSyncListenerFunction)(channel_t,ClockSyncStatus,void*); + +typedef struct { + unsigned long id; + ClockSyncListenerFunction function; + void *arg; +} ClockSyncListener; + +typedef void (*InputMonitorListenerFunction)(channel_t,int,void*); + +typedef struct { + unsigned long id; + InputMonitorListenerFunction function; + void *arg; +} InputMonitorListener; + +struct _jack_engine; +struct _jack_driver; + +typedef int (*JackDriverAttachFunction)(struct _jack_driver *, struct _jack_engine *); +typedef int (*JackDriverDetachFunction)(struct _jack_driver *, struct _jack_engine *); +typedef int (*JackDriverWaitFunction)(struct _jack_driver *); +typedef nframes_t (*JackDriverFramesSinceCycleStartFunction)(struct _jack_driver *); +typedef ClockSyncStatus (*JackDriverClockSyncStatusFunction)(struct _jack_driver *, channel_t); +typedef int (*JackDriverAudioStopFunction)(struct _jack_driver *); +typedef int (*JackDriverAudioStartFunction)(struct _jack_driver *); +typedef void (*JackDriverSetHwMonitoringFunction) (struct _jack_driver *, int); +typedef int (*JackDriverChangeSampleClockFunction) (struct _jack_driver *, SampleClockMode mode); +typedef int (*JackDriverResetParametersFunction) (struct _jack_driver *, nframes_t frames_per_cycle, nframes_t rate); +typedef void (*JackDriverMarkChannelSilentFunction) (struct _jack_driver *, unsigned long chn); +typedef void (*JackDriverRequestMonitorInputFunction) (struct _jack_driver *, unsigned long chn, int yn); +typedef void (*JackDriverRequestAllMonitorInputFunction) (struct _jack_driver *, int yn); +typedef int (*JackDriverMonitoringInputFunction)(struct _jack_driver *,channel_t chn); + +#define JACK_DRIVER_DECL \ + nframes_t frame_rate; \ + nframes_t frames_per_cycle; \ + nframes_t capture_frame_latency; \ + nframes_t playback_frame_latency; \ + unsigned long period_interval; \ + SampleClockMode clock_mode; \ + unsigned long input_monitor_mask; \ + struct _jack_engine *engine; \ + GSList *clock_sync_listeners; \ + pthread_mutex_t clock_sync_lock; \ + unsigned long next_clock_sync_listener_id; \ + GSList *input_monitor_listeners; \ + pthread_mutex_t input_monitor_lock; \ + unsigned long next_input_monitor_listener_id; \ + char hw_monitoring : 1; \ + char all_monitor_in : 1; \ + char has_clock_sync_reporting : 1; \ + char has_hw_monitoring : 1; \ + lt_dlhandle handle; \ + void (*finish)(struct _jack_driver *);\ +\ + /* These are the "core" driver functions */ \ +\ + JackDriverAttachFunction attach; \ + JackDriverDetachFunction detach; \ + JackDriverWaitFunction wait; \ +\ + /* These support the "audio" side of a driver, which arguably \ + could/should be provided by a different kind of object. \ + */ \ +\ + JackDriverFramesSinceCycleStartFunction frames_since_cycle_start; \ + JackDriverClockSyncStatusFunction clock_sync_status; \ + JackDriverAudioStopFunction audio_stop; \ + JackDriverAudioStartFunction audio_start; \ + JackDriverSetHwMonitoringFunction set_hw_monitoring; \ + JackDriverChangeSampleClockFunction change_sample_clock; \ + JackDriverResetParametersFunction reset_parameters; \ + JackDriverMarkChannelSilentFunction mark_channel_silent; \ + JackDriverRequestMonitorInputFunction request_monitor_input; \ + JackDriverRequestAllMonitorInputFunction request_all_monitor_input; \ + JackDriverMonitoringInputFunction monitoring_input; + +typedef struct _jack_driver { + + JACK_DRIVER_DECL + +} jack_driver_t; + +void jack_driver_init (jack_driver_t *); +void jack_driver_release (jack_driver_t *); + +jack_driver_t *jack_driver_load (const char *path_to_so, ...); +void jack_driver_unload (jack_driver_t *); + +int jack_driver_listen_for_clock_sync_status (jack_driver_t *, ClockSyncListenerFunction, void *arg); +int jack_driver_stop_listen_for_clock_sync_status (jack_driver_t *, int); + +int jack_driver_listen_for_input_monitor_status (jack_driver_t *, InputMonitorListenerFunction, void *arg); +int jack_driver_stop_listen_for_input_monitor_status (jack_driver_t *, int); + +void jack_driver_clock_sync_notify (jack_driver_t *, channel_t chn, ClockSyncStatus); +void jack_driver_input_monitor_notify (jack_driver_t *, channel_t chn, int); + +#endif /* __jack_driver_h__ */ + + + + + + + + diff --git a/jack/engine.h b/jack/engine.h new file mode 100644 index 0000000..332b650 --- /dev/null +++ b/jack/engine.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_engine_h__ +#define __jack_engine_h__ + +#include <jack/jack.h> +#include <jack/internal.h> + +struct _jack_driver; +struct _jack_client_internal; +struct _jack_port_internal; + +struct _jack_engine { + jack_control_t *control; + struct _jack_driver *driver; + int (*process)(struct _jack_engine *, nframes_t frames); + int (*set_buffer_size)(struct _jack_engine *, nframes_t frames); + int (*set_sample_rate)(struct _jack_engine *, nframes_t frames); + pthread_mutex_t graph_lock; + pthread_mutex_t buffer_lock; + pthread_mutex_t port_lock; + int period_msecs; + int port_max; + int control_shm_id; + key_t control_key; + key_t port_segment_key; /* XXX fix me */ + void *port_segment_address; /* XXX fix me */ + pthread_t audio_thread; + pthread_t server_thread; + + /* these lists are protected by `buffer_lock' */ + + GSList *port_segments; + GSList *port_buffer_freelist; + + /* these lists are all protected by `graph_lock' */ + + GSList *clients; + GSList *clients_waiting; + GSList *connections; + + struct _jack_port_internal *internal_ports; + + GSList *port_types; /* holds ptrs to jack_port_type_info_t */ + + int fds[2]; + jack_client_id_t next_client_id; + size_t pfd_size; + size_t pfd_max; + struct pollfd *pfd; + struct _jack_client_internal *timebase_client; + jack_port_buffer_info_t *silent_buffer; + char fifo_prefix[FIFO_NAME_SIZE+1]; + int *fifo; + unsigned long fifo_size; + unsigned long external_client_cnt; + int rtpriority; + int getthehelloutathere; +}; + +/* public functions */ + +jack_engine_t *jack_engine_new (int real_time, int real_time_priority); +int jack_engine_delete (jack_engine_t *); +int jack_run (jack_engine_t *engine); +int jack_wait (jack_engine_t *engine); +int jack_use_driver (jack_engine_t *, struct _jack_driver *); + +#endif /* __jack_engine_h__ */ diff --git a/jack/error.h b/jack/error.h new file mode 100644 index 0000000..bdc5ce5 --- /dev/null +++ b/jack/error.h @@ -0,0 +1,8 @@ +#ifndef __jack_error_h__ +#define __jack_error_h__ + +extern void (*jack_error)(const char *fmt, ...); +void jack_set_error_function (void (*func)(const char *, ...)); + + +#endif /* __jack_error_h__ */ diff --git a/jack/generic.h b/jack/generic.h new file mode 100644 index 0000000..00a8caa --- /dev/null +++ b/jack/generic.h @@ -0,0 +1,7 @@ +#ifndef __jack_generic_h__ +#define __jack_generic_h__ + +jack_hardware_t * +jack_alsa_generic_hw_new (alsa_driver_t *driver); + +#endif /* __jack_generic_h__*/ diff --git a/jack/hammerfall.h b/jack/hammerfall.h new file mode 100644 index 0000000..d183f47 --- /dev/null +++ b/jack/hammerfall.h @@ -0,0 +1,17 @@ +#ifndef __jack_hammerfall_h__ +#define __jack_hammerfall_h__ + +#include <sys/time.h> + +typedef struct { + int lock_status[3]; + int sync_status[3]; + int said_that_spdif_is_fine; + pthread_t monitor_thread; + alsa_driver_t *driver; + struct timespec monitor_interval; +} hammerfall_t; + +jack_hardware_t *jack_alsa_hammerfall_hw_new (alsa_driver_t *driver); + +#endif /* __jack_hammerfall_h__*/ diff --git a/jack/hardware.h b/jack/hardware.h new file mode 100644 index 0000000..1e35ee2 --- /dev/null +++ b/jack/hardware.h @@ -0,0 +1,27 @@ +#ifndef __jack_hardware_h__ +#define __jack_hardware_h__ + +#include <jack/types.h> + +struct _jack_hardware; + +typedef void (*JackHardwareReleaseFunction)(struct _jack_hardware *); +typedef int (*JackHardwareSetInputMonitorMaskFunction)(struct _jack_hardware *, unsigned long); +typedef int (*JackHardwareChangeSampleClockFunction)(struct _jack_hardware *, SampleClockMode); + +typedef struct _jack_hardware { + + unsigned long capabilities; + unsigned long input_monitor_mask; + + JackHardwareChangeSampleClockFunction change_sample_clock; + JackHardwareSetInputMonitorMaskFunction set_input_monitor_mask; + JackHardwareReleaseFunction release; + + void *private; + +} jack_hardware_t; + +jack_hardware_t * jack_hardware_new (); + +#endif /* __jack_hardware_h__ */ diff --git a/jack/internal.h b/jack/internal.h new file mode 100644 index 0000000..fb09da0 --- /dev/null +++ b/jack/internal.h @@ -0,0 +1,215 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_internal_h__ +#define __jack_internal_h__ + +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <dlfcn.h> +#include <sys/types.h> +#include <sys/time.h> +#include <pthread.h> +#include <glib.h> + +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/port.h> + +#define FIFO_NAME_SIZE 32 + +typedef void * dlhandle; + +typedef struct { + int shm_key; + size_t offset; +} jack_port_buffer_info_t; + +typedef struct { + int shm_key; + char *address; +} jack_port_segment_info_t; + +typedef struct { + + nframes_t frame_time; + pid_t engine_pid; + unsigned long sample_rate; + unsigned long buffer_size; + char real_time; + int client_priority; + unsigned long port_max; + jack_port_shared_t ports[0]; + +} jack_control_t; + +typedef enum { + BufferSizeChange, + SampleRateChange, + NewPortBufferSegment, + PortConnected, + PortDisconnected, + GraphReordered, + PortRegistered, + PortUnregistered, + PortMonitor, + PortUnMonitor, +} AudioEngineEventType; + +typedef struct { + AudioEngineEventType type; + union { + unsigned long n; + jack_port_id_t port_id; + jack_port_id_t self_id; + } x; + union { + unsigned long n; + jack_port_id_t other_id; + } y; +} jack_event_t; + +typedef enum { + ClientDynamic, /* connect request just names .so */ + ClientDriver, /* code is loaded along with driver */ + ClientOutOfProcess /* client is in another process */ +} ClientType; + +#define JACK_CLIENT_STATE_NOT_TRIGGERED 19 +#define JACK_CLIENT_STATE_TRIGGERED 23 +#define JACK_CLIENT_STATE_FINISHED 24 + +typedef volatile struct { + + volatile nframes_t frame_time; /* w: client r: engine (if client is timebase) */ + volatile int id; /* w: engine r: engine and client */ + volatile nframes_t nframes; /* w: engine r: client */ + volatile char state; /* w: engine and client r: engine JACK_CLIENT_STATE_... */ + volatile char name[JACK_CLIENT_NAME_SIZE+1]; + volatile ClientType type; /* w: engine r: engine and client */ + volatile char active : 1; /* w: engine r: engine and client */ + volatile char dead : 1; /* r/w: engine */ + + /* callbacks */ + + JackProcessCallback process; + void *process_arg; + JackBufferSizeCallback bufsize; + void *bufsize_arg; + JackSampleRateCallback srate; + void *srate_arg; + JackPortRegistrationCallback port_register; + void *port_register_arg; + JackPortMonitorCallback port_monitor; + void *port_monitor_arg; + + /* for engine use only */ + + void *private_internal_client; + +} jack_client_control_t; + +typedef struct { + + ClientType type; + + char name[JACK_CLIENT_NAME_SIZE+1]; + char object_path[PATH_MAX+1]; + +} jack_client_connect_request_t; + +typedef struct { + + int status; + + int client_key; + int control_key; + + char fifo_prefix[FIFO_NAME_SIZE+1]; + + int realtime; + int realtime_priority; + + /* these two are valid only if the connect request + was for type == ClientDriver. + */ + + jack_client_control_t *client_control; + jack_control_t *engine_control; + + /* XXX need to be able to use more than one port segment key */ + + key_t port_segment_key; + void *port_segment_address; + +} jack_client_connect_result_t; + +typedef struct { + jack_client_id_t client_id; +} jack_client_connect_ack_request_t; + +typedef struct { + char status; +} jack_client_connect_ack_result_t; + +typedef enum { + RegisterPort = 1, + UnRegisterPort = 2, + ConnectPorts = 3, + DisconnectPorts = 4, + SetTimeBaseClient = 5, + DropClient = 6, + ActivateClient = 7, + DeactivateClient = 8, + RequestPortMonitor = 9, + RequestPortUnMonitor = 10 +} AudioEngineRequestType; + +typedef struct { + + AudioEngineRequestType type; + union { + struct { + char name[JACK_PORT_NAME_SIZE+1]; + char type[JACK_PORT_TYPE_SIZE+1]; + unsigned long flags; + unsigned long buffer_size; + jack_port_id_t port_id; + jack_client_id_t client_id; + } port_info; + struct { + char source_port[JACK_PORT_NAME_SIZE+1]; + char destination_port[JACK_PORT_NAME_SIZE+1]; + } connect; + jack_client_id_t client_id; + } x; + int status; +} jack_request_t; + +extern int jack_client_handle_port_connection (jack_client_t *client, jack_event_t *event); + +jack_client_t *jack_driver_become_client (const char *client_name); + +#endif /* __jack_internal_h__ */ + + + + diff --git a/jack/jack.h b/jack/jack.h new file mode 100644 index 0000000..439f97b --- /dev/null +++ b/jack/jack.h @@ -0,0 +1,228 @@ + +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_h__ +#define __jack_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <glib.h> +#include <jack/types.h> +#include <jack/port.h> +#include <jack/error.h> + +jack_client_t *jack_client_new (const char *client_name); +int jack_client_close (jack_client_t *client); + +int jack_set_process_callback (jack_client_t *, JackProcessCallback, void *arg); +int jack_set_buffer_size_callback (jack_client_t *, JackBufferSizeCallback, void *arg); +int jack_set_sample_rate_callback (jack_client_t *, JackSampleRateCallback, void *arg); +int jack_set_port_registration_callback (jack_client_t *, JackPortRegistrationCallback, void *); +int jack_set_port_monitor_callback (jack_client_t *, JackPortMonitorCallback, void *); + +int jack_get_process_start_fd (jack_client_t *); +int jack_get_process_done_fd (jack_client_t *); + +int jack_activate (jack_client_t *client); +int jack_deactivate (jack_client_t *client); + +/* this creates a new port for the client. + + a port is an object used for moving data in or out of the client. + the data may be of any type. ports may be connected to each other + in various ways. + + a port has a short name, which may be any non-NULL and non-zero + length string, and is passed as the first argument. a port's full + name is the name of the client concatenated with a colon (:) and + then its short name. + + a port has a type, which may be any non-NULL and non-zero length + string, and is passed as the second argument. for types that are + not built into the jack API (currently just + JACK_DEFAULT_AUDIO_TYPE) the client MUST supply a non-zero size + for the buffer as the fourth argument. for builtin types, the + fourth argument is ignored. + + a port has a set of flags, enumerated below and passed as the third + argument in the form of a bitmask created by AND-ing together the + desired flags. the flags "IsInput" and "IsOutput" are mutually + exclusive and it is an error to use them both. + +*/ + +enum JackPortFlags { + + JackPortIsInput = 0x1, + JackPortIsOutput = 0x2, + JackPortIsPhysical = 0x4, /* refers to a physical connection */ + + /* if JackPortCanMonitor is set, then a call to + jack_port_request_monitor() makes sense. + + Precisely what this means is dependent on the client. A typical + result of it being called with TRUE as the second argument is + that data that would be available from an output port (with + JackPortIsPhysical set) is sent to a physical output connector + as well, so that it can be heard/seen/whatever. + + Clients that do not control physical interfaces + should never create ports with this bit set. + + Clients that do set this bit must have provided a + port_monitor callback before creating any ports with + this bit set. + */ + + JackPortCanMonitor = 0x8 +}; + +#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" + +jack_port_t * +jack_port_register (jack_client_t *, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size); + +/* this removes the port from the client */ + +int jack_port_unregister (jack_client_t *, jack_port_t *); + +/* This returns a pointer to the memory area associated with the + specified port. It can only be called from within the client's + "process" callback. For an output port, it will be a memory area + that can be written to; for an input port, it will be an area + containing the data from the port's connection(s), or + zero-filled. if there are multiple inbound connections, the data + will be mixed appropriately. +*/ + +void *jack_port_get_buffer (jack_port_t *, nframes_t); + +/* these two functions establish and disestablish a connection + between two ports. when a connection exists, data written + to the source port will be available to be read at the destination + port. + + the types of both ports must be identical to establish a connection. + + the flags of the source port must include PortIsOutput. + the flags of the destination port must include PortIsInput. +*/ + +int jack_port_connect (jack_client_t *, + const char *source_port, + const char *destination_port); + +int jack_port_disconnect (jack_client_t *, + const char *source_port, + const char *destination_port); + +/* A client may call this on a pair of its own ports to + semi-permanently wire them together. This means that + a client that wants to direct-wire an input port to + an output port can call this and then no longer + have to worry about moving data between them. Any data + arriving at the input port will appear automatically + at the output port. + + The `destination' port must be an output port. The `source' + port must be an input port. Both ports must belong to + the same client. You cannot use this to tie ports between + clients. Thats what a connection is for. +*/ + +int jack_port_tie (jack_port_t *dst, jack_port_t *src); + +/* This undoes the effect of jack_port_tie(). The port + should be same as the `destination' port passed to + jack_port_tie(). +*/ + +int jack_port_untie (jack_port_t *port); + +/* a client may call this function to prevent other objects + from changing the connection status of the named port. +*/ + +int jack_port_lock (jack_client_t *, jack_port_t *); +int jack_port_unlock (jack_client_t *, jack_port_t *); + + +/* if JackPortCanMonitor is set for a port, then this function will + turn on/off input monitoring for the port. if JackPortCanMonitor + is not set, then this function will do nothing. +*/ + +int jack_port_request_monitor (jack_client_t *, const char *port_name, int onoff); + +/* this returns the sample rate of the jack */ + +unsigned long jack_get_sample_rate (jack_client_t *); + +/* this returns the current maximum size that will + ever be passed to the "process" callback. it should only + be used *before* the client has been activated. +*/ + +nframes_t jack_get_buffer_size (jack_client_t *); + +/* This function returns a list of ports that match the specified + arguments. + + port_name_pattern: a regular expression used to select ports by name. + if NULL or of zero length, no selection based on + name will be carried out. + + type_name_pattern: a regular expression used to select ports by type. + if NULL or of zero length, no selection based on + type will be carried out. + + flags: a value used to select ports by their flags. if + zero, no selection based on flags will be carried out. +*/ + +GSList *jack_get_ports (jack_client_t *, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags); + +/* If a client is told to become the timebase for the entire system, + it calls this function. If it returns zero, then the client has + the responsibility to call jack_update_time() at the end + of its process() callback. Whatever time it provides (in frames + since its reference zero time) becomes the current timebase + for the entire system. +*/ + +int jack_engine_takeover_timebase (jack_client_t *); +void jack_update_time (jack_client_t *, nframes_t); + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_h__ */ + diff --git a/jack/memops.h b/jack/memops.h new file mode 100644 index 0000000..9951eb8 --- /dev/null +++ b/jack/memops.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 1999-2000 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_memops_h__ +#define __jack_memops_h__ + +#include <jack/types.h> + +void sample_move_d32u24_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t); +void sample_move_d16_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t); + +void sample_move_dS_s32u24 (sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip); +void sample_move_dS_s16 (sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip); + +void sample_merge_d16_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t); +void sample_merge_d32u24_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t); + +static __inline__ void +sample_merge (sample_t *dst, sample_t *src, unsigned long cnt, gain_t gain) + +{ + if (gain == 1.0) { + while (cnt--) { + *dst += *src; + dst++; + src++; + } + } else { + while (cnt--) { + *dst += (*src * gain); + dst++; + src++; + } + } +} + +static __inline__ void +sample_memcpy (sample_t *dst, sample_t *src, unsigned long cnt, gain_t gain) + +{ + if (gain == 1.0) { + memcpy (dst, src, cnt * sizeof (sample_t)); + } else { + while (cnt--) { + *dst = (*src * gain); + dst++; + src++; + } + } +} + +void memset_interleave (char *dst, char val, unsigned long bytes, unsigned long unit_bytes, unsigned long skip_bytes); +void memcpy_fake (char *dst, char *src, unsigned long src_bytes, unsigned long foo, unsigned long bar); + +void memcpy_interleave_d16_s16 (char *dst, char *src, unsigned long src_bytes, unsigned long dst_skip_bytes, unsigned long src_skip_bytes); +void memcpy_interleave_d32_s32 (char *dst, char *src, unsigned long src_bytes, unsigned long dst_skip_bytes, unsigned long src_skip_bytes); + +void merge_memcpy_interleave_d16_s16 (char *dst, char *src, unsigned long src_bytes, unsigned long dst_skip_bytes, unsigned long src_skip_bytes); +void merge_memcpy_interleave_d32_s32 (char *dst, char *src, unsigned long src_bytes, unsigned long dst_skip_bytes, unsigned long src_skip_bytes); + +void merge_memcpy_d16_s16 (char *dst, char *src, unsigned long src_bytes, unsigned long foo, unsigned long bar); +void merge_memcpy_d32_s32 (char *dst, char *src, unsigned long src_bytes, unsigned long foo, unsigned long bar); + +#endif /* __jack_memops_h__ */ + + + + + + + + diff --git a/jack/pool.h b/jack/pool.h new file mode 100644 index 0000000..d1d80d6 --- /dev/null +++ b/jack/pool.h @@ -0,0 +1,29 @@ +/* + Copyright (C) 2001 Paul Davis +x + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_pool_h__ +#define __jack_pool_h__ + +#include <sys/types.h> + +void * jack_pool_alloc (size_t bytes); +void jack_pool_release (void *); + +#endif /* __jack_pool_h__ */ diff --git a/jack/port.h b/jack/port.h new file mode 100644 index 0000000..6eed4b5 --- /dev/null +++ b/jack/port.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_port_h__ +#define __jack_port_h__ + +#include <pthread.h> +#include <jack/types.h> + +typedef struct _jack_port_type_info { + const char *type_name; /* what do you think ? */ + + void (*mixdown)(jack_port_t *, nframes_t); /* function to mixdown multiple inputs to a buffer. can be + NULL, indicating that multiple input connections + are not legal for this data type. + */ + + long buffer_scale_factor; /* If == 1, then a buffer to handle nframes worth of + data is sizeof(sample_t) * nframes bytes large. + + If anything other than 1, the buffer allocated + for input mixing will be this value times + sizeof (sample_t) * nframes bytes in size. + + Obviously, for non-audio data types, it may have + a different value. + + if < 0, then the value should be ignored, and + port->shared->buffer_size should be used. + */ +} jack_port_type_info_t; + +/* This is the data structure allocated in shared memory + by the engine. +*/ + +typedef struct _jack_port_shared { + void *buffer; + unsigned long flags; + unsigned long buffer_size; + jack_port_id_t id; + char name[JACK_CLIENT_NAME_SIZE+JACK_PORT_NAME_SIZE+2]; + jack_port_type_info_t type_info; + jack_client_id_t client_id; + + char in_use : 1; + char locked : 1; +} jack_port_shared_t; + +/* This is the data structure allocated by the client + in local memory. The `shared' pointer points + the the corresponding structure in shared memory. +*/ + +struct _jack_port { + struct _jack_port_shared *shared; + GSList *connections; + struct _jack_port *tied; + void *own_buffer; +}; + +/* this is the structure allocated by the engine in local + memory. +*/ + +typedef struct _jack_port_internal { + struct _jack_port_shared *shared; + GSList *connections; +} jack_port_internal_t; + +#endif /* __jack_port_h__ */ + diff --git a/jack/types.h b/jack/types.h new file mode 100644 index 0000000..c0f478e --- /dev/null +++ b/jack/types.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __jack_engine_types_h__ +#define __jack_engine_types_h__ + +typedef float sample_t; +typedef unsigned long nframes_t; +typedef long jack_port_id_t; +typedef unsigned long jack_client_id_t; +typedef float gain_t; +typedef long channel_t; + +typedef int (*JackProcessCallback)(nframes_t, void *); +typedef int (*JackBufferSizeCallback)(nframes_t, void *); +typedef int (*JackSampleRateCallback)(nframes_t, void *); +typedef void (*JackPortRegistrationCallback)(jack_port_id_t,int,void*); +typedef void (*JackPortMonitorCallback)(jack_port_id_t,int,void*); + +#define NoChannel -1 +#define NoPort -1 + +typedef struct _jack_engine jack_engine_t; +typedef struct _jack_port jack_port_t; +typedef struct _jack_client jack_client_t; + +#define JACK_PORT_NAME_SIZE 32 +#define JACK_PORT_TYPE_SIZE 32 +#define JACK_CLIENT_NAME_SIZE 32 + +typedef enum { + Cap_HardwareMonitoring = 0x1, + Cap_AutoSync = 0x2, + Cap_WordClock = 0x4, + Cap_ClockMaster = 0x8, + Cap_ClockLockReporting = 0x10 +} Capabilities; + +typedef enum { + AutoSync, + WordClock, + ClockMaster +} SampleClockMode; + +typedef enum { + Lock = 0x1, + NoLock = 0x2, + Sync = 0x4, + NoSync = 0x8 +} ClockSyncStatus; + +#endif /* __jack_engine_types_h__ */ @@ -0,0 +1,157 @@ +#include <stdio.h> +#include <signal.h> +#include <getopt.h> + +#include <jack/engine.h> +#include <jack/internal.h> +#include <jack/driver.h> + +static sigset_t signals; + +static jack_engine_t *engine; + +static void * +signal_thread (void *arg) + +{ + int sig; + int err; + + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + err = sigwait (&signals, &sig); + fprintf (stderr, "exiting due to signal %d\n", sig); + jack_engine_delete (engine); + exit (err); + + /*NOTREACHED*/ + return 0; +} + +int +catch_signals (void) + +{ + pthread_t thread_id; + + sigemptyset (&signals); + sigaddset(&signals, SIGHUP); + sigaddset(&signals, SIGINT); + sigaddset(&signals, SIGQUIT); + sigaddset(&signals, SIGILL); + sigaddset(&signals, SIGTRAP); + sigaddset(&signals, SIGABRT); + sigaddset(&signals, SIGIOT); + sigaddset(&signals, SIGFPE); + sigaddset(&signals, SIGKILL); + sigaddset(&signals, SIGPIPE); + sigaddset(&signals, SIGTERM); + sigaddset(&signals, SIGCHLD); + sigaddset(&signals, SIGCONT); + sigaddset(&signals, SIGSTOP); + sigaddset(&signals, SIGTSTP); + sigaddset(&signals, SIGTTIN); + sigaddset(&signals, SIGTTOU); + + /* all child threads will inherit this mask */ + + pthread_sigmask (SIG_BLOCK, &signals, 0); + + /* start a thread to wait for signals */ + + if (pthread_create (&thread_id, 0, signal_thread, 0)) { + fprintf (stderr, "cannot create signal catching thread"); + return -1; + } + + pthread_detach (thread_id); + + return 0; +} + +static char *alsa_pcm_name = "default"; +static nframes_t frames_per_interrupt = 64; +static nframes_t srate = 48000; +static int realtime = 0; +static int realtime_priority = 10; + +static void usage () + +{ + fprintf (stderr, "usage: engine [ -d ALSA PCM device ] [ -r sample-rate ] [ -p frames_per_interrupt ] [ -R [ -P priority ] ]\n"); +} + +int +main (int argc, char *argv[]) + +{ + jack_driver_t *driver; + const char *options = "hd:r:p:RP:"; + struct option long_options[] = + { + { "device", 1, 0, 'd' }, + { "srate", 1, 0, 'r' }, + { "frames-per-interrupt", 1, 0, 'p' }, + { "help", 0, 0, 'h' }, + { "realtime", 0, 0, 'R' }, + { "realtime-priority", 1, 0, 'P' }, + { 0, 0, 0, 0 } + }; + int option_index; + int opt; + + catch_signals (); + + opterr = 0; + while ((opt = getopt_long (argc, argv, options, long_options, &option_index)) != EOF) { + switch (opt) { + case 'd': + alsa_pcm_name = optarg; + break; + + case 'r': + srate = atoi (optarg); + break; + + case 'p': + frames_per_interrupt = atoi (optarg); + break; + + case 'R': + realtime = 1; + break; + + case 'P': + realtime_priority = atoi (optarg); + break; + + case 'h': + default: + fprintf (stderr, "unknown option character %c\n", opt); + usage(); + return -1; + } + } + + if ((engine = jack_engine_new (realtime, realtime_priority)) == 0) { + fprintf (stderr, "cannot create engine\n"); + return 1; + } + + if ((driver = jack_driver_load ("/usr/local/lib/jack_alsa.so", alsa_pcm_name, frames_per_interrupt, srate)) == 0) { + fprintf (stderr, "cannot load ALSA driver module\n"); + return 1; + } + + jack_use_driver (engine, driver); + + printf ("start engine ...\n"); + + jack_run (engine); + jack_wait (engine); + + return 0; +} + + + diff --git a/memops.c b/memops.c new file mode 100644 index 0000000..76804cb --- /dev/null +++ b/memops.c @@ -0,0 +1,257 @@ +/* + Copyright (C) 2000 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <memory.h> +#include <stdlib.h> + +#include <jack/memops.h> + +#define SAMPLE_MAX_24BIT 8388608.0f +#define SAMPLE_MAX_16BIT 32767.0f + +/* XXX we could use rint(), but for now we'll accept whatever default + floating-point => int conversion the compiler provides. +*/ + +void sample_move_d32u24_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t gain) + +{ + /* ALERT: signed sign-extension portability !!! */ + + if (gain == 1.0) { + while (nsamples--) { + *((int *) dst) = ((int) (*src * SAMPLE_MAX_24BIT)) << 8; + dst += dst_skip; + src++; + } + } else { + while (nsamples--) { + *((int *) dst) = ((int) ((*src * gain) * SAMPLE_MAX_24BIT)) << 8; + dst += dst_skip; + src++; + } + } +} + +void sample_move_dS_s32u24 (sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip) +{ + /* ALERT: signed sign-extension portability !!! */ + + while (nsamples--) { + *dst = (*((int *) src) >> 8) / SAMPLE_MAX_24BIT; + dst++; + src += src_skip; + } +} + +void sample_move_d16_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t gain) + +{ + /* ALERT: signed sign-extension portability !!! */ + + /* XXX good to use x86 assembler here, since float->short + sucks that h/w. + */ + + + if (gain == 1.0) { + while (nsamples--) { + *((short *) dst) = (short) (*src * SAMPLE_MAX_16BIT); + dst += dst_skip; + src++; + } + } else { + while (nsamples--) { + *((short *) dst) = (short) ((*src * gain) * SAMPLE_MAX_16BIT); + dst += dst_skip; + src++; + } + } +} + +void sample_move_dS_s16 (sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip) + +{ + /* ALERT: signed sign-extension portability !!! */ + while (nsamples--) { + *dst = (*((short *) src)) / SAMPLE_MAX_16BIT; + dst++; + src += src_skip; + } +} + +void sample_merge_d16_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t gain) + +{ + /* ALERT: signed sign-extension portability !!! */ + + if (gain == 1.0) { + while (nsamples--) { + *((short *) dst) += (short) (*src * SAMPLE_MAX_16BIT); + dst += dst_skip; + src++; + } + } else { + while (nsamples--) { + *((short *) dst) += (short) ((*src * gain) * SAMPLE_MAX_16BIT); + dst += dst_skip; + src++; + } + } +} + +void sample_merge_d32u24_sS (char *dst, sample_t *src, unsigned long nsamples, unsigned long dst_skip, gain_t gain) + +{ + /* ALERT: signed sign-extension portability !!! */ + + if (gain == 1.0) { + while (nsamples--) { + *((int *) dst) += (((int) (*src * SAMPLE_MAX_24BIT)) << 8); + dst += dst_skip; + src++; + } + } else { + while (nsamples--) { + *((int *) dst) += (((int) ((*src * gain) * SAMPLE_MAX_24BIT)) << 8); + dst += dst_skip; + src++; + } + } +} + +void memset_interleave (char *dst, char val, unsigned long bytes, + unsigned long unit_bytes, + unsigned long skip_bytes) +{ + switch (unit_bytes) { + case 1: + while (bytes--) { + *dst = val; + dst += skip_bytes; + } + break; + case 2: + while (bytes) { + *((short *) dst) = (short) val; + dst += skip_bytes; + bytes -= 2; + } + break; + case 4: + while (bytes) { + *((int *) dst) = (int) val; + dst += skip_bytes; + bytes -= 4; + } + break; + } +} + +/* COPY FUNCTIONS: used to move data from an input channel to an + output channel. Note that we assume that the skip distance + is the same for both channels. This is completely fine + unless the input and output were on different audio interfaces that + were interleaved differently. We don't try to handle that. +*/ + +void +memcpy_fake (char *dst, char *src, unsigned long src_bytes, unsigned long foo, unsigned long bar) +{ + memcpy (dst, src, src_bytes); +} + +void +merge_memcpy_d16_s16 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) +{ + while (src_bytes) { + *((short *) dst) += *((short *) src); + dst += 2; + src += 2; + src_bytes -= 2; + } +} + +void +merge_memcpy_d32_s32 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) + +{ + while (src_bytes) { + *((int *) dst) += *((int *) src); + dst += 4; + src += 4; + src_bytes -= 4; + } +} + +void +merge_memcpy_interleave_d16_s16 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) + +{ + while (src_bytes) { + *((short *) dst) += *((short *) src); + dst += dst_skip_bytes; + src += src_skip_bytes; + src_bytes -= 2; + } +} + +void +merge_memcpy_interleave_d32_s32 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) +{ + while (src_bytes) { + *((int *) dst) += *((int *) src); + dst += dst_skip_bytes; + src += src_skip_bytes; + src_bytes -= 4; + } +} + +void +memcpy_interleave_d16_s16 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) +{ + while (src_bytes) { + *((short *) dst) = *((short *) src); + dst += dst_skip_bytes; + src += src_skip_bytes; + src_bytes -= 2; + } +} + +void +memcpy_interleave_d32_s32 (char *dst, char *src, unsigned long src_bytes, + unsigned long dst_skip_bytes, unsigned long src_skip_bytes) + +{ + while (src_bytes) { + *((int *) dst) = *((int *) src); + dst += dst_skip_bytes; + src += src_skip_bytes; + src_bytes -= 4; + } +} diff --git a/monitor_client.c b/monitor_client.c new file mode 100644 index 0000000..123458e --- /dev/null +++ b/monitor_client.c @@ -0,0 +1,23 @@ +#include <stdio.h> +#include <unistd.h> + +#include <jack/jack.h> + +int +main (int argc, char *argv[]) + +{ + jack_client_t *client; + + if ((client = jack_client_new ("input monitoring")) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_port_request_monitor (client, "ALSA I/O:Input 1", TRUE); + sleep (10); + jack_port_request_monitor (client, "ALSA I/O:Input 1", FALSE); + jack_client_close (client); + exit (0); +} + @@ -0,0 +1,31 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <stdlib.h> +#include <jack/pool.h> + +void * +jack_pool_alloc (size_t bytes) + +{ + /* XXX need RT-pool based allocator here */ + + return malloc (bytes); +} @@ -0,0 +1,51 @@ +/* + Copyright (C) 2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#ifndef __audioengine_port_h__ +#endif __audioengine_port_h__ + +#define AUDIOENGINE_PORT_NAME_SIZE 32 +#define AUDIOENGINE_PORT_TYPE_SIZE 32 + +struct _audioengine_port_t { + + /* clients can use this value, and only this value */ + + void *buffer; + + /* the rest of this is private, for use by the engine only */ + + unsigned long flags; + GSList *connections; + void *own_buffer; + audioengine_port_t *tied; + unsigned long buffer_size; + char name[AUDIOENGINE_PORT_NAME_SIZE+1]; + char type[AUDIOENGINE_PORT_TYPE_SIZE+1]; + char client[AUDIOENGINE_CLIENT_NAME_SIZE+1]; + pthread_mutex_t lock; + audioengine_port_id_t id; + unsigned long client_id; + char in_use : 1; + char builtin : 1; + char locked : 1; +}; + +#endif /* __audioengine_port_h__ */ diff --git a/simple_client.c b/simple_client.c new file mode 100644 index 0000000..83bbb9a --- /dev/null +++ b/simple_client.c @@ -0,0 +1,83 @@ +#include <stdio.h> +#include <errno.h> +#include <unistd.h> + +#include <jack/jack.h> + +jack_port_t *my_input_port; +jack_port_t *my_output_port; + +int +process (nframes_t nframes, void *arg) + +{ + sample_t *out = (sample_t *) jack_port_get_buffer (my_output_port, nframes); + sample_t *in = (sample_t *) jack_port_get_buffer (my_input_port, nframes); + + memcpy (out, in, sizeof (sample_t) * nframes); + + return 0; +} + +int +bufsize (nframes_t nframes, void *arg) + +{ + printf ("the maximum buffer size is now %lu\n", nframes); + return 0; +} + +int +srate (nframes_t nframes, void *arg) + +{ + printf ("the sample rate is now %lu/sec\n", nframes); + return 0; +} + +int +main (int argc, char *argv[]) + +{ + jack_client_t *client; + + if (argc < 2) { + fprintf (stderr, "usage: aeclient <name>\n"); + return 1; + } + + if ((client = jack_client_new (argv[1])) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_set_process_callback (client, process, 0); + jack_set_buffer_size_callback (client, bufsize, 0); + jack_set_sample_rate_callback (client, srate, 0); + + printf ("engine sample rate: %lu\n", jack_get_sample_rate (client)); + + my_input_port = jack_port_register (client, "myinput", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + my_output_port = jack_port_register (client, "myoutput", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + } + + printf ("client activated\n"); + + if (jack_port_connect (client, "ALSA I/O:Input 1", my_input_port->shared->name)) { + fprintf (stderr, "cannot connect input ports\n"); + } + + if (jack_port_connect (client, my_output_port->shared->name, "ALSA I/O:Output 1")) { + fprintf (stderr, "cannot connect output ports\n"); + } + + sleep (5); + + printf ("done sleeping, now closing...\n"); + jack_client_close (client); + exit (0); +} + |