pax_global_header00006660000000000000000000000064126151212060014506gustar00rootroot0000000000000052 comment=802fc32c6b8360a8d5d1a648f279efc7d280cbca xwax-1.6-beta2/000077500000000000000000000000001261512120600133365ustar00rootroot00000000000000xwax-1.6-beta2/.gitignore000066400000000000000000000002531261512120600153260ustar00rootroot00000000000000TAGS xwax mktimecode *.o *.d .config .version dist tests/cues tests/external tests/library tests/midi tests/observer tests/scan-bpm tests/timecoder tests/track tests/ttf xwax-1.6-beta2/CHANGES000066400000000000000000000067241261512120600143420ustar00rootroot00000000000000v1.6 (work in progress) ----------------------- * Added timecode creator (for the adventurous) * Correct interpretation of ALSA's buffer size v1.5 (2014-02-09) ----------------- * Scan the music library in the background on startup * Function to re-scan a crate * Performance improovement to audio resampler * Additional font paths * Minor bug fixes v1.4 (2013-06-08) ----------------- * Scalable user interface * Simple control of timecode levels for 'software pre-amp' * Bug fix: MIDI interface now works when only JACK audio is used * Dicer flag renamed to '--dicer' * Minor fixes Acknowledgements: Daniel Holbach v1.3 (2012-11-29) ----------------- * Tempo (BPM) field, and sorting and changing of sort modes * Allow initial X window size and position to be set by the user * Makefile modifications for packagers * Track loading errors are reported to the main interface Acknowledgements: Engine, Lukas Fleischer, Daniel Holbach, Matej Laitl v1.2 (2012-03-23) ----------------- * Scan directories only for known file extensions * Support Novation 'Dicer' controller * Cue points, including 'punch' feature * Extra protection against skips: optionally lock memory into RAM Acknowledgements: Novation, Olivier Gauthier, Christoph Krapp, Mitchell Smith v1.1 (2012-01-30) ----------------- * Bug fix: incorrect display of track time remaining * Instant loading of duplicate tracks * Optionally protect decks during playback * Improvements to music selector * Increase speed building look-up tables v1.0 (2011-08-01) ----------------- * Changing of timecode at runtime * Improved parsing of vinyl track numbers * Bug fix: affecting multiple decks with different sample rates * Optimise timecode error checking during scratching * Require realtime priority; don't start without it * Internal restructuring Acknowledgements: Robert Flechtner, Daniel Holbach, Lukas Fleischer v0.9 (2011-04-19) ----------------- * Internal cleanups * Filtering of duplicate entries in the record library * Scanning of ordered playlists * Improved response when scratching * New parsing rules for filenames * A single button toggles timecode control on/off Acknowledgements: Daniel Holbach, Robert Vettel v0.8 (2010-11-08) ----------------- * 45 RPM control * Conversion of non-44100Hz MP3 files * Code cleanups and minor fixes Acknowledgements: Ewan Colsell, Robert Flechtner, Daniel Holbach, Matej Laitl v0.7 (2010-02-26) ----------------- * Multiple crates in music selector (Yves Adler) * Fix a potential buffer overflow (Matej Laitl) * Higher quality resampler * Improved pitch stability over long mixes * Installation script and man page for distributions * Moved from Bitstream Vera to DejaVu font * Minor fixes Acknowledgements: Yves Adler, Matej Laitl v0.6 (2009-09-03) ----------------- * Modular scanning of music library * Improved parsing of pathnames * Decreased memory use of timecode decoder * Minor fixes Acknowledgements: Yves Adler v0.5 (2009-07-03) ----------------- * Configurable sample rates * Timecode support for MixVibes vinyls * Rewritten timecode decoder with 4x resolution * Rewritten timecode tracking with improved accuracy * JACK audio device support * Clearer display of current position in the track overview * Minor fixes Acknowledgements: Daniel Fasnacht, Yves Adler v0.4 (2008-05-07) ----------------- * Timecode improvements v0.3 (2007-12-04) ----------------- * ALSA device support * Timecode optimisations * Minor fixes v0.2 (2007-06-12) ----------------- * First open-source release xwax-1.6-beta2/COPYING000066400000000000000000000431031261512120600143720ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. , 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 Lesser General Public License instead of this License. xwax-1.6-beta2/INSTALL000066400000000000000000000022051261512120600143660ustar00rootroot00000000000000The recommended way to build xwax is to run "make" with your chosen compile options, followed optionally by "make install" to install at the given prefix; eg. $ make PREFIX=/usr ALSA=yes $ make PREFIX=/usr ALSA=yes install # as root If PREFIX is not given, the user's home directory is used and "make install" does not need to be run as root. Different audio device types are enabled using the compile options: ALSA=yes JACK=yes OSS=yes If you are doing multiple builds you may like to put the compile options in a file named '.config' in the source directory instead of on the command line. There is a script to generate this file; for more information run $ ./configure --help Compilation errors are most likely the result of missing libraries. You need the libraries and header files installed for: * libSDL: http://www.libsdl.org/ * SDL_ttf (sometimes part of the SDL package, sometimes not) Optional dependencies are: * libasound: http://www.alsa-project.org/ (for ALSA=yes) * JACK: http://jackaudio.org/ (for JACK=yes) These libraries are packaged with most Linux distributions and this is the recommended way to install them. xwax-1.6-beta2/Makefile000066400000000000000000000077621261512120600150120ustar00rootroot00000000000000# Copyright (C) 2015 Mark Hills # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, as # published by the Free Software Foundation. # # 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 version 2 for more details. # # You should have received a copy of the GNU General Public License # version 2 along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # Import the optional configuration -include .config # Libraries and dependencies INSTALL ?= install SDL_CFLAGS ?= `sdl-config --cflags` SDL_LIBS ?= `sdl-config --libs` -lSDL_ttf ALSA_LIBS ?= -lasound JACK_LIBS ?= -ljack # Installation paths PREFIX ?= $(HOME) BINDIR ?= $(PREFIX)/bin EXECDIR ?= $(PREFIX)/libexec MANDIR ?= $(PREFIX)/share/man DOCDIR ?= $(PREFIX)/share/doc # Build flags CFLAGS ?= -O3 CFLAGS += -Wall CPPFLAGS += -MMD LDFLAGS ?= -O3 # Core objects and libraries OBJS = controller.o \ cues.o \ deck.o \ device.o \ dummy.o \ excrate.o \ external.o \ index.o \ interface.o \ library.o \ listbox.o \ lut.o \ player.o \ realtime.o \ rig.o \ selector.o \ status.o \ thread.o \ timecoder.o \ track.o \ xwax.o DEVICE_CPPFLAGS = DEVICE_LIBS = TESTS = tests/cues \ tests/external \ tests/library \ tests/observer \ tests/status \ tests/timecoder \ tests/track \ tests/ttf # Optional device types ifdef ALSA OBJS += alsa.o dicer.o midi.o DEVICE_CPPFLAGS += -DWITH_ALSA DEVICE_LIBS += $(ALSA_LIBS) endif ifdef JACK OBJS += jack.o DEVICE_CPPFLAGS += -DWITH_JACK DEVICE_LIBS += $(JACK_LIBS) endif ifdef OSS OBJS += oss.o DEVICE_CPPFLAGS += -DWITH_OSS endif TEST_OBJS = $(addsuffix .o,$(TESTS)) DEPS = $(OBJS:.o=.d) $(TEST_OBJS:.o=.d) mktimecode.d # Rules .PHONY: all all: xwax mktimecode tests # Dynamic versioning .PHONY: FORCE .version: FORCE ./mkversion -r VERSION = $(shell ./mkversion) # Main binary xwax: $(OBJS) xwax: LDLIBS += $(SDL_LIBS) $(DEVICE_LIBS) -lm xwax: LDFLAGS += -pthread interface.o: CFLAGS += $(SDL_CFLAGS) xwax.o: CFLAGS += $(SDL_CFLAGS) xwax.o: CPPFLAGS += $(DEVICE_CPPFLAGS) xwax.o: CPPFLAGS += -DEXECDIR=\"$(EXECDIR)\" -DVERSION=\"$(VERSION)\" xwax.o: .version # Supporting programs mktimecode: mktimecode.o mktimecode: LDLIBS += -lm # Install to system .PHONY: install install: $(INSTALL) -D xwax $(DESTDIR)$(BINDIR)/xwax $(INSTALL) -D scan $(DESTDIR)$(EXECDIR)/xwax-scan $(INSTALL) -D import $(DESTDIR)$(EXECDIR)/xwax-import $(INSTALL) -D -m 0644 xwax.1 $(DESTDIR)$(MANDIR)/man1/xwax.1 $(INSTALL) -D -m 0644 CHANGES $(DESTDIR)$(DOCDIR)/xwax/CHANGES $(INSTALL) -D -m 0644 COPYING $(DESTDIR)$(DOCDIR)/xwax/COPYING $(INSTALL) -D -m 0644 README $(DESTDIR)$(DOCDIR)/xwax/README # Distribution archive from Git source code .PHONY: dist dist: .version ./mkdist $(VERSION) # Editor tags files TAGS: $(OBJS:.o=.c) etags $^ # Manual tests .PHONY: tests tests: $(TESTS) tests: CPPFLAGS += -I. tests/cues: tests/cues.o cues.o tests/external: tests/external.o external.o tests/library: tests/library.o excrate.o external.o index.o library.o rig.o status.o thread.o track.o tests/library: LDFLAGS += -pthread tests/midi: tests/midi.o midi.o tests/midi: LDLIBS += $(ALSA_LIBS) tests/observer: tests/observer.o tests/status: tests/status.o status.o tests/timecoder: tests/timecoder.o lut.o timecoder.o tests/track: tests/track.o excrate.o external.o index.o library.o rig.o status.o thread.o track.o tests/track: LDFLAGS += -pthread tests/track: LDLIBS += -lm tests/ttf.o: tests/ttf.c # not needed except to workaround Make 3.81 tests/ttf.o: CFLAGS += $(SDL_CFLAGS) tests/ttf: LDLIBS += $(SDL_LIBS) .PHONY: clean clean: rm -f xwax \ $(OBJS) $(DEPS) \ $(TESTS) $(TEST_OBJS) \ mktimecode mktimecode.o \ TAGS -include $(DEPS) xwax-1.6-beta2/README000066400000000000000000000020451261512120600142170ustar00rootroot00000000000000xwax: Digital vinyl on Linux (C) Copyright 2015 Mark Hills For installation instructions, see the INSTALL file. Instructions can be found in the xwax(1) man page and http://xwax.org/ xwax is a digital vinyl system (DVS) for Linux. It allows DJs and turntablists to playback digital audio files (MP3, Ogg Vorbis, FLAC, AAC and more), controlled using a normal pair of turntables via timecoded vinyls. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2, as published by the Free Software Foundation. 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 version 2 for more details. You should have received a copy of the GNU General Public License version 2 along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. xwax-1.6-beta2/alsa.c000066400000000000000000000245501261512120600144300ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include "alsa.h" /* This structure doesn't have corresponding functions to be an * abstraction of the ALSA calls; it is merely a container for these * variables. */ struct alsa_pcm { snd_pcm_t *pcm; struct pollfd *pe; size_t pe_count; /* number of pollfd entries */ signed short *buf; snd_pcm_uframes_t period; int rate; }; struct alsa { struct alsa_pcm capture, playback; }; static void alsa_error(const char *msg, int r) { fprintf(stderr, "ALSA %s: %s\n", msg, snd_strerror(r)); } static bool chk(const char *s, int r) { if (r < 0) { alsa_error(s, r); return false; } else { return true; } } static int pcm_open(struct alsa_pcm *alsa, const char *device_name, snd_pcm_stream_t stream, int rate, int buffer_time) { int r, dir; unsigned int p; size_t bytes; snd_pcm_hw_params_t *hw_params; r = snd_pcm_open(&alsa->pcm, device_name, stream, SND_PCM_NONBLOCK); if (!chk("open", r)) return -1; snd_pcm_hw_params_alloca(&hw_params); r = snd_pcm_hw_params_any(alsa->pcm, hw_params); if (!chk("hw_params_any", r)) return -1; r = snd_pcm_hw_params_set_access(alsa->pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (!chk("hw_params_set_access", r)) return -1; r = snd_pcm_hw_params_set_format(alsa->pcm, hw_params, SND_PCM_FORMAT_S16); if (!chk("hw_params_set_format", r)) { fprintf(stderr, "16-bit signed format is not available. " "You may need to use a 'plughw' device.\n"); return -1; } r = snd_pcm_hw_params_set_rate(alsa->pcm, hw_params, rate, 0); if (!chk("hw_params_set_rate", r )) { fprintf(stderr, "%dHz sample rate not available. You may need to use " "a 'plughw' device.\n", rate); return -1; } alsa->rate = rate; r = snd_pcm_hw_params_set_channels(alsa->pcm, hw_params, DEVICE_CHANNELS); if (!chk("hw_params_set_channels", r)) { fprintf(stderr, "%d channel audio not available on this device.\n", DEVICE_CHANNELS); return -1; } p = buffer_time * 1000; /* microseconds */ dir = -1; r = snd_pcm_hw_params_set_buffer_time_near(alsa->pcm, hw_params, &p, &dir); if (!chk("hw_params_set_buffer_time_near", r)) { fprintf(stderr, "Buffer of %dms may be too small for this hardware.\n", buffer_time); return -1; } p = 2; /* double buffering */ dir = 1; r = snd_pcm_hw_params_set_periods_min(alsa->pcm, hw_params, &p, &dir); if (!chk("hw_params_set_periods_min", r)) { fprintf(stderr, "Buffer of %dms may be too small for this hardware.\n", buffer_time); return -1; } r = snd_pcm_hw_params(alsa->pcm, hw_params); if (!chk("hw_params", r)) return -1; r = snd_pcm_hw_params_get_period_size(hw_params, &alsa->period, &dir); if (!chk("get_period_size", r)) return -1; bytes = alsa->period * DEVICE_CHANNELS * sizeof(signed short); alsa->buf = malloc(bytes); if (!alsa->buf) { perror("malloc"); return -1; } /* snd_pcm_readi() returns uninitialised memory on first call, * possibly caused by premature POLLIN. Keep valgrind happy. */ memset(alsa->buf, 0, bytes); return 0; } static void pcm_close(struct alsa_pcm *alsa) { if (snd_pcm_close(alsa->pcm) < 0) abort(); free(alsa->buf); } static ssize_t pcm_pollfds(struct alsa_pcm *alsa, struct pollfd *pe, size_t z) { int r, count; count = snd_pcm_poll_descriptors_count(alsa->pcm); if (count > z) return -1; if (count == 0) alsa->pe = NULL; else { r = snd_pcm_poll_descriptors(alsa->pcm, pe, count); if (r < 0) { alsa_error("poll_descriptors", r); return -1; } alsa->pe = pe; } alsa->pe_count = count; return count; } static int pcm_revents(struct alsa_pcm *alsa, unsigned short *revents) { int r; r = snd_pcm_poll_descriptors_revents(alsa->pcm, alsa->pe, alsa->pe_count, revents); if (r < 0) { alsa_error("poll_descriptors_revents", r); return -1; } return 0; } /* Start the audio device capture and playback */ static void start(struct device *dv) { struct alsa *alsa = (struct alsa*)dv->local; if (snd_pcm_start(alsa->capture.pcm) < 0) abort(); } /* Register this device's interest in a set of pollfd file * descriptors */ static ssize_t pollfds(struct device *dv, struct pollfd *pe, size_t z) { int total, r; struct alsa *alsa = (struct alsa*)dv->local; total = 0; r = pcm_pollfds(&alsa->capture, pe, z); if (r < 0) return -1; pe += r; z -= r; total += r; r = pcm_pollfds(&alsa->playback, pe, z); if (r < 0) return -1; total += r; return total; } /* Collect audio from the player and push it into the device's buffer, * for playback */ static int playback(struct device *dv) { int r; struct alsa *alsa = (struct alsa*)dv->local; device_collect(dv, alsa->playback.buf, alsa->playback.period); r = snd_pcm_writei(alsa->playback.pcm, alsa->playback.buf, alsa->playback.period); if (r < 0) return r; if (r < alsa->playback.period) { fprintf(stderr, "alsa: playback underrun %d/%ld.\n", r, alsa->playback.period); } return 0; } /* Pull audio from the device's buffer for capture, and pass it * through to the timecoder */ static int capture(struct device *dv) { int r; struct alsa *alsa = (struct alsa*)dv->local; r = snd_pcm_readi(alsa->capture.pcm, alsa->capture.buf, alsa->capture.period); if (r < 0) return r; if (r < alsa->capture.period) { fprintf(stderr, "alsa: capture underrun %d/%ld.\n", r, alsa->capture.period); } device_submit(dv, alsa->capture.buf, r); return 0; } /* After poll() has returned, instruct a device to do all it can at * the present time. Return zero if success, otherwise -1 */ static int handle(struct device *dv) { int r; unsigned short revents; struct alsa *alsa = (struct alsa*)dv->local; /* Check input buffer for timecode capture */ r = pcm_revents(&alsa->capture, &revents); if (r < 0) return -1; if (revents & POLLIN) { r = capture(dv); if (r < 0) { if (r == -EPIPE) { fputs("ALSA: capture xrun.\n", stderr); r = snd_pcm_prepare(alsa->capture.pcm); if (r < 0) { alsa_error("prepare", r); return -1; } r = snd_pcm_start(alsa->capture.pcm); if (r < 0) { alsa_error("start", r); return -1; } } else { alsa_error("capture", r); return -1; } } } /* Check the output buffer for playback */ r = pcm_revents(&alsa->playback, &revents); if (r < 0) return -1; if (revents & POLLOUT) { r = playback(dv); if (r < 0) { if (r == -EPIPE) { fputs("ALSA: playback xrun.\n", stderr); r = snd_pcm_prepare(alsa->playback.pcm); if (r < 0) { alsa_error("prepare", r); return -1; } /* The device starts when data is written. POLLOUT * events are generated in prepared state. */ } else { alsa_error("playback", r); return -1; } } } return 0; } static unsigned int sample_rate(struct device *dv) { struct alsa *alsa = (struct alsa*)dv->local; return alsa->capture.rate; } /* Close ALSA device and clear any allocations */ static void clear(struct device *dv) { struct alsa *alsa = (struct alsa*)dv->local; pcm_close(&alsa->capture); pcm_close(&alsa->playback); free(dv->local); } static struct device_ops alsa_ops = { .pollfds = pollfds, .handle = handle, .sample_rate = sample_rate, .start = start, .clear = clear }; /* Open ALSA device. Do not operate on audio until device_start() */ int alsa_init(struct device *dv, const char *device_name, int rate, int buffer_time) { struct alsa *alsa; alsa = malloc(sizeof *alsa); if (alsa == NULL) { perror("malloc"); return -1; } if (pcm_open(&alsa->capture, device_name, SND_PCM_STREAM_CAPTURE, rate, buffer_time) < 0) { fputs("Failed to open device for capture.\n", stderr); goto fail; } if (pcm_open(&alsa->playback, device_name, SND_PCM_STREAM_PLAYBACK, rate, buffer_time) < 0) { fputs("Failed to open device for playback.\n", stderr); goto fail_capture; } device_init(dv, &alsa_ops); dv->local = alsa; return 0; fail_capture: pcm_close(&alsa->capture); fail: free(alsa); return -1; } /* ALSA caches information when devices are open. Provide a call * to clear these caches so that valgrind output is clean. */ void alsa_clear_config_cache(void) { int r; r = snd_config_update_free_global(); if (r < 0) alsa_error("config_update_free_global", r); } xwax-1.6-beta2/alsa.h000066400000000000000000000016411261512120600144310ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef ALSA_H #define ALSA_H #include "device.h" int alsa_init(struct device *dv, const char *name, int rate, int buffer_time); void alsa_clear_config_cache(void); #endif xwax-1.6-beta2/configure000077500000000000000000000045741261512120600152570ustar00rootroot00000000000000#!/bin/sh # # Copyright (C) 2012 Mark Hills # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2, as # published by the Free Software Foundation. # # 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 version 2 for more details. # # You should have received a copy of the GNU General Public License # version 2 along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # OUTPUT=.config usage() { cat < Set the install prefix --enable-alsa Enable ALSA audio device --enable-jack Enable JACK audio device --enable-oss Enable OSS audio device --debug Debug build --profile Profile build EOF } # Set some defaults and parse the command line PREFIX= ALSA=false JACK=false OSS=false DEBUG=false PROFILE=false while [ $# -ge 1 ]; do case $1 in --help) usage exit 0 ;; --enable-alsa) ALSA=true ;; --enable-jack) JACK=true ;; --enable-oss) OSS=true ;; --prefix) if [ -z "$2" ]; then echo "--prefix requires a pathname argument" >&2 exit 1 fi PREFIX=$2 shift ;; --debug) DEBUG=true ;; --profile) PROFILE=true ;; esac shift done # Construct the output file > $OUTPUT if [ -n "$PREFIX" ]; then echo "Installation prefix $PREFIX" echo "PREFIX = $PREFIX" >> $OUTPUT fi if $ALSA; then echo "ALSA enabled" echo "ALSA = yes" >> $OUTPUT else echo "ALSA disabled" fi if $JACK; then echo "JACK enabled" echo "JACK = yes" >> $OUTPUT else echo "JACK disabled" fi if $OSS; then echo "OSS enabled" echo "OSS = yes" >> $OUTPUT else echo "OSS disabled" fi if $DEBUG && $PROFILE; then echo "Debug and profile build cannot be used together" >&2 exit 1 fi if $DEBUG; then echo "Debug build" echo "CFLAGS += -O0 -g" >> $OUTPUT fi if $PROFILE; then echo "Profile build" echo "CFLAGS += -g -fno-inline-functions -fno-inline-functions-called-once -fno-optimize-sibling-calls" >> $OUTPUT fi # Explain the next step echo "Be sure to run 'make clean' if you have changed the configuration." echo "Run 'make' to compile xwax." xwax-1.6-beta2/controller.c000066400000000000000000000043651261512120600156750ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include "controller.h" #include "deck.h" #include "debug.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) int controller_init(struct controller *c, struct controller_ops *ops, void *local, struct rt *rt) { debug("%p", c); c->fault = false; c->ops = ops; c->local = local; return rt_add_controller(rt, c); } void controller_clear(struct controller *c) { debug("%p", c); c->ops->clear(c); } /* * Add a deck to this controller, if possible */ void controller_add_deck(struct controller *c, struct deck *d) { debug("%p adding deck %p", c, d); if (c->ops->add_deck(c, d) == 0) { debug("deck was added"); assert(d->ncontrol < ARRAY_SIZE(d->control)); /* FIXME: report error */ d->control[d->ncontrol++] = c; /* for callbacks */ } } /* * Get file descriptors which should be polled for this controller * * Important on systems where only callback-based audio devices * (eg. JACK) are used. We need to return some descriptors so * that the realtime thread runs. * * Return: the number of pollfd filled, or -1 on error */ ssize_t controller_pollfds(struct controller *c, struct pollfd *pe, size_t z) { if (c->ops->pollfds != NULL) return c->ops->pollfds(c, pe, z); else return 0; } void controller_handle(struct controller *c) { if (c->fault) return; if (c->ops->realtime(c) != 0) { c->fault = true; fputs("Error handling hardware controller; disabling it\n", stderr); } } xwax-1.6-beta2/controller.h000066400000000000000000000033531261512120600156760ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef CONTROLLER_H #define CONTROLLER_H #include #include #include #include struct deck; struct rt; /* * Base state of a 'controller', which is a MIDI controller or HID * device used to control the program */ struct controller { bool fault; void *local; struct controller_ops *ops; }; /* * Functions which must be implemented for a controller */ struct controller_ops { int (*add_deck)(struct controller *c, struct deck *deck); ssize_t (*pollfds)(struct controller *c, struct pollfd *pe, size_t z); int (*realtime)(struct controller *c); void (*clear)(struct controller *c); }; int controller_init(struct controller *c, struct controller_ops *t, void *local, struct rt *rt); void controller_clear(struct controller *c); void controller_add_deck(struct controller *c, struct deck *d); ssize_t controller_pollfds(struct controller *c, struct pollfd *pe, size_t z); void controller_handle(struct controller *c); #endif xwax-1.6-beta2/cues.c000066400000000000000000000042701261512120600144440ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include "cues.h" #include "debug.h" void cues_reset(struct cues *q) { size_t n; for (n = 0; n < MAX_CUES; n++) q->position[n] = CUE_UNSET; } /* * Unset the given cue point */ void cues_unset(struct cues *q, unsigned int label) { debug("clearing cue point %d", label); q->position[label] = CUE_UNSET; } void cues_set(struct cues *q, unsigned int label, double position) { debug("setting cue point %d to %0.2f", label, position); assert(label < MAX_CUES); q->position[label] = position; } double cues_get(const struct cues *q, unsigned int label) { assert(label < MAX_CUES); return q->position[label]; } /* * Return: the previous cue point before the current position, or CUE_UNSET */ double cues_prev(const struct cues *q, double current) { size_t n; double r; r = CUE_UNSET; for (n = 0; n < MAX_CUES; n++) { double p; p = q->position[n]; if (p == CUE_UNSET) continue; if (p > r && p < current) r = p; } return r; } /* * Return: the next cue point after the given position, or CUE_UNSET */ double cues_next(const struct cues *q, double current) { size_t n; double r; r = CUE_UNSET; for (n = 0; n < MAX_CUES; n++) { double p; p = q->position[n]; if (p == CUE_UNSET) continue; if (p < r && p > current) r = p; } return r; } xwax-1.6-beta2/cues.h000066400000000000000000000023451261512120600144520ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef CUES_H #define CUES_H #include #define MAX_CUES 16 #define CUE_UNSET (HUGE_VAL) /* * A set of cue points */ struct cues { double position[MAX_CUES]; }; void cues_reset(struct cues *q); void cues_unset(struct cues *q, unsigned int label); void cues_set(struct cues *q, unsigned int label, double position); double cues_get(const struct cues *q, unsigned int label); double cues_prev(const struct cues *q, double current); double cues_next(const struct cues *q, double current); #endif xwax-1.6-beta2/debug.h000066400000000000000000000020131261512120600145710ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef DEBUG_H #define DEBUG_H #include #ifdef DEBUG #define debug(...) { \ fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ fputc('\n', stderr); \ } #define dassert(x) assert(x) #else #define debug(...) #define dassert(x) #endif #endif xwax-1.6-beta2/deck.c000066400000000000000000000103161261512120600144110ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include "controller.h" #include "cues.h" #include "deck.h" #include "status.h" #include "rig.h" /* * An empty record, is used briefly until a record is loaded * to a deck */ static const struct record no_record = { .artist = "", .title = "" }; /* * Initialise a deck * * A deck is a logical grouping of the various components which * reflects the user's view on a deck in the system. * * Pre: deck->device is valid */ int deck_init(struct deck *d, struct rt *rt, struct timecode_def *timecode, const char *importer, double speed, bool phono, bool protect) { unsigned int rate; if (rt_add_device(rt, &d->device) == -1) return -1; d->ncontrol = 0; d->record = &no_record; d->punch = NO_PUNCH; d->protect = protect; assert(importer != NULL); d->importer = importer; rate = device_sample_rate(&d->device); assert(timecode != NULL); timecoder_init(&d->timecoder, timecode, speed, rate, phono); player_init(&d->player, rate, track_acquire_empty(), &d->timecoder); cues_reset(&d->cues); /* The timecoder and player are driven by requests from * the audio device */ device_connect_timecoder(&d->device, &d->timecoder); device_connect_player(&d->device, &d->player); return 0; } void deck_clear(struct deck *d) { /* FIXME: remove from rig and rt */ player_clear(&d->player); timecoder_clear(&d->timecoder); device_clear(&d->device); } bool deck_is_locked(const struct deck *d) { return (d->protect && player_is_active(&d->player)); } /* * Load a record from the library to a deck */ void deck_load(struct deck *d, struct record *record) { struct track *t; if (deck_is_locked(d)) { status_printf(STATUS_WARN, "Stop deck to load a different track"); return; } t = track_acquire_by_import(d->importer, record->pathname); if (t == NULL) return; d->record = record; player_set_track(&d->player, t); /* passes reference */ } void deck_recue(struct deck *d) { if (deck_is_locked(d)) { status_printf(STATUS_WARN, "Stop deck to recue"); return; } player_recue(&d->player); } void deck_clone(struct deck *d, const struct deck *from) { d->record = from->record; player_clone(&d->player, &from->player); } /* * Clear the cue point, ready to be set again */ void deck_unset_cue(struct deck *d, unsigned int label) { cues_unset(&d->cues, label); } /* * Seek the current playback position to a cue point position, * or set the cue point if unset */ void deck_cue(struct deck *d, unsigned int label) { double p; p = cues_get(&d->cues, label); if (p == CUE_UNSET) cues_set(&d->cues, label, player_get_elapsed(&d->player)); else player_seek_to(&d->player, p); } /* * Seek to a cue point ready to return from it later. Overrides an * existing punch operation. */ void deck_punch_in(struct deck *d, unsigned int label) { double p, e; e = player_get_elapsed(&d->player); p = cues_get(&d->cues, label); if (p == CUE_UNSET) { cues_set(&d->cues, label, e); return; } if (d->punch != NO_PUNCH) e -= d->punch; player_seek_to(&d->player, p); d->punch = p - e; } /* * Return from a cue point */ void deck_punch_out(struct deck *d) { double e; if (d->punch == NO_PUNCH) return; e = player_get_elapsed(&d->player); player_seek_to(&d->player, e - d->punch); d->punch = NO_PUNCH; } xwax-1.6-beta2/deck.h000066400000000000000000000035561261512120600144260ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef DECK_H #define DECK_H #include #include "cues.h" #include "device.h" #include "index.h" #include "player.h" #include "realtime.h" #include "timecoder.h" #define NO_PUNCH (HUGE_VAL) struct deck { struct device device; struct timecoder timecoder; const char *importer; bool protect; struct player player; const struct record *record; struct cues cues; /* Punch */ double punch; /* A controller adds itself here */ size_t ncontrol; struct controller *control[4]; }; int deck_init(struct deck *deck, struct rt *rt, struct timecode_def *timecode, const char *importer, double speed, bool phono, bool protect); void deck_clear(struct deck *deck); bool deck_is_locked(const struct deck *deck); void deck_load(struct deck *deck, struct record *record); void deck_recue(struct deck *deck); void deck_clone(struct deck *deck, const struct deck *from); void deck_unset_cue(struct deck *deck, unsigned int label); void deck_cue(struct deck *deck, unsigned int label); void deck_punch_in(struct deck *d, unsigned int label); void deck_punch_out(struct deck *d); #endif xwax-1.6-beta2/device.c000066400000000000000000000063451261512120600147510ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include "debug.h" #include "device.h" #include "player.h" #include "timecoder.h" void device_init(struct device *dv, struct device_ops *ops) { debug("%p", dv); dv->fault = false; dv->ops = ops; } /* * Clear (destruct) the device. The corresponding constructor is * specific to each particular audio system */ void device_clear(struct device *dv) { if (dv->ops->clear != NULL) dv->ops->clear(dv); } void device_connect_timecoder(struct device *dv, struct timecoder *tc) { dv->timecoder = tc; } void device_connect_player(struct device *dv, struct player *pl) { dv->player = pl; } /* * Return: the sample rate of the device in Hz */ unsigned int device_sample_rate(struct device *dv) { assert(dv->ops->sample_rate != NULL); return dv->ops->sample_rate(dv); } /* * Start the device inputting and outputting audio */ void device_start(struct device *dv) { if (dv->ops->start != NULL) dv->ops->start(dv); } /* * Stop the device */ void device_stop(struct device *dv) { if (dv->ops->stop != NULL) dv->ops->stop(dv); } /* * Get file descriptors which should be polled for this device * * Do not return anything for callback-based audio systems. If the * return value is > 0, there must be a handle() function available. * * Return: the number of pollfd filled, or -1 on error */ ssize_t device_pollfds(struct device *dv, struct pollfd *pe, size_t z) { if (dv->ops->pollfds != NULL) return dv->ops->pollfds(dv, pe, z); else return 0; } /* * Handle any available input or output on the device * * This function can be called when there is activity on any file * descriptor, not specifically one returned by this device. */ void device_handle(struct device *dv) { if (dv->fault) return; if (dv->ops->handle == NULL) return; if (dv->ops->handle(dv) != 0) { dv->fault = true; fputs("Error handling audio device; disabling it\n", stderr); } } /* * Send audio from a device for processing * * Pre: buffer pcm contains n stereo samples */ void device_submit(struct device *dv, signed short *pcm, size_t n) { assert(dv->timecoder != NULL); timecoder_submit(dv->timecoder, pcm, n); } /* * Collect audio from the processing to send to a device * * Post: buffer pcm is filled with n stereo samples */ void device_collect(struct device *dv, signed short *pcm, size_t n) { assert(dv->player != NULL); player_collect(dv->player, pcm, n); } xwax-1.6-beta2/device.h000066400000000000000000000036261261512120600147550ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef DEVICE_H #define DEVICE_H #include #include #include #define DEVICE_CHANNELS 2 struct device { bool fault; void *local; struct device_ops *ops; struct timecoder *timecoder; struct player *player; }; struct device_ops { ssize_t (*pollfds)(struct device *dv, struct pollfd *pe, size_t z); int (*handle)(struct device *dv); unsigned int (*sample_rate)(struct device *dv); void (*start)(struct device *dv); void (*stop)(struct device *dv); void (*clear)(struct device *dv); }; void device_init(struct device *dv, struct device_ops *ops); void device_clear(struct device *dv); void device_connect_timecoder(struct device *dv, struct timecoder *tc); void device_connect_player(struct device *dv, struct player *pl); unsigned int device_sample_rate(struct device *dv); void device_start(struct device *dv); void device_stop(struct device *dv); ssize_t device_pollfds(struct device *dv, struct pollfd *pe, size_t z); void device_handle(struct device *dv); void device_submit(struct device *dv, signed short *pcm, size_t npcm); void device_collect(struct device *dv, signed short *pcm, size_t npcm); #endif xwax-1.6-beta2/dicer.c000066400000000000000000000241521261512120600145740ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Specialised functions for the Novation Dicer controller * * The Dicer is a standard MIDI device, with buttons on input and the * corresponding LEDs on output. A single MIDI device consists of two * units, one for each turntable. * * Each unit has 5 buttons, but there are three 'pages' of buttons * controlled in the firmware, and then a shift mode for each. So we * see the full MIDI device as 60 possible buttons. */ #include #include "controller.h" #include "debug.h" #include "deck.h" #include "dicer.h" #include "midi.h" #include "realtime.h" #define NBUTTONS 5 #define CUE 0 #define LOOP 1 #define ROLL 2 #ifdef DEBUG static const char *actions[] = { "CUE", "LOOP", "ROLL" }; #endif /* LED states */ typedef unsigned char led_t; #define ON 0x1 #define PRESSED 0x2 #define SYNCED 0x4 struct dicer { struct midi midi; struct deck *left, *right; led_t left_led[NBUTTONS], right_led[NBUTTONS]; char obuf[180]; size_t ofill; }; /* * Add a deck to the dicer or pair of dicer * * Return: -1 if the deck could not be added, otherwise zero */ static int add_deck(struct controller *c, struct deck *k) { struct dicer *d = c->local; debug("%p add deck %p", d, k); if (d->left != NULL && d->right != NULL) return -1; if (d->left == NULL) { d->left = k; } else { d->right = k; } return 0; } /* * Write a MIDI command sequence which would bring the given LED * up-to-date * * Return: n, or -1 if not enough buffer space * Post: if buffer space is available, n bytes are written to buf */ static ssize_t led_cmd(led_t led, char *buf, size_t len, bool right, unsigned char action, bool shift, unsigned char button) { if (len < 3) return -1; assert(action <= ROLL); buf[0] = (right ? 0x9d : 0x9a) + action; assert(button < NBUTTONS); buf[1] = (shift ? 0x41 : 0x3c) + button; /* The Dicer allows us to use any colour in any mode. For * simplicity, we tie the colour to the mode at this layer */ switch (action) { case CUE: buf[2] = 0x00; break; case LOOP: buf[2] = 0x70; break; case ROLL: buf[2] = 0x40; break; default: abort(); } if (led & ON) buf[2] += 0xa; if (led & PRESSED) buf[2] += 0x5; debug("compiling LED command: %02hhx %02hhx %02hhx", buf[0], buf[1], buf[2]); return 3; } /* * Push control code for a particular output LED * * Return: n, or -1 if not enough buffer space * Post: if buf is large enough, LED is synced and n bytes are written */ static ssize_t sync_one_led(led_t *led, char *buf, size_t len, bool right, unsigned char button) { unsigned int a; size_t t; if (*led & SYNCED) return 0; debug("syncing LED: %s %d", right ? "right" : "left", button); /* For simplicify we light all LEDs in all modes the same: * (cue, loop, roll) x (shift, non-shift) */ t = 0; for (a = 0; a <= ROLL; a++) { ssize_t z; z = led_cmd(*led, buf, len, right, a, false, button); if (z == -1) return -1; buf += z; len -= z; t += z; z = led_cmd(*led, buf, len, right, a, true, button); if (z == -1) return -1; buf += z; len -= z; t += z; } *led |= SYNCED; return t; } /* * Return: number of bytes written to the buffer */ static size_t sync_one_dicer(led_t led[NBUTTONS], bool right, char *buf, size_t len) { size_t n, t; t = 0; for (n = 0; n < NBUTTONS; n++) { ssize_t z; z = sync_one_led(&led[n], buf, len, right, n); if (z == -1) { debug("output buffer full; expect incorrect LEDs"); break; } buf += z; len -= z; t += z; } return t; } /* * Write a MIDI command sequence to sync all hardware LEDs with the * state held in memory. * * The Dicer first appears to only have five output LEDs on two * controllers. But there are three modes for each, and then shift * on/off modes: total (5 * 2 * 3 * 2) = 60 */ static void sync_all_leds(struct dicer *d) { size_t w; char *buf; size_t len; buf = d->obuf + d->ofill; len = sizeof(d->obuf) - d->ofill; /* Top-up the buffer, even if not empty */ w = sync_one_dicer(d->left_led, false, buf, len); buf += w; len -= w; d->ofill += w; w = sync_one_dicer(d->right_led, true, buf, len); buf += w; len -= w; d->ofill += w; if (d->ofill > 0) { ssize_t z; debug("writing %zd bytes of MIDI command", d->ofill); z = midi_write(&d->midi, d->obuf, d->ofill); if (z == -1) return; if (z < d->ofill) memmove(d->obuf, d->obuf + z, z); d->ofill -= z; } } /* * Modify state flags of an LED * * Post: *led is updated with the new flags */ static void set_led(led_t *led, unsigned char set, unsigned char clear) { led_t n; n = (*led & ~clear) | set; if (n != *led) *led = n & ~SYNCED; } /* * Act on an event, and update the given LED status */ static void event_decoded(struct deck *d, led_t led[NBUTTONS], unsigned char action, bool shift, unsigned char button, bool on) { /* Always toggle the LED status */ if (on) { set_led(&led[button], PRESSED, 0); } else { set_led(&led[button], 0, PRESSED); } /* FIXME: We assume that we are the only operator of the cue * points; we should change the LEDs via a callback from deck */ if (shift && on) { deck_unset_cue(d, button); set_led(&led[button], 0, ON); } if (shift) return; if (action == CUE && on) { deck_cue(d, button); set_led(&led[button], ON, 0); } if (action == LOOP) { if (on) { deck_punch_in(d, button); set_led(&led[button], ON, 0); } else { deck_punch_out(d); } } } /* * Process an event from the device, given the MIDI control codes */ static void event(struct dicer *d, unsigned char buf[3]) { struct deck *deck; led_t *led; unsigned char action, button; bool on, shift; /* Ignore signal that the second controller is (un)plugged */ if (buf[0] == 0xba && buf[1] == 0x11 && (buf[2] == 0x0 || buf[2] == 0x08)) return; switch (buf[0]) { case 0x9a: case 0x9b: case 0x9c: deck = d->left; led = d->left_led; action = buf[0] - 0x9a; break; case 0x9d: case 0x9e: case 0x9f: deck = d->right; led = d->right_led; action = buf[0] - 0x9d; break; default: abort(); } if (deck == NULL) /* no deck assigned to this unit */ return; switch (buf[1]) { case 0x3c: case 0x3d: case 0x3e: case 0x3f: case 0x40: button = buf[1] - 0x3c; shift = false; break; case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: button = buf[1] - 0x41; shift = true; break; default: abort(); } switch (buf[2]) { case 0x00: on = false; break; case 0x7f: on = true; break; default: abort(); } debug("%s button %s%hhd %s, deck %p", actions[action], shift ? "SHIFT-" : "", button, on ? "ON" : "OFF", deck); event_decoded(deck, led, action, shift, button, on); } static ssize_t pollfds(struct controller *c, struct pollfd *pe, size_t z) { struct dicer *d = c->local; return midi_pollfds(&d->midi, pe, z); } /* * Handler in the realtime thread, which polls on both input * and output */ static int realtime(struct controller *c) { struct dicer *d = c->local; for (;;) { unsigned char buf[3]; ssize_t z; z = midi_read(&d->midi, buf, sizeof buf); if (z == -1) return -1; if (z == 0) break; debug("got event"); event(d, buf); } sync_all_leds(d); return 0; } static void clear(struct controller *c) { struct dicer *d = c->local; size_t n; debug("%p", d); /* FIXME: Uses non-blocking functionality really intended * for realtime; no guarantee buffer is emptied */ for (n = 0; n < NBUTTONS; n++) { set_led(&d->left_led[n], 0, ON); set_led(&d->right_led[n], 0, ON); } sync_all_leds(d); midi_close(&d->midi); free(c->local); } static struct controller_ops dicer_ops = { .add_deck = add_deck, .pollfds = pollfds, .realtime = realtime, .clear = clear, }; int dicer_init(struct controller *c, struct rt *rt, const char *hw) { size_t n; struct dicer *d; debug("init %p from %s", c, hw); d = malloc(sizeof *d); if (d == NULL) { perror("malloc"); return -1; } if (midi_open(&d->midi, hw) == -1) goto fail; d->left = NULL; d->right = NULL; d->ofill = 0; for (n = 0; n < NBUTTONS; n++) { d->left_led[n] = 0; d->right_led[n] = 0; } if (controller_init(c, &dicer_ops, d, rt) == -1) goto fail_midi; return 0; fail_midi: midi_close(&d->midi); fail: free(d); return -1; } xwax-1.6-beta2/dicer.h000066400000000000000000000015601261512120600145770ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef DICER_H #define DICER_H struct controller; struct rt; int dicer_init(struct controller *c, struct rt *rt, const char *hw); #endif xwax-1.6-beta2/dummy.c000066400000000000000000000017211261512120600146360ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include "dummy.h" static unsigned int sample_rate(struct device *d) { return 48000; } static struct device_ops dummy_ops = { .sample_rate = sample_rate, }; void dummy_init(struct device *d) { device_init(d, &dummy_ops); } xwax-1.6-beta2/dummy.h000066400000000000000000000015041261512120600146420ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef DUMMY_H #define DUMMY_H #include "device.h" void dummy_init(struct device *d); #endif xwax-1.6-beta2/excrate.c000066400000000000000000000120661261512120600151420ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * External record library ('excrate') * * Implement search in an external script. The results are streamed * back into a local listing. */ #include #include #include #include #include #include "debug.h" #include "excrate.h" #include "rig.h" #include "status.h" static struct list excrates = LIST_INIT(excrates); static int excrate_init(struct excrate *e, const char *script, const char *search, struct listing *storage) { pid_t pid; fprintf(stderr, "External scan '%s'...\n", search); pid = fork_pipe_nb(&e->fd, script, "scan", search, NULL); if (pid == -1) return -1; e->pid = pid; e->pe = NULL; e->terminated = false; e->refcount = 0; rb_reset(&e->rb); listing_init(&e->listing); e->storage = storage; event_init(&e->completion); e->search = search; list_add(&e->excrates, &excrates); rig_post_excrate(e); return 0; } static void excrate_clear(struct excrate *e) { assert(e->pid == 0); list_del(&e->excrates); listing_clear(&e->listing); event_clear(&e->completion); } struct excrate* excrate_acquire_by_scan(const char *script, const char *search, struct listing *storage) { struct excrate *e; debug("get_by_scan %s, %s", script, search); e = malloc(sizeof *e); if (e == NULL) { perror("malloc"); return NULL; } if (excrate_init(e, script, search, storage) == -1) { free(e); return NULL; } excrate_acquire(e); debug("returning %p", e) return e; } void excrate_acquire(struct excrate *e) { debug("get %p", e); e->refcount++; } /* * Request premature termination of the scan */ static void terminate(struct excrate *e) { assert(e->pid != 0); debug("terminating %d", e->pid); if (kill(e->pid, SIGTERM) == -1) abort(); e->terminated = true; } void excrate_release(struct excrate *e) { debug("put %p, refcount=%d", e, e->refcount); e->refcount--; /* Scan must terminate before this object goes away */ if (e->refcount == 1 && e->pid != 0) { debug("%p still executing but not longer required", e); terminate(e); return; } if (e->refcount == 0) { excrate_clear(e); free(e); } } /* * Get entry for use by poll() * * Pre: scan is running * Post: *pe contains poll entry */ void excrate_pollfd(struct excrate *e, struct pollfd *pe) { assert(e->pid != 0); pe->fd = e->fd; pe->events = POLLIN; e->pe = pe; } static void do_wait(struct excrate *e) { int status; assert(e->pid != 0); debug("waiting on pid %d", e->pid); if (close(e->fd) == -1) abort(); if (waitpid(e->pid, &status, 0) == -1) abort(); debug("wait for pid %d returned %d", e->pid, status); if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { fprintf(stderr, "Scan completed\n"); } else { fprintf(stderr, "Scan completed with status %d\n", status); if (!e->terminated) status_printf(STATUS_ALERT, "Error scanning %s", e->search); } e->pid = 0; } /* * Return: -1 on completion, otherwise zero */ static int read_from_pipe(struct excrate *e) { for (;;) { char *line; ssize_t z; struct record *d, *x; z = get_line(e->fd, &e->rb, &line); if (z == -1) { if (errno == EAGAIN) return 0; perror("get_line"); return -1; } if (z == 0) return -1; debug("got line '%s'", line); d = get_record(line); if (d == NULL) { free(line); continue; /* ignore malformed entries */ } x = listing_add(e->storage, d); if (x == NULL) return -1; if (x != d) /* our new record is a duplicate */ free(d); x = listing_add(&e->listing, x); if (x == NULL) return -1; } } void excrate_handle(struct excrate *e) { assert(e->pid != 0); if (e->pe == NULL) return; if (e->pe->revents == 0) return; if (read_from_pipe(e) != -1) return; do_wait(e); fire(&e->completion, NULL); list_del(&e->rig); excrate_release(e); /* may invalidate e */ } xwax-1.6-beta2/excrate.h000066400000000000000000000031421261512120600151420ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef EXCRATE_H #define EXCRATE_H #include #include #include "external.h" #include "list.h" #include "library.h" #include "observer.h" struct excrate { struct list excrates; unsigned int refcount; const char *search; struct listing listing, *storage; struct event completion; /* State of the external scan process */ struct list rig; pid_t pid; int fd; struct pollfd *pe; bool terminated; /* State of reader */ struct rb rb; }; struct excrate* excrate_acquire_by_scan(const char *script, const char *search, struct listing *storage); void excrate_acquire(struct excrate *e); void excrate_release(struct excrate *e); /* Used by the rig and main thread */ void excrate_pollfd(struct excrate *tr, struct pollfd *pe); void excrate_handle(struct excrate *tr); #endif xwax-1.6-beta2/external.c000066400000000000000000000143641261512120600153340ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #define _DEFAULT_SOURCE /* vfork() */ #include #include #include #include #include #include #include #include #include "debug.h" #include "external.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) /* * Fork a child process, attaching stdout to the given pipe * * Return: -1 on error, or pid on success * Post: on success, *fd is file handle for reading */ static pid_t do_fork(int pp[2], const char *path, char *argv[]) { pid_t pid; pid = vfork(); if (pid == -1) { perror("vfork"); return -1; } if (pid == 0) { /* child */ if (close(pp[0]) != 0) abort(); if (dup2(pp[1], STDOUT_FILENO) == -1) { perror("dup2"); _exit(EXIT_FAILURE); /* vfork() was used */ } if (close(pp[1]) != 0) abort(); if (execv(path, argv) == -1) { perror(path); _exit(EXIT_FAILURE); /* vfork() was used */ } abort(); /* execv() does not return */ } if (close(pp[1]) != 0) abort(); return pid; } /* * Wrapper on do_fork which uses va_list * * The caller passes in the pipe for use, rather us handing one * back. This is because if the caller wishes to have a non-blocking * pipe, then the cleanup is messy if the process has already been * forked. */ static pid_t vext(int pp[2], const char *path, char *arg, va_list ap) { char *args[16]; size_t n; args[0] = arg; n = 1; /* Convert to an array; there's no va_list variant of exec() */ for (;;) { char *x; x = va_arg(ap, char*); assert(n < ARRAY_SIZE(args)); args[n++] = x; if (x == NULL) break; } return do_fork(pp, path, args); } /* * Fork a child process with stdout connected to this process * via a pipe * * Return: PID on success, otherwise -1 * Post: on success, *fd is file descriptor for reading */ pid_t fork_pipe(int *fd, const char *path, char *arg, ...) { int pp[2]; pid_t r; va_list va; if (pipe(pp) == -1) { perror("pipe"); return -1; } va_start(va, arg); r = vext(pp, path, arg, va); va_end(va); if (r == -1) { if (close(pp[0]) != 0) abort(); if (close(pp[1]) != 0) abort(); } *fd = pp[0]; return r; } /* * Make the given file descriptor non-blocking * * Return: 0 on success, otherwise -1 * Post: if 0 is returned, file descriptor is non-blocking */ static int make_non_blocking(int fd) { if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl"); return -1; } return 0; } /* * Fork a child process with stdout connected to this process * via a non-blocking pipe * * Return: PID on success, otherwise -1 * Post: on success, *fd is non-blocking file descriptor for reading */ pid_t fork_pipe_nb(int *fd, const char *path, char *arg, ...) { int pp[2]; pid_t r; va_list va; if (pipe(pp) == -1) { perror("pipe"); return -1; } if (make_non_blocking(pp[0]) == -1) goto fail; va_start(va, arg); r = vext(pp, path, arg, va); va_end(va); assert(r != 0); if (r < 0) goto fail; *fd = pp[0]; return r; fail: if (close(pp[0]) != 0) abort(); if (close(pp[1]) != 0) abort(); return -1; } void rb_reset(struct rb *rb) { rb->len = 0; } bool rb_is_full(const struct rb *rb) { return (rb->len == sizeof rb->buf); } /* * Read, within reasonable limits (ie. memory or time) * from the fd into the buffer * * Return: -1 on error, 0 on EOF, otherwise the number of bytes added */ static ssize_t top_up(struct rb *rb, int fd) { size_t remain; ssize_t z; assert(rb->len < sizeof rb->buf); remain = sizeof(rb->buf) - rb->len; z = read(fd, rb->buf + rb->len, remain); if (z == -1) return -1; rb->len += z; return z; } /* * Pop the front of the buffer to end-of-line * * Return: 0 if not found, -1 if not enough memory, * otherwise string length (incl. terminator) * Post: if return is > 0, q points to alloc'd string */ static ssize_t pop(struct rb *rb, char **q) { const char *x; char *s; size_t len; x = memchr(rb->buf, '\n', rb->len); if (!x) { debug("pop %p exhausted", rb); return 0; } len = x - rb->buf; debug("pop %p got %u", rb, len); s = strndup(rb->buf, len); if (!s) { debug("strndup: %s", strerror(errno)); return -1; } *q = s; /* Simple compact of the buffer. If this is a bottleneck of any * kind (unlikely) then a circular buffer should be used */ memmove(rb->buf, x + 1, rb->len - len - 1); rb->len = rb->len - len - 1; return len + 1; } /* * Read a terminated string from the given file descriptor via * the buffer. * * Handles non-blocking file descriptors too. If fd is non-blocking, * then the semantics are the same as a non-blocking read() -- * ie. EAGAIN may be returned as an error. * * Return: 0 on EOF, or -1 on error * Post: if -1 is returned, errno is set accordingly */ ssize_t get_line(int fd, struct rb *rb, char **string) { ssize_t y, z; y = top_up(rb, fd); if (y < 0) return y; z = pop(rb, string); if (z != 0) return z; if (rb_is_full(rb)) errno = ENOBUFS; else if (y > 0) errno = EAGAIN; else return 0; /* true EOF: no more data and empty buffer */ return -1; } xwax-1.6-beta2/external.h000066400000000000000000000023411261512120600153310ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Utility functions for launching external processes */ #ifndef EXTERNAL_H #define EXTERNAL_H #include #include /* * A handy read buffer; an equivalent of fread() but for * non-blocking file descriptors */ struct rb { char buf[4096]; size_t len; }; pid_t fork_pipe(int *fd, const char *path, char *arg, ...); pid_t fork_pipe_nb(int *fd, const char *path, char *arg, ...); void rb_reset(struct rb *rb); ssize_t get_line(int fd, struct rb *rb, char **string); #endif xwax-1.6-beta2/import000077500000000000000000000012571261512120600146030ustar00rootroot00000000000000#!/bin/sh # # Audio import handler for xwax # # This script takes an output sample rate and filename as arguments, # and outputs signed, little-endian, 16-bit, 2 channel audio on # standard output. Errors to standard error. # # You can adjust this script yourself to customise the support for # different file formats and codecs. # FILE="$1" RATE="$2" case "$FILE" in *.cdaudio) echo "Calling CD extract..." >&2 exec cdparanoia -r `cat "$FILE"` - ;; *.mp3) echo "Calling MP3 decoder..." >&2 exec mpg123 -q -s --rate "$RATE" --stereo "$FILE" ;; *) echo "Calling fallback decoder..." >&2 exec ffmpeg -v 0 -i "$FILE" -f s16le -ar "$RATE" - ;; esac xwax-1.6-beta2/index.c000066400000000000000000000207451261512120600146210ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #define _GNU_SOURCE /* strcasestr(), strdupa() */ #include #include #include #include #include #include #include "index.h" #define BLOCK 1024 #define MAX_WORDS 32 #define SEPARATOR ' ' #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) /* * Initialise a record index */ void index_init(struct index *ls) { ls->record = NULL; ls->size = 0; ls->entries = 0; } /* * Deallocate resources associated with this index * * The index does not allocate records itself, so it is not * responsible for deallocating them. */ void index_clear(struct index *ls) { if (ls->record != NULL) free(ls->record); } /* * Blank the index so it contains no entries * * We don't de-allocate memory, but this gives us an advantage where * index re-use is of similar size. */ void index_blank(struct index *ls) { ls->entries = 0; } /* * Enlarge the storage space of the index to at least the target * size * * Return: 0 on success or -1 on memory allocation failure * Post: size of index is greater than or equal to target */ static int enlarge(struct index *ls, size_t target) { size_t p; struct record **ln; if (target <= ls->size) return 0; p = target + BLOCK - 1; /* pre-allocate additional entries */ ln = realloc(ls->record, sizeof(struct record*) * p); if (ln == NULL) { perror("realloc"); return -1; } ls->record = ln; ls->size = p; return 0; } /* * Return: false if the caller did not call index_reserve(), otherwise * true */ static bool has_space(const struct index *i) { return i->entries < i->size; } /* * Add a record to the index * * Pre: at least one entry is reserved * Post: lr is the record at the end of the index */ void index_add(struct index *ls, struct record *lr) { assert(lr != NULL); assert(has_space(ls)); ls->record[ls->entries++] = lr; } /* * Standard comparison function between two records */ static int record_cmp_artist(const struct record *a, const struct record *b) { int r; r = strcasecmp(a->artist, b->artist); if (r < 0) return -1; else if (r > 0) return 1; r = strcasecmp(a->title, b->title); if (r < 0) return -1; else if (r > 0) return 1; return strcmp(a->pathname, b->pathname); } /* * Compare two records principally by BPM, fastest to slowest * followed by unknown */ static int record_cmp_bpm(const struct record *a, const struct record *b) { if (a->bpm < b->bpm) return 1; if (a->bpm > b->bpm) return -1; return record_cmp_artist(a, b); } /* * Check if a record matches the given string. This function is the * definitive code which defines what constitutes a 'match'. * * Return: true if this is a match, otherwise false */ static bool record_match_word(struct record *re, const char *match) { if (strcasestr(re->artist, match) != NULL) return true; if (strcasestr(re->title, match) != NULL) return true; return false; } /* * Check for a match against the given search criteria * * Return: true if the given record matches, otherwise false */ bool record_match(struct record *re, const struct match *h) { char *const *matches; matches = h->words; while (*matches != NULL) { if (!record_match_word(re, *matches)) return false; matches++; } return true; } /* * Copy the source index * * Return: 0 on success or -1 on memory allocation failure * Post: on failure, dest is valid but incomplete */ int index_copy(const struct index *src, struct index *dest) { int n; index_blank(dest); if (index_reserve(dest, src->entries) == -1) return -1; for (n = 0; n < src->entries; n++) index_add(dest, src->record[n]); return 0; } /* * Compile a search object from a given string * * Pre: search string is within length */ void match_compile(struct match *h, const char *d) { char *buf; size_t n; assert(strlen(d) < sizeof h->buf); strcpy(h->buf, d); buf = h->buf; n = 0; for (;;) { char *s; if (n == ARRAY_SIZE(h->words) - 1) { fputs("Ignoring excessive words in match string.\n", stderr); break; } h->words[n] = buf; n++; s = strchr(buf, SEPARATOR); if (s == NULL) break; *s = '\0'; buf = s + 1; /* skip separator */ } h->words[n] = NULL; /* terminate list */ } /* * Find entries from the source index which match * * Copy the subset of the source index which matches the given * string into the destination. * * Return: 0 on success, or -1 on memory allocation failure * Post: on failure, dest is valid but incomplete */ int index_match(struct index *src, struct index *dest, const struct match *match) { int n; struct record *re; index_blank(dest); for (n = 0; n < src->entries; n++) { re = src->record[n]; if (record_match(re, match)) { if (index_reserve(dest, 1) == -1) return -1; index_add(dest, re); } } return 0; } /* * Binary search of sorted index * * We implement our own binary search rather than using the bsearch() * from stdlib.h, because we need to know the position to insert to if * the item is not found. * * Pre: base is sorted * Return: position of match >= item * Post: on exact match, *found is true */ static size_t bin_search(struct record **base, size_t n, struct record *item, int sort, bool *found) { int r; size_t mid; struct record *x; /* Return the first entry ordered after this one */ if (n == 0) { *found = false; return 0; } mid = n / 2; x = base[mid]; switch (sort) { case SORT_ARTIST: r = record_cmp_artist(item, x); break; case SORT_BPM: r = record_cmp_bpm(item, x); break; case SORT_PLAYLIST: default: abort(); } if (r < 0) return bin_search(base, mid, item, sort, found); if (r > 0) { return mid + 1 + bin_search(base + mid + 1, n - mid - 1, item, sort, found); } *found = true; return mid; } /* * Insert or re-use an entry in a sorted index * * Pre: index is sorted * Pre: at least one entry is reserved * Return: pointer to item, or existing entry (ie. not NULL) * Post: index is sorted and contains item or a matching item */ struct record* index_insert(struct index *ls, struct record *item, int sort) { bool found; size_t z; z = bin_search(ls->record, ls->entries, item, sort, &found); if (found) return ls->record[z]; assert(has_space(ls)); memmove(ls->record + z + 1, ls->record + z, sizeof(struct record*) * (ls->entries - z)); ls->record[z] = item; ls->entries++; return item; } /* * Reserve space in the index for the addition of n new items * * This function exists separately to the insert and addition * functions because it carries the error case. * * Return: -1 if not enough memory, otherwise zero * Post: if zero is returned, index has at least n free slots */ int index_reserve(struct index *i, unsigned int n) { return enlarge(i, i->entries + n); } /* * Find an identical entry, or the nearest match */ size_t index_find(struct index *ls, struct record *item, int sort) { bool found; size_t z; z = bin_search(ls->record, ls->entries, item, sort, &found); return z; } /* * Debug the content of a index to standard error */ void index_debug(struct index *ls) { int n; for (n = 0; n < ls->entries; n++) fprintf(stderr, "%d: %s\n", n, ls->record[n]->pathname); } xwax-1.6-beta2/index.h000066400000000000000000000037311261512120600146220ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef INDEX_H #define INDEX_H #include #define SORT_ARTIST 0 #define SORT_BPM 1 #define SORT_PLAYLIST 2 #define SORT_END 3 struct record { char *pathname, *artist, *title; double bpm; /* or 0.0 if not known */ }; /* Index points to records, but does not manage those pointers */ struct index { struct record **record; size_t size, entries; }; /* A 'compiled' search criteria, so we can repeat searches and * matches efficiently */ struct match { char buf[512]; char *words[32]; /* NULL-terminated array */ }; void index_init(struct index *ls); void index_clear(struct index *ls); void index_blank(struct index *ls); void index_add(struct index *li, struct record *lr); bool record_match(struct record *re, const struct match *h); int index_copy(const struct index *src, struct index *dest); void match_compile(struct match *h, const char *d); int index_match(struct index *src, struct index *dest, const struct match *match); struct record* index_insert(struct index *ls, struct record *item, int sort); int index_reserve(struct index *i, unsigned int n); size_t index_find(struct index *ls, struct record *item, int sort); void index_debug(struct index *ls); #endif xwax-1.6-beta2/interface.c000066400000000000000000001263251261512120600154530ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "interface.h" #include "layout.h" #include "player.h" #include "rig.h" #include "selector.h" #include "status.h" #include "timecoder.h" #include "xwax.h" /* Screen refresh time in milliseconds */ #define REFRESH 10 /* Font definitions */ #define FONT "DejaVuSans.ttf" #define FONT_SIZE 10 #define FONT_SPACE 15 #define EM_FONT "DejaVuSans-Oblique.ttf" #define BIG_FONT "DejaVuSans-Bold.ttf" #define BIG_FONT_SIZE 14 #define BIG_FONT_SPACE 19 #define CLOCK_FONT FONT #define CLOCK_FONT_SIZE 32 #define DECI_FONT FONT #define DECI_FONT_SIZE 20 #define DETAIL_FONT "DejaVuSansMono-Bold.ttf" #define DETAIL_FONT_SIZE 9 #define DETAIL_FONT_SPACE 12 /* Screen size (pixels) */ #define DEFAULT_WIDTH 960 #define DEFAULT_HEIGHT 720 /* Relationship between pixels and screen units */ #define DEFAULT_SCALE 1.0 /* Dimensions in our own screen units */ #define BORDER 12 #define SPACER 8 #define HALF_SPACER 4 #define CURSOR_WIDTH 4 #define PLAYER_HEIGHT 213 #define OVERVIEW_HEIGHT 16 #define LIBRARY_MIN_WIDTH 64 #define LIBRARY_MIN_HEIGHT 64 #define DEFAULT_METER_SCALE 8 #define MAX_METER_SCALE 11 #define SEARCH_HEIGHT (FONT_SPACE) #define STATUS_HEIGHT (DETAIL_FONT_SPACE) #define BPM_WIDTH 32 #define SORT_WIDTH 21 #define RESULTS_ARTIST_WIDTH 200 #define TOKEN_SPACE 2 #define CLOCKS_WIDTH 160 #define SPINNER_SIZE (CLOCK_FONT_SIZE * 2 - 6) #define SCOPE_SIZE (CLOCK_FONT_SIZE * 2 - 6) #define SCROLLBAR_SIZE 10 #define METER_WARNING_TIME 20 /* time in seconds for "red waveform" warning */ /* Function key (F1-F12) definitions */ #define FUNC_LOAD 0 #define FUNC_RECUE 1 #define FUNC_TIMECODE 2 /* Types of SDL_USEREVENT */ #define EVENT_TICKER (SDL_USEREVENT) #define EVENT_QUIT (SDL_USEREVENT + 1) #define EVENT_STATUS (SDL_USEREVENT + 2) #define EVENT_SELECTOR (SDL_USEREVENT + 3) /* Macro functions */ #define MIN(x,y) ((x)<(y)?(x):(y)) #define SQ(x) ((x)*(x)) #define LOCK(sf) if (SDL_MUSTLOCK(sf)) SDL_LockSurface(sf) #define UNLOCK(sf) if (SDL_MUSTLOCK(sf)) SDL_UnlockSurface(sf) #define UPDATE(sf, rect) SDL_UpdateRect(sf, (rect)->x, (rect)->y, \ (rect)->w, (rect)->h) /* List of directories to use as search path for fonts. */ static const char *font_dirs[] = { "/usr/X11R6/lib/X11/fonts/TTF", "/usr/share/fonts/truetype/ttf-dejavu/", "/usr/share/fonts/ttf-dejavu", "/usr/share/fonts/dejavu", "/usr/share/fonts/TTF", "/usr/share/fonts/truetype/dejavu", "/usr/share/fonts/truetype/ttf-dejavu", NULL }; static TTF_Font *clock_font, *deci_font, *detail_font, *font, *em_font, *big_font; static SDL_Color background_col = {0, 0, 0, 255}, text_col = {224, 224, 224, 255}, alert_col = {192, 64, 0, 255}, ok_col = {32, 128, 3, 255}, elapsed_col = {0, 32, 255, 255}, cursor_col = {192, 0, 0, 255}, selected_col = {0, 48, 64, 255}, detail_col = {128, 128, 128, 255}, needle_col = {255, 255, 255, 255}, artist_col = {16, 64, 0, 255}, bpm_col = {64, 16, 0, 255}; static unsigned short *spinner_angle, spinner_size; static int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, meter_scale = DEFAULT_METER_SCALE; static Uint32 video_flags = SDL_RESIZABLE; static float scale = DEFAULT_SCALE; static pthread_t ph; static struct selector selector; static struct observer on_status, on_selector; /* * Scale a dimension according to the current zoom level * * FIXME: This function is used where a rendering does not * acknowledge the scale given in the local rectangle. * These cases should be removed. */ static int zoom(int d) { return d * scale; } /* * Convert the given time (in milliseconds) to displayable time */ static void time_to_clock(char *buf, char *deci, int t) { int minutes, seconds, frac; bool neg; if (t < 0) { t = abs(t); neg = true; } else neg = false; minutes = (t / 60 / 1000) % (60*60); seconds = (t / 1000) % 60; frac = t % 1000; if (neg) *buf++ = '-'; sprintf(buf, "%02d:%02d.", minutes, seconds); sprintf(deci, "%03d", frac); } /* * Calculate a lookup which maps a position on screen to an angle, * relative to the centre of the spinner */ static void calculate_angle_lut(unsigned short *lut, int size) { int r, c, nr, nc; float theta, rat; for (r = 0; r < size; r++) { nr = r - size / 2; for (c = 0; c < size; c++) { nc = c - size / 2; if (nr == 0) theta = M_PI_2; else if (nc == 0) { theta = 0; if (nr < 0) theta = M_PI; } else { rat = (float)(nc) / -nr; theta = atanf(rat); if (rat < 0) theta += M_PI; } if (nc <= 0) theta += M_PI; /* The angles stored in the lookup table range from 0 to * 1023 (where 1024 is 360 degrees) */ lut[r * size + c] = ((int)(theta * 1024 / (M_PI * 2)) + 1024) % 1024; } } } static int init_spinner(int size) { spinner_angle = malloc(size * size * (sizeof *spinner_angle)); if (spinner_angle == NULL) { perror("malloc"); return -1; } calculate_angle_lut(spinner_angle, size); spinner_size = size; return 0; } static void clear_spinner(void) { free(spinner_angle); } /* * Open a font, given the leafname * * This scans the available font directories for the file, to account * for different software distributions. * * As this is an SDL (it is not an X11 app) we prefer to avoid the use * of fontconfig to select fonts. */ static TTF_Font* open_font(const char *name, int size) { int r, pt; char buf[256]; const char **dir; struct stat st; TTF_Font *font; pt = zoom(size); dir = &font_dirs[0]; while (*dir) { sprintf(buf, "%s/%s", *dir, name); r = stat(buf, &st); if (r != -1) { /* something exists at this path */ fprintf(stderr, "Loading font '%s', %dpt...\n", buf, pt); font = TTF_OpenFont(buf, pt); if (!font) fprintf(stderr, "Font error: %s\n", TTF_GetError()); return font; /* or NULL */ } if (errno != ENOENT) { perror("stat"); return NULL; } dir++; continue; } fprintf(stderr, "Font '%s' cannot be found in", name); dir = &font_dirs[0]; while (*dir) { fputc(' ', stderr); fputs(*dir, stderr); dir++; } fputc('.', stderr); fputc('\n', stderr); return NULL; } /* * Load all fonts */ static int load_fonts(void) { clock_font = open_font(CLOCK_FONT, CLOCK_FONT_SIZE); if (!clock_font) return -1; deci_font = open_font(DECI_FONT, DECI_FONT_SIZE); if (!deci_font) return -1; font = open_font(FONT, FONT_SIZE); if (!font) return -1; em_font = open_font(EM_FONT, FONT_SIZE); if (!em_font) return -1; big_font = open_font(BIG_FONT, BIG_FONT_SIZE); if (!big_font) return -1; detail_font = open_font(DETAIL_FONT, DETAIL_FONT_SIZE); if (!detail_font) return -1; return 0; } /* * Free resources associated with fonts */ static void clear_fonts(void) { TTF_CloseFont(clock_font); TTF_CloseFont(deci_font); TTF_CloseFont(font); TTF_CloseFont(em_font); TTF_CloseFont(big_font); TTF_CloseFont(detail_font); } static Uint32 palette(SDL_Surface *sf, SDL_Color *col) { return SDL_MapRGB(sf->format, col->r, col->g, col->b); } /* * Draw text at the given coordinates * * Return: width of text drawn */ static int draw_text(SDL_Surface *sf, const struct rect *rect, const char *buf, TTF_Font *font, SDL_Color fg, SDL_Color bg) { SDL_Surface *rendered; SDL_Rect dst, src, fill; if (buf == NULL) { src.w = 0; src.h = 0; } else if (buf[0] == '\0') { /* SDL_ttf fails for empty string */ src.w = 0; src.h = 0; } else { rendered = TTF_RenderText_Shaded(font, buf, fg, bg); src.x = 0; src.y = 0; src.w = MIN(rect->w, rendered->w); src.h = MIN(rect->h, rendered->h); dst.x = rect->x; dst.y = rect->y; SDL_BlitSurface(rendered, &src, sf, &dst); SDL_FreeSurface(rendered); } /* Complete the remaining space with a blank rectangle */ if (src.w < rect->w) { fill.x = rect->x + src.w; fill.y = rect->y; fill.w = rect->w - src.w; fill.h = rect->h; SDL_FillRect(sf, &fill, palette(sf, &bg)); } if (src.h < rect->h) { fill.x = rect->x; fill.y = rect->y + src.h; fill.w = src.w; /* the x-fill rectangle does the corner */ fill.h = rect->h - src.h; SDL_FillRect(sf, &fill, palette(sf, &bg)); } return src.w; } /* * Given a rectangle and font, calculate rendering bounds * for another font so that the baseline matches. */ static void track_baseline(const struct rect *rect, const TTF_Font *a, struct rect *aligned, const TTF_Font *b) { split(*rect, pixels(from_top(TTF_FontAscent(a) - TTF_FontAscent(b), 0)), NULL, aligned); } /* * Draw a coloured rectangle */ static void draw_rect(SDL_Surface *surface, const struct rect *rect, SDL_Color col) { SDL_Rect b; b.x = rect->x; b.y = rect->y; b.w = rect->w; b.h = rect->h; SDL_FillRect(surface, &b, palette(surface, &col)); } /* * Draw some text in a box */ static void draw_token(SDL_Surface *surface, const struct rect *rect, const char *buf, SDL_Color text_col, SDL_Color col, SDL_Color bg_col) { struct rect b; draw_rect(surface, rect, bg_col); b = shrink(*rect, TOKEN_SPACE); draw_text(surface, &b, buf, detail_font, text_col, col); } /* * Dim a colour for display */ static SDL_Color dim(const SDL_Color x, int n) { SDL_Color c; c.r = x.r >> n; c.g = x.g >> n; c.b = x.b >> n; return c; } /* * Get a colour from RGB values */ static SDL_Color rgb(double r, double g, double b) { SDL_Color c; c.r = r * 255; c.g = g * 255; c.b = b * 255; return c; } /* * Get a colour from HSV values * * Pre: h is in degrees, in the range 0.0 to 360.0 */ static SDL_Color hsv(double h, double s, double v) { int i; double f, p, q, t; if (s == 0.0) return rgb(v, v, v); h /= 60; i = floor(h); f = h - i; p = v * (1 - s); q = v * (1 - s * f); t = v * (1 - s * (1 - f)); switch (i) { case 0: return rgb(v, t, p); case 1: return rgb(q, v, p); case 2: return rgb(p, v, t); case 3: return rgb(p, q, v); case 4: return rgb(t, p, v); case 5: case 6: return rgb(v, p, q); default: abort(); } } static bool show_bpm(double bpm) { return (bpm > 20.0 && bpm < 400.0); } /* * Draw the beats-per-minute indicator */ static void draw_bpm(SDL_Surface *surface, const struct rect *rect, double bpm, SDL_Color bg_col) { static const double min = 60.0, max = 240.0; char buf[32]; double f, h; sprintf(buf, "%5.1f", bpm); /* Safety catch against bad BPM values, NaN, infinity etc. */ if (bpm < min || bpm > max) { draw_token(surface, rect, buf, detail_col, bg_col, bg_col); return; } /* Colour compatible BPMs the same; cycle 360 degrees * every time the BPM doubles */ f = log2(bpm); f -= floor(f); h = f * 360.0; /* degrees */ draw_token(surface, rect, buf, text_col, hsv(h, 1.0, 0.3), bg_col); } /* * Draw the BPM field, or a gap */ static void draw_bpm_field(SDL_Surface *surface, const struct rect *rect, double bpm, SDL_Color bg_col) { if (show_bpm(bpm)) draw_bpm(surface, rect, bpm, bg_col); else draw_rect(surface, rect, bg_col); } /* * Draw the record information in the deck */ static void draw_record(SDL_Surface *surface, const struct rect *rect, const struct record *record) { struct rect artist, title, left, right; split(*rect, from_top(BIG_FONT_SPACE, 0), &artist, &title); draw_text(surface, &artist, record->artist, big_font, text_col, background_col); /* Layout changes slightly if BPM is known */ if (show_bpm(record->bpm)) { split(title, from_left(BPM_WIDTH, 0), &left, &right); draw_bpm(surface, &left, record->bpm, background_col); split(right, from_left(HALF_SPACER, 0), &left, &title); draw_rect(surface, &left, background_col); } draw_text(surface, &title, record->title, font, text_col, background_col); } /* * Draw a single time in milliseconds in hours:minutes.seconds format */ static void draw_clock(SDL_Surface *surface, const struct rect *rect, int t, SDL_Color col) { char hms[8], deci[8]; short int v; struct rect sr; time_to_clock(hms, deci, t); v = draw_text(surface, rect, hms, clock_font, col, background_col); split(*rect, pixels(from_left(v, 0)), NULL, &sr); track_baseline(&sr, clock_font, &sr, deci_font); draw_text(surface, &sr, deci, deci_font, col, background_col); } /* * Draw the visual monitor of the input audio to the timecoder */ static void draw_scope(SDL_Surface *surface, const struct rect *rect, struct timecoder *tc) { int r, c, v, mid; Uint8 *p; mid = tc->mon_size / 2; for (r = 0; r < tc->mon_size; r++) { for (c = 0; c < tc->mon_size; c++) { p = surface->pixels + (rect->y + r) * surface->pitch + (rect->x + c) * surface->format->BytesPerPixel; v = tc->mon[r * tc->mon_size + c]; if ((r == mid || c == mid) && v < 64) v = 64; p[0] = v; p[1] = p[0]; p[2] = p[1]; } } } /* * Draw the spinner * * The spinner shows the rotational position of the record, and * matches the physical rotation of the vinyl record. */ static void draw_spinner(SDL_Surface *surface, const struct rect *rect, struct player *pl) { int x, y, r, c, rangle, pangle; double elapsed, remain, rps; Uint8 *rp, *p; SDL_Color col; x = rect->x; y = rect->y; elapsed = player_get_elapsed(pl); remain = player_get_remain(pl); rps = timecoder_revs_per_sec(pl->timecoder); rangle = (int)(player_get_position(pl) * 1024 * rps) % 1024; if (elapsed < 0 || remain < 0) col = alert_col; else col = ok_col; for (r = 0; r < spinner_size; r++) { /* Store a pointer to this row of the framebuffer */ rp = surface->pixels + (y + r) * surface->pitch; for (c = 0; c < spinner_size; c++) { /* Use the lookup table to provide the angle at each * pixel */ pangle = spinner_angle[r * spinner_size + c]; /* Calculate the final pixel location and set it */ p = rp + (x + c) * surface->format->BytesPerPixel; if ((rangle - pangle + 1024) % 1024 < 512) { p[0] = col.b >> 2; p[1] = col.g >> 2; p[2] = col.r >> 2; } else { p[0] = col.b; p[1] = col.g; p[2] = col.r; } } } } /* * Draw the clocks which show time elapsed and time remaining */ static void draw_deck_clocks(SDL_Surface *surface, const struct rect *rect, struct player *pl, struct track *track) { int elapse, remain; struct rect upper, lower; SDL_Color col; split(*rect, from_top(CLOCK_FONT_SIZE, 0), &upper, &lower); elapse = player_get_elapsed(pl) * 1000; remain = player_get_remain(pl) * 1000; if (elapse < 0) col = alert_col; else if (remain > 0) col = ok_col; else col = text_col; draw_clock(surface, &upper, elapse, col); if (remain <= 0) col = alert_col; else col = text_col; if (track_is_importing(track)) col = dim(col, 2); draw_clock(surface, &lower, -remain, col); } /* * Draw the high-level overview meter which shows the whole length * of the track */ static void draw_overview(SDL_Surface *surface, const struct rect *rect, struct track *tr, int position) { int x, y, w, h, r, c, sp, fade, bytes_per_pixel, pitch, height, current_position; Uint8 *pixels, *p; SDL_Color col; x = rect->x; y = rect->y; w = rect->w; h = rect->h; pixels = surface->pixels; bytes_per_pixel = surface->format->BytesPerPixel; pitch = surface->pitch; if (tr->length) current_position = (long long)position * w / tr->length; else current_position = 0; for (c = 0; c < w; c++) { /* Collect the correct meter value for this column */ sp = (long long)tr->length * c / w; if (sp < tr->length) /* account for rounding */ height = track_get_overview(tr, sp) * h / 256; else height = 0; /* Choose a base colour to display in */ if (!tr->length) { col = background_col; fade = 0; } else if (c == current_position) { col = needle_col; fade = 1; } else if (position > tr->length - tr->rate * METER_WARNING_TIME) { col = alert_col; fade = 3; } else { col = elapsed_col; fade = 3; } if (track_is_importing(tr)) col = dim(col, 1); if (c < current_position) col = dim(col, 1); /* Store a pointer to this column of the framebuffer */ p = pixels + y * pitch + (x + c) * bytes_per_pixel; r = h; while (r > height) { p[0] = col.b >> fade; p[1] = col.g >> fade; p[2] = col.r >> fade; p += pitch; r--; } while (r) { p[0] = col.b; p[1] = col.g; p[2] = col.r; p += pitch; r--; } } } /* * Draw the close-up meter, which can be zoomed to a level set by * 'scale' */ static void draw_closeup(SDL_Surface *surface, const struct rect *rect, struct track *tr, int position, int scale) { int x, y, w, h, c; size_t bytes_per_pixel, pitch; Uint8 *pixels; x = rect->x; y = rect->y; w = rect->w; h = rect->h; pixels = surface->pixels; bytes_per_pixel = surface->format->BytesPerPixel; pitch = surface->pitch; /* Draw in columns. This may seem like a performance hit, * but oprofile shows it makes no difference */ for (c = 0; c < w; c++) { int r, sp, height, fade; Uint8 *p; SDL_Color col; /* Work out the meter height in pixels for this column */ sp = position - (position % (1 << scale)) + ((c - w / 2) << scale); if (sp < tr->length && sp > 0) height = track_get_ppm(tr, sp) * h / 256; else height = 0; /* Select the appropriate colour */ if (c == w / 2) { col = needle_col; fade = 1; } else { col = elapsed_col; fade = 3; } /* Get a pointer to the top of the column, and increment * it for each row */ p = pixels + y * pitch + (x + c) * bytes_per_pixel; r = h; while (r > height) { p[0] = col.b >> fade; p[1] = col.g >> fade; p[2] = col.r >> fade; p += pitch; r--; } while (r) { p[0] = col.b; p[1] = col.g; p[2] = col.r; p += pitch; r--; } } } /* * Draw the audio meters for a deck */ static void draw_meters(SDL_Surface *surface, const struct rect *rect, struct track *tr, int position, int scale) { struct rect overview, closeup; split(*rect, from_top(OVERVIEW_HEIGHT, SPACER), &overview, &closeup); if (closeup.h > OVERVIEW_HEIGHT) draw_overview(surface, &overview, tr, position); else closeup = *rect; draw_closeup(surface, &closeup, tr, position, scale); } /* * Draw the current playback status -- clocks, spinner and scope */ static void draw_deck_top(SDL_Surface *surface, const struct rect *rect, struct player *pl, struct track *track) { struct rect clocks, left, right, spinner, scope; split(*rect, from_left(CLOCKS_WIDTH, SPACER), &clocks, &right); /* If there is no timecoder to display information on, or not enough * available space, just draw clocks which span the overall space */ if (!pl->timecode_control || right.w < 0) { draw_deck_clocks(surface, rect, pl, track); return; } draw_deck_clocks(surface, &clocks, pl, track); split(right, from_right(SPINNER_SIZE, SPACER), &left, &spinner); if (left.w < 0) return; split(spinner, from_bottom(SPINNER_SIZE, 0), NULL, &spinner); draw_spinner(surface, &spinner, pl); split(left, from_right(SCOPE_SIZE, SPACER), &clocks, &scope); if (clocks.w < 0) return; split(scope, from_bottom(SCOPE_SIZE, 0), NULL, &scope); draw_scope(surface, &scope, pl->timecoder); } /* * Draw the textual description of playback status, which includes * information on the timecode */ static void draw_deck_status(SDL_Surface *surface, const struct rect *rect, const struct deck *deck) { char buf[128], *c; int tc; const struct player *pl = &deck->player; c = buf; c += sprintf(c, "%s: ", pl->timecoder->def->name); tc = timecoder_get_position(pl->timecoder, NULL); if (pl->timecode_control && tc != -1) { c += sprintf(c, "%7d ", tc); } else { c += sprintf(c, " "); } sprintf(c, "pitch:%+0.2f (sync %0.2f %+.5fs = %+0.2f) %s%s", pl->pitch, pl->sync_pitch, pl->last_difference, pl->pitch * pl->sync_pitch, pl->recalibrate ? "RCAL " : "", deck_is_locked(deck) ? "LOCK " : ""); draw_text(surface, rect, buf, detail_font, detail_col, background_col); } /* * Draw a single deck */ static void draw_deck(SDL_Surface *surface, const struct rect *rect, struct deck *deck, int meter_scale) { int position; struct rect track, top, meters, status, rest, lower; struct player *pl; struct track *t; pl = &deck->player; t = pl->track; position = player_get_elapsed(pl) * t->rate; split(*rect, from_top(FONT_SPACE + BIG_FONT_SPACE, 0), &track, &rest); if (rest.h < 160) rest = *rect; else draw_record(surface, &track, deck->record); split(rest, from_top(CLOCK_FONT_SIZE * 2, SPACER), &top, &lower); if (lower.h < 64) lower = rest; else draw_deck_top(surface, &top, pl, t); split(lower, from_bottom(FONT_SPACE, SPACER), &meters, &status); if (meters.h < 64) meters = lower; else draw_deck_status(surface, &status, deck); draw_meters(surface, &meters, t, position, meter_scale); } /* * Draw all the decks in the system left to right */ static void draw_decks(SDL_Surface *surface, const struct rect *rect, struct deck deck[], size_t ndecks, int meter_scale) { int d; struct rect left, right; right = *rect; for (d = 0; d < ndecks; d++) { split(right, columns(d, ndecks, BORDER), &left, &right); draw_deck(surface, &left, &deck[d], meter_scale); } } /* * Draw the status bar */ static void draw_status(SDL_Surface *sf, const struct rect *rect) { SDL_Color fg, bg; switch (status_level()) { case STATUS_ALERT: case STATUS_WARN: fg = text_col; bg = dim(alert_col, 2); break; default: fg = detail_col; bg = background_col; } draw_text(sf, rect, status(), detail_font, fg, bg); } /* * Draw the search field which the user types into */ static void draw_search(SDL_Surface *surface, const struct rect *rect, struct selector *sel) { int s; const char *buf; char cm[32]; SDL_Rect cursor; struct rect rtext; split(*rect, from_left(SCROLLBAR_SIZE, SPACER), NULL, &rtext); if (sel->search[0] != '\0') buf = sel->search; else buf = NULL; s = draw_text(surface, &rtext, buf, font, text_col, background_col); cursor.x = rtext.x + s; cursor.y = rtext.y; cursor.w = CURSOR_WIDTH * rect->scale; /* FIXME: use proper UI funcs */ cursor.h = rtext.h; SDL_FillRect(surface, &cursor, palette(surface, &cursor_col)); if (sel->view_index->entries > 1) sprintf(cm, "%zd matches", sel->view_index->entries); else if (sel->view_index->entries > 0) sprintf(cm, "1 match"); else sprintf(cm, "no matches"); rtext.x += s + CURSOR_WIDTH + SPACER; rtext.w -= s + CURSOR_WIDTH + SPACER; draw_text(surface, &rtext, cm, em_font, detail_col, background_col); } /* * Draw a vertical scroll bar representing our view on a list of the * given number of entries */ static void draw_scroll_bar(SDL_Surface *surface, const struct rect *rect, const struct listbox *scroll) { SDL_Rect box; SDL_Color bg; bg = dim(selected_col, 1); box.x = rect->x; box.y = rect->y; box.w = rect->w; box.h = rect->h; SDL_FillRect(surface, &box, palette(surface, &bg)); if (scroll->entries > 0) { box.x = rect->x; box.y = rect->y + rect->h * scroll->offset / scroll->entries; box.w = rect->w; box.h = rect->h * MIN(scroll->lines, scroll->entries) / scroll->entries; SDL_FillRect(surface, &box, palette(surface, &selected_col)); } } /* * A callback function for drawing a row. Included here for * readability where it is used. */ typedef void (*draw_row_t)(const void *context, SDL_Surface *surface, const struct rect rect, unsigned int entry, bool selected); /* * Draw a listbox, using the given function to draw each row */ static void draw_listbox(const struct listbox *lb, SDL_Surface *surface, const struct rect rect, const void *context, draw_row_t draw) { struct rect left, remain; unsigned int row; split(rect, from_left(SCROLLBAR_SIZE, SPACER), &left, &remain); draw_scroll_bar(surface, &left, lb); row = 0; for (row = 0;; row++) { int entry; bool selected; struct rect line; entry = listbox_map(lb, row); if (entry == -1) break; if (entry == listbox_current(lb)) selected = true; else selected = false; split(remain, from_top(FONT_SPACE, 0), &line, &remain); draw(context, surface, line, entry, selected); } draw_rect(surface, &remain, background_col); } static void draw_crate_row(const void *context, SDL_Surface *surface, const struct rect rect, unsigned int entry, bool selected) { const struct selector *selector = context; const struct crate *crate; struct rect left, right; SDL_Color col; crate = selector->library->crate[entry]; if (crate->is_fixed) col = detail_col; else col = text_col; if (!selected) { draw_text(surface, &rect, crate->name, font, col, background_col); return; } split(rect, from_right(SORT_WIDTH, 0), &left, &right); switch (selector->sort) { case SORT_ARTIST: draw_token(surface, &right, "ART", text_col, artist_col, selected_col); break; case SORT_BPM: draw_token(surface, &right, "BPM", text_col, bpm_col, selected_col); break; case SORT_PLAYLIST: draw_token(surface, &right, "PLS", text_col, selected_col, selected_col); break; default: abort(); } if (crate->is_busy) { split(left, from_right(25, 0), &left, &right); draw_token(surface, &right, "BUSY", text_col, dim(alert_col, 2), selected_col); } draw_text(surface, &left, crate->name, font, col, selected_col); } /* * Draw a crate index, with scrollbar and current selection */ static void draw_crates(SDL_Surface *surface, const struct rect rect, const struct selector *x) { draw_listbox(&x->crates, surface, rect, x, draw_crate_row); } static void draw_record_row(const void *context, SDL_Surface *surface, const struct rect rect, unsigned int entry, bool selected) { int width; struct record *record; const struct index *index = context; struct rect left, right; SDL_Color col; if (selected) col = selected_col; else col = background_col; width = rect.w / 2; if (width > RESULTS_ARTIST_WIDTH) width = RESULTS_ARTIST_WIDTH; record = index->record[entry]; split(rect, from_left(BPM_WIDTH, 0), &left, &right); draw_bpm_field(surface, &left, record->bpm, col); split(right, from_left(SPACER, 0), &left, &right); draw_rect(surface, &left, col); split(right, from_left(width, 0), &left, &right); draw_text(surface, &left, record->artist, font, text_col, col); split(right, from_left(SPACER, 0), &left, &right); draw_rect(surface, &left, col); draw_text(surface, &right, record->title, font, text_col, col); } /* * Display a record library index, with scrollbar and current * selection */ static void draw_index(SDL_Surface *surface, const struct rect rect, const struct selector *x) { draw_listbox(&x->records, surface, rect, x->view_index, draw_record_row); } /* * Display the music library, which consists of the query, and search * results */ static void draw_library(SDL_Surface *surface, const struct rect *rect, struct selector *sel) { struct rect rsearch, rlists, rcrates, rrecords; unsigned int rows; split(*rect, from_top(SEARCH_HEIGHT, SPACER), &rsearch, &rlists); rows = count_rows(rlists, FONT_SPACE); if (rows == 0) { /* Hide the selector: draw nothing, and make it a 'virtual' * one row selector. This is enough to use it from the search * field and status only */ draw_search(surface, rect, sel); selector_set_lines(sel, 1); return; } draw_search(surface, &rsearch, sel); selector_set_lines(sel, rows); split(rlists, columns(0, 4, SPACER), &rcrates, &rrecords); if (rcrates.w > LIBRARY_MIN_WIDTH) { draw_index(surface, rrecords, sel); draw_crates(surface, rcrates, sel); } else { draw_index(surface, *rect, sel); } } /* * Handle a single key event * * Return: true if the selector needs to be redrawn, otherwise false */ static bool handle_key(SDLKey key, SDLMod mod) { struct selector *sel = &selector; if (key >= SDLK_a && key <= SDLK_z) { selector_search_refine(sel, (key - SDLK_a) + 'a'); return true; } else if (key >= SDLK_0 && key <= SDLK_9) { selector_search_refine(sel, (key - SDLK_0) + '0'); return true; } else if (key == SDLK_SPACE) { selector_search_refine(sel, ' '); return true; } else if (key == SDLK_BACKSPACE) { selector_search_expand(sel); return true; } else if (key == SDLK_PERIOD) { selector_search_refine(sel, '.'); return true; } else if (key == SDLK_HOME) { selector_top(sel); return true; } else if (key == SDLK_END) { selector_bottom(sel); return true; } else if (key == SDLK_UP) { selector_up(sel); return true; } else if (key == SDLK_DOWN) { selector_down(sel); return true; } else if (key == SDLK_PAGEUP) { selector_page_up(sel); return true; } else if (key == SDLK_PAGEDOWN) { selector_page_down(sel); return true; } else if (key == SDLK_LEFT) { selector_prev(sel); return true; } else if (key == SDLK_RIGHT) { selector_next(sel); return true; } else if (key == SDLK_TAB) { if (mod & KMOD_CTRL) { if (mod & KMOD_SHIFT) selector_rescan(sel); else selector_toggle_order(sel); } else { selector_toggle(sel); } return true; } else if ((key == SDLK_EQUALS) || (key == SDLK_PLUS)) { meter_scale--; if (meter_scale < 0) meter_scale = 0; fprintf(stderr, "Meter scale decreased to %d\n", meter_scale); } else if (key == SDLK_MINUS) { meter_scale++; if (meter_scale > MAX_METER_SCALE) meter_scale = MAX_METER_SCALE; fprintf(stderr, "Meter scale increased to %d\n", meter_scale); } else if (key >= SDLK_F1 && key <= SDLK_F12) { size_t d; /* Handle the function key press in groups of four -- * F1-F4 (deck 0), F5-F8 (deck 1) etc. */ d = (key - SDLK_F1) / 4; if (d < ndeck) { int func; struct deck *de; struct player *pl; struct record *re; struct timecoder *tc; func = (key - SDLK_F1) % 4; de = &deck[d]; pl = &de->player; tc = &de->timecoder; /* Some undocumented and 'special' functions exist * here for the developer */ if (mod & KMOD_SHIFT && !(mod & KMOD_CTRL)) { if (func < ndeck) deck_clone(de, &deck[func]); } else switch(func) { case FUNC_LOAD: re = selector_current(sel); if (re != NULL) deck_load(de, re); break; case FUNC_RECUE: deck_recue(de); break; case FUNC_TIMECODE: if (mod & KMOD_CTRL) { if (mod & KMOD_SHIFT) player_set_internal_playback(pl); else timecoder_cycle_definition(tc); } else { (void)player_toggle_timecode_control(pl); } break; } } } return false; } /* * Action on size change event on the main window */ static SDL_Surface* set_size(int w, int h, struct rect *r) { SDL_Surface *surface; surface = SDL_SetVideoMode(w, h, 32, video_flags); if (surface == NULL) { fprintf(stderr, "%s\n", SDL_GetError()); return NULL; } *r = shrink(rect(0, 0, w, h, scale), BORDER); fprintf(stderr, "New interface size is %dx%d.\n", w, h); return surface; } static void push_event(int t) { SDL_Event e; if (!SDL_PeepEvents(&e, 1, SDL_PEEKEVENT, SDL_EVENTMASK(t))) { e.type = t; if (SDL_PushEvent(&e) == -1) abort(); } } /* * Timer which posts a screen redraw event */ static Uint32 ticker(Uint32 interval, void *p) { push_event(EVENT_TICKER); return interval; } /* * Callback to tell the interface that status has changed */ static void defer_status_redraw(struct observer *o, void *x) { push_event(EVENT_STATUS); } static void defer_selector_redraw(struct observer *o, void *x) { push_event(EVENT_SELECTOR); } /* * The SDL interface thread */ static int interface_main(void) { bool library_update, decks_update, status_update; SDL_Event event; SDL_TimerID timer; SDL_Surface *surface; struct rect rworkspace, rplayers, rlibrary, rstatus, rtmp; surface = set_size(width, height, &rworkspace); if (!surface) return -1; decks_update = true; status_update = true; library_update = true; /* The final action is to add the timer which triggers refresh */ timer = SDL_AddTimer(REFRESH, ticker, NULL); rig_lock(); for (;;) { rig_unlock(); if (SDL_WaitEvent(&event) < 0) break; rig_lock(); switch(event.type) { case SDL_QUIT: /* user request to quit application; eg. window close */ if (rig_quit() == -1) return -1; break; case SDL_VIDEORESIZE: surface = set_size(event.resize.w, event.resize.h, &rworkspace); if (!surface) return -1; library_update = true; decks_update = true; status_update = true; break; case EVENT_TICKER: decks_update = true; break; case EVENT_QUIT: /* internal request to finish this thread */ goto finish; case EVENT_STATUS: status_update = true; break; case EVENT_SELECTOR: library_update = true; break; case SDL_KEYDOWN: if (handle_key(event.key.keysym.sym, event.key.keysym.mod)) { struct record *r; r = selector_current(&selector); if (r != NULL) { status_set(STATUS_VERBOSE, r->pathname); } else { status_set(STATUS_VERBOSE, "No search results found"); } } } /* switch(event.type) */ /* Split the display into the various areas. If an area is too * small, abandon any actions to happen in that area. */ split(rworkspace, from_bottom(STATUS_HEIGHT, SPACER), &rtmp, &rstatus); if (rtmp.h < 128 || rtmp.w < 0) { rtmp = rworkspace; status_update = false; } split(rtmp, from_top(PLAYER_HEIGHT, SPACER), &rplayers, &rlibrary); if (rlibrary.h < LIBRARY_MIN_HEIGHT || rlibrary.w < LIBRARY_MIN_WIDTH) { rplayers = rtmp; library_update = false; } if (rplayers.h < 0 || rplayers.w < 0) decks_update = false; if (!library_update && !decks_update && !status_update) continue; LOCK(surface); if (library_update) draw_library(surface, &rlibrary, &selector); if (status_update) draw_status(surface, &rstatus); if (decks_update) draw_decks(surface, &rplayers, deck, ndeck, meter_scale); UNLOCK(surface); if (library_update) { UPDATE(surface, &rlibrary); library_update = false; } if (status_update) { UPDATE(surface, &rstatus); status_update = false; } if (decks_update) { UPDATE(surface, &rplayers); decks_update = false; } } /* main loop */ finish: rig_unlock(); SDL_RemoveTimer(timer); return 0; } static void* launch(void *p) { interface_main(); return NULL; } /* * Parse and action the given geometry string * * Geometry string includes size, position and scale. The format is * "[x][++][@]". Some examples: * * 960x720 * +10+10 * 960x720+10+10 * @1.6 * 1920x1200@1.6 * * Return: -1 if string could not be actioned, otherwise 0 */ static int parse_geometry(const char *s) { int n, x, y, len; char buf[128]; /* The %n in format strings is not a token, see scanf(3) man page */ n = sscanf(s, "%[0-9]x%d%n", buf, &height, &len); switch (n) { case EOF: return 0; case 0: break; case 2: /* we used a format to prevent parsing the '+' in the next block */ width = atoi(buf); s += len; break; default: return -1; } n = sscanf(s, "+%d+%d%n", &x, &y, &len); switch (n) { case EOF: return 0; case 0: break; case 2: /* Not a desirable way to get geometry information to * SDL, but it seems to be the only way */ sprintf(buf, "SDL_VIDEO_WINDOW_POS=%d,%d", x, y); if (putenv(buf) != 0) return -1; s += len; break; default: return -1; } n = sscanf(s, "/%f%n", &scale, &len); switch (n) { case EOF: return 0; case 0: break; case 1: if (scale <= 0.0) return -1; s += len; break; default: return -1; } if (*s != '\0') return -1; return 0; } /* * Start the SDL interface * * FIXME: There are multiple points where resources are leaked on * error */ int interface_start(struct library *lib, const char *geo, bool decor) { size_t n; if (parse_geometry(geo) == -1) { fprintf(stderr, "Window geometry ('%s') is not valid.\n", geo); return -1; } if (!decor) video_flags |= SDL_NOFRAME; for (n = 0; n < ndeck; n++) { if (timecoder_monitor_init(&deck[n].timecoder, zoom(SCOPE_SIZE)) == -1) return -1; } if (init_spinner(zoom(SPINNER_SIZE)) == -1) return -1; selector_init(&selector, lib); watch(&on_status, &status_changed, defer_status_redraw); watch(&on_selector, &selector.changed, defer_selector_redraw); status_set(STATUS_VERBOSE, banner); fprintf(stderr, "Initialising SDL...\n"); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) { fprintf(stderr, "%s\n", SDL_GetError()); return -1; } SDL_WM_SetCaption(banner, NULL); SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); /* Initialise the fonts */ if (TTF_Init() == -1) { fprintf(stderr, "%s\n", TTF_GetError()); return -1; } if (load_fonts() == -1) return -1; fprintf(stderr, "Launching interface thread...\n"); if (pthread_create(&ph, NULL, launch, NULL)) { perror("pthread_create"); return -1; } return 0; } /* * Synchronise with the SDL interface and exit */ void interface_stop(void) { size_t n; push_event(EVENT_QUIT); if (pthread_join(ph, NULL) != 0) abort(); for (n = 0; n < ndeck; n++) timecoder_monitor_clear(&deck[n].timecoder); clear_spinner(); ignore(&on_status); ignore(&on_selector); selector_clear(&selector); clear_fonts(); TTF_Quit(); SDL_Quit(); } xwax-1.6-beta2/interface.h000066400000000000000000000016321261512120600154510ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef INTERFACE_H #define INTERFACE_H #include "deck.h" #include "library.h" int interface_start(struct library *lib, const char *geo, bool decor); void interface_stop(); #endif xwax-1.6-beta2/jack.c000066400000000000000000000207061261512120600144170ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include "device.h" #include "jack.h" #define MAX_BLOCK 512 /* samples */ #define SCALE 32768 struct jack { bool started; jack_port_t *input_port[DEVICE_CHANNELS], *output_port[DEVICE_CHANNELS]; }; static jack_client_t *client = NULL; static unsigned rate, ndeck = 0, nstarted = 0; static struct device *device[4]; /* Interleave samples from a set of JACK buffers into a local buffer */ static void interleave(signed short *buf, jack_default_audio_sample_t *jbuf[], jack_nframes_t nframes) { while (nframes--) { int n; for (n = 0; n < DEVICE_CHANNELS; n++) { *buf = (signed short)(*jbuf[n] * SCALE); buf++; jbuf[n]++; } } } /* Uninterleave samples from a local buffer into a set of JACK buffers */ static void uninterleave(jack_default_audio_sample_t *jbuf[], signed short *buf, jack_nframes_t nframes) { while (nframes--) { int n; for (n = 0; n < DEVICE_CHANNELS; n++) { *jbuf[n] = (jack_default_audio_sample_t)*buf / SCALE; buf++; jbuf[n]++; } } } /* Process the given number of frames of audio on input and output * of the given JACK device */ static void process_deck(struct device *dv, jack_nframes_t nframes) { int n; jack_default_audio_sample_t *in[DEVICE_CHANNELS], *out[DEVICE_CHANNELS]; jack_nframes_t remain; struct jack *jack = (struct jack*)dv->local; assert(dv->timecoder != NULL); assert(dv->player != NULL); for (n = 0; n < DEVICE_CHANNELS; n++) { in[n] = jack_port_get_buffer(jack->input_port[n], nframes); assert(in[n] != NULL); out[n] = jack_port_get_buffer(jack->output_port[n], nframes); assert(out[n] != NULL); } /* For large values of nframes, communicate with the timecoder and * player in smaller blocks */ remain = nframes; while (remain > 0) { signed short buf[MAX_BLOCK * DEVICE_CHANNELS]; jack_nframes_t block; if (remain < MAX_BLOCK) block = remain; else block = MAX_BLOCK; /* Timecode input */ interleave(buf, in, block); device_submit(dv, buf, block); /* Audio output is handle in the inner loop, so that * we get the timecoder applied in small steps */ device_collect(dv, buf, block); uninterleave(out, buf, block); remain -= block; } } /* Process callback, which triggers the processing of audio on all * decks controlled by this file */ static int process_callback(jack_nframes_t nframes, void *local) { size_t n; for (n = 0; n < ndeck; n++) { struct jack *jack; jack = (struct jack*)device[n]->local; if (jack->started) process_deck(device[n], nframes); } return 0; } /* Shutdown callback */ static void shutdown_callback(void *local) { } /* Initialise ourselves as a JACK client, called once per xwax * session, not per deck */ static int start_jack_client(void) { const char *server_name; jack_status_t status; client = jack_client_open("xwax", JackNullOption, &status, &server_name); if (client == NULL) { if (status & JackServerFailed) fprintf(stderr, "JACK: Failed to connect\n"); else fprintf(stderr, "jack_client_open: Failed (0x%x)\n", status); return -1; } if (jack_set_process_callback(client, process_callback, NULL) != 0) { fprintf(stderr, "JACK: Failed to set process callback\n"); return -1; } jack_on_shutdown(client, shutdown_callback, NULL); rate = jack_get_sample_rate(client); fprintf(stderr, "JACK: %dHz\n", rate); return 0; } /* Close the JACK client, at which happens when all decks have been * cleared */ static int stop_jack_client(void) { if (jack_client_close(client) != 0) { fprintf(stderr, "jack_client_close: Failed\n"); return -1; } client = NULL; return 0; } /* Register the JACK ports needed for a single deck */ static int register_ports(struct jack *jack, const char *name) { size_t n; assert(DEVICE_CHANNELS == 2); for (n = 0; n < DEVICE_CHANNELS; n++) { static const char channel[] = { 'L', 'R' }; char port_name[32]; sprintf(port_name, "%s_timecode_%c", name, channel[n]); jack->input_port[n] = jack_port_register(client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); if (jack->input_port[n] == NULL) { fprintf(stderr, "JACK: Failed to register timecode input port\n"); return -1; } sprintf(port_name, "%s_playback_%c", name, channel[n]); jack->output_port[n] = jack_port_register(client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (jack->output_port[n] == NULL) { fprintf(stderr, "JACK: Failed to register audio playback port\n"); return -1; } } return 0; } /* Return the sample rate */ static unsigned int sample_rate(struct device *dv) { return rate; /* the same rate is used for all decks */ } /* Start audio rolling on this deck */ static void start(struct device *dv) { struct jack *jack = (struct jack*)dv->local; assert(dv->timecoder != NULL); assert(dv->player != NULL); /* On the first call to start, start audio rolling for all decks */ if (nstarted == 0) { if (jack_activate(client) != 0) abort(); } nstarted++; jack->started = true; } /* Stop audio rolling on this deck */ static void stop(struct device *dv) { struct jack *jack = (struct jack*)dv->local; jack->started = false; nstarted--; /* On the final stop call, stop JACK rolling */ if (nstarted == 0) { if (jack_deactivate(client) != 0) abort(); } } /* Close JACK deck and any allocations */ static void clear(struct device *dv) { struct jack *jack = (struct jack*)dv->local; int n; /* Unregister ports */ for (n = 0; n < DEVICE_CHANNELS; n++) { if (jack_port_unregister(client, jack->input_port[n]) != 0) abort(); if (jack_port_unregister(client, jack->output_port[n]) != 0) abort(); } free(dv->local); /* Remove this from the global list, so that potentially xwax could * continue to run even if a deck is removed */ for (n = 0; n < ndeck; n++) { if (device[n] == dv) break; } assert(n != ndeck); if (ndeck == 1) { /* this is the last remaining deck */ stop_jack_client(); ndeck = 0; } else { device[n] = device[ndeck - 1]; /* compact the list */ ndeck--; } } static struct device_ops jack_ops = { .sample_rate = sample_rate, .start = start, .stop = stop, .clear = clear }; /* Initialise a new JACK deck, creating a new JACK client if required, * and the approporiate input and output ports */ int jack_init(struct device *dv, const char *name) { struct jack *jack; /* If this is the first JACK deck, initialise the global JACK services */ if (client == NULL) { if (start_jack_client() == -1) return -1; } jack = malloc(sizeof *jack); if (jack == NULL) { perror("malloc"); return -1; } jack->started = false; if (register_ports(jack, name) == -1) goto fail; device_init(dv, &jack_ops); dv->local = jack; assert(ndeck < sizeof device); device[ndeck] = dv; ndeck++; return 0; fail: free(jack); return -1; } xwax-1.6-beta2/jack.h000066400000000000000000000015231261512120600144200ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef JACK_H #define JACK_H #include "device.h" int jack_init(struct device *dv, const char *name); #endif xwax-1.6-beta2/layout.h000066400000000000000000000131521261512120600150260ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Layout functions for use by low-level UI code */ #ifndef LAYOUT_H #define LAYOUT_H #define LAYOUT_VERTICAL 0x01 #define LAYOUT_SECONDARY 0x02 #define LAYOUT_PIXELS 0x04 /* default is our own screen units */ typedef signed short pix_t; /* * A rectangle defines an on-screen area, and other attributes * for anything that gets into the area */ struct rect { pix_t x, y, w, h; /* pixels */ float scale; }; struct layout { unsigned char flags; float portion; unsigned int distance, space; /* may be pixels, or units */ }; /* * Helper function to make layout specs */ static inline struct layout absolute(unsigned char flags, unsigned int distance, unsigned int space) { struct layout l; l.flags = flags; l.portion = 0.0; l.distance = distance; l.space = space; return l; } static inline struct layout from_left(unsigned int distance, unsigned int space) { return absolute(0, distance, space); } static inline struct layout from_right(unsigned int distance, unsigned int space) { return absolute(LAYOUT_SECONDARY, distance, space); } static inline struct layout from_top(unsigned int distance, unsigned int space) { return absolute(LAYOUT_VERTICAL, distance, space); } static inline struct layout from_bottom(unsigned int distance, unsigned int space) { return absolute(LAYOUT_VERTICAL | LAYOUT_SECONDARY, distance, space); } static inline struct layout portion(unsigned char flags, double f, unsigned int space) { struct layout l; l.flags = flags; l.portion = f; l.distance = 0; l.space = space; return l; } static inline struct layout columns(unsigned int n, unsigned int total, unsigned int space) { assert(n < total); return portion(0, 1.0 / (total - n), space); } static inline struct layout rows(unsigned int n, unsigned int total, unsigned int space) { assert(n < total); return portion(LAYOUT_VERTICAL, 1.0 / (total - n), space); } /* * Take an existing layout spec and request that it be in pixels * * Most dimensions are done in terms of 'screen units' but sometimes * we need to apply layout based on a pixel measurement (eg. returned * to us when drawing text) */ static inline struct layout pixels(struct layout j) { struct layout r; r = j; r.flags |= LAYOUT_PIXELS; return r; } /* * Create a new rectangle from pixels */ static inline struct rect rect(pix_t x, pix_t y, pix_t w, pix_t h, float scale) { struct rect r; r.x = x; r.y = y; r.w = w; r.h = h; r.scale = scale; return r; } /* * Apply a layout request to split two rectangles * * The caller is allowed to use the same rectangle for output * as is the input. */ static inline void split(const struct rect x, const struct layout spec, struct rect *a, struct rect *b) { unsigned char flags; signed short p, q, full, distance, space; struct rect discard, in; in = x; /* allow caller to re-use x as an output */ if (!a) a = &discard; if (!b) b = &discard; flags = spec.flags; if (flags & LAYOUT_VERTICAL) full = in.h; else full = in.w; if (flags & LAYOUT_PIXELS) { space = spec.space; distance = spec.distance; } else { space = spec.space * in.scale; distance = spec.distance * in.scale; } if (spec.portion != 0.0) distance = spec.portion * full - space / 2; if (flags & LAYOUT_SECONDARY) { p = full - distance - space; q = full - distance; } else { p = distance; q = distance + space; } if (flags & LAYOUT_VERTICAL) { *a = rect(in.x, in.y, in.w, p, in.scale); *b = rect(in.x, in.y + q, in.w, in.h - q, in.scale); } else { *a = rect(in.x, in.y, p, in.h, in.scale); *b = rect(in.x + q, in.y, in.w - q, in.h, in.scale); } } /* * Shrink a rectangle to leave a border on all sides */ static inline struct rect shrink(const struct rect in, int distance) { struct rect out; out = in; distance *= in.scale; if (distance * 2 < in.w) { out.x = in.x + distance; out.w = in.w - distance * 2; } if (distance * 2 < in.h) { out.y = in.y + distance; out.h = in.h - distance * 2; } return out; } /* * Calculate the number of lines we can expect to fit if we * do splits of the given row height */ static inline unsigned int count_rows(struct rect in, unsigned int row_height) { unsigned int pixels; pixels = row_height * in.scale; return in.h / pixels; } #endif xwax-1.6-beta2/library.c000066400000000000000000000264761261512120600151650ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #define _GNU_SOURCE /* strdupa() */ #include #include #include /* basename() */ #include /* isfinite() */ #include #include #include #include #include "excrate.h" #include "external.h" #define CRATE_ALL "All records" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) void listing_init(struct listing *l) { index_init(&l->by_artist); index_init(&l->by_bpm); index_init(&l->by_order); event_init(&l->addition); } void listing_clear(struct listing *l) { index_clear(&l->by_artist); index_clear(&l->by_bpm); index_clear(&l->by_order); event_clear(&l->addition); } /* * Base initialiser for a crate, shared by the other init functions * * Note the deep copy of the crate name * * Return: 0 on success or -1 on memory allocation failure */ static int crate_init(struct crate *c, const char *name) { c->name = strdup(name); if (c->name == NULL) { perror("strdup"); return -1; } c->is_busy = false; event_init(&c->activity); event_init(&c->refresh); event_init(&c->addition); return 0; } /* * Propagate an addition event on the listing upwards -- as an * addition event on this crate */ static void propagate_addition(struct observer *o, void *x) { struct crate *c = container_of(o, struct crate, on_addition); fire(&c->addition, x); } /* * Propagate notification that the scan has finished */ static void propagate_completion(struct observer *o, void *x) { struct crate *c = container_of(o, struct crate, on_completion); c->is_busy = false; fire(&c->activity, NULL); } /* * Initialise the crate which shows the entire library content * * Return: 0 on success, -1 on memory allocation failure */ static int crate_init_all(struct library *l, struct crate *c, const char *name) { if (crate_init(c, name) == -1) return -1; c->is_fixed = true; c->listing = &l->storage; watch(&c->on_addition, &c->listing->addition, propagate_addition); c->excrate = NULL; return 0; } /* * Wire in the excrate to this crate, including events */ static void hook_up_excrate(struct crate *c, struct excrate *e) { if (!c->is_busy) { c->is_busy = true; fire(&c->activity, NULL); } c->excrate = e; c->listing = &e->listing; fire(&c->refresh, NULL); watch(&c->on_addition, &c->listing->addition, propagate_addition); watch(&c->on_completion, &e->completion, propagate_completion); } /* * Initialise a crate which has a fixed scan as its source * * Not all crates have a source (eg. 'all' crate.) This is also * convenient as in future there may be other sources such as virtual * crates or external searches. * * Return: 0 on success or -1 on error */ static int crate_init_scan(struct library *l, struct crate *c, const char *name, const char *scan, const char *path) { struct excrate *e; if (crate_init(c, name) == -1) return -1; c->is_fixed = false; c->scan = scan; c->path = path; e = excrate_acquire_by_scan(scan, path, &l->storage); if (e == NULL) return -1; hook_up_excrate(c, e); return 0; } /* * Re-run a crate which has a scan as its source * * Return: 0 on success, -1 on error */ static int crate_rescan(struct crate *c, struct library *l) { struct excrate *e; assert(c->excrate != NULL); /* Replace the excrate in-place. Care needed to re-wire * everything back up again as before */ e = excrate_acquire_by_scan(c->scan, c->path, &l->storage); if (e == NULL) return -1; ignore(&c->on_completion); ignore(&c->on_addition); excrate_release(c->excrate); hook_up_excrate(c, e); return 0; } /* * Deallocate resources associated with this crate * * The crate does not 'own' the memory allocated by the records * in it, so we don't free them here. */ static void crate_clear(struct crate *c) { ignore(&c->on_addition); if (c->excrate != NULL) { ignore(&c->on_completion); excrate_release(c->excrate); } event_clear(&c->activity); event_clear(&c->refresh); event_clear(&c->addition); free(c->name); } /* * Comparison function for two crates */ static int crate_cmp(const struct crate *a, const struct crate *b) { if (a->is_fixed && !b->is_fixed) return -1; if (!a->is_fixed && b->is_fixed) return 1; return strcmp(a->name, b->name); } /* * Add a record into a crate and its various indexes * * Return: Pointer to existing entry, NULL if out of memory * Post: Record added to the crate */ struct record* listing_add(struct listing *l, struct record *r) { struct record *x; assert(r != NULL); /* Do all the memory reservation up-front as we can't * un-wind if it errors later */ if (index_reserve(&l->by_artist, 1) == -1) return NULL; if (index_reserve(&l->by_bpm, 1) == -1) return NULL; if (index_reserve(&l->by_order, 1) == -1) return NULL; x = index_insert(&l->by_artist, r, SORT_ARTIST); assert(x != NULL); if (x != r) return x; x = index_insert(&l->by_bpm, r, SORT_BPM); assert(x == r); index_add(&l->by_order, r); fire(&l->addition, r); return r; } /* * Comparison function, see qsort(3) */ static int qcompar(const void *a, const void *b) { return crate_cmp(*(struct crate**)a, *(struct crate**)b); } /* * Sort all crates into a defined order */ static void sort_crates(struct library *lib) { qsort(lib->crate, lib->crates, sizeof(struct crate*), qcompar); } /* * Add a crate to the list of all crates * * Return: 0 on success or -1 on memory allocation failure */ static int add_crate(struct library *lib, struct crate *c) { struct crate **cn; cn = realloc(lib->crate, sizeof(struct crate*) * (lib->crates + 1)); if (cn == NULL) { perror("realloc"); return -1; } lib->crate = cn; lib->crate[lib->crates++] = c; sort_crates(lib); return 0; } /* * Get a crate by the given name * * Beware: The match could match the fixed crates if the name is the * same. * * Return: pointer to crate, or NULL if no crate has the given name */ struct crate* get_crate(struct library *lib, const char *name) { int n; for (n = 0; n < lib->crates; n++) { if (strcmp(lib->crate[n]->name, name) == 0) return lib->crate[n]; } return NULL; } /* * Initialise the record library * * Return: 0 on success or -1 on memory allocation failure */ int library_init(struct library *li) { li->crate = NULL; li->crates = 0; listing_init(&li->storage); if (crate_init_all(li, &li->all, CRATE_ALL) == -1) return -1; if (add_crate(li, &li->all) == -1) { crate_clear(&li->all); return -1; } return 0; } /* * Free resources associated with a record */ static void record_clear(struct record *re) { free(re->pathname); } /* * Free resources associated with the music library */ void library_clear(struct library *li) { int n; /* This object is responsible for all the record pointers */ for (n = 0; n < li->storage.by_artist.entries; n++) { struct record *re; re = li->storage.by_artist.record[n]; record_clear(re); free(re); } /* Clear crates */ for (n = 1; n < li->crates; n++) { /* skip the 'all' crate */ struct crate *crate; crate = li->crate[n]; crate_clear(crate); free(crate); } free(li->crate); crate_clear(&li->all); listing_clear(&li->storage); } /* * Translate a string from the scan to our internal BPM value * * Return: internal BPM value, or INFINITY if string is malformed */ static double parse_bpm(const char *s) { char *endptr; double bpm; if (s[0] == '\0') /* empty string, valid for 'unknown BPM' */ return 0.0; errno = 0; bpm = strtod(s, &endptr); if (errno == ERANGE || *endptr != '\0' || !isfinite(bpm) || bpm <= 0.0) return INFINITY; return bpm; } /* * Split string into array of fields (destructive) * * Return: number of fields found * Post: array x is filled with n values */ static size_t split(char *s, char *x[], size_t len) { size_t n; for (n = 0; n < len; n++) { char *y; y = strchr(s, '\t'); if (y == NULL) { x[n] = s; return n + 1; } *y = '\0'; x[n] = s; s = y + 1; } return n; } /* * Convert a line from the scan script to a record structure in memory * * Return: pointer to alloc'd record, or NULL on error * Post: if successful, responsibility for pointer line is taken */ struct record* get_record(char *line) { int n; struct record *x; char *field[4]; x = malloc(sizeof *x); if (!x) { perror("malloc"); return NULL; } x->bpm = 0.0; n = split(line, field, ARRAY_SIZE(field)); switch (n) { case 4: x->bpm = parse_bpm(field[3]); if (!isfinite(x->bpm)) { fprintf(stderr, "%s: Ignoring malformed BPM '%s'\n", field[0], field[3]); x->bpm = 0.0; } /* fall-through */ case 3: x->pathname = field[0]; x->artist = field[1]; x->title = field[2]; break; case 2: case 1: default: fprintf(stderr, "Malformed record '%s'\n", line); goto bad; } return x; bad: free(x); return NULL; } /* * Scan a record library * * Launch the given scan script and pass it the path argument. * Parse the results into the crates. * * Return: 0 on success, -1 on fatal error (may leak) */ int library_import(struct library *li, const char *scan, const char *path) { char *cratename, *pathname; struct crate *crate; pathname = strdupa(path); cratename = basename(pathname); /* POSIX version, see basename(3) */ assert(cratename != NULL); crate = malloc(sizeof *crate); if (crate == NULL) { perror("malloc"); return -1; } if (crate_init_scan(li, crate, cratename, scan, path) == -1) goto fail; if (add_crate(li, crate) == -1) goto fail_crate; return 0; fail_crate: crate_clear(crate); fail: free(crate); return -1; } /* * Request a rescan on the given crate * * Only crates with an external source can be rescanned, others result * in a no-op. * * Return: -1 if scan is not possible, otherwise 0 on success */ int library_rescan(struct library *l, struct crate *c) { if (!c->excrate) return -1; else return crate_rescan(c, l); } xwax-1.6-beta2/library.h000066400000000000000000000037031261512120600151560ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef LIBRARY_H #define LIBRARY_H #include #include #include "index.h" #include "observer.h" /* A set of records, with several optimised indexes */ struct listing { struct index by_artist, by_bpm, by_order; struct event addition; }; /* A single crate of records */ struct crate { bool is_fixed, is_busy; char *name; struct listing *listing; struct observer on_addition, on_completion; struct event activity, /* at the crate level, not the listing */ refresh, addition; /* Optionally, the corresponding source */ const char *scan, *path; struct excrate *excrate; }; /* The complete music library, which consists of multiple crates */ struct library { struct listing storage; /* owns the record pointers */ struct crate all, **crate; size_t crates; }; void listing_init(struct listing *l); void listing_clear(struct listing *l); struct record* listing_add(struct listing *l, struct record *r); int library_init(struct library *li); void library_clear(struct library *li); struct record* get_record(char *line); int library_import(struct library *lib, const char *scan, const char *path); int library_rescan(struct library *l, struct crate *c); #endif xwax-1.6-beta2/list.h000066400000000000000000000056021261512120600144650ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Double-linked lists */ #ifndef LIST_H #define LIST_H #include #include /* offsetof() */ #define container_of(ptr, type, member) \ ((type*)((char*)ptr - offsetof(type, member))) struct list { struct list *prev, *next; }; #define LIST_INIT(list) { \ .next = &list, \ .prev = &list \ } static inline void list_init(struct list *list) { list->next = list; list->prev = list; } static inline void __list_add(struct list *new, struct list *prev, struct list *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /* * Insert a new list entry after the given head */ static inline void list_add(struct list *new, struct list *head) { __list_add(new, head, head->next); } /* * Insert a new list entry before the given head (ie. end of list) */ static inline void list_add_tail(struct list *new, struct list *head) { __list_add(new, head->prev, head); } static inline void list_del(struct list *entry) { struct list *next, *prev; next = entry->next; prev = entry->prev; next->prev = prev; prev->next = next; } static inline bool list_empty(const struct list *head) { return head->next == head; } #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /* * Iterate through each item in the list */ #define list_for_each(item, head, member) \ for (item = list_entry((head)->next, typeof(*item), member); \ &item->member != (head); \ item = list_entry(item->member.next, typeof(*item), member)) /* * Iterate through each item in the list, with a buffer to support * the safe removal of a list entry. * * pos: current entry (struct type*) * tmp: temporary storage (struct type*) * head: top of list (struct list*) * member: the name of the 'struct list' within 'struct type' */ #define list_for_each_safe(pos, tmp, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ tmp = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = tmp, tmp = list_entry(tmp->member.next, typeof(*tmp), member)) #endif xwax-1.6-beta2/listbox.c000066400000000000000000000100751261512120600151710ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include "listbox.h" void listbox_init(struct listbox *s) { s->lines = 0; s->entries = 0; s->offset = 0; s->selected = -1; } /* * Set the number of lines displayed on screen * * Zero is valid, to mean that the display is hidden. In all other * cases, the current selection is moved to within range. */ void listbox_set_lines(struct listbox *s, unsigned int lines) { s->lines = lines; if (s->selected >= s->offset + s->lines) s->selected = s->offset + s->lines - 1; if (s->offset + s->lines > s->entries) { s->offset = s->entries - s->lines; if (s->offset < 0) s->offset = 0; } } /* * Set the number of entries in the list which backs the scrolling * display. Bring the current selection within the bounds given. */ void listbox_set_entries(struct listbox *s, unsigned int entries) { s->entries = entries; if (s->selected >= s->entries) s->selected = s->entries - 1; if (s->offset + s->lines > s->entries) { s->offset = s->entries - s->lines; if (s->offset < 0) s->offset = 0; } /* If we went previously had zero entries, reset the selection */ if (s->selected < 0) s->selected = 0; } /* * Scroll the selection up by n lines. Move the window offset if * needed */ void listbox_up(struct listbox *s, unsigned int n) { s->selected -= n; if (s->selected < 0) s->selected = 0; /* Move the viewing offset up, if necessary */ if (s->selected < s->offset) { s->offset = s->selected + s->lines / 2 - s->lines + 1; if (s->offset < 0) s->offset = 0; } } void listbox_down(struct listbox *s, unsigned int n) { s->selected += n; if (s->selected >= s->entries) s->selected = s->entries - 1; /* Move the viewing offset down, if necessary */ if (s->selected >= s->offset + s->lines) { s->offset = s->selected - s->lines / 2; if (s->offset + s->lines > s->entries) s->offset = s->entries - s->lines; } } /* * Scroll to the first entry on the list */ void listbox_first(struct listbox *s) { s->selected = 0; s->offset = 0; } /* * Scroll to the final entry on the list */ void listbox_last(struct listbox *s) { s->selected = s->entries - 1; s->offset = s->selected - s->lines + 1; if (s->offset < 0) s->offset = 0; } /* * Scroll to an entry by index */ void listbox_to(struct listbox *s, unsigned int n) { int p; assert(s->selected != -1); assert(n < s->entries); /* Retain the on-screen position of the current selection */ p = s->selected - s->offset; s->selected = n; s->offset = s->selected - p; if (s->offset < 0) s->offset = 0; } /* * Return the index of the current selected list entry, or -1 if * no current selection */ int listbox_current(const struct listbox *s) { if (s->entries == 0) return -1; else return s->selected; } /* * Translate the given row on-screen (starting with row zero) * into a position in the list * * Return: -1 if the row is out of range, otherwise entry number */ int listbox_map(const struct listbox *s, int row) { int e; assert(row >= 0); if (row >= s->lines) return -1; e = s->offset + row; if (e >= s->entries) return -1; return e; } xwax-1.6-beta2/listbox.h000066400000000000000000000030161261512120600151730ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Generic UI listbox widget internals */ #ifndef LISTBOX_H #define LISTBOX_H /* Managed context of a scrolling window, of a number of fixed-height * lines, backed by a list of a known number of entries */ struct listbox { int entries, lines, offset, selected; }; void listbox_init(struct listbox *s); void listbox_set_lines(struct listbox *s, unsigned int lines); void listbox_set_entries(struct listbox *s, unsigned int entries); /* Scroll functions */ void listbox_up(struct listbox *s, unsigned int n); void listbox_down(struct listbox *s, unsigned int n); void listbox_first(struct listbox *s); void listbox_last(struct listbox *s); void listbox_to(struct listbox *s, unsigned int n); int listbox_current(const struct listbox *s); int listbox_map(const struct listbox *s, int row); #endif xwax-1.6-beta2/lut.c000066400000000000000000000052301261512120600143060ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include "lut.h" /* The number of bits to form the hash, which governs the overall size * of the hash lookup table, and hence the amount of chaining */ #define HASH_BITS 16 #define HASH(timecode) ((timecode) & ((1 << HASH_BITS) - 1)) #define NO_SLOT ((unsigned)-1) /* Initialise an empty hash lookup table to store the given number * of timecode -> position lookups */ int lut_init(struct lut *lut, int nslots) { int n, hashes; size_t bytes; hashes = 1 << HASH_BITS; bytes = sizeof(struct slot) * nslots + sizeof(slot_no_t) * hashes; fprintf(stderr, "Lookup table has %d hashes to %d slots" " (%d slots per hash, %zuKb)\n", hashes, nslots, nslots / hashes, bytes / 1024); lut->slot = malloc(sizeof(struct slot) * nslots); if (lut->slot == NULL) { perror("malloc"); return -1; } lut->table = malloc(sizeof(slot_no_t) * hashes); if (lut->table == NULL) { perror("malloc"); return -1; } for (n = 0; n < hashes; n++) lut->table[n] = NO_SLOT; lut->avail = 0; return 0; } void lut_clear(struct lut *lut) { free(lut->table); free(lut->slot); } void lut_push(struct lut *lut, unsigned int timecode) { unsigned int hash; slot_no_t slot_no; struct slot *slot; slot_no = lut->avail++; /* take the next available slot */ slot = &lut->slot[slot_no]; slot->timecode = timecode; hash = HASH(timecode); slot->next = lut->table[hash]; lut->table[hash] = slot_no; } unsigned int lut_lookup(struct lut *lut, unsigned int timecode) { unsigned int hash; slot_no_t slot_no; struct slot *slot; hash = HASH(timecode); slot_no = lut->table[hash]; while (slot_no != NO_SLOT) { slot = &lut->slot[slot_no]; if (slot->timecode == timecode) return slot_no; slot_no = slot->next; } return (unsigned)-1; } xwax-1.6-beta2/lut.h000066400000000000000000000023231261512120600143130ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef LUT_H #define LUT_H typedef unsigned int slot_no_t; struct slot { unsigned int timecode; slot_no_t next; /* next slot with the same hash */ }; struct lut { struct slot *slot; slot_no_t *table, /* hash -> slot lookup */ avail; /* next available slot */ }; int lut_init(struct lut *lut, int nslots); void lut_clear(struct lut *lut); void lut_push(struct lut *lut, unsigned int timecode); unsigned int lut_lookup(struct lut *lut, unsigned int timecode); #endif xwax-1.6-beta2/midi.c000066400000000000000000000046761261512120600144410ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include "midi.h" /* * Print error code from ALSA */ static void alsa_error(const char *msg, int r) { fprintf(stderr, "ALSA %s: %s\n", msg, snd_strerror(r)); } int midi_open(struct midi *m, const char *name) { int r; r = snd_rawmidi_open(&m->in, &m->out, name, SND_RAWMIDI_NONBLOCK); if (r < 0) { alsa_error("rawmidi_open", r); return -1; } return 0; } void midi_close(struct midi *m) { if (snd_rawmidi_close(m->in) < 0) abort(); if (snd_rawmidi_close(m->out) < 0) abort(); } /* * Get the poll descriptors for reading on this MIDI device * * Pre: len is maximum size of array pe * Return: -1 if len is not large enough, otherwise n on success * Post: on success, pe is filled with n entries */ ssize_t midi_pollfds(struct midi *m, struct pollfd *pe, size_t len) { int r; if (snd_rawmidi_poll_descriptors_count(m->in) > len) return -1; r = snd_rawmidi_poll_descriptors(m->in, pe, len); assert(r >= 0); return r; } /* * Read raw bytes of input * * Pre: len is maximum size of buffer * Return: -1 on error, otherwise n on success * Post: on success, buf is filled with n bytes of data */ ssize_t midi_read(struct midi *m, void *buf, size_t len) { int r; r = snd_rawmidi_read(m->in, buf, len); if (r < 0) { if (r == -EAGAIN) return 0; alsa_error("rawmidi_read", r); return -1; } return r; } ssize_t midi_write(struct midi *m, const void *buf, size_t len) { int r; r = snd_rawmidi_write(m->out, buf, len); if (r < 0) { if (r == -EAGAIN) return 0; alsa_error("rawmidi_write", r); return -1; } return r; } xwax-1.6-beta2/midi.h000066400000000000000000000022541261512120600144340ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef MIDI_H #define MIDI_H #include /* * State information for an open, non-blocking MIDI device */ struct midi { snd_rawmidi_t *in, *out; }; int midi_open(struct midi *m, const char *name); void midi_close(struct midi *m); ssize_t midi_pollfds(struct midi *m, struct pollfd *pe, size_t len); ssize_t midi_read(struct midi *m, void *buf, size_t len); ssize_t midi_write(struct midi *m, const void *buf, size_t len); #endif xwax-1.6-beta2/mkdist000077500000000000000000000011231261512120600145540ustar00rootroot00000000000000#!/bin/sh # # Make an xwax distribution archive from a Git repo, baking in the # version number # set -e VERSION="$1" if [ -z "$VERSION" ]; then echo "usage: $0 " >&2 exit 1 fi T=`mktemp -dt xwax-mkdist.XXXXXX` trap 'rm -r $T' 0 # Extract HEAD from Git D="xwax-$VERSION" git archive --prefix="$D/" -o "$T/$D.tar" HEAD # Bake in the version number mkdir "$T/$D" install -m 0644 -t "$T/$D" .version (cd "$T" && tar rf "$D.tar" "$D/.version") # Final compressed archive mkdir -p dist A="dist/xwax-$VERSION.tar.gz" gzip --best < "$T/$D.tar" > "$A" echo "Created $A" >&2 exit 0 xwax-1.6-beta2/mktimecode.c000066400000000000000000000070611261512120600156270ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Experimental program to generate a timecode signal for use * with xwax. */ #define _GNU_SOURCE /* sincos() */ #include #include #include #include #include #define BANNER "xwax timecode generator " \ "(C) Copyright 2015 Mark Hills " #define RATE 44100 #define RESOLUTION 4000 #define SEED 0x00001 #define TAPS 0x00002 #define BITS 22 #define MAX(x,y) ((x)>(y)?(x):(y)) typedef unsigned int bits_t; /* * Calculate the next bit in the LFSR sequence */ static inline bits_t lfsr(bits_t code, bits_t taps) { bits_t taken; int xrs; taken = code & taps; xrs = 0; while (taken != 0x0) { xrs += taken & 0x1; taken >>= 1; } return xrs & 0x1; } /* * LFSR in the forward direction */ static inline bits_t fwd(bits_t current, bits_t taps, unsigned int nbits) { bits_t l; /* New bits are added at the MSB; shift right by one */ l = lfsr(current, taps | 0x1); return (current >> 1) | (l << (nbits - 1)); } static inline double dither(void) { return (double)(rand() % 32768) / 32768.0 - 0.5; } int main(int argc, char *argv[]) { unsigned int s; bits_t b; int length; fputs(BANNER, stderr); fputc('\n', stderr); fprintf(stderr, "Generating %d-bit %dHz timecode sampled at %dKhz\n", BITS, RESOLUTION, RATE); b = SEED; length = 0; for (s = 0; ; s++) { double time, cycle, angle, modulate, x, y; signed short c[2]; time = (double)s / RATE; cycle = time * RESOLUTION; angle = cycle * M_PI * 2; sincos(angle, &x, &y); /* Modulate the waveform according to the bitstream */ modulate = 1.0 - (-cos(angle) + 1.0) * 0.25 * ((b & 0x1) == 0); x *= modulate; y *= modulate; /* Write out 16-bit PCM data */ c[0] = -y * SHRT_MAX * 0.5 + dither(); c[1] = x * SHRT_MAX * 0.5 + dither(); fwrite(c, sizeof(signed short), 2, stdout); /* Advance the bitstream if required */ if ((int)cycle > length) { assert((int)cycle - length == 1); b = fwd(b, TAPS, BITS); if (b == SEED) /* LFSR period reached */ break; length = cycle; } } fprintf(stderr, "Generated %0.1f seconds of timecode\n", (double)length / RESOLUTION); fprintf(stderr, "\n"); fprintf(stderr, " {\n"); fprintf(stderr, " .resolution = %d,\n", RESOLUTION); fprintf(stderr, " .bits = %d,\n", BITS); fprintf(stderr, " .seed = 0x%08x,\n", SEED); fprintf(stderr, " .taps = 0x%08x,\n", TAPS); fprintf(stderr, " .length = %d,\n", length); fprintf(stderr, " .safe = %d,\n", MAX(0, length - 4 * RESOLUTION)); fprintf(stderr, " }\n"); return 0; } xwax-1.6-beta2/mkversion000077500000000000000000000011011261512120600152720ustar00rootroot00000000000000#!/bin/sh # # Generate version information from Git for use in a Makefile # # Output the current version number so it can be assigned to a # variable, and touch a file which can be used as a dependency. # VF=.version if [ "$1" = "-r" ]; then # Refresh the version number, if we can VERSION=`git describe 2> /dev/null` if [ $? -eq 0 ]; then if ! echo $VERSION | diff - $VF > /dev/null 2>&1; then echo $VERSION > $VF fi fi else # Output the version number if [ ! -r $VF ]; then echo "$0: Version number is not known" >&2 exit 1 fi cat .version fi exit 0 xwax-1.6-beta2/mutex.h000066400000000000000000000032361261512120600146550ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Mutex locking for syncronisation between low priority threads */ #ifndef MUTEX_H #define MUTEX_H #include "realtime.h" typedef pthread_mutex_t mutex; static inline void mutex_init(mutex *m) { if (pthread_mutex_init(m, NULL) != 0) abort(); } /* * Pre: lock is not held */ static inline void mutex_clear(mutex *m) { int r; r = pthread_mutex_destroy(m); if (r != 0) { errno = r; perror("pthread_mutex_destroy"); abort(); } } /* * Take a mutex lock * * Pre: lock is initialised * Pre: lock is not held by this thread * Post: lock is held by this thread */ static inline void mutex_lock(mutex *m) { rt_not_allowed(); if (pthread_mutex_lock(m) != 0) abort(); } /* * Release a mutex lock * * Pre: lock is held by this thread * Post: lock is not held */ static inline void mutex_unlock(mutex *m) { if (pthread_mutex_unlock(m) != 0) abort(); } #endif xwax-1.6-beta2/observer.h000066400000000000000000000045111261512120600153370ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Implementation of "observer pattern" * * There are several cases in the code where we need to notify * when something changes (eg. to update a UI.) * * The use of simple function calls is problematic because it creates * cyclical dependencies in header files, and is not sufficiently * modular to allow the code to be re-used in a self-contained test. * * So, reluctantly introduce a slots and signals concept; xwax is * getting to be quite a lot of code and structure now. */ #ifndef OBSERVE_H #define OBSERVE_H #include #include "list.h" struct event { struct list observers; }; struct observer { struct list event; void (*func)(struct observer*, void*); }; #define EVENT_INIT(event) { \ .observers = LIST_INIT(event.observers) \ } static inline void event_init(struct event *s) { list_init(&s->observers); } static inline void event_clear(struct event *s) { assert(list_empty(&s->observers)); } /* * Pre: observer is not watching anything * Post: observer is watching the given event */ static inline void watch(struct observer *observer, struct event *sig, void (*func)(struct observer*, void*)) { list_add(&observer->event, &sig->observers); observer->func = func; } static inline void ignore(struct observer *observer) { list_del(&observer->event); } /* * Call the callback in all slots which are watching the given event */ static inline void fire(struct event *s, void *data) { struct observer *t; list_for_each(t, &s->observers, event) { assert(t->func != NULL); t->func(t, data); } } #endif xwax-1.6-beta2/oss.c000066400000000000000000000113611261512120600143100ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include "oss.h" #define FRAME 32 /* maximum read size */ struct oss { int fd; struct pollfd *pe; unsigned int rate; }; static void clear(struct device *dv) { int r; struct oss *oss = (struct oss*)dv->local; r = close(oss->fd); if (r == -1) { perror("close"); abort(); } free(dv->local); } /* Push audio into the device's buffer, for playback */ static int push(int fd, signed short *pcm, int samples) { int r, bytes; bytes = samples * DEVICE_CHANNELS * sizeof(short); r = write(fd, pcm, bytes); if (r == -1) { perror("write"); return -1; } if (r < bytes) fprintf(stderr, "Device output overrun.\n"); return r / DEVICE_CHANNELS / sizeof(short); } /* Pull audio from the device, for recording */ static int pull(int fd, signed short *pcm, int samples) { int r, bytes; bytes = samples * DEVICE_CHANNELS * sizeof(short); r = read(fd, pcm, bytes); if (r == -1) { perror("read"); return -1; } if (r < bytes) fprintf(stderr, "Device input underrun.\n"); return r / DEVICE_CHANNELS / sizeof(short); } static int handle(struct device *dv) { signed short pcm[FRAME * DEVICE_CHANNELS]; int samples; struct oss *oss = (struct oss*)dv->local; /* Check input buffer for recording */ if (oss->pe->revents & POLLIN) { samples = pull(oss->fd, pcm, FRAME); if (samples == -1) return -1; device_submit(dv, pcm, samples); } /* Check the output buffer for playback */ if (oss->pe->revents & POLLOUT) { device_collect(dv, pcm, FRAME); samples = push(oss->fd, pcm, FRAME); if (samples == -1) return -1; } return 0; } static ssize_t pollfds(struct device *dv, struct pollfd *pe, size_t z) { struct oss *oss = (struct oss*)dv->local; if (z < 1) return -1; pe->fd = oss->fd; pe->events = POLLIN | POLLOUT; oss->pe = pe; return 1; } static unsigned int sample_rate(struct device *dv) { struct oss *oss = (struct oss*)dv->local; return oss->rate; } static struct device_ops oss_ops = { .pollfds = pollfds, .handle = handle, .sample_rate = sample_rate, .clear = clear }; int oss_init(struct device *dv, const char *filename, unsigned int rate, unsigned short buffers, unsigned short fragment) { int p, fd; struct oss *oss; fd = open(filename, O_RDWR, 0); if (fd == -1) { perror("open"); return -1; } /* Ideally would set independent settings for the record and * playback buffers. Recording needs to buffer where necessary, as * lost audio results in zero-crossings which corrupt the * timecode. Playback buffer needs to be short to avoid high * latency */ p = (buffers << 16) | fragment; if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &p) == -1) { perror("SNDCTL_DSP_SETFRAGMENT"); goto fail; } p = AFMT_S16_LE; if (ioctl(fd, SNDCTL_DSP_SETFMT, &p) == -1) { perror("SNDCTL_DSP_SETFMT"); goto fail; } p = DEVICE_CHANNELS; if (ioctl(fd, SNDCTL_DSP_CHANNELS, &p) == -1) { perror("SNDCTL_DSP_CHANNELS"); goto fail; } p = rate; if (ioctl(fd, SNDCTL_DSP_SPEED, &p) == -1) { perror("SNDCTL_DSP_SPEED"); goto fail; } p = fcntl(fd, F_GETFL); if (p == -1) { perror("F_GETFL"); goto fail; } p |= O_NONBLOCK; if (fcntl(fd, F_SETFL, p) == -1) { perror("fcntl"); return -1; } oss = malloc(sizeof *oss); if (oss == NULL) { perror("malloc"); goto fail; } oss->fd = fd; oss->pe = NULL; oss->rate = rate; device_init(dv, &oss_ops); dv->local = oss; return 0; fail: close(fd); return -1; } xwax-1.6-beta2/oss.h000066400000000000000000000016451261512120600143210ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef OSS_H #define OSS_H #include "device.h" int oss_init(struct device *dv, const char *filename, unsigned int rate, unsigned short buffers, unsigned short fragment); #endif xwax-1.6-beta2/pitch.h000066400000000000000000000034211261512120600146160ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef PITCH_H #define PITCH_H /* Values for the filter concluded experimentally */ #define ALPHA (1.0/512) #define BETA (ALPHA/256) /* State of the pitch calculation filter */ struct pitch { double dt, x, v; }; /* Prepare the filter for observations every dt seconds */ static inline void pitch_init(struct pitch *p, double dt) { p->dt = dt; p->x = 0.0; p->v = 0.0; } /* Input an observation to the filter; in the last dt seconds the * position has moved by dx. * * Because the vinyl uses timestamps, the values for dx are discrete * rather than smooth. */ static inline void pitch_dt_observation(struct pitch *p, double dx) { double predicted_x, predicted_v, residual_x; predicted_x = p->x + p->v * p->dt; predicted_v = p->v; residual_x = dx - predicted_x; p->x = predicted_x + residual_x * ALPHA; p->v = predicted_v + residual_x * BETA / p->dt; p->x -= dx; /* relative to previous */ } /* Get the pitch after filtering */ static inline double pitch_current(struct pitch *p) { return p->v; } #endif xwax-1.6-beta2/player.c000066400000000000000000000267431261512120600150120ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include "device.h" #include "player.h" #include "track.h" #include "timecoder.h" /* Bend playback speed to compensate for the difference between our * current position and that given by the timecode */ #define SYNC_TIME (1.0 / 2) /* time taken to reach sync */ #define SYNC_PITCH 0.05 /* don't sync at low pitches */ #define SYNC_RC 0.05 /* filter to 1.0 when no timecodes available */ /* If the difference between our current position and that given by * the timecode is greater than this value, recover by jumping * straight to the position given by the timecode. */ #define SKIP_THRESHOLD (1.0 / 8) /* before dropping audio */ /* The base volume level. A value of 1.0 leaves no headroom to play * louder when the record is going faster than 1.0. */ #define VOLUME (7.0/8) #define SQ(x) ((x)*(x)) #define TARGET_UNKNOWN INFINITY /* * Return: the cubic interpolation of the sample at position 2 + mu */ static inline double cubic_interpolate(signed short y[4], double mu) { signed long a0, a1, a2, a3; double mu2; mu2 = SQ(mu); a0 = y[3] - y[2] - y[0] + y[1]; a1 = y[0] - y[1] - a0; a2 = y[2] - y[0]; a3 = y[1]; return (mu * mu2 * a0) + (mu2 * a1) + (mu * a2) + a3; } /* * Return: Random dither, between -0.5 and 0.5 */ double dither(void) { short bit; static short x = 0xbabe; /* Use a 16-bit maximal-length LFSR as our random number. * This is faster than rand() */ bit = (x ^ (x >> 2) ^ (x >> 3) ^ (x >> 5)) & 1; x = x >> 1 | (bit << 15); return (double)x / 65536 - 0.5; /* not quite whole range */ } /* * Build a block of PCM audio, resampled from the track * * This is just a basic resampler which has a small amount of aliasing * where pitch > 1.0. * * Return: number of seconds advanced in the source audio track * Post: buffer at pcm is filled with the given number of samples */ static double build_pcm(signed short *pcm, unsigned samples, double sample_dt, struct track *tr, double position, double pitch, double start_vol, double end_vol) { int s; double sample, step, vol, gradient; sample = position * tr->rate; step = sample_dt * pitch * tr->rate; vol = start_vol; gradient = (end_vol - start_vol) / samples; for (s = 0; s < samples; s++) { int c, sa, q; double f; signed short i[PLAYER_CHANNELS][4]; /* 4-sample window for interpolation */ sa = (int)sample; if (sample < 0.0) sa--; f = sample - sa; sa--; for (q = 0; q < 4; q++, sa++) { if (sa < 0 || sa >= tr->length) { for (c = 0; c < PLAYER_CHANNELS; c++) i[c][q] = 0; } else { signed short *ts; int c; ts = track_get_sample(tr, sa); for (c = 0; c < PLAYER_CHANNELS; c++) i[c][q] = ts[c]; } } for (c = 0; c < PLAYER_CHANNELS; c++) { double v; v = vol * cubic_interpolate(i[c], f) + dither(); if (v > SHRT_MAX) { *pcm++ = SHRT_MAX; } else if (v < SHRT_MIN) { *pcm++ = SHRT_MIN; } else { *pcm++ = (signed short)v; } } sample += step; vol += gradient; } return sample_dt * pitch * samples; } /* * Equivalent to build_pcm, but for use when the track is * not available * * Return: number of seconds advanced in the audio track * Post: buffer at pcm is filled with silence */ static double build_silence(signed short *pcm, unsigned samples, double sample_dt, double pitch) { memset(pcm, '\0', sizeof(*pcm) * PLAYER_CHANNELS * samples); return sample_dt * pitch * samples; } /* * Change the timecoder used by this playback */ void player_set_timecoder(struct player *pl, struct timecoder *tc) { assert(tc != NULL); pl->timecoder = tc; pl->recalibrate = true; pl->timecode_control = true; } /* * Post: player is initialised */ void player_init(struct player *pl, unsigned int sample_rate, struct track *track, struct timecoder *tc) { assert(track != NULL); assert(sample_rate != 0); spin_init(&pl->lock); pl->sample_dt = 1.0 / sample_rate; pl->track = track; player_set_timecoder(pl, tc); pl->position = 0.0; pl->offset = 0.0; pl->target_position = TARGET_UNKNOWN; pl->last_difference = 0.0; pl->pitch = 0.0; pl->sync_pitch = 1.0; pl->volume = 0.0; } /* * Pre: player is initialised * Post: no resources are allocated by the player */ void player_clear(struct player *pl) { spin_clear(&pl->lock); track_release(pl->track); } /* * Enable or disable timecode control */ void player_set_timecode_control(struct player *pl, bool on) { if (on && !pl->timecode_control) pl->recalibrate = true; pl->timecode_control = on; } /* * Toggle timecode control * * Return: the new state of timecode control */ bool player_toggle_timecode_control(struct player *pl) { pl->timecode_control = !pl->timecode_control; if (pl->timecode_control) pl->recalibrate = true; return pl->timecode_control; } void player_set_internal_playback(struct player *pl) { pl->timecode_control = false; pl->pitch = 1.0; } double player_get_position(struct player *pl) { return pl->position; } double player_get_elapsed(struct player *pl) { return pl->position - pl->offset; } double player_get_remain(struct player *pl) { return (double)pl->track->length / pl->track->rate + pl->offset - pl->position; } bool player_is_active(const struct player *pl) { return (fabs(pl->pitch) > 0.01); } /* * Cue to the zero position of the track */ void player_recue(struct player *pl) { pl->offset = pl->position; } /* * Set the track used for the playback * * Pre: caller holds reference on track * Post: caller does not hold reference on track */ void player_set_track(struct player *pl, struct track *track) { struct track *x; assert(track != NULL); assert(track->refcount > 0); spin_lock(&pl->lock); /* Synchronise with the playback thread */ x = pl->track; pl->track = track; spin_unlock(&pl->lock); track_release(x); /* discard the old track */ } /* * Set the playback of one player to match another, used * for "instant doubles" and beat juggling */ void player_clone(struct player *pl, const struct player *from) { double elapsed; struct track *x, *t; elapsed = from->position - from->offset; pl->offset = pl->position - elapsed; t = from->track; track_acquire(t); spin_lock(&pl->lock); x = pl->track; pl->track = t; spin_unlock(&pl->lock); track_release(x); } /* * Synchronise to the position and speed given by the timecoder * * Return: 0 on success or -1 if the timecoder is not currently valid */ static int sync_to_timecode(struct player *pl) { double when, tcpos; signed int timecode; timecode = timecoder_get_position(pl->timecoder, &when); /* Instruct the caller to disconnect the timecoder if the needle * is outside the 'safe' zone of the record */ if (timecode != -1 && timecode > timecoder_get_safe(pl->timecoder)) return -1; /* If the timecoder is alive, use the pitch from the sine wave */ pl->pitch = timecoder_get_pitch(pl->timecoder); /* If we can read an absolute time from the timecode, then use it */ if (timecode == -1) { pl->target_position = TARGET_UNKNOWN; } else { tcpos = (double)timecode / timecoder_get_resolution(pl->timecoder); pl->target_position = tcpos + pl->pitch * when; } return 0; } /* * Synchronise to the position given by the timecoder without * affecting the audio playback position */ static void calibrate_to_timecode_position(struct player *pl) { assert(pl->target_position != TARGET_UNKNOWN); pl->offset += pl->target_position - pl->position; pl->position = pl->target_position; } void retarget(struct player *pl) { double diff; if (pl->recalibrate) { calibrate_to_timecode_position(pl); pl->recalibrate = false; } /* Calculate the pitch compensation required to get us back on * track with the absolute timecode position */ diff = pl->position - pl->target_position; pl->last_difference = diff; /* to print in user interface */ if (fabs(diff) > SKIP_THRESHOLD) { /* Jump the track to the time */ pl->position = pl->target_position; fprintf(stderr, "Seek to new position %.2lfs.\n", pl->position); } else if (fabs(pl->pitch) > SYNC_PITCH) { /* Re-calculate the drift between the timecoder pitch from * the sine wave and the timecode values */ pl->sync_pitch = pl->pitch / (diff / SYNC_TIME + pl->pitch); } } /* * Seek to the given position */ void player_seek_to(struct player *pl, double seconds) { pl->offset = pl->position - seconds; } /* * Get a block of PCM audio data to send to the soundcard * * This is the main function which retrieves audio for playback. The * clock of playback is decoupled from the clock of the timecode * signal. * * Post: buffer at pcm is filled with the given number of samples */ void player_collect(struct player *pl, signed short *pcm, unsigned samples) { double r, pitch, dt, target_volume; dt = pl->sample_dt * samples; if (pl->timecode_control) { if (sync_to_timecode(pl) == -1) pl->timecode_control = false; } if (pl->target_position != TARGET_UNKNOWN) { /* Bias the pitch towards a known target, and acknowledge that * we did so */ retarget(pl); pl->target_position = TARGET_UNKNOWN; } else { /* Without a known target, tend sync_pitch towards 1.0, to * avoid using outlier values from scratching for too long */ pl->sync_pitch += dt / (SYNC_RC + dt) * (1.0 - pl->sync_pitch); } target_volume = fabs(pl->pitch) * VOLUME; if (target_volume > 1.0) target_volume = 1.0; /* Sync pitch is applied post-filtering */ pitch = pl->pitch * pl->sync_pitch; /* We must return audio immediately to stay realtime. A spin * lock protects us from changes to the audio source */ if (!spin_try_lock(&pl->lock)) { r = build_silence(pcm, samples, pl->sample_dt, pitch); } else { r = build_pcm(pcm, samples, pl->sample_dt, pl->track, pl->position - pl->offset, pitch, pl->volume, target_volume); spin_unlock(&pl->lock); } pl->position += r; pl->volume = target_volume; } xwax-1.6-beta2/player.h000066400000000000000000000045131261512120600150060ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef PLAYER_H #define PLAYER_H #include #include "spin.h" #include "track.h" #define PLAYER_CHANNELS 2 struct player { double sample_dt; spin lock; struct track *track; /* Current playback parameters */ double position, /* seconds */ target_position, /* seconds, or TARGET_UNKNOWN */ offset, /* track start point in timecode */ last_difference, /* last known position minus target_position */ pitch, /* from timecoder */ sync_pitch, /* pitch required to sync to timecode signal */ volume; /* Timecode control */ struct timecoder *timecoder; bool timecode_control, recalibrate; /* re-sync offset at next opportunity */ }; void player_init(struct player *pl, unsigned int sample_rate, struct track *track, struct timecoder *timecoder); void player_clear(struct player *pl); void player_set_timecoder(struct player *pl, struct timecoder *tc); void player_set_timecode_control(struct player *pl, bool on); bool player_toggle_timecode_control(struct player *pl); void player_set_internal_playback(struct player *pl); void player_set_track(struct player *pl, struct track *track); void player_clone(struct player *pl, const struct player *from); double player_get_position(struct player *pl); double player_get_elapsed(struct player *pl); double player_get_remain(struct player *pl); bool player_is_active(const struct player *pl); void player_seek_to(struct player *pl, double seconds); void player_recue(struct player *pl); void player_collect(struct player *pl, signed short *pcm, unsigned samples); #endif xwax-1.6-beta2/realtime.c000066400000000000000000000135361261512120600153140ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include "controller.h" #include "debug.h" #include "device.h" #include "realtime.h" #include "thread.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) /* * Raise the priority of the current thread * * Return: -1 if priority could not be satisfactorily raised, otherwise 0 */ static int raise_priority(int priority) { int max_pri; struct sched_param sp; max_pri = sched_get_priority_max(SCHED_FIFO); if (priority > max_pri) { fprintf(stderr, "Invalid scheduling priority (maximum %d).\n", max_pri); return -1; } if (sched_getparam(0, &sp)) { perror("sched_getparam"); return -1; } sp.sched_priority = priority; if (sched_setscheduler(0, SCHED_FIFO, &sp)) { perror("sched_setscheduler"); fprintf(stderr, "Failed to get realtime priorities\n"); return -1; } return 0; } /* * The realtime thread */ static void rt_main(struct rt *rt) { int r; size_t n; debug("%p", rt); thread_to_realtime(); if (rt->priority != 0) { if (raise_priority(rt->priority) == -1) rt->finished = true; } if (sem_post(&rt->sem) == -1) abort(); /* under our control; see sem_post(3) */ while (!rt->finished) { r = poll(rt->pt, rt->npt, -1); if (r == -1) { if (errno == EINTR) { continue; } else { perror("poll"); abort(); } } for (n = 0; n < rt->nctl; n++) controller_handle(rt->ctl[n]); for (n = 0; n < rt->ndv; n++) device_handle(rt->dv[n]); } } static void* launch(void *p) { rt_main(p); return NULL; } /* * Initialise state of realtime handler */ void rt_init(struct rt *rt) { debug("%p", rt); rt->finished = false; rt->ndv = 0; rt->nctl = 0; rt->npt = 0; } /* * Clear resources associated with the realtime handler */ void rt_clear(struct rt *rt) { } /* * Add a device to this realtime handler * * Return: -1 if the device could not be added, otherwise 0 * Post: if 0 is returned the device is added */ int rt_add_device(struct rt *rt, struct device *dv) { ssize_t z; debug("%p adding device %p", rt, dv); if (rt->ndv == ARRAY_SIZE(rt->dv)) { fprintf(stderr, "Too many audio devices\n"); return -1; } /* The requested poll events never change, so populate the poll * entry table before entering the realtime thread */ z = device_pollfds(dv, &rt->pt[rt->npt], sizeof(rt->pt) - rt->npt); if (z == -1) { fprintf(stderr, "Device failed to return file descriptors.\n"); return -1; } rt->npt += z; rt->dv[rt->ndv] = dv; rt->ndv++; return 0; } /* * Add a controller to the realtime handler * * Return: -1 if the device could not be added, otherwise 0 */ int rt_add_controller(struct rt *rt, struct controller *c) { ssize_t z; debug("%p adding controller %p", rt, c); if (rt->nctl == ARRAY_SIZE(rt->ctl)) { fprintf(stderr, "Too many controllers\n"); return -1; } /* Similar to adding a PCM device */ z = controller_pollfds(c, &rt->pt[rt->npt], sizeof(rt->pt) - rt->npt); if (z == -1) { fprintf(stderr, "Controller failed to return file descriptors.\n"); return -1; } rt->npt += z; rt->ctl[rt->nctl++] = c; return 0; } /* * Start realtime handling of the given devices * * This forks the realtime thread if it is required (eg. ALSA). Some * devices (eg. JACK) start their own thread. * * Return: -1 on error, otherwise 0 */ int rt_start(struct rt *rt, int priority) { size_t n; assert(priority >= 0); rt->priority = priority; /* If there are any devices which returned file descriptors for * poll() then launch the realtime thread to handle them */ if (rt->npt > 0) { int r; fprintf(stderr, "Launching realtime thread to handle devices...\n"); if (sem_init(&rt->sem, 0, 0) == -1) { perror("sem_init"); return -1; } r = pthread_create(&rt->ph, NULL, launch, (void*)rt); if (r != 0) { errno = r; perror("pthread_create"); if (sem_destroy(&rt->sem) == -1) abort(); return -1; } /* Wait for the realtime thread to declare it is initialised */ if (sem_wait(&rt->sem) == -1) abort(); if (sem_destroy(&rt->sem) == -1) abort(); if (rt->finished) { if (pthread_join(rt->ph, NULL) != 0) abort(); return -1; } } for (n = 0; n < rt->ndv; n++) device_start(rt->dv[n]); return 0; } /* * Stop realtime handling, which was previously started by rt_start() */ void rt_stop(struct rt *rt) { size_t n; rt->finished = true; /* Stop audio rolling on devices */ for (n = 0; n < rt->ndv; n++) device_stop(rt->dv[n]); if (rt->npt > 0) { if (pthread_join(rt->ph, NULL) != 0) abort(); } } xwax-1.6-beta2/realtime.h000066400000000000000000000027001261512120600153100ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef REALTIME_H #define REALTIME_H #include #include #include #include /* * State data for the realtime thread, maintained during rt_start and * rt_stop */ struct rt { pthread_t ph; sem_t sem; bool finished; int priority; size_t ndv; struct device *dv[3]; size_t nctl; struct controller *ctl[3]; size_t npt; struct pollfd pt[32]; }; int rt_global_init(); void rt_not_allowed(); void rt_init(struct rt *rt); void rt_clear(struct rt *rt); int rt_add_device(struct rt *rt, struct device *dv); int rt_add_controller(struct rt *rt, struct controller *c); int rt_start(struct rt *rt, int priority); void rt_stop(struct rt *rt); #endif xwax-1.6-beta2/rig.c000066400000000000000000000120241261512120600142620ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include "list.h" #include "mutex.h" #include "realtime.h" #include "rig.h" #define EVENT_WAKE 0 #define EVENT_QUIT 1 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) static int event[2]; /* pipe to wake up service thread */ static struct list tracks = LIST_INIT(tracks), excrates = LIST_INIT(excrates); mutex lock; int rig_init() { /* Create a pipe which will be used to wake us from other threads */ if (pipe(event) == -1) { perror("pipe"); return -1; } if (fcntl(event[0], F_SETFL, O_NONBLOCK) == -1) { perror("fcntl"); if (close(event[1]) == -1) abort(); if (close(event[0]) == -1) abort(); return -1; } mutex_init(&lock); return 0; } void rig_clear() { mutex_clear(&lock); if (close(event[0]) == -1) abort(); if (close(event[1]) == -1) abort(); } /* * Main thread which handles input and output * * The rig is the main thread of execution. It is responsible for all * non-priority event driven operations (eg. everything but audio). * * The SDL interface requires its own event loop, and so whilst the * rig is technically responsible for managing it, it does very little * on its behalf. In future if there are other interfaces or * controllers (which expected to use more traditional file-descriptor * I/O), the rig will also be responsible for them. */ int rig_main() { struct pollfd pt[4]; const struct pollfd *px = pt + ARRAY_SIZE(pt); /* Monitor event pipe from external threads */ pt[0].fd = event[0]; pt[0].revents = 0; pt[0].events = POLLIN; mutex_lock(&lock); for (;;) { /* exit via EVENT_QUIT */ int r; struct pollfd *pe; struct track *track, *xtrack; struct excrate *excrate, *xexcrate; pe = &pt[1]; /* Do our best if we run out of poll entries */ list_for_each(track, &tracks, rig) { if (pe == px) break; track_pollfd(track, pe); pe++; } list_for_each(excrate, &excrates, rig) { if (pe == px) break; excrate_pollfd(excrate, pe); pe++; } mutex_unlock(&lock); r = poll(pt, pe - pt, -1); if (r == -1) { if (errno == EINTR) { mutex_lock(&lock); continue; } else { perror("poll"); return -1; } } /* Process all events on the event pipe */ if (pt[0].revents != 0) { for (;;) { char e; size_t z; z = read(event[0], &e, 1); if (z == -1) { if (errno == EAGAIN) { break; } else { perror("read"); return -1; } } switch (e) { case EVENT_WAKE: break; case EVENT_QUIT: goto finish; default: abort(); } } } mutex_lock(&lock); list_for_each_safe(track, xtrack, &tracks, rig) track_handle(track); list_for_each_safe(excrate, xexcrate, &excrates, rig) excrate_handle(excrate); } finish: return 0; } /* * Post a simple event into the rig event loop */ static int post_event(char e) { rt_not_allowed(); if (write(event[1], &e, 1) == -1) { perror("write"); return -1; } return 0; } /* * Ask the rig to exit from another thread or signal handler */ int rig_quit() { return post_event(EVENT_QUIT); } void rig_lock(void) { mutex_lock(&lock); } void rig_unlock(void) { mutex_unlock(&lock); } /* * Add a track to be handled until import has completed */ void rig_post_track(struct track *t) { track_acquire(t); list_add(&t->rig, &tracks); post_event(EVENT_WAKE); } void rig_post_excrate(struct excrate *e) { excrate_acquire(e); list_add(&e->rig, &excrates); post_event(EVENT_WAKE); } xwax-1.6-beta2/rig.h000066400000000000000000000017531261512120600142760ustar00rootroot00000000000000/* * Copyright (C) 2015 Mark Hills * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef RIG_H #define RIG_H #include "excrate.h" #include "track.h" int rig_init(); void rig_clear(); int rig_main(); int rig_quit(); void rig_lock(); void rig_unlock(); void rig_post_track(struct track *t); void rig_post_excrate(struct excrate *e); #endif xwax-1.6-beta2/scan000077500000000000000000000022671261512120600142170ustar00rootroot00000000000000#!/bin/bash # # Take a pathname as an argument and output a playlist to standard # output and errors to standard error. # # The output format is repeated sequences of: # # \t\t[\t<bpm>]\n # # If the tab (\t) or newline (\n) characters appear in a filename, # unexpected things will happen. set -eu -o pipefail # pipefail requires bash, not sh PATHNAME="$1" if [ -d "$PATHNAME" ]; then find -L "$PATHNAME" -type f -regextype posix-egrep \ -iregex '.*\.(ogg|oga|aac|cdaudio|mp3|flac|wav|aif|aiff|m4a|wma)' else cat "$PATHNAME" fi | # Parse artist and title information from matching filenames sed -n ' { # /[<ABnum>[.]] <artist> - <title>.ext s:/\([A-H]\?[A0-9]\?[0-9].\? \+\)\?\([^/]*\) \+- \+\([^/]*\)\.[A-Z0-9]*$:\0\t\2\t\3:pi t # /<artist> - <album>[/(Disc|Side) <name>]/[<ABnum>[.]] <title>.ext s:/\([^/]*\) \+- \+\([^/]*\)\(/\(disc\|side\) [0-9A-Z][^/]*\)\?/\([A-H]\?[A0-9]\?[0-9].\? \+\)\?\([^/]*\)\.[A-Z0-9]*$:\0\t\1\t\6:pi t # /[<ABnum>[.]] <name>.ext s:/\([A-H]\?[A0-9]\?[0-9].\? \+\)\?\([^/]*\)\.[A-Z0-9]*$:\0\t\t\2:pi }' | # Extract BPM metadata from title (eg. "Ghostbusters (115.6 BPM)") sed ' { # BPM s:\(.*\) *(\([0-9]\+\.\?[0-9]\+\) *BPM)$:\1\t\2: }' �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/selector.c���������������������������������������������������������������������������0000664�0000000�0000000�00000024212�12615121206�0015323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org>, * Yves Adler <yves.adler@googlemail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <assert.h> #include <stdlib.h> #include "selector.h" /* * Scroll to our target entry if it can be found, otherwise leave our * position unchanged */ static void retain_target(struct selector *sel) { size_t n; struct index *l; if (sel->target == NULL) return; l = sel->view_index; switch (sel->sort) { case SORT_ARTIST: case SORT_BPM: n = index_find(l, sel->target, sel->sort); break; case SORT_PLAYLIST: /* Linear search */ for (n = 0; n < l->entries; n++) { if (l->record[n] == sel->target) break; } break; default: abort(); } if (n < l->entries) listbox_to(&sel->records, n); } /* * Optimised version of retain_target where our position may * only have moved due to insertion of a single record */ static void hunt_target(struct selector *s) { struct index *l; size_t n; if (s->target == NULL) return; l = s->view_index; n = listbox_current(&s->records); if (n < l->entries && l->record[n + 1] == s->target) { struct listbox *x; /* Retain selection in the same position on screen * FIXME: listbox should provide this functionality */ x = &s->records; x->selected++; x->offset++; } } /* * Return: the currently selected crate */ static struct crate* current_crate(struct selector *sel) { int n; n = listbox_current(&sel->crates); assert(n != -1); return sel->library->crate[n]; } /* * Return the index which acts as the starting point before * string matching, based on the current crate */ static struct index* initial(struct selector *sel) { struct crate *c; c = current_crate(sel); assert(c != NULL); switch (sel->sort) { case SORT_ARTIST: return &c->listing->by_artist; case SORT_BPM: return &c->listing->by_bpm; case SORT_PLAYLIST: return &c->listing->by_order; default: abort(); } } static void notify(struct selector *s) { fire(&s->changed, NULL); } /* * When the crate has changed, update the current index to reflect * the crate and the search criteria */ static void do_content_change(struct selector *sel) { (void)index_match(initial(sel), sel->view_index, &sel->match); listbox_set_entries(&sel->records, sel->view_index->entries); retain_target(sel); notify(sel); } /* * Callback notification that the crate has changed at the top * level (eg. it's gone from busy to no longer busy) */ static void handle_activity(struct observer *o, void *x) { struct selector *s = container_of(o, struct selector, on_activity); notify(s); } /* * Callback notification that the crate has changed, including * removal of items */ static void handle_refresh(struct observer *o, void *x) { struct selector *s = container_of(o, struct selector, on_refresh); assert(x == NULL); do_content_change(s); notify(s); } /* * A new record has been added to the currently selected crate. Merge * this new addition into the current view, if applicable. */ static void merge_addition(struct observer *o, void *x) { struct selector *s = container_of(o, struct selector, on_addition); struct record *r = x; assert(r != NULL); if (!record_match(r, &s->match)) return; /* If we're out of memory then silently drop it */ if (index_reserve(s->view_index, 1) == -1) return; if (s->sort == SORT_PLAYLIST) index_add(s->view_index, r); else index_insert(s->view_index, r, s->sort); listbox_set_entries(&s->records, s->view_index->entries); /* If this addition is what we've been looking for, send the * cursor to it (not optimal, in some cases we know the position * from the insert above.) Otherwise track the target one step */ if (r == s->target) retain_target(s); else hunt_target(s); notify(s); } /* * Attach callbacks to the relevant crate * * So that we are notified when the crate content changes and * can update the GUI accordingly. */ static void watch_crate(struct selector *s, struct crate *c) { watch(&s->on_activity, &c->activity, handle_activity); watch(&s->on_refresh, &c->refresh, handle_refresh); watch(&s->on_addition, &c->addition, merge_addition); } void selector_init(struct selector *sel, struct library *lib) { struct crate *c; sel->library = lib; listbox_init(&sel->records); listbox_init(&sel->crates); assert(lib->crates > 0); listbox_set_entries(&sel->crates, lib->crates); sel->toggled = false; sel->sort = SORT_ARTIST; sel->search[0] = '\0'; sel->search_len = 0; sel->target = NULL; index_init(&sel->index_a); index_init(&sel->index_b); sel->view_index = &sel->index_a; sel->swap_index = &sel->index_b; c = current_crate(sel); watch_crate(sel, c); (void)index_copy(initial(sel), sel->view_index); listbox_set_entries(&sel->records, sel->view_index->entries); event_init(&sel->changed); } void selector_clear(struct selector *sel) { event_clear(&sel->changed); ignore(&sel->on_activity); ignore(&sel->on_refresh); ignore(&sel->on_addition); index_clear(&sel->index_a); index_clear(&sel->index_b); } /* * Set the number of display lines in use * * If the selector is invisible, it must continue to exist with 1 or * more lines to provide a current selected crate and/or record. * * Pre: lines is greater than zero */ void selector_set_lines(struct selector *sel, unsigned int lines) { assert(lines > 0); listbox_set_lines(&sel->crates, lines); listbox_set_lines(&sel->records, lines); } /* * Return: the currently selected record, or NULL if none selected */ struct record* selector_current(struct selector *sel) { int i; i = listbox_current(&sel->records); if (i == -1) { return NULL; } else { return sel->view_index->record[i]; } } /* * Make a note of the current selected record, and make it the * position we will try and retain if the crate is changed etc. */ static void set_target(struct selector *sel) { struct record *x; x = selector_current(sel); if (x != NULL) sel->target = x; } void selector_up(struct selector *sel) { listbox_up(&sel->records, 1); set_target(sel); notify(sel); } void selector_down(struct selector *sel) { listbox_down(&sel->records, 1); set_target(sel); notify(sel); } void selector_page_up(struct selector *sel) { listbox_up(&sel->records, sel->records.lines); set_target(sel); notify(sel); } void selector_page_down(struct selector *sel) { listbox_down(&sel->records, sel->records.lines); set_target(sel); notify(sel); } void selector_top(struct selector *sel) { listbox_first(&sel->records); set_target(sel); notify(sel); } void selector_bottom(struct selector *sel) { listbox_last(&sel->records); set_target(sel); notify(sel); } /* * Helper function when we have switched crate */ static void do_crate_change(struct selector *sel) { struct crate *c; c = current_crate(sel); ignore(&sel->on_activity); ignore(&sel->on_refresh); ignore(&sel->on_addition); watch_crate(sel, c); do_content_change(sel); } void selector_prev(struct selector *sel) { listbox_up(&sel->crates, 1); sel->toggled = false; do_crate_change(sel); } void selector_next(struct selector *sel) { listbox_down(&sel->crates, 1); sel->toggled = false; do_crate_change(sel); } /* * Toggle between the current crate and the 'all' crate */ void selector_toggle(struct selector *sel) { if (!sel->toggled) { sel->toggle_back = listbox_current(&sel->crates); listbox_first(&sel->crates); sel->toggled = true; } else { listbox_to(&sel->crates, sel->toggle_back); sel->toggled = false; } do_crate_change(sel); } /* * Toggle between sort order */ void selector_toggle_order(struct selector *sel) { set_target(sel); sel->sort = (sel->sort + 1) % SORT_END; do_content_change(sel); } /* * Request a re-scan on the currently selected crate */ void selector_rescan(struct selector *sel) { /* Ignore any errors at this point. A rescan must not leak * resources or cause a crash */ (void)library_rescan(sel->library, current_crate(sel)); } /* * Expand the search. Do not disrupt the running process on memory * allocation failure, leave the view index incomplete */ void selector_search_expand(struct selector *sel) { if (sel->search_len == 0) return; sel->search[--sel->search_len] = '\0'; match_compile(&sel->match, sel->search); do_content_change(sel); } /* * Refine the search. Do not distrupt the running process on memory * allocation failure, leave the view index incomplete */ void selector_search_refine(struct selector *sel, char key) { struct index *tmp; if (sel->search_len >= sizeof(sel->search) - 1) /* would overflow */ return; sel->search[sel->search_len] = key; sel->search[++sel->search_len] = '\0'; match_compile(&sel->match, sel->search); (void)index_match(sel->view_index, sel->swap_index, &sel->match); tmp = sel->view_index; sel->view_index = sel->swap_index; sel->swap_index = tmp; listbox_set_entries(&sel->records, sel->view_index->entries); set_target(sel); notify(sel); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/selector.h���������������������������������������������������������������������������0000664�0000000�0000000�00000004352�12615121206�0015333�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org>, * Yves Adler <yves.adler@googlemail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef SELECTOR_H #define SELECTOR_H #include <stdbool.h> #include "library.h" #include "listbox.h" #include "index.h" struct selector { struct library *library; struct index *view_index, /* base_index + search filter applied */ *swap_index, /* used to swap between a and b indexes */ index_a, index_b; struct listbox records, crates; bool toggled; int toggle_back, sort; struct record *target; struct observer on_activity, on_refresh, on_addition; size_t search_len; char search[256]; struct match match; /* the compiled search, kept in-sync */ struct event changed; }; void selector_init(struct selector *sel, struct library *lib); void selector_clear(struct selector *sel); void selector_set_lines(struct selector *sel, unsigned int lines); void selector_up(struct selector *sel); void selector_down(struct selector *sel); void selector_page_up(struct selector *sel); void selector_page_down(struct selector *sel); void selector_top(struct selector *sel); void selector_bottom(struct selector *sel); struct record* selector_current(struct selector *sel); void selector_prev(struct selector *sel); void selector_next(struct selector *sel); void selector_toggle(struct selector *sel); void selector_toggle_order(struct selector *sel); void selector_rescan(struct selector *sel); void selector_search_expand(struct selector *sel); void selector_search_refine(struct selector *sel, char key); #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/spin.h�������������������������������������������������������������������������������0000664�0000000�0000000�00000004111�12615121206�0014455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Spinlock routines for synchronising with the realtime thread */ #ifndef SPIN_H #define SPIN_H #include <errno.h> #include <pthread.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include "realtime.h" typedef pthread_spinlock_t spin; static inline void spin_init(spin *s) { if (pthread_spin_init(s, PTHREAD_PROCESS_PRIVATE) != 0) abort(); } static inline void spin_clear(spin *s) { if (pthread_spin_destroy(s) != 0) abort(); } /* * Take a spinlock * * Pre: lock is initialised * Pre: lock is not held by the current thread * Pre: current thread is not realtime * Post: lock is held by the current thread */ static inline void spin_lock(spin *s) { rt_not_allowed(); if (pthread_spin_lock(s) != 0) abort(); } /* * Try and take a spinlock * * Pre: lock is initialised * Pre: lock is not held by the current thread * Post: if true is returned, lock is held by the current thread */ static inline bool spin_try_lock(spin *s) { int r; r = pthread_spin_trylock(s); switch (r) { case 0: return true; case EBUSY: return false; default: abort(); } } /* * Release a spinlock * * Pre: lock is held by this thread * Post: lock is not held */ static inline void spin_unlock(spin *s) { if (pthread_spin_unlock(s) != 0) abort(); } #endif �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/status.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000003027�12615121206�0015027�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <stdio.h> #include "status.h" struct event status_changed = EVENT_INIT(status_changed); static const char *message = ""; static int level = 0; /* * Return: current status string */ const char* status(void) { return message; } int status_level(void) { return level; } /* * Set status to reference a static string * * Post: reference on s is held */ void status_set(int l, const char *s) { message = s; level = l; if (l >= STATUS_INFO) { fputs(s, stderr); fputc('\n', stderr); } fire(&status_changed, (void*)s); } /* * Set status to a formatted string */ void status_printf(int lvl, const char *t, ...) { static char buf[256]; va_list l; va_start(l, t); vsnprintf(buf, sizeof buf, t, l); va_end(l); status_set(lvl, buf); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/status.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000002213�12615121206�0015030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * Implement a global one-line status console */ #ifndef STATUS_H #define STATUS_H #include <stdarg.h> #include "observer.h" #define STATUS_VERBOSE 0 #define STATUS_INFO 1 #define STATUS_WARN 2 #define STATUS_ALERT 3 extern struct event status_changed; const char* status(void); int status_level(void); void status_set(int level, const char *s); void status_printf(int level, const char *s, ...); #endif �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�12615121206�0014500�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/cues.c�������������������������������������������������������������������������0000664�0000000�0000000�00000000462�12615121206�0015605�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <assert.h> #include <stdio.h> #include "cues.h" /* * Self-contained test of cue points */ int main(int argc, char *argv[]) { struct cues q; cues_reset(&q); assert(cues_get(&q, 0) == CUE_UNSET); cues_set(&q, 0, 100.0); assert(cues_get(&q, 0) == 100.0); return 0; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/external.c���������������������������������������������������������������������0000664�0000000�0000000�00000002136�12615121206�0016470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <assert.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/wait.h> #include "external.h" int main(int argc, char *argv[]) { int fd, status; unsigned cycles, strings; pid_t pid, p; struct pollfd pe; struct rb rb; pid = fork_pipe_nb(&fd, "/usr/bin/find", "find", NULL); if (pid == -1) return -1; pe.fd = fd; pe.revents = POLLIN; rb_reset(&rb); cycles = 0; strings = 0; for (;;) { char *s; ssize_t z; cycles++; if (poll(&pe, 1, -1) == -1) { perror("poll"); return -1; } z = get_line(fd, &rb, &s); if (z == -1) { if (errno == EAGAIN) continue; break; } if (z == 0) break; strings++; fprintf(stderr, "(%u, %u) %s\n", cycles, strings, s); free(s); } fprintf(stderr, "%u cycles, %u strings\n", cycles, strings); if (close(fd) == -1) abort(); p = wait(&status); assert(p == pid); return 0; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/library.c����������������������������������������������������������������������0000664�0000000�0000000�00000003241�12615121206�0016310�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2012 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <signal.h> #include <stdio.h> #include "library.h" #include "rig.h" #include "thread.h" void handle(int signum) { rig_quit(); } /* * Manual test of the record library. Mainly for use with * valgrind to check for memory bugs etc. */ int main(int argc, char *argv[]) { const char *scan; size_t n; struct library lib; if (argc < 3) { fprintf(stderr, "usage: %s <scan> <path> [...]\n", argv[0]); return -1; } scan = argv[1]; if (thread_global_init() == -1) return -1; if (rig_init() == -1) return -1; if (signal(SIGINT, handle) == SIG_ERR) { perror("signal"); return -1; } if (library_init(&lib) == -1) return -1; for (n = 2; n < argc; n++) { if (library_import(&lib, scan, argv[n]) == -1) return -1; } rig_main(); library_clear(&lib); rig_clear(); thread_global_clear(); return 0; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/midi.c�������������������������������������������������������������������������0000664�0000000�0000000�00000003527�12615121206�0015575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2012 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include "midi.h" #define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) int main(int argc, char *argv[]) { struct midi m; struct pollfd pt[8]; if (argc != 2) { fprintf(stderr, "usage: %s <device>\n", argv[0]); return -1; } if (midi_open(&m, argv[1]) == -1) return -1; for (;;) { ssize_t z; char buf[32]; z = midi_pollfds(&m, pt, ARRAY_SIZE(pt)); if (z == -1) return -1; fputs("poll...\n", stderr); if (poll(pt, z, -1) == -1) { perror("poll"); return -1; } fputs("... return\n", stderr); for (;;) { size_t n; ssize_t z; z = midi_read(&m, buf, sizeof buf); if (z == -1) return -1; if (z == 0) break; for (n = 0; n < z; n++) printf(" %02hhx", buf[n]); z = midi_write(&m, buf, z); printf(" =%d", z); if (z == -1) return -1; } printf("\n"); fflush(stdout); } midi_close(&m); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/observer.c���������������������������������������������������������������������0000664�0000000�0000000�00000000701�12615121206�0016471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <stdio.h> #include "observer.h" void callback(struct observer *t, void *x) { fprintf(stderr, "observer %p fired with argument %p\n", t, x); } int main(int argc, char *argv[]) { struct event s; struct observer t, u; event_init(&s); watch(&t, &s, callback); watch(&u, &s, callback); fire(&s, (void*)0xbeef); ignore(&u); fire(&s, (void*)0xcafe); ignore(&t); event_clear(&s); return 0; } ���������������������������������������������������������������xwax-1.6-beta2/tests/scan-bpm�����������������������������������������������������������������������0000775�0000000�0000000�00000000455�12615121206�0016132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # # xwax 'scan' script which generates a series to test the BPM listing; eg. # # xwax -s test-scan-bpm -l /dev/null # LOWEST=30.0 HIGHEST=320.0 STEP=0.1 seq "$LOWEST" "$STEP" "$HIGHEST" | awk -- ' BEGIN { OFS="\t" } { print "/dev/null", "BPM test", "Track at " $1 " BPM", $1 } ' �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/status.c�����������������������������������������������������������������������0000664�0000000�0000000�00000001045�12615121206�0016167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <stdio.h> #include "status.h" void callback(struct observer *o, void *x) { const char *s = x; printf("notify: %s -> %s\n", s, status()); } int main(int argc, char *argv[]) { struct observer o; printf("initial: %s\n", status()); status_set(STATUS_VERBOSE, "lemon"); printf("%s\n", status()); status_printf(STATUS_INFO, "%s", "carrot"); printf("%s\n", status()); watch(&o, &status_changed, callback); status_set(STATUS_ALERT, "apple"); status_set(STATUS_ALERT, "orange"); return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/timecoder.c��������������������������������������������������������������������0000664�0000000�0000000�00000003342�12615121206�0016621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2012 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <assert.h> #include <stdio.h> #include "timecoder.h" #define STEREO 2 #define RATE 96000 #define INTERVAL 4096 /* * Manual test of the timecoder's movement tracking. Read raw sample * information and write decoded pitch information. */ int main(int argc, char *argv[]) { unsigned int s; signed short sample[STEREO]; struct timecoder tc; struct timecode_def *def; def = timecoder_find_definition("serato_2a"); assert(def != NULL); timecoder_init(&tc, def, 1.0, RATE, false); s = 0; for(;;) { size_t z; z = fread(&sample, sizeof(short), STEREO, stdin); if (z != 2) break; timecoder_submit(&tc, sample, 1); if (s % (RATE / INTERVAL) == 0) { float pitch; pitch = timecoder_get_pitch(&tc); printf("%f\t%.12f\n", (float)s / RATE, pitch); } s++; } fflush(stdout); timecoder_clear(&tc); timecoder_free_lookup(); return 0; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/track.c������������������������������������������������������������������������0000664�0000000�0000000�00000002463�12615121206�0015755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2012 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <stdio.h> #include "rig.h" #include "thread.h" #include "track.h" /* * Self-contained manual test of a track import operation */ int main(int argc, char *argv[]) { struct track *track; if (argc != 3) { fprintf(stderr, "usage: %s <command> <path>\n", argv[0]); return -1; } if (thread_global_init() == -1) return -1; rig_init(); track = track_acquire_by_import(argv[1], argv[2]); if (track == NULL) return -1; rig_main(); track_release(track); rig_clear(); thread_global_clear(); return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/tests/ttf.c��������������������������������������������������������������������������0000664�0000000�0000000�00000002655�12615121206�0015451�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Illustrate clipping of characters */ #include <stdio.h> #include <SDL.h> #include <SDL_ttf.h> SDL_Surface *surface; TTF_Font *font; void draw(void) { const SDL_Color fg = { 255, 255, 255, 255 }, bg = { 0, 0, 0, 255 }; SDL_Surface *rendered; SDL_Rect dest, source; rendered = TTF_RenderText_Shaded(font, "Track at 101.0 BPM a0a0", fg, bg); source.x = 0; source.y = 0; source.w = rendered->w; source.h = rendered->h; dest.x = 0; dest.y = 0; SDL_BlitSurface(rendered, &source, surface, &dest); SDL_FreeSurface(rendered); SDL_UpdateRect(surface, 0, 0, source.w, source.h); } int main(int argc, char *argv[]) { if (argc != 2) { fputs("usage: test-ttf <font-file>\n", stderr); return 1; } if (SDL_Init(SDL_INIT_VIDEO) == -1) abort(); if (TTF_Init() == -1) abort(); font = TTF_OpenFont(argv[1], 15); if (font == NULL) abort(); #ifdef TTF_HINTING_NONE TTF_SetFontHinting(font, TTF_HINTING_NONE); #endif TTF_SetFontKerning(font, 1); surface = SDL_SetVideoMode(400, 200, 32, 0); if (surface == NULL) abort(); for (;;) { SDL_Event event; if (SDL_WaitEvent(&event) < 0) abort(); switch (event.type) { case SDL_QUIT: goto done; } draw(); } done: TTF_CloseFont(font); TTF_Quit(); SDL_Quit(); return 0; } �����������������������������������������������������������������������������������xwax-1.6-beta2/thread.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000003432�12615121206�0014753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <errno.h> #include <pthread.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include "thread.h" static pthread_key_t key; /* * Put in place checks for realtime and non-realtime threads * * Return: 0 on success, otherwise -1 */ int thread_global_init(void) { int r; r = pthread_key_create(&key, NULL); if (r != 0) { errno = r; perror("pthread_key_create"); return -1; } if (pthread_setspecific(key, (void*)false) != 0) abort(); return 0; } void thread_global_clear(void) { if (pthread_key_delete(key) != 0) abort(); } /* * Inform that this thread is a realtime thread, for assertions later */ void thread_to_realtime(void) { if (pthread_setspecific(key, (void*)true) != 0) abort(); } /* * Check for programmer error * * Pre: the current thread is non realtime */ void rt_not_allowed() { bool rt; rt = (bool)pthread_getspecific(key); if (rt) { fprintf(stderr, "Realtime thread called a blocking function\n"); abort(); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/thread.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000001662�12615121206�0014763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ /* * General helper functions for threads */ #ifndef THREAD_H #define THREAD_H int thread_global_init(void); void thread_global_clear(void); void thread_to_realtime(void); void rt_not_allowed(); #endif ������������������������������������������������������������������������������xwax-1.6-beta2/timecoder.c��������������������������������������������������������������������������0000664�0000000�0000000�00000034613�12615121206�0015464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <assert.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "debug.h" #include "timecoder.h" #define ZERO_THRESHOLD (128 << 16) #define ZERO_RC 0.001 /* time constant for zero/rumble filter */ #define REF_PEAKS_AVG 48 /* in wave cycles */ /* The number of correct bits which come in before the timecode is * declared valid. Set this too low, and risk the record skipping * around (often to blank areas of track) during scratching */ #define VALID_BITS 24 #define MONITOR_DECAY_EVERY 512 /* in samples */ #define SQ(x) ((x)*(x)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) /* Timecode definitions */ #define SWITCH_PHASE 0x1 /* tone phase difference of 270 (not 90) degrees */ #define SWITCH_PRIMARY 0x2 /* use left channel (not right) as primary */ #define SWITCH_POLARITY 0x4 /* read bit values in negative (not positive) */ static struct timecode_def timecodes[] = { { .name = "serato_2a", .desc = "Serato 2nd Ed., side A", .resolution = 1000, .bits = 20, .seed = 0x59017, .taps = 0x361e4, .length = 712000, .safe = 707000, }, { .name = "serato_2b", .desc = "Serato 2nd Ed., side B", .resolution = 1000, .bits = 20, .seed = 0x8f3c6, .taps = 0x4f0d8, /* reverse of side A */ .length = 922000, .safe = 917000, }, { .name = "serato_cd", .desc = "Serato CD", .resolution = 1000, .bits = 20, .seed = 0xd8b40, .taps = 0x34d54, .length = 950000, .safe = 940000, }, { .name = "traktor_a", .desc = "Traktor Scratch, side A", .resolution = 2000, .flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE, .bits = 23, .seed = 0x134503, .taps = 0x041040, .length = 1500000, .safe = 1480000, }, { .name = "traktor_b", .desc = "Traktor Scratch, side B", .resolution = 2000, .flags = SWITCH_PRIMARY | SWITCH_POLARITY | SWITCH_PHASE, .bits = 23, .seed = 0x32066c, .taps = 0x041040, /* same as side A */ .length = 2110000, .safe = 2090000, }, { .name = "mixvibes_v2", .desc = "MixVibes V2", .resolution = 1300, .flags = SWITCH_PHASE, .bits = 20, .seed = 0x22c90, .taps = 0x00008, .length = 950000, .safe = 923000, }, { .name = "mixvibes_7inch", .desc = "MixVibes 7\"", .resolution = 1300, .flags = SWITCH_PHASE, .bits = 20, .seed = 0x22c90, .taps = 0x00008, .length = 312000, .safe = 310000, }, }; /* * Calculate LFSR bit */ static inline bits_t lfsr(bits_t code, bits_t taps) { bits_t taken; int xrs; taken = code & taps; xrs = 0; while (taken != 0x0) { xrs += taken & 0x1; taken >>= 1; } return xrs & 0x1; } /* * Linear Feedback Shift Register in the forward direction. New values * are generated at the least-significant bit. */ static inline bits_t fwd(bits_t current, struct timecode_def *def) { bits_t l; /* New bits are added at the MSB; shift right by one */ l = lfsr(current, def->taps | 0x1); return (current >> 1) | (l << (def->bits - 1)); } /* * Linear Feedback Shift Register in the reverse direction */ static inline bits_t rev(bits_t current, struct timecode_def *def) { bits_t l, mask; /* New bits are added at the LSB; shift left one and mask */ mask = (1 << def->bits) - 1; l = lfsr(current, (def->taps >> 1) | (0x1 << (def->bits - 1))); return ((current << 1) & mask) | l; } /* * Where necessary, build the lookup table required for this timecode * * Return: -1 if not enough memory could be allocated, otherwise 0 */ static int build_lookup(struct timecode_def *def) { unsigned int n; bits_t current; if (def->lookup) return 0; fprintf(stderr, "Building LUT for %d bit %dHz timecode (%s)\n", def->bits, def->resolution, def->desc); if (lut_init(&def->lut, def->length) == -1) return -1; current = def->seed; for (n = 0; n < def->length; n++) { bits_t next; /* timecode must not wrap */ dassert(lut_lookup(&def->lut, current) == (unsigned)-1); lut_push(&def->lut, current); /* check symmetry of the lfsr functions */ next = fwd(current, def); dassert(rev(next, def) == current); current = next; } def->lookup = true; return 0; } /* * Find a timecode definition by name * * Return: pointer to timecode definition, or NULL if not found */ struct timecode_def* timecoder_find_definition(const char *name) { struct timecode_def *def, *end; def = &timecodes[0]; end = def + ARRAY_SIZE(timecodes); for (;;) { if (!strcmp(def->name, name)) break; def++; if (def == end) return NULL; } if (build_lookup(def) == -1) return NULL; return def; } /* * Free the timecoder lookup tables when they are no longer needed */ void timecoder_free_lookup(void) { struct timecode_def *def, *end; def = &timecodes[0]; end = def + ARRAY_SIZE(timecodes); while (def < end) { if (def->lookup) lut_clear(&def->lut); def++; } } /* * Initialise filter values for one channel */ static void init_channel(struct timecoder_channel *ch) { ch->positive = false; ch->zero = 0; } /* * Initialise a timecode decoder at the given reference speed * * Return: -1 if the timecoder could not be initialised, otherwise 0 */ void timecoder_init(struct timecoder *tc, struct timecode_def *def, double speed, unsigned int sample_rate, bool phono) { assert(def != NULL); /* A definition contains a lookup table which can be shared * across multiple timecoders */ assert(def->lookup); tc->def = def; tc->speed = speed; tc->dt = 1.0 / sample_rate; tc->zero_alpha = tc->dt / (ZERO_RC + tc->dt); tc->threshold = ZERO_THRESHOLD; if (phono) tc->threshold >>= 5; /* approx -36dB */ tc->forwards = 1; init_channel(&tc->primary); init_channel(&tc->secondary); pitch_init(&tc->pitch, tc->dt); tc->ref_level = INT_MAX; tc->bitstream = 0; tc->timecode = 0; tc->valid_counter = 0; tc->timecode_ticker = 0; tc->mon = NULL; } /* * Clear resources associated with a timecode decoder */ void timecoder_clear(struct timecoder *tc) { assert(tc->mon == NULL); } /* * Initialise a raster display of the incoming audio * * The monitor (otherwise known as 'scope' in the interface) is an x-y * display of the post-calibrated incoming audio. * * Return: -1 if not enough memory could be allocated, otherwise 0 */ int timecoder_monitor_init(struct timecoder *tc, int size) { assert(tc->mon == NULL); tc->mon_size = size; tc->mon = malloc(SQ(tc->mon_size)); if (tc->mon == NULL) { perror("malloc"); return -1; } memset(tc->mon, 0, SQ(tc->mon_size)); tc->mon_counter = 0; return 0; } /* * Clear the monitor on the given timecoder */ void timecoder_monitor_clear(struct timecoder *tc) { assert(tc->mon != NULL); free(tc->mon); tc->mon = NULL; } /* * Update channel information with axis-crossings */ static void detect_zero_crossing(struct timecoder_channel *ch, signed int v, double alpha, signed int threshold) { ch->crossing_ticker++; ch->swapped = false; if (v > ch->zero + threshold && !ch->positive) { ch->swapped = true; ch->positive = true; ch->crossing_ticker = 0; } else if (v < ch->zero - threshold && ch->positive) { ch->swapped = true; ch->positive = false; ch->crossing_ticker = 0; } ch->zero += alpha * (v - ch->zero); } /* * Plot the given sample value in the x-y monitor */ static void update_monitor(struct timecoder *tc, signed int x, signed int y) { int px, py, size, ref; if (!tc->mon) return; size = tc->mon_size; ref = tc->ref_level; /* Decay the pixels already in the montior */ if (++tc->mon_counter % MONITOR_DECAY_EVERY == 0) { int p; for (p = 0; p < SQ(size); p++) { if (tc->mon[p]) tc->mon[p] = tc->mon[p] * 7 / 8; } } assert(ref > 0); /* ref_level is half the prevision of signal level */ px = size / 2 + (long long)x * size / ref / 8; py = size / 2 + (long long)y * size / ref / 8; if (px < 0 || px >= size || py < 0 || py >= size) return; tc->mon[py * size + px] = 0xff; /* white */ } /* * Extract the bitstream from the sample value */ static void process_bitstream(struct timecoder *tc, signed int m) { bits_t b; b = m > tc->ref_level; /* Add it to the bitstream, and work out what we were expecting * (timecode). */ /* tc->bitstream is always in the order it is physically placed on * the vinyl, regardless of the direction. */ if (tc->forwards) { tc->timecode = fwd(tc->timecode, tc->def); tc->bitstream = (tc->bitstream >> 1) + (b << (tc->def->bits - 1)); } else { bits_t mask; mask = ((1 << tc->def->bits) - 1); tc->timecode = rev(tc->timecode, tc->def); tc->bitstream = ((tc->bitstream << 1) & mask) + b; } if (tc->timecode == tc->bitstream) tc->valid_counter++; else { tc->timecode = tc->bitstream; tc->valid_counter = 0; } /* Take note of the last time we read a valid timecode */ tc->timecode_ticker = 0; /* Adjust the reference level based on this new peak */ tc->ref_level -= tc->ref_level / REF_PEAKS_AVG; tc->ref_level += m / REF_PEAKS_AVG; debug("%+6d zero, %+6d (ref %+6d)\t= %d%c (%5d)", tc->primary.zero, m, tc->ref_level, b, tc->valid_counter == 0 ? 'x' : ' ', tc->valid_counter); } /* * Process a single sample from the incoming audio * * The two input signals (primary and secondary) are in the full range * of a signed int; ie. 32-bit signed. */ static void process_sample(struct timecoder *tc, signed int primary, signed int secondary) { detect_zero_crossing(&tc->primary, primary, tc->zero_alpha, tc->threshold); detect_zero_crossing(&tc->secondary, secondary, tc->zero_alpha, tc->threshold); /* If an axis has been crossed, use the direction of the crossing * to work out the direction of the vinyl */ if (tc->primary.swapped || tc->secondary.swapped) { bool forwards; if (tc->primary.swapped) { forwards = (tc->primary.positive != tc->secondary.positive); } else { forwards = (tc->primary.positive == tc->secondary.positive); } if (tc->def->flags & SWITCH_PHASE) forwards = !forwards; if (forwards != tc->forwards) { /* direction has changed */ tc->forwards = forwards; tc->valid_counter = 0; } } /* If any axis has been crossed, register movement using the pitch * counters */ if (!tc->primary.swapped && !tc->secondary.swapped) pitch_dt_observation(&tc->pitch, 0.0); else { double dx; dx = 1.0 / tc->def->resolution / 4; if (!tc->forwards) dx = -dx; pitch_dt_observation(&tc->pitch, dx); } /* If we have crossed the primary channel in the right polarity, * it's time to read off a timecode 0 or 1 value */ if (tc->secondary.swapped && tc->primary.positive == ((tc->def->flags & SWITCH_POLARITY) == 0)) { signed int m; /* scale to avoid clipping */ m = abs(primary / 2 - tc->primary.zero / 2); process_bitstream(tc, m); } tc->timecode_ticker++; } /* * Cycle to the next timecode definition which has a valid lookup * * Return: pointer to timecode definition */ static struct timecode_def* next_definition(struct timecode_def *def) { assert(def != NULL); do { def++; if (def > timecodes + ARRAY_SIZE(timecodes)) def = timecodes; } while (!def->lookup); return def; } /* * Change the timecode definition to the next available */ void timecoder_cycle_definition(struct timecoder *tc) { tc->def = next_definition(tc->def); tc->valid_counter = 0; tc->timecode_ticker = 0; } /* * Submit and decode a block of PCM audio data to the timecode decoder * * PCM data is in the full range of signed short; ie. 16-bit signed. */ void timecoder_submit(struct timecoder *tc, signed short *pcm, size_t npcm) { while (npcm--) { signed int left, right, primary, secondary; left = pcm[0] << 16; right = pcm[1] << 16; if (tc->def->flags & SWITCH_PRIMARY) { primary = left; secondary = right; } else { primary = right; secondary = left; } process_sample(tc, primary, secondary); update_monitor(tc, left, right); pcm += TIMECODER_CHANNELS; } } /* * Get the last-known position of the timecode * * If now data is available or if too few bits have been error * checked, then this counts as invalid. The last known position is * given along with the time elapsed since the position stamp was * read. * * Return: the known position of the timecode, or -1 if not known * Post: if when != NULL, *when is the elapsed time in seconds */ signed int timecoder_get_position(struct timecoder *tc, double *when) { signed int r; if (tc->valid_counter <= VALID_BITS) return -1; r = lut_lookup(&tc->def->lut, tc->bitstream); if (r == -1) return -1; if (when) *when = tc->timecode_ticker * tc->dt; return r; } ���������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/timecoder.h��������������������������������������������������������������������������0000664�0000000�0000000�00000007541�12615121206�0015471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef TIMECODER_H #define TIMECODER_H #include <stdbool.h> #include "lut.h" #include "pitch.h" #define TIMECODER_CHANNELS 2 typedef unsigned int bits_t; struct timecode_def { char *name, *desc; int bits, /* number of bits in string */ resolution, /* wave cycles per second */ flags; bits_t seed, /* LFSR value at timecode zero */ taps; /* central LFSR taps, excluding end taps */ unsigned int length, /* in cycles */ safe; /* last 'safe' timecode number (for auto disconnect) */ bool lookup; /* true if lut has been generated */ struct lut lut; }; struct timecoder_channel { bool positive, /* wave is in positive part of cycle */ swapped; /* wave recently swapped polarity */ signed int zero; unsigned int crossing_ticker; /* samples since we last crossed zero */ }; struct timecoder { struct timecode_def *def; double speed; /* Precomputed values */ double dt, zero_alpha; signed int threshold; /* Pitch information */ bool forwards; struct timecoder_channel primary, secondary; struct pitch pitch; /* Numerical timecode */ signed int ref_level; bits_t bitstream, /* actual bits from the record */ timecode; /* corrected timecode */ unsigned int valid_counter, /* number of successful error checks */ timecode_ticker; /* samples since valid timecode was read */ /* Feedback */ unsigned char *mon; /* x-y array */ int mon_size, mon_counter; }; struct timecode_def* timecoder_find_definition(const char *name); void timecoder_free_lookup(void); void timecoder_init(struct timecoder *tc, struct timecode_def *def, double speed, unsigned int sample_rate, bool phono); void timecoder_clear(struct timecoder *tc); int timecoder_monitor_init(struct timecoder *tc, int size); void timecoder_monitor_clear(struct timecoder *tc); void timecoder_cycle_definition(struct timecoder *tc); void timecoder_submit(struct timecoder *tc, signed short *pcm, size_t npcm); signed int timecoder_get_position(struct timecoder *tc, double *when); /* * The timecode definition currently in use by this decoder */ static inline struct timecode_def* timecoder_get_definition(struct timecoder *tc) { return tc->def; } /* * Return the pitch relative to reference playback speed */ static inline double timecoder_get_pitch(struct timecoder *tc) { return pitch_current(&tc->pitch) / tc->speed; } /* * The last 'safe' timecode value on the record. Beyond this value, we * probably want to ignore the timecode values, as we will hit the * label of the record. */ static inline unsigned int timecoder_get_safe(struct timecoder *tc) { return tc->def->safe; } /* * The resolution of the timecode. This is the number of bits per * second at reference playback speed */ static inline double timecoder_get_resolution(struct timecoder *tc) { return tc->def->resolution * tc->speed; } /* * The number of revolutions per second of the timecode vinyl, * used only for visual display */ static inline double timecoder_revs_per_sec(struct timecoder *tc) { return (33.0 + 1.0 / 3) * tc->speed / 60; } #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/track.c������������������������������������������������������������������������������0000664�0000000�0000000�00000023440�12615121206�0014611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <assert.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/mman.h> /* mlock() */ #include "debug.h" #include "external.h" #include "list.h" #include "realtime.h" #include "rig.h" #include "status.h" #include "track.h" #define RATE 44100 #define SAMPLE (sizeof(signed short) * TRACK_CHANNELS) /* bytes per sample */ #define TRACK_BLOCK_PCM_BYTES (TRACK_BLOCK_SAMPLES * SAMPLE) #define _STR(tok) #tok #define STR(tok) _STR(tok) static struct list tracks = LIST_INIT(tracks); static bool use_mlock = false; /* * An empty track is used rarely, and is easier than * continuous checks for NULL throughout the code */ static struct track empty = { .refcount = 1, .rate = RATE, .bytes = 0, .length = 0, .blocks = 0, .pid = 0 }; /* * Request that memory for tracks is locked into RAM as it is * allocated */ void track_use_mlock(void) { use_mlock = true; } /* * Allocate more memory * * Return: -1 if memory could not be allocated, otherwize 0 */ static int more_space(struct track *tr) { struct track_block *block; rt_not_allowed(); if (tr->blocks >= TRACK_MAX_BLOCKS) { fprintf(stderr, "Maximum track length reached.\n"); return -1; } block = malloc(sizeof(struct track_block)); if (block == NULL) { perror("malloc"); return -1; } if (use_mlock && mlock(block, sizeof(struct track_block)) == -1) { perror("mlock"); free(block); return -1; } /* No memory barrier is needed here, because nobody else tries to * access these blocks until tr->length is actually incremented */ tr->block[tr->blocks++] = block; debug("allocated new track block (%d blocks, %zu bytes)", tr->blocks, tr->blocks * TRACK_BLOCK_SAMPLES * SAMPLE); return 0; } /* * Get access to the PCM buffer for incoming audio * * Return: pointer to buffer * Post: len contains the length of the buffer, in bytes */ static void* access_pcm(struct track *tr, size_t *len) { unsigned int block; size_t fill; block = tr->bytes / TRACK_BLOCK_PCM_BYTES; if (block == tr->blocks) { if (more_space(tr) == -1) return NULL; } fill = tr->bytes % TRACK_BLOCK_PCM_BYTES; *len = TRACK_BLOCK_PCM_BYTES - fill; return (void*)tr->block[block]->pcm + fill; } /* * Notify that audio has been placed in the buffer * * The parameter is the number of stereo samples which have been * placed in the buffer. */ static void commit_pcm_samples(struct track *tr, unsigned int samples) { unsigned int fill, n; signed short *pcm; struct track_block *block; block = tr->block[tr->length / TRACK_BLOCK_SAMPLES]; fill = tr->length % TRACK_BLOCK_SAMPLES; pcm = block->pcm + TRACK_CHANNELS * fill; assert(samples <= TRACK_BLOCK_SAMPLES - fill); /* Meter the new audio */ for (n = samples; n > 0; n--) { unsigned short v; unsigned int w; v = abs(pcm[0]) + abs(pcm[1]); /* PPM-style fast meter approximation */ if (v > tr->ppm) tr->ppm += (v - tr->ppm) >> 3; else tr->ppm -= (tr->ppm - v) >> 9; block->ppm[fill / TRACK_PPM_RES] = tr->ppm >> 8; /* Update the slow-metering overview. Fixed point arithmetic * going on here */ w = v << 16; if (w > tr->overview) tr->overview += (w - tr->overview) >> 8; else tr->overview -= (tr->overview - w) >> 17; block->overview[fill / TRACK_OVERVIEW_RES] = tr->overview >> 24; fill++; pcm += TRACK_CHANNELS; } /* Increment the track length. A memory barrier ensures the * realtime or UI thread does not access garbage audio */ __sync_fetch_and_add(&tr->length, samples); } /* * Notify that data has been placed in the buffer * * This function passes any whole samples to commit_pcm_samples() * and leaves the residual in the buffer ready for next time. */ static void commit(struct track *tr, size_t len) { tr->bytes += len; commit_pcm_samples(tr, tr->bytes / SAMPLE - tr->length); } /* * Initialise object which will hold PCM audio data, and start * importing the data * * Post: track is initialised * Post: track is importing */ static int track_init(struct track *t, const char *importer, const char *path) { pid_t pid; fprintf(stderr, "Importing '%s'...\n", path); pid = fork_pipe_nb(&t->fd, importer, "import", path, STR(RATE), NULL); if (pid == -1) return -1; t->pid = pid; t->pe = NULL; t->terminated = false; t->refcount = 0; t->blocks = 0; t->rate = RATE; t->bytes = 0; t->length = 0; t->ppm = 0; t->overview = 0; t->importer = importer; t->path = path; list_add(&t->tracks, &tracks); rig_post_track(t); return 0; } /* * Destroy this track from memory * * Terminates any import processes and frees any memory allocated by * this object. * * Pre: track is not importing * Pre: track is initialised */ static void track_clear(struct track *tr) { int n; assert(tr->pid == 0); for (n = 0; n < tr->blocks; n++) free(tr->block[n]); list_del(&tr->tracks); } /* * Get a pointer to a track object already in memory * * Return: pointer, or NULL if no such track exists */ static struct track* track_get_again(const char *importer, const char *path) { struct track *t; list_for_each(t, &tracks, tracks) { if (t->importer == importer && t->path == path) { track_acquire(t); return t; } } return NULL; } /* * Get a pointer to a track object for the given importer and path * * Return: pointer, or NULL if not enough resources */ struct track* track_acquire_by_import(const char *importer, const char *path) { struct track *t; t = track_get_again(importer, path); if (t != NULL) return t; t = malloc(sizeof *t); if (t == NULL) { perror("malloc"); return NULL; } if (track_init(t, importer, path) == -1) { free(t); return NULL; } track_acquire(t); return t; } /* * Get a pointer to a static track containing no audio * * Return: pointer, not NULL */ struct track* track_acquire_empty(void) { empty.refcount++; return ∅ } void track_acquire(struct track *t) { t->refcount++; } /* * Request premature termination of an import operation */ static void terminate(struct track *t) { assert(t->pid != 0); if (kill(t->pid, SIGTERM) == -1) abort(); t->terminated = true; } /* * Finish use of a track object */ void track_release(struct track *t) { t->refcount--; /* When importing, a reference is held. If it's the * only one remaining terminate it to save resources */ if (t->refcount == 1 && t->pid != 0) { terminate(t); return; } if (t->refcount == 0) { assert(t != &empty); track_clear(t); free(t); } } /* * Get entry for use by poll() * * Pre: track is importing * Post: *pe contains poll entry */ void track_pollfd(struct track *t, struct pollfd *pe) { assert(t->pid != 0); pe->fd = t->fd; pe->events = POLLIN; t->pe = pe; } /* * Read the next block of data from the file handle into the track's * PCM data * * Return: -1 on completion, otherwise zero */ static int read_from_pipe(struct track *tr) { for (;;) { void *pcm; size_t len; ssize_t z; pcm = access_pcm(tr, &len); if (pcm == NULL) return -1; z = read(tr->fd, pcm, len); if (z == -1) { if (errno == EAGAIN) { return 0; } else { perror("read"); return -1; } } if (z == 0) /* EOF */ break; commit(tr, z); } return -1; /* completion without error */ } /* * Synchronise with the import process and complete it * * Pre: track is importing * Post: track is not importing */ static void stop_import(struct track *t) { int status; assert(t->pid != 0); if (close(t->fd) == -1) abort(); if (waitpid(t->pid, &status, 0) == -1) abort(); if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { fprintf(stderr, "Track import completed\n"); } else { fprintf(stderr, "Track import completed with status %d\n", status); if (!t->terminated) status_printf(STATUS_ALERT, "Error importing %s", t->path); } t->pid = 0; } /* * Handle any file descriptor activity on this track * * Return: true if import has completed, otherwise false */ void track_handle(struct track *tr) { assert(tr->pid != 0); /* A track may be added while poll() was waiting, * in which case it has no return data from poll */ if (tr->pe == NULL) return; if (tr->pe->revents == 0) return; if (read_from_pipe(tr) != -1) return; stop_import(tr); list_del(&tr->rig); track_release(tr); /* may delete the track */ } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/track.h������������������������������������������������������������������������������0000664�0000000�0000000�00000006233�12615121206�0014617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef TRACK_H #define TRACK_H #include <stdbool.h> #include <sys/poll.h> #include <sys/types.h> #include "list.h" #define TRACK_CHANNELS 2 #define TRACK_MAX_BLOCKS 64 #define TRACK_BLOCK_SAMPLES (2048 * 1024) #define TRACK_PPM_RES 64 #define TRACK_OVERVIEW_RES 2048 struct track_block { signed short pcm[TRACK_BLOCK_SAMPLES * TRACK_CHANNELS]; unsigned char ppm[TRACK_BLOCK_SAMPLES / TRACK_PPM_RES], overview[TRACK_BLOCK_SAMPLES / TRACK_OVERVIEW_RES]; }; struct track { struct list tracks; unsigned int refcount; int rate; /* pointers to external data */ const char *importer, *path; size_t bytes; /* loaded in */ unsigned int length, /* track length in samples */ blocks; /* number of blocks allocated */ struct track_block *block[TRACK_MAX_BLOCKS]; /* State of audio import */ struct list rig; pid_t pid; int fd; struct pollfd *pe; bool terminated; /* Current value of audio meters when loading */ unsigned short ppm; unsigned int overview; }; void track_use_mlock(void); /* Tracks are dynamically allocated and reference counted */ struct track* track_acquire_by_import(const char *importer, const char *path); struct track* track_acquire_empty(void); void track_acquire(struct track *t); void track_release(struct track *t); /* Functions used by the rig and main thread */ void track_pollfd(struct track *tr, struct pollfd *pe); void track_handle(struct track *tr); /* Return true if the track importer is running, otherwise false */ static inline bool track_is_importing(struct track *tr) { return tr->pid != 0; } /* Return the pseudo-PPM meter value for the given sample */ static inline unsigned char track_get_ppm(struct track *tr, int s) { struct track_block *b; b = tr->block[s / TRACK_BLOCK_SAMPLES]; return b->ppm[(s % TRACK_BLOCK_SAMPLES) / TRACK_PPM_RES]; } /* Return the overview meter value for the given sample */ static inline unsigned char track_get_overview(struct track *tr, int s) { struct track_block *b; b = tr->block[s / TRACK_BLOCK_SAMPLES]; return b->overview[(s % TRACK_BLOCK_SAMPLES) / TRACK_OVERVIEW_RES]; } /* Return a pointer to (not value of) the sample data for each channel */ static inline signed short* track_get_sample(struct track *tr, int s) { struct track_block *b; b = tr->block[s / TRACK_BLOCK_SAMPLES]; return &b->pcm[(s % TRACK_BLOCK_SAMPLES) * TRACK_CHANNELS]; } #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/xwax.1�������������������������������������������������������������������������������0000664�0000000�0000000�00000016565�12615121206�0014424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.TH XWAX "1" .SH NAME xwax \- Digital vinyl on Linux .SH SYNOPSIS .B xwax [\fIoptions\fR] .SH DESCRIPTION .P xwax is vinyl emulation software for Linux. It allows DJs and turntablists to playback digital audio files (MP3, Ogg Vorbis, FLAC, AAC and more), controlled using a normal pair of turntables via timecoded vinyls. .SH OPTIONS .P The ordering of options is important. Most options apply to subsequent music libraries or decks, which can be given multiple times. See the .B EXAMPLES below. .TP .B \-l \fIpath\fR Scan the music library or playlist at the given path. .TP .B \-t \fIname\fR Use the named timecode for subsequent decks. See \-h for a list of valid timecodes. You will need the corresponding timecode signal on vinyl to control playback. .TP .B \-33 Set the reference playback speed for subsequent decks to 33 and one third revolutions per minute. This is the default. .TP .B \-45 Set the reference playback speed for subsequent decks to 45 revolutions per minute. .TP .B \-c Protect subsequent decks against certain operations during playback. .TP .B \-u Allow all operations on a deck during playback. This is the inverse of the .B \-c option, and is the default. .TP .B \-\-phono Adjust the noise thresholds of subsequent decks to tolerate a cartridge-level signal connected to a line-level audio interface. This is a 'software pre-amp'. Unless your audio path has low noise, this will give worse results or may not work at all; a true phono pre-amplifier is always preferred. .TP .B \-\-line Set noise thresholds of subsequent decks to standard audio levels. This reverses the effect of the .B \-\-phono option, and is the default. .TP .B \-i \fIpath\fR Use the given importer executable for subsequent decks. .TP .B \-s \fIpath\fR Use the given scanner executable to scan subsequent music libraries. .TP .B \-\-dummy Create a deck which is not connected to any audio device, used for testing. .TP .B \-k Lock into RAM any memory required for real-time use. This includes audio tracks held in memory which can be large. Use .B ulimit \-l to raise the kernel's memory limit to allow this. .TP .B \-q \fIn\fR Change the real-time priority of the process. A priority of 0 gives the process no priority, and is used for testing only. .TP .B \-g [\fIn\fRx\fIn\fR][+\fIn\fR+\fIn\fR][/\fIf\fR] Change the geometry of the display in size, position and scale (zoom) respectively. The size and position is passed to SDL, which may use it to set the display mode, or size of an X window. See the .B EXAMPLES. .TP .B \-\-no\-decor Request to the window manager to create a 'frameless' window which does not have the regular controls such as title bars and buttons. This can be useful in conjunction with the .B \-g flag for dedicated xwax installations. .TP .B \-h Display the help message and default values. .SH "ALSA DEVICE OPTIONS" .P The following options are available only when xwax is compiled with ALSA support. .TP .B \-a \fIdevice\fR Create a deck which uses the given ALSA device (eg. plughw:0). .TP .B \-r \fIhz\fR Set the sample rate for subsequent decks. .TP .B \-m \fImilliseconds\fR Set the ALSA buffer time for subsequent decks. .SH "JACK DEVICE OPTIONS" .P The following options are available only when xwax is compiled with JACK support. .TP .B \-j \fIname\fR Create a deck which connects to JACK and registers under the given name. .P xwax does not set the sample rate for JACK devices; it uses the sample rate given in the global JACK configuration. .SH "OSS DEVICE OPTIONS" .P The following options are available only when xwax is compiled with OSS support. .TP .B \-d \fIpathname\fR Create a deck which uses the given OSS device (eg. /dev/dsp). .TP .B \-r \fIhz\fR Set the sample rate for subsequent decks. .TP .B \-b \fIn\fR Set the number of OSS buffers for subsequent decks. .TP .B \-f \fIn\fR Set the OSS buffer size (2^n bytes). .SH HARDWARE CONTROLLER OPTIONS .P The following options are available only when xwax is compiled with ALSA support. .TP .B \-\-dicer \fIdevice\fR Use one or two Dicer controllers connected as the given ALSA device (eg. hw:Dicer). See the section .B NOVATION DICER CONTROLS for more information. .P Adding a hardware controller results in control over subsequent decks, up to the limit of the hardware. .SH KEYBOARD CONTROLS .P The playback of each deck (direction, speed and position) is controlled via the incoming timecode signal from the turntables. The keyboard provides additional controls. .P "C-" and "S-" means a keypress is combined with the 'Control' or 'Shift' key, respectively. .P Record selection controls: .TP cursor up, cursor down Move highlighted record up/down by one. .TP page up, page down Scroll the record listing up/down by one page. .TP left cursor, right cursor Switch to the previous/next crate of records. .TP tab Toggle between the current crate and the 'All records' crate. .TP C-tab Toggle sort mode between: artist/track name, BPM and 'playlist' order. Playlist order is the order in which records were returned from the scanner. .TP C-S-tab Re-scan the currently selected crate. .P To filter the current list of records type a portion of a record name. Separate multiple searches with a space, and use backspace to delete. .P Deck-specific controls: .TS l l l l. Deck 0 Deck 1 Deck 2 F1 F5 F9 Load currently selected track to this deck F2 F6 F10 Reset start of track to the current position F3 F7 F11 Toggle timecode control on/off C-F3 C-F7 C-F11 Cycle between available timecodes .TE .P The "available timecodes" are those which have been the subject of any .B \-t flag on the command line. Audio display controls: .TP +, \- Zoom in/out the close-up audio meters for all decks. .SH NOVATION DICER CONTROLS .P The Novation Dicer provides hardware control of cue points. The controls are: .TP cue mode: dice button (1-5) Jump to the specified cue point, or set it if unset. .TP loop-roll mode: dicer button (1-5) "Punch" to the specified cue point, or set it if unset. Returns playback to normal when the button is released. .TP mode button + dice button (1-5) Clear the specified cue point. .P The dice buttons are lit to show that the corresponding cue point is set. .SH EXAMPLES .P 2-deck setup using one directory of music and OSS devices: .sp .RS xwax \-l ~/music \-d /dev/dsp \-d /dev/dsp1 .RE .P As above, but using ALSA devices: .sp .RS xwax \-l ~/music \-d hw:0 \-d hw:1 .RE .P 2-deck setup using a different timecode on each deck: .sp .RS xwax \-l ~/music \-t serato_2a \-d hw:0 \-t mixvibes_v2 \-d hw:1 .RE .P As above, but with the second deck at 45 RPM: .sp .RS xwax \-l ~/music \-t serato_2a \-d hw:0 \-t mixvibes_v2 \-45 \-d hw:1 .RE .P Default to the same timecode, but allow switching at runtime: .sp .RS xwax \-l ~/music \-t serato_2a \-t mixvibes_v2 \-d hw:0 \-d hw:1 .RE .P 3-deck setup with the third deck at a higher sample rate: .sp .RS xwax \-l ~/music \-r 48000 \-a hw:0 \-a hw:1 \-r 96000 \-a hw:2 .RE .P Using all three device types simultaneously, one deck on each: .sp .RS xwax \-l ~/music \-a hw:0 \-d /dev/dsp1 \-j jack0 .RE .P Scan multiple music libraries: .sp .RS xwax \-l ~/music \-l ~/sounds \-l ~/mixes \-a hw:0 .RE .P Scan a second music library using a custom script: .sp .RS xwax \-l ~/music \-i ./custom-scan \-l ~/sounds \-a hw:0 .RE .P Control two decks with Dicer hardware: .sp .RS xwax \-\-dicer hw:Dicer \-a hw:0 \-a hw:1 .RE .P Use a high resolution and enlarge the user interface: .sp .RS xwax -g 1920x1200/1.8 -a hw:0 .RE .SH HOMEPAGE http://xwax.org/ .SH AUTHOR Mark Hills <mark@xwax.org> �������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/xwax.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000037215�12615121206�0014501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> /* mlockall() */ #include <SDL.h> /* may override main() */ #include "alsa.h" #include "controller.h" #include "device.h" #include "dicer.h" #include "dummy.h" #include "interface.h" #include "jack.h" #include "library.h" #include "oss.h" #include "realtime.h" #include "thread.h" #include "rig.h" #include "timecoder.h" #include "track.h" #include "xwax.h" #define DEFAULT_OSS_BUFFERS 8 #define DEFAULT_OSS_FRAGMENT 7 #define DEFAULT_ALSA_BUFFER 8 /* milliseconds */ #define DEFAULT_RATE 44100 #define DEFAULT_PRIORITY 80 #define DEFAULT_IMPORTER EXECDIR "/xwax-import" #define DEFAULT_SCANNER EXECDIR "/xwax-scan" #define DEFAULT_TIMECODE "serato_2a" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) char *banner = "xwax " VERSION \ " (C) Copyright 2015 Mark Hills <mark@xwax.org>"; size_t ndeck; struct deck deck[3]; static size_t nctl; static struct controller ctl[2]; static struct rt rt; static double speed; static bool protect, phono; static const char *importer; static struct timecode_def *timecode; static void usage(FILE *fd) { fprintf(fd, "Usage: xwax [<options>]\n\n"); fprintf(fd, "Program-wide options:\n" " -k Lock real-time memory into RAM\n" " -q <n> Real-time priority (0 for no priority, default %d)\n" " -g <s> Set display geometry (see man page)\n" " --no-decor Request a window with no decorations\n" " -h Display this message to stdout and exit\n\n", DEFAULT_PRIORITY); fprintf(fd, "Music library options:\n" " -l <path> Location to scan for audio tracks\n" " -s <program> Library scanner (default '%s')\n\n", DEFAULT_SCANNER); fprintf(fd, "Deck options:\n" " -t <name> Timecode name\n" " -33 Use timecode at 33.3RPM (default)\n" " -45 Use timecode at 45RPM\n" " -c Protect against certain operations while playing\n" " -u Allow all operations when playing\n" " --line Line level signal (default)\n" " --phono Tolerate cartridge level signal ('software pre-amp')\n" " -i <program> Importer (default '%s')\n" " --dummy Build a dummy deck with no audio device\n\n", DEFAULT_IMPORTER); #ifdef WITH_OSS fprintf(fd, "OSS device options:\n" " -d <device> Build a deck connected to OSS audio device\n" " -r <hz> Sample rate (default %dHz)\n" " -b <n> Number of buffers (default %d)\n" " -f <n> Buffer size to request (2^n bytes, default %d)\n\n", DEFAULT_RATE, DEFAULT_OSS_BUFFERS, DEFAULT_OSS_FRAGMENT); #endif #ifdef WITH_ALSA fprintf(fd, "ALSA device options:\n" " -a <device> Build a deck connected to ALSA audio device\n" " -r <hz> Sample rate (default %dHz)\n" " -m <ms> Buffer time (default %dms)\n\n", DEFAULT_RATE, DEFAULT_ALSA_BUFFER); #endif #ifdef WITH_JACK fprintf(fd, "JACK device options:\n" " -j <name> Create a JACK deck with the given name\n\n"); #endif #ifdef WITH_ALSA fprintf(fd, "MIDI control:\n" " --dicer <dev> Novation Dicer\n\n"); #endif fprintf(fd, "The ordering of options is important. Options apply to subsequent\n" "music libraries or decks, which can be given multiple times. See the\n" "manual for details.\n\n" "Available timecodes (for use with -t):\n" " serato_2a (default), serato_2b, serato_cd,\n" " traktor_a, traktor_b, mixvibes_v2, mixvibes_7inch\n\n" "See the xwax(1) man page for full information and examples.\n"); } static struct device* start_deck(const char *desc) { fprintf(stderr, "Initialising deck %zd (%s)...\n", ndeck, desc); if (ndeck == ARRAY_SIZE(deck)) { fprintf(stderr, "Too many decks.\n"); return NULL; } return &deck[ndeck].device; } static int commit_deck(void) { int r; struct deck *d; size_t n; /* Fallback to a default timecode. Don't initialise this at the * front of the program to avoid buildling unnecessary LUTs */ if (timecode == NULL) { timecode = timecoder_find_definition(DEFAULT_TIMECODE); assert(timecode != NULL); } d = &deck[ndeck]; r = deck_init(d, &rt, timecode, importer, speed, phono, protect); if (r == -1) return -1; /* Connect this deck to available controllers */ for (n = 0; n < nctl; n++) controller_add_deck(&ctl[n], d); ndeck++; return 0; } int main(int argc, char *argv[]) { int rc = -1, n, priority; const char *scanner, *geo; char *endptr; bool use_mlock, decor; struct library library; #if defined WITH_OSS || WITH_ALSA int rate; #endif #ifdef WITH_OSS int oss_buffers, oss_fragment; #endif #ifdef WITH_ALSA int alsa_buffer; #endif fprintf(stderr, "%s\n\n" NOTICE "\n\n", banner); if (thread_global_init() == -1) return -1; if (rig_init() == -1) return -1; rt_init(&rt); library_init(&library); ndeck = 0; geo = ""; decor = true; nctl = 0; priority = DEFAULT_PRIORITY; importer = DEFAULT_IMPORTER; scanner = DEFAULT_SCANNER; timecode = NULL; speed = 1.0; protect = false; phono = false; use_mlock = false; #if defined WITH_OSS || WITH_ALSA rate = DEFAULT_RATE; #endif #ifdef WITH_ALSA alsa_buffer = DEFAULT_ALSA_BUFFER; #endif #ifdef WITH_OSS oss_fragment = DEFAULT_OSS_FRAGMENT; oss_buffers = DEFAULT_OSS_BUFFERS; #endif /* Skip over command name */ argv++; argc--; while (argc > 0) { if (!strcmp(argv[0], "-h")) { usage(stdout); return 0; #ifdef WITH_OSS } else if (!strcmp(argv[0], "-f")) { /* Set fragment size for subsequent devices */ if (argc < 2) { fprintf(stderr, "-f requires an integer argument.\n"); return -1; } oss_fragment = strtol(argv[1], &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "-f requires an integer argument.\n"); return -1; } /* Fragment sizes greater than the default aren't useful * as they are dependent on DEVICE_FRAME */ if (oss_fragment < DEFAULT_OSS_FRAGMENT) { fprintf(stderr, "Fragment size must be %d or more; aborting.\n", DEFAULT_OSS_FRAGMENT); return -1; } argv += 2; argc -= 2; } else if (!strcmp(argv[0], "-b")) { /* Set number of buffers for subsequent devices */ if (argc < 2) { fprintf(stderr, "-b requires an integer argument.\n"); return -1; } oss_buffers = strtol(argv[1], &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "-b requires an integer argument.\n"); return -1; } argv += 2; argc -= 2; #endif #if defined WITH_OSS || WITH_ALSA } else if (!strcmp(argv[0], "-r")) { /* Set sample rate for subsequence devices */ if (argc < 2) { fprintf(stderr, "-r requires an integer argument.\n"); return -1; } rate = strtol(argv[1], &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "-r requires an integer argument.\n"); return -1; } argv += 2; argc -= 2; #endif #ifdef WITH_ALSA } else if (!strcmp(argv[0], "-m")) { /* Set size of ALSA buffer for subsequence devices */ if (argc < 2) { fprintf(stderr, "-m requires an integer argument.\n"); return -1; } alsa_buffer = strtol(argv[1], &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "-m requires an integer argument.\n"); return -1; } argv += 2; argc -= 2; #endif } else if (!strcmp(argv[0], "-d") || !strcmp(argv[0], "-a") || !strcmp(argv[0], "-j")) { int r; struct device *device; /* Create a deck */ if (argc < 2) { fprintf(stderr, "-%c requires a device name as an argument.\n", argv[0][1]); return -1; } device = start_deck(argv[1]); if (device == NULL) return -1; /* Work out which device type we are using, and initialise * an appropriate device. */ switch(argv[0][1]) { #ifdef WITH_OSS case 'd': r = oss_init(device, argv[1], rate, oss_buffers, oss_fragment); break; #endif #ifdef WITH_ALSA case 'a': r = alsa_init(device, argv[1], rate, alsa_buffer); break; #endif #ifdef WITH_JACK case 'j': r = jack_init(device, argv[1]); break; #endif default: fprintf(stderr, "Device type is not supported by this " "distribution of xwax.\n"); return -1; } if (r == -1) return -1; commit_deck(); argv += 2; argc -= 2; } else if (!strcmp(argv[0], "--dummy")) { struct device *v; v = start_deck("dummy"); if (v == NULL) return -1; dummy_init(v); commit_deck(); argv++; argc--; } else if (!strcmp(argv[0], "-t")) { /* Set the timecode definition to use */ if (argc < 2) { fprintf(stderr, "-t requires a name as an argument.\n"); return -1; } timecode = timecoder_find_definition(argv[1]); if (timecode == NULL) { fprintf(stderr, "Timecode '%s' is not known.\n", argv[1]); return -1; } argv += 2; argc -= 2; } else if (!strcmp(argv[0], "-33")) { speed = 1.0; argv++; argc--; } else if (!strcmp(argv[0], "-45")) { speed = 1.35; argv++; argc--; } else if (!strcmp(argv[0], "-c")) { protect = true; argv++; argc--; } else if (!strcmp(argv[0], "-u")) { protect = false; argv++; argc--; } else if (!strcmp(argv[0], "--line")) { phono = false; argv++; argc--; } else if (!strcmp(argv[0], "--phono")) { phono = true; argv++; argc--; } else if (!strcmp(argv[0], "-k")) { use_mlock = true; track_use_mlock(); argv++; argc--; } else if (!strcmp(argv[0], "-q")) { if (argc < 2) { fprintf(stderr, "-q requires an integer argument.\n"); return -1; } priority = strtol(argv[1], &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "-q requires an integer argument.\n"); return -1; } if (priority < 0) { fprintf(stderr, "Priority (%d) must be zero or positive.\n", priority); return -1; } argv += 2; argc -= 2; } else if (!strcmp(argv[0], "-g")) { if (argc < 2) { fprintf(stderr, "-g requires an argument.\n"); return -1; } geo = argv[1]; argv += 2; argc -= 2; } else if (!strcmp(argv[0], "--no-decor")) { decor = false; argv++; argc--; } else if (!strcmp(argv[0], "-i")) { /* Importer script for subsequent decks */ if (argc < 2) { fprintf(stderr, "-i requires an executable path " "as an argument.\n"); return -1; } importer = argv[1]; argv += 2; argc -= 2; } else if (!strcmp(argv[0], "-s")) { /* Scan script for subsequent libraries */ if (argc < 2) { fprintf(stderr, "-s requires an executable path " "as an argument.\n"); return -1; } scanner = argv[1]; argv += 2; argc -= 2; } else if (!strcmp(argv[0], "-l")) { /* Load in a music library */ if (argc < 2) { fprintf(stderr, "-l requires a pathname as an argument.\n"); return -1; } if (library_import(&library, scanner, argv[1]) == -1) return -1; argv += 2; argc -= 2; #ifdef WITH_ALSA } else if (!strcmp(argv[0], "--dicer")) { struct controller *c; if (nctl == sizeof ctl) { fprintf(stderr, "Too many controllers; aborting.\n"); return -1; } c = &ctl[nctl]; if (argc < 2) { fprintf(stderr, "Dicer requires an ALSA device name.\n"); return -1; } if (dicer_init(c, &rt, argv[1]) == -1) return -1; nctl++; argv += 2; argc -= 2; #endif } else { fprintf(stderr, "'%s' argument is unknown; try -h.\n", argv[0]); return -1; } } #ifdef WITH_ALSA alsa_clear_config_cache(); #endif if (ndeck == 0) { fprintf(stderr, "You need to give at least one audio device to use " "as a deck; try -h.\n"); return -1; } rc = EXIT_FAILURE; /* until clean exit */ /* Order is important: launch realtime thread first, then mlock. * Don't mlock the interface, use sparingly for audio threads */ if (rt_start(&rt, priority) == -1) return -1; if (use_mlock && mlockall(MCL_CURRENT) == -1) { perror("mlockall"); goto out_rt; } if (interface_start(&library, geo, decor) == -1) goto out_rt; if (rig_main() == -1) goto out_interface; rc = EXIT_SUCCESS; fprintf(stderr, "Exiting cleanly...\n"); out_interface: interface_stop(); out_rt: rt_stop(&rt); for (n = 0; n < ndeck; n++) deck_clear(&deck[n]); for (n = 0; n < nctl; n++) controller_clear(&ctl[n]); timecoder_free_lookup(); library_clear(&library); rt_clear(&rt); rig_clear(); thread_global_clear(); if (rc == EXIT_SUCCESS) fprintf(stderr, "Done.\n"); return rc; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/xwax.h�������������������������������������������������������������������������������0000664�0000000�0000000�00000002222�12615121206�0014474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (C) 2015 Mark Hills <mark@xwax.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2, as published by the Free Software Foundation. * * 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 version 2 for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifndef XWAX_H #define XWAX_H #include "deck.h" extern char *banner; #define NOTICE \ "This software is supplied WITHOUT ANY WARRANTY; without even the implied\n"\ "warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This is\n"\ "free software, and you are welcome to redistribute it under certain\n"\ "conditions; see the file COPYING for details." extern size_t ndeck; extern struct deck deck[]; #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������xwax-1.6-beta2/.version�����������������������������������������������������������������������������0000644�0001750�0000144�00000000012�12615121755�015177� 0����������������������������������������������������������������������������������������������������ustar�00mark����������������������������users���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������1.6-beta2 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������