pax_global_header 0000666 0000000 0000000 00000000064 14427473437 0014531 g ustar 00root root 0000000 0000000 52 comment=6f55528eda93496d94613b27aa4819f94f9a166b
xwax-1.9/ 0000775 0000000 0000000 00000000000 14427473437 0012371 5 ustar 00root root 0000000 0000000 xwax-1.9/.gitignore 0000664 0000000 0000000 00000000253 14427473437 0014361 0 ustar 00root root 0000000 0000000 TAGS
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.9/CHANGES 0000664 0000000 0000000 00000011007 14427473437 0013363 0 ustar 00root root 0000000 0000000 v1.9 (2023-05-12)
-----------------
* Replaced ALSA '-m' flag with a new '--buffer' in samples
* Revised ALSA buffer handling to improve latencies
* Renamed '-r' flag to '--rate'
* Restrict ALSA sample rates to those implemented in hardware
* Fixes for compatibility with musl C library
* Minor fixes
Acknowledgements: Daniel Schürmann
v1.8 (2021-08-18)
-----------------
* Change to the license to GPL version 3
* Compatibility with Pioneer DJ "RekordBox" vinyls
* Fix a bug where decimal numbers are parsed in an unexpected way
Acknowledgements: Hugh Frater, Kilian, Yves Adler
v1.7 (2018-01-19)
-----------------
* Fix a bug that prevented dithering from being used
* Honour the system locale; allow the full character set in track names
* When searching, match characters in other locales
Acknowledgements: Stefan Berg-Johansen
v1.6 (2016-08-13)
-----------------
* Added timecode creator (for the adventurous)
* Correct interpretation of ALSA's buffer size
* Command-line flag to remove all window decorations (--no-decor)
* A dummy deck for test cases
* Internal restructing in preparation for new features
* Build without warnings on modern compilers
Acknowledgements: Daniel James, Alessio Treglia
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.9/COPYING 0000664 0000000 0000000 00000104515 14427473437 0013432 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. 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
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
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
state 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 3 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, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU 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. But first, please read
.
xwax-1.9/INSTALL 0000664 0000000 0000000 00000002205 14427473437 0013421 0 ustar 00root root 0000000 0000000 The 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.9/Makefile 0000664 0000000 0000000 00000007660 14427473437 0014042 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2021 Mark Hills
#
# This file is part of "xwax".
#
# "xwax" is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License, version 3 as
# published by the Free Software Foundation.
#
# "xwax" 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, see .
#
# 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 -MP
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.9/README 0000664 0000000 0000000 00000001700 14427473437 0013247 0 ustar 00root root 0000000 0000000 xwax: Digital vinyl on Linux
(C) Copyright 2021 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.
"xwax" is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License, version 3 as
published by the Free Software Foundation.
"xwax" 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, see .
xwax-1.9/alsa.c 0000664 0000000 0000000 00000030701 14427473437 0013456 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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 */
int rate;
};
struct alsa {
struct alsa_pcm capture, playback;
bool playing;
};
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;
}
}
/* "rate" of zero means automatically select an appropriate rate */
static int pcm_open(struct alsa_pcm *alsa, const char *device_name,
snd_pcm_stream_t stream, unsigned int rate, int buffer)
{
int r, dir;
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t frames;
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_MMAP_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;
}
/* Prevent accidentally introducing excess resamplers. There is
* already one on the signal path to handle pitch adjustments.
* This is even if a 'plug' device is used, which effectively lets
* the user unknowingly select any sample rate. */
r = snd_pcm_hw_params_set_rate_resample(alsa->pcm, hw_params, 0);
if (!chk("hw_params_set_rate_resample", r))
return -1;
if (rate) {
r = snd_pcm_hw_params_set_rate(alsa->pcm, hw_params, rate, 0);
if (!chk("hw_params_set_rate", r)) {
fprintf(stderr, "Sample rate of %dHz is not implemented by the hardware.\n",
rate);
return -1;
}
} else {
/* The 'best' sample rate on this hardware. Prefer 48kHz over
* 44.1kHz because it typically allows for smaller buffers.
* No need to match the source material; it's never playing at
* a fixed sample rate anyway. */
dir = -1;
rate = 48000;
r = snd_pcm_hw_params_set_rate_near(alsa->pcm, hw_params, &rate, &dir);
if (!chk("hw_params_set_rate_near", r))
return -1;
/* "rate" is set on return */
}
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;
}
/* This is fundamentally a latency-sensitive application that is
* likely to be the primary application running, so assume we want
* the hardware to be giving us immediate wakeups */
r = snd_pcm_hw_params_set_period_size_first(alsa->pcm, hw_params, &frames, &dir);
if (!chk("hw_params_set_buffer_time_near", r))
return -1;
switch (stream) {
case SND_PCM_STREAM_CAPTURE:
/* Maximum buffer to minimise drops */
r = snd_pcm_hw_params_set_buffer_size_last(alsa->pcm, hw_params, &frames);
if (!chk("hw_params_set_buffer_size_last", r))
return -1;
break;
case SND_PCM_STREAM_PLAYBACK:
/* Smallest possible buffer to keep latencies low */
r = snd_pcm_hw_params_set_buffer_size(alsa->pcm, hw_params, buffer);
if (!chk("hw_params_set_buffer_size", r)) {
fprintf(stderr, "Buffer of %u samples is probably too small; try increasing it with -m\n",
buffer);
return -1;
}
break;
default:
abort();
}
r = snd_pcm_hw_params(alsa->pcm, hw_params);
if (!chk("hw_params", r))
return -1;
return 0;
}
static void pcm_close(struct alsa_pcm *alsa)
{
if (snd_pcm_close(alsa->pcm) < 0)
abort();
}
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;
}
/* Access the interleaved area presented by the ALSA library. The
* device is opened SND_PCM_FORMAT_S16 which is in the local endianess
* and therefore is "signed short" */
static signed short *buffer(const snd_pcm_channel_area_t *area,
snd_pcm_uframes_t offset)
{
assert(area->first % 8 == 0);
assert(area->step == 32); /* 2 channel 16-bit interleaved */
return area->addr + area->first / 8 + offset * area->step / 8;
}
/* Collect audio from the player and push it into the device's buffer,
* for playback */
static int playback(struct device *dv)
{
int r;
snd_pcm_state_t state;
snd_pcm_uframes_t frames, offset;
const snd_pcm_channel_area_t *area;
struct alsa *alsa = (struct alsa*)dv->local;
state = snd_pcm_state(alsa->capture.pcm);
if (state == SND_PCM_STATE_XRUN)
return -EPIPE;
frames = snd_pcm_avail_update(alsa->playback.pcm);
if (frames < 0)
return (int)frames;
r = snd_pcm_mmap_begin(alsa->playback.pcm, &area, &offset, &frames);
if (r < 0)
return r;
assert(frames > 0); /* otherwise we were woken unnecessarily */
device_collect(dv, buffer(&area[0], offset), frames);
r = snd_pcm_mmap_commit(alsa->playback.pcm, offset, frames);
if (r < 0)
return r;
/* If this is the initial write, assume the buffer gets filled to
* the maximum and it's time to consume the buffer */
if (!alsa->playing) {
r = snd_pcm_start(alsa->playback.pcm);
if (r < 0)
return r;
alsa->playing = true;
}
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;
snd_pcm_state_t state;
snd_pcm_uframes_t frames, offset;
const snd_pcm_channel_area_t *area;
struct alsa *alsa = (struct alsa*)dv->local;
state = snd_pcm_state(alsa->capture.pcm);
if (state == SND_PCM_STATE_XRUN)
return -EPIPE;
frames = snd_pcm_avail(alsa->capture.pcm);
if (frames < 0)
return (int)frames;
r = snd_pcm_mmap_begin(alsa->capture.pcm, &area, &offset, &frames);
if (r < 0)
return r;
assert(frames > 0); /* otherwise we were woken unnecessarily */
device_submit(dv, buffer(&area[0], offset), frames);
r = snd_pcm_mmap_commit(alsa->capture.pcm, offset, frames);
if (r < 0)
return 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;
}
alsa->playing = false;
/* POLLOUT events will be generated now, and we
* explicitly start the device when writing */
} 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,
unsigned int rate, unsigned int buffer)
{
struct alsa *alsa;
alsa = malloc(sizeof *alsa);
if (alsa == NULL) {
perror("malloc");
return -1;
}
alsa->playing = false;
if (pcm_open(&alsa->capture, device_name, SND_PCM_STREAM_CAPTURE,
rate, buffer) < 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) < 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.9/alsa.h 0000664 0000000 0000000 00000001553 14427473437 0013466 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef ALSA_H
#define ALSA_H
#include "device.h"
int alsa_init(struct device *dv, const char *name,
unsigned int rate, unsigned int buffer_time);
void alsa_clear_config_cache(void);
#endif
xwax-1.9/configure 0000775 0000000 0000000 00000004402 14427473437 0014300 0 ustar 00root root 0000000 0000000 #!/bin/sh
#
# This file is part of "xwax".
#
# "xwax" is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License, version 3 as
# published by the Free Software Foundation.
#
# "xwax" 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, see .
#
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.9/controller.c 0000664 0000000 0000000 00000004255 14427473437 0014726 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/controller.h 0000664 0000000 0000000 00000003237 14427473437 0014732 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/cues.c 0000664 0000000 0000000 00000004160 14427473437 0013475 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/cues.h 0000664 0000000 0000000 00000002235 14427473437 0013503 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/debug.h 0000664 0000000 0000000 00000002156 14427473437 0013634 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef DEBUG_H
#define DEBUG_H
#include
/*
* Enable a specific debug message by prefixing with an underscore,
* otherwise -DDEBUG to enable all within that particular compile.
*/
#define _debug(...) { \
fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
}
#ifdef DEBUG
#define debug(...) _debug(__VA_ARGS__)
#else
#define debug(...)
#endif
#define not_implemented() abort()
#endif
xwax-1.9/deck.c 0000664 0000000 0000000 00000010206 14427473437 0013442 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/deck.h 0000664 0000000 0000000 00000003446 14427473437 0013457 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/device.c 0000664 0000000 0000000 00000006235 14427473437 0014002 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/device.h 0000664 0000000 0000000 00000003512 14427473437 0014002 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/dicer.c 0000664 0000000 0000000 00000024042 14427473437 0013625 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/dicer.h 0000664 0000000 0000000 00000001450 14427473437 0013630 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/dummy.c 0000664 0000000 0000000 00000001611 14427473437 0013667 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/dummy.h 0000664 0000000 0000000 00000001374 14427473437 0013702 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef DUMMY_H
#define DUMMY_H
#include "device.h"
void dummy_init(struct device *d);
#endif
xwax-1.9/excrate.c 0000664 0000000 0000000 00000011756 14427473437 0014202 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/excrate.h 0000664 0000000 0000000 00000003026 14427473437 0014176 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/external.c 0000664 0000000 0000000 00000014254 14427473437 0014365 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/external.h 0000664 0000000 0000000 00000002231 14427473437 0014362 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/import 0000775 0000000 0000000 00000001012 14427473437 0013623 0 ustar 00root root 0000000 0000000 #!/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"` -
;;
*)
exec ffmpeg -v 0 -i "$FILE" -f s16le -ar "$RATE" -
;;
esac
xwax-1.9/index.c 0000664 0000000 0000000 00000021201 14427473437 0013640 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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)
{
free(ls->record); /* may be NULL */
}
/*
* 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)
{
/* Some records provide a dedicated string for matching against,
* in the same locale as "match" */
if (re->match) {
if (strcasestr(re->match, match) != NULL)
return true;
} else {
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.9/index.h 0000664 0000000 0000000 00000004175 14427473437 0013660 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef INDEX_H
#define INDEX_H
#include
#define SORT_ARTIST 0
#define SORT_BPM 1
#define SORT_PLAYLIST 2
#define SORT_END 3
/* A single music track in our listings */
struct record {
char *pathname, *artist, *title; /* a single malloc */
/* An optional extra string may be used to match against search
* input; allows us to handle locale but still type in ASCII */
char *match; /* or NULL */
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.9/interface.c 0000664 0000000 0000000 00000132232 14427473437 0014500 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "debug.h"
#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 iconv_t utf;
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[9], char deci[4], 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
*
* Render the string "buf" text inside the given "rect". If "locale"
* is set then a conversion from the system locale is done.
*
* Return: width of text drawn
*/
static int do_draw_text(SDL_Surface *sf, const struct rect *rect,
const char *buf, TTF_Font *font,
SDL_Color fg, SDL_Color bg, bool locale)
{
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 {
if (!locale) {
rendered = TTF_RenderText_Shaded(font, buf, fg, bg);
} else {
char ubuf[256], /* fixed buffer is reasonable for rendering */
*in, *out;
size_t len, fill;
out = ubuf;
fill = sizeof(ubuf) - 1; /* always leave space for \0 */
if (iconv(utf, NULL, NULL, &out, &fill) == -1)
abort();
in = strdupa(buf);
len = strlen(in);
(void)iconv(utf, &in, &len, &out, &fill);
*out = '\0';
rendered = TTF_RenderUTF8_Shaded(font, ubuf, 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;
}
static int draw_text(SDL_Surface *sf, const struct rect *rect,
const char *buf, TTF_Font *font,
SDL_Color fg, SDL_Color bg)
{
return do_draw_text(sf, rect, buf, font, fg, bg, false);
}
static int draw_text_in_locale(SDL_Surface *sf, const struct rect *rect,
const char *buf, TTF_Font *font,
SDL_Color fg, SDL_Color bg)
{
return do_draw_text(sf, rect, buf, font, fg, bg, true);
}
/*
* 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_in_locale(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_in_locale(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[9], deci[4];
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_in_locale(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_in_locale(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_in_locale(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_in_locale(surface, &left, record->artist, font, text_col, col);
split(right, from_left(SPACER, 0), &left, &right);
draw_rect(surface, &left, col);
draw_text_in_locale(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;
}
/*
* Cleanup resources associated with this user interface
*/
static void cleanup()
{
size_t n;
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();
if (iconv_close(utf) == -1)
abort();
TTF_Quit();
SDL_Quit();
}
/*
* Start the SDL interface
*/
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;
/*
* Start allocating resources
*
* Many exit paths here; get the ones most likely to fail (user error)
* out of the way first.
*/
/*
* Fonts
*/
fprintf(stderr, "Initialising fonts...\n");
if (TTF_Init() == -1) {
fprintf(stderr, "%s\n", TTF_GetError());
return -1;
}
if (load_fonts() == -1) {
goto fail_fonts;
}
/*
* SDL
*/
fprintf(stderr, "Initialising SDL...\n");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
fprintf(stderr, "%s\n", SDL_GetError());
goto fail_fonts;
}
SDL_WM_SetCaption(banner, NULL);
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
/*
* Character translations; internally UTF8 is used
*/
utf = iconv_open("UTF8", "");
if (utf == (iconv_t)-1) {
perror("iconv_open");
goto fail_sdl;
}
/*
* Timecode monitors
*/
if (init_spinner(zoom(SPINNER_SIZE)) == -1)
goto fail_sdl;
for (n = 0; n < ndeck; n++) {
if (timecoder_monitor_init(&deck[n].timecoder, zoom(SCOPE_SIZE)) == -1)
not_implemented();
}
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, "Launching interface thread...\n");
if (pthread_create(&ph, NULL, launch, NULL)) {
perror("pthread_create");
cleanup();
return -1;
}
return 0;
fail_sdl:
SDL_Quit();
fail_fonts:
TTF_Quit();
return -1;
}
/*
* Synchronise with the SDL interface and exit
*/
void interface_stop(void)
{
push_event(EVENT_QUIT);
if (pthread_join(ph, NULL) != 0)
abort();
cleanup();
}
xwax-1.9/interface.h 0000664 0000000 0000000 00000001522 14427473437 0014502 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/jack.c 0000664 0000000 0000000 00000020576 14427473437 0013457 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/jack.h 0000664 0000000 0000000 00000001413 14427473437 0013451 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef JACK_H
#define JACK_H
#include "device.h"
int jack_init(struct device *dv, const char *name);
#endif
xwax-1.9/layout.h 0000664 0000000 0000000 00000013042 14427473437 0014057 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/library.c 0000664 0000000 0000000 00000032420 14427473437 0014202 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#define _GNU_SOURCE /* strdupa() */
#include
#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))
/* The locale used for searches */
static iconv_t ascii = (iconv_t)-1;
int library_global_init(void)
{
assert(ascii == (iconv_t)-1);
/* A simplified conversion is used to broaden text searches */
ascii = iconv_open("ASCII//TRANSLIT", "");
/* Transliteration is not available on some platforms, eg. musl. */
if (ascii == (iconv_t)-1 && errno == EINVAL) {
fprintf(stderr, "Text searches are compromised; no transliteration on this system\n");
ascii = iconv_open("ASCII", "");
}
if (ascii == (iconv_t)-1) {
perror("iconv_open");
return -1;
}
return 0;
}
void library_global_clear(void)
{
assert(ascii != (iconv_t)-1);
if (iconv_close(ascii) == -1)
abort();
}
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(re->match); /* may be NULL */
}
/*
* 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;
}
/*
* Construct a string for matching against
*
* Only construct the string if the given "artist" and "title" contain
* characters which require converting to the ASCII locale which is
* used for searches.
*
* Return: string with responsibility, or NULL if not required
*/
static char* matchable(const char *artist, const char *title)
{
char *buf, *in, *out;
size_t len, fill, nonrev;
/*
* Append all the strings of interest into a single buffer
*/
len = strlen(artist) + strlen(title) + 1;
buf = alloca(len + 1); /* include \0 terminator */
assert(buf);
sprintf(buf, "%s %s", artist, title);
/*
* Perform iconv
*
* We know ASCII is the shorter encoding, so we can use the input
* buffer as also the output buffer
*/
out = buf;
fill = len; /* does not include \0 */
assert(ascii != (iconv_t)-1);
if (iconv(ascii, NULL, NULL, &out, &fill) == -1)
abort();
in = buf;
nonrev = iconv(ascii, &in, &len, &out, &fill);
*out = '\0';
if (nonrev == 0)
return NULL;
return strdup(buf);
}
/*
* 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;
}
/* Decide if this record needs a character-equivalent in the
* locale used for searching */
x->match = matchable(x->artist, x->title);
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.9/library.h 0000664 0000000 0000000 00000003674 14427473437 0014220 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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;
};
int library_global_init(void);
void library_global_clear(void);
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.9/list.h 0000664 0000000 0000000 00000005472 14427473437 0013525 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/listbox.c 0000664 0000000 0000000 00000007765 14427473437 0014240 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/listbox.h 0000664 0000000 0000000 00000002706 14427473437 0014233 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/lut.c 0000664 0000000 0000000 00000005120 14427473437 0013337 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/lut.h 0000664 0000000 0000000 00000002213 14427473437 0013344 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/midi.c 0000664 0000000 0000000 00000004566 14427473437 0013472 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/midi.h 0000664 0000000 0000000 00000002144 14427473437 0013465 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/mkdist 0000775 0000000 0000000 00000001123 14427473437 0013607 0 ustar 00root root 0000000 0000000 #!/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.9/mktimecode.c 0000664 0000000 0000000 00000006751 14427473437 0014667 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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 2021 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.9/mkversion 0000775 0000000 0000000 00000001101 14427473437 0014325 0 ustar 00root root 0000000 0000000 #!/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.9/mutex.h 0000664 0000000 0000000 00000003126 14427473437 0013706 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/observer.h 0000664 0000000 0000000 00000004401 14427473437 0014370 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* 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.9/oss.c 0000664 0000000 0000000 00000011245 14427473437 0013344 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/oss.h 0000664 0000000 0000000 00000001535 14427473437 0013352 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/pitch.h 0000664 0000000 0000000 00000003310 14427473437 0013646 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/player.c 0000664 0000000 0000000 00000027205 14427473437 0014037 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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
*/
static double dither(void)
{
unsigned int bit, v;
static unsigned int x = 0xbeefface;
/* Maximum length LFSR sequence with 32-bit state */
bit = (x ^ (x >> 1) ^ (x >> 21) ^ (x >> 31)) & 1;
x = x << 1 | bit;
/* We can adjust the balance between randomness and performance
* by our chosen bit permutation; here we use a 12 bit subset
* of the state */
v = (x & 0x0000000f)
| ((x & 0x000f0000) >> 12)
| ((x & 0x0f000000) >> 16);
return (double)v / 4096 - 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.9/player.h 0000664 0000000 0000000 00000004403 14427473437 0014037 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/realtime.c 0000664 0000000 0000000 00000013371 14427473437 0014344 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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;
const int policy = SCHED_FIFO;
struct sched_param sp;
max_pri = sched_get_priority_max(policy);
if (priority > max_pri) {
fprintf(stderr, "Invalid scheduling priority (maximum %d).\n", max_pri);
return -1;
}
sp.sched_priority = priority;
errno = pthread_setschedparam(pthread_self(), policy, &sp);
if (errno) {
perror("pthread_setschedparam");
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.9/realtime.h 0000664 0000000 0000000 00000002570 14427473437 0014350 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/rig.c 0000664 0000000 0000000 00000011710 14427473437 0013316 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/rig.h 0000664 0000000 0000000 00000001643 14427473437 0013327 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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.9/scan 0000775 0000000 0000000 00000002267 14427473437 0013252 0 ustar 00root root 0000000 0000000 #!/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]\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 '
{
# /[[.]] - .ext
s:/\([A-H]\?[A0-9]\?[0-9].\? \+\)\?\([^/]*\) \+- \+\([^/]*\)\.[A-Z0-9]*$:\0\t\2\t\3:pi
t
# / - [/(Disc|Side) ]/[[.]] .ext
s:/\([^/]*\) \+- \+\([^/]*\)\(/\(disc\|side\) [0-9A-Z][^/]*\)\?/\([A-H]\?[A0-9]\?[0-9].\? \+\)\?\([^/]*\)\.[A-Z0-9]*$:\0\t\1\t\6:pi
t
# /[[.]] .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.9/selector.c 0000664 0000000 0000000 00000024004 14427473437 0014355 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#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.9/selector.h 0000664 0000000 0000000 00000004144 14427473437 0014365 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#ifndef SELECTOR_H
#define SELECTOR_H
#include
#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.9/spin.h 0000664 0000000 0000000 00000004001 14427473437 0013506 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* Spinlock routines for synchronising with the realtime thread
*/
#ifndef SPIN_H
#define SPIN_H
#include
#include
#include
#include
#include
#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.9/status.c 0000664 0000000 0000000 00000002717 14427473437 0014067 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#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.9/status.h 0000664 0000000 0000000 00000002103 14427473437 0014061 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* Implement a global one-line status console
*/
#ifndef STATUS_H
#define STATUS_H
#include
#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.9/tests/ 0000775 0000000 0000000 00000000000 14427473437 0013533 5 ustar 00root root 0000000 0000000 xwax-1.9/tests/cues.c 0000664 0000000 0000000 00000001715 14427473437 0014642 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#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.9/tests/external.c 0000664 0000000 0000000 00000003365 14427473437 0015530 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#include
#include
#include
#include
#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.9/tests/library.c 0000664 0000000 0000000 00000003131 14427473437 0015341 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#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 [...]\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.9/tests/midi.c 0000664 0000000 0000000 00000003417 14427473437 0014626 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#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 \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.9/tests/observer.c 0000664 0000000 0000000 00000002134 14427473437 0015526 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#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.9/tests/scan-bpm 0000775 0000000 0000000 00000000455 14427473437 0015165 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.9/tests/status.c 0000664 0000000 0000000 00000002300 14427473437 0015215 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#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.9/tests/timecoder.c 0000664 0000000 0000000 00000003232 14427473437 0015652 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#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.9/tests/track.c 0000664 0000000 0000000 00000002353 14427473437 0015006 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#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 \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.9/tests/ttf.c 0000664 0000000 0000000 00000004110 14427473437 0014470 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
/*
* Illustrate clipping of characters
*/
#include
#include
#include
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 \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.9/thread.c 0000664 0000000 0000000 00000003322 14427473437 0014004 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see .
*
*/
#include
#include
#include
#include
#include
#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.9/thread.h 0000664 0000000 0000000 00000001552 14427473437 0014014 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Mark Hills
*
* This file is part of "xwax".
*
* "xwax" is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 3 as
* published by the Free Software Foundation.
*
* "xwax" 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, see