Merge branch 'teleinfo' of https://github.com/hallard/Tasmota into teleinfo

This commit is contained in:
Charles 2020-08-16 11:54:19 +02:00
commit 30e786c0b8
612 changed files with 270976 additions and 265 deletions

View File

@ -53,10 +53,12 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog ## Changelog
### Version 8.4.0.1 ### Version 8.4.0.2
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Fix ESP32 PWM range - Fix ESP32 PWM range
- Add Zigbee better support for IKEA Motion Sensor - Add Zigbee better support for IKEA Motion Sensor
- Add ESP32 Analog input support for GPIO32 to GPIO39 - Add ESP32 Analog input support for GPIO32 to GPIO39
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig`` - Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046) - Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
- Add better config corruption recovery (#9046)

View File

@ -0,0 +1,88 @@
# Run whenever a PR is generated or updated.
# Most jobs check out the code, ensure Python3 is installed, and for build
# tests the ESP8266 toolchain is cached when possible to speed up execution.
name: ESP8266Audio
on:
push:
branches:
- master
pull_request:
jobs:
build-esp8266:
name: Build ESP8266
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Build Sketches
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
BUILD_TYPE: build
BUILD_MOD: 5
BUILD_REM: ${{ matrix.chunk }}
run: |
bash ./tests/common.sh
build-esp32:
name: Build ESP-32
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Build Sketches
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
BUILD_TYPE: build_esp32
BUILD_MOD: 5
BUILD_REM: ${{ matrix.chunk }}
run: |
bash ./tests/common.sh
# Run host test suite under valgrind for runtime checking of code.
host-tests:
name: Host tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run host tests
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install valgrind lcov gcc-multilib g++-multilib libc6-dbg:i386
cd ./tests/host/
make
valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all --error-exitcode=999 ./mp3
valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all --error-exitcode=999 ./aac
valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all --error-exitcode=999 ./wav
valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all --error-exitcode=999 ./midi

674
lib/ESP8266Audio/LICENSE Executable file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 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 <http://www.gnu.org/licenses/>.
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:
{project} Copyright (C) {year} {fullname}
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
<http://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

264
lib/ESP8266Audio/README.md Executable file
View File

@ -0,0 +1,264 @@
# ESP8266Audio - supports ESP8266 & ESP32 [![Gitter](https://badges.gitter.im/ESP8266Audio/community.svg)](https://gitter.im/ESP8266Audio/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Arduino library for parsing and decoding MOD, WAV, MP3, FLAC, MIDI, AAC, and RTTL files and playing them on an I2S DAC or even using a software-simulated delta-sigma DAC with dynamic 32x-128x oversampling.
ESP8266 is fully supported and most mature, but ESP32 is also mostly there with built-in DAC as well as external ones.
For real-time, autonomous speech synthesis, check out [ESP8266SAM](https://github.com/earlephilhower/ESP8266SAM), a library which uses this one and a port of an ancient format-based synthesis program to allow your ESP8266 to talk with low memory and no network required.
## Disclaimer
All this code is released under the GPL, and all of it is to be used at your own risk. If you find any bugs, please let me know via the GitHub issue tracker or drop me an email. The MOD and MP3 routines were taken from StellaPlayer and libMAD respectively. The software I2S delta-sigma 32x oversampling DAC was my own creation, and sounds quite good if I do say so myself.
The AAC decode code is from the Helix project and licensed under RealNetwork's RSPL license. For commercial use you're still going to need the usual AAC licensing from [Via Licensing](http://www.via-corp.com/us/en/licensing/aac/overview.html).
On the ESP32, AAC-SBR is supported (many webradio stations use this to reduce bandwidth even further). The ESP8266, however, does not support it due to a lack of onboard RAM.
MIDI decoding comes from a highly ported [MIDITONES](https://github.com/LenShustek/miditones) combined with a massively memory-optimized [TinySoundFont](https://github.com/schellingb/TinySoundFont), see the respective source files for more information.
Opus, OGG, and OpusFile are from [Xiph.org](https://xiph.org) with the Xiph license and patent described in src/{opusfile,libggg,libopus}/COPYING.. **NOTE** Opus decoding currently only works on the ESP32 due to the large memory requirements of opusfile. PRs to rewrite it to be less memory intensive would be much appreciated.
## Neat Things People Have Done With ESP8266Audio
If you have a neat use for this library, [I'd love to hear about it](mailto:earlephilhower@yahoo.com)!
My personal use of the ESP8266Audio library is only to drive a 3D-printed, network-time-setting alarm clock for my kids which can play an MP3 instead of a bell to wake them up, called [Psychoclock](https://github.com/earlephilhower/psychoclock).
Harald Sattler has built a neat German [word clock with MP3 alarm](http://www.harald-sattler.de/html/mini-wecker.htm). Detailed discussion on the process and models are included.
Erich Heinemann has developed a Stomper (instrument for playing samples in real-time during a live stage performance) that you can find more info about [here](https://github.com/ErichHeinemann/hman-stomper).
Dagnall53 has integrated this into a really neat MQTT based model train controller to add sounds to his set. More info is available [here](https://github.com/dagnall53/ESPMQTTRocnetSound), including STL files for 3D printed components!
JohannesMTC has built a similar project especially for model trains: https://github.com/JohannesMTC/ESP32_MAS
A neat MQTT-driven ESP8266 light-and-sound device (alarm? toy? who can say!) was built by @CosmicMac, available at https://github.com/CosmicMac/ESParkle
A very interesting "linear clock" with a stepper motor, NTP time keeping, and configurable recorded chimes with schematics, 3D printer plans, and source code, is now available http://home.kpn.nl/bderogee1980/projects/linear_clock/linear_clock.html
## Prerequisites
First, make sure you are running the 2.6.3/later or GIT head version of the Arduino libraries for ESP8266, or the latest ESP32 SDK from Espressif.
You can use GIT to pull right from GitHub: see [this README](https://github.com/esp8266/Arduino/blob/master/README.md#using-git-version) for detailed instructions.
## Installation
Install the library in your ~/Arduino/libraries
```sh
mkdir -p ~/Arduino/libraries
cd ~/Arduino/libraries
git clone https://github.com/earlephilhower/ESP8266Audio
```
When in the IDE please select the following options on the ESP8266:
```
Tools->lwIP Variant->v1.4 Open Source, or V2 Higher Bandwidth
Tools->CPU Frequency->160MHz
```
## Usage
Create an AudioInputXXX source pointing to your input file, an AudioOutputXXX sink as either an I2S, I2S-sw-DAC, or as a "SerialWAV" which simply writes a WAV file to the Serial port which can be dumped to a file on your development system, and an AudioGeneratorXXX to actually take that input and decode it and send to the output.
After creation, you need to call the AudioGeneratorXXX::loop() routine from inside your own main loop() one or more times. This will automatically read as much of the file as needed and fill up the I2S buffers and immediately return. Since this is not interrupt driven, if you have large delay()s in your code, you may end up with hiccups in playback. Either break large delays into very small ones with calls to AudioGenerator::loop(), or reduce the sampling rate to require fewer samples per second.
## Example
See the examples directory for some simple examples, but the following snippet can play an MP3 file over the simulated I2S DAC:
```cpp
#include <Arduino.h>
#include "AudioFileSourceSPIFFS.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"
AudioGeneratorMP3 *mp3;
AudioFileSourceSPIFFS *file;
AudioOutputI2SNoDAC *out;
void setup()
{
Serial.begin(115200);
delay(1000);
SPIFFS.begin();
file = new AudioFileSourceSPIFFS("/jamonit.mp3");
out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3();
mp3->begin(file, out);
}
void loop()
{
if (mp3->isRunning()) {
if (!mp3->loop()) mp3->stop();
} else {
Serial.printf("MP3 done\n");
delay(1000);
}
}
```
## AudioFileSource classes
AudioFileSource: Base class which implements a very simple read-only "file" interface. Required because it seems everyone has invented their own filesystem on the Arduino with their own unique twist. Using this wrapper lets that be abstracted and makes the AudioGenerator simpler as it only calls these simple functions.
AudioFileSourceSPIFFS: Reads a file from the SPIFFS filesystem
AudioFileSourcePROGMEM: Reads a file from a PROGMEM array. Under UNIX you can use "xxd -i file.mp3 > file.h" to get the basic format, then add "const" and "PROGMEM" to the generated array and include it in your sketch. See the example .h files for a concrete example.
AudioFileSourceHTTPStream: Simple implementation of a streaming HTTP reader for ShoutCast-type MP3 streaming. Not yet resilient, and at 44.1khz 128bit stutters due to CPU limitations, but it works more or less.
## AudioFileSourceBuffer - Double buffering, useful for HTTP streams
AudioFileSourceBuffer is an input source that simpy adds an additional RAM buffer of the output of any other AudioFileSource. This is particularly useful for web streaming where you need to have 1-2 packets in memory to ensure hiccup-free playback.
Create your standard input file source, create the buffer with the original source as its input, and pass this buffer object to the generator.
```cpp
...
AudioGeneratorMP3 *mp3;
AudioFileSourceHTTPStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2SNoDAC *out;
...
// Create the HTTP stream normally
file = new AudioFileSourceHTTPStream("http://your.url.here/mp3");
// Create a buffer using that stream
buff = new AudioFileSourceBuffer(file, 2048);
out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3();
// Pass in the *buffer*, not the *http stream* to enable buffering
mp3->begin(buff, out);
...
```
## AudioFileSourceID3 - ID3 stream parser filter with a user-specified callback
This class, which takes as input any other AudioFileSource and outputs an AudioFileSource suitable for any decoder, automatically parses out ID3 tags from MP3 files. You need to specify a callback function, which will be called as tags are decoded and allow you to update your UI state with this information. See the PlayMP3FromSPIFFS example for more information.
## AudioGenerator classes
AudioGenerator: Base class for all file decoders. Takes a AudioFileSource and an AudioOutput object to get the data from and to write decoded samples to. Call its loop() function as often as you can to ensure the buffers are always kept full and your music won't skip.
AudioGeneratorWAV: Reads and plays Microsoft WAVE (.WAV) format files of 8 or 16 bits.
AudioGeneratorMOD: Reads and plays Amiga ModTracker files (.MOD). Use a 160MHz clock as this requires tons of SPIFFS reads (which are painfully slow) to get raw instrument sample data for every output sample. See https://modarchive.org for many free MOD files.
AudioGeneratorMP3: Reads and plays MP3 format files (.MP3) using a ported libMAD library. Use a 160MHz clock to ensure enough compute power to decode 128KBit 44.1KHz without hiccups. For complete porting history with the gory details, look at https://github.com/earlephilhower/libmad-8266
AudioGeneratorFLAC: Plays FLAC files via ported libflac-1.3.2. On the order of 30KB heap and minimal stack required as-is.
AudioGeneratorMIDI: Plays a MIDI file using a wavetable synthesizer and a SoundFont2 wavetable input. Theoretically up to 16 simultaneous notes available, but depending on the memory needed for the SF2 structures you may not be able to get that many before hitting OOM.
AudioGeneratorAAC: Requires about 30KB of heap and plays a mono or stereo AAC file using the Helix fixed-point AAC decoder.
AudioGeneratorRTTTL: Enjoy the pleasures of monophonic, 4-octave ringtones on your ESP8266. Very low memory and CPU requirements for simple tunes.
## AudioOutput classes
AudioOutput: Base class for all output drivers. Takes a sample at a time and returns true/false if there is buffer space for it. If it returns false, it is the calling object's (AudioGenerator's) job to keep the data that didn't fit and try again later.
AudioOutputI2S: Interface for any I2S 16-bit DAC. Sends stereo or mono signals out at whatever frequency set. Tested with Adafruit's I2SDAC and a Beyond9032 DAC from eBay. Tested up to 44.1KHz. To use the internal DAC on ESP32, instantiate this class as `AudioOutputI2S(0,1)`, see example `PlayMODFromPROGMEMToDAC` and code in [AudioOutputI2S.cpp](src/AudioOutputI2S.cpp#L29) for details.
AudioOutputI2SNoDAC: Abuses the I2S interface to play music without a DAC. Turns it into a 32x (or higher) oversampling delta-sigma DAC. Use the schematic below to drive a speaker or headphone from the I2STx pin (i.e. Rx). Note that with this interface, depending on the transistor used, you may need to disconnect the Rx pin from the driver to perform serial uploads. Mono-only output, of course.
AudioOutputSPDIF (experimental): Another way to abuse the I2S peripheral to send out BMC encoded S/PDIF bitstream. To interface with S/PDIF receiver it needs optical or coaxial transceiver, for which some examples can be found at https://www.epanorama.net/documents/audio/spdif.html. It should work even with the simplest form with red LED and current limiting resistor, fed into TOSLINK cable. Minimum sample rate supported by is 32KHz. Due to BMC coding, actual symbol rate on the pin is 4x normal I2S data rate, which drains DMA buffers quickly. See more details inside [AudioOutputSPDIF.cpp](src/AudioOutputSPDIF.cpp#L17)
AudioOutputSerialWAV: Writes a binary WAV format with headers to the Serial port. If you capture the serial output to a file you can play it back on your development system.
AudioOutputSPIFFSWAV: Writes a binary WAV format with headers to a SPIFFS filesystem. Ensure the FS is mounted and SPIFFS is started before calling. USe the SetFilename() call to pick the output file before starting.
AudioOutputNull: Just dumps samples to /dev/null. Used for speed testing as it doesn't artificially limit the AudioGenerator output speed since there are no buffers to fill/drain.
## I2S DACs
I've used both the Adafruit [I2S +3W amp DAC](https://www.adafruit.com/product/3006) and a generic PCM5102 based DAC with success. The biggest problems I've seen from users involve pinouts from the ESP8266 for GPIO and hooking up all necessary pins on the DAC board.
### Adafruit I2S DAC
This is quite simple and only needs the GND, VIN, LRC, BCLK< and DIN pins to be wired. Be sure to use +5V on the VIN to get the loudest sound. See the [Adafruit example page](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp) for more info.
### PCM5102 DAC
I've used several versions of PCM5102 DAC boards purchased from eBay. They've all had the same pinout, no matter the form factor. There are several input configuration pins beyond the I2S interface itself that need to be wired:
* 3.3V from ESP8266 -> VCC, 33V, XMT
* GND from ESP8266 -> GND, FLT, DMP, FMT, SCL
* (Standard I2S interface) BCLK->BCK, I2SO->DIN, and LRCLK(WS)->LCK
### Others
There are many other variants out there, and they should all work reasonably well with this code and the ESP8266. Please be certain you've read the datasheet and are applying proper input voltages, and be sure to tie off any unused inputs to GND or VCC as appropriate. LEaving an input pin floating on any integrated circuit can cause unstable operation as it may pick up noise from the environment (very low input capacitance) and cause havoc with internal IC settings.
## Software I2S Delta-Sigma DAC (i.e. playing music with a single transistor and speaker)
For the best fidelity, and stereo to boot, spend the money on a real I2S DAC. Adafruit makes a great mono one with amplifier, and you can find stereo unamplified ones on eBay or elsewhere quite cheaply. However, thanks to the software delta-sigma DAC with 32x oversampling (up to 128x if the audio rate is low enough) you can still have pretty good sound!
Use the `AudioOutputI2S*No*DAC` object instead of the `AudioOutputI2S` in your code, and the following schematic to drive a 2-3W speaker using a single $0.05 NPN 2N3904 transistor and ~1K resistor:
```
2N3904 (NPN)
+---------+
| | +-|
| E B C | / S|
+-|--|--|-+ | P|
| | +------+ E|
| | | A|
ESP8266-GND ------------------+ | +------+ K|
| | | E|
ESP8266-I2SOUT (Rx) -----/\/\/\--+ | \ R|
| +-|
USB 5V -----------------------------+
You may also want to add a 220uF cap from USB5V to GND just to help filter out any voltage droop during high volume playback.
```
If you don't have a 5V source available on your ESP model, you can use the 5V from your USB serial adapter, or even the 3V from the ESP8266 (but it'll be lower volume). Don't try and drive the speaker without the transistor, the ESP8266 pins can't give enough current to drive even a headphone well and you may end up damaging your device.
Connections are as a follows:
```
ESP8266-RX(I2S tx) -- Resistor (~1K ohm, not critical) -- 2N3904 Base
ESP8266-GND -- 2N3904 Emitter
USB-5V -- Speaker + Terminal
2N3904-Collector -- Speaker - Terminal
```
*NOTE*: A prior version of this schematic had a direct connection from the ESP8266 to the base of the transistor. While this does provide the maximum amplitude, it also can draw more current from the 8266 than is safe, and can also cause the transistor to overheat.
As of the latest ESP8266Audio release, with the software delta-sigma DAC the LRCLK and BCLK pins *can* be used by an application. Simply use normal `pinMode` and `dicitalWrite` or `digitalRead` as desired.
### High pitched buzzing with the 1-T circuit
The 1-T amp can _NOT_ drive any sort of amplified speaker. If there is a power or USB input to the speaker, or it has lights or Bluetooth or a battery, it can _NOT_ be used with this circuit.
The 1T output is a binary signal at 0 or 5V, with nothing in between. When you connect to a 8ohm paper physical speaker directly, the speaker cone itself has inertia and acts as a low pass filter and averages the density of pulses in order to give a nice, analog output.
When you feed the 1T output to an amp you are alternatively grounding and overdriving the op-amp's input at a high frequency. That causes ringing and the opamp has a frequency response high enough to amplify the high frequency noise and you get that buzzing.
The same problem may happen with piezo speakers. They have a very high frequency response, normally, and have (almost) no inertia. So you hear the buzzing at high frequency.
You could attach the 1T output to a low pass and feed that into an amplifier. But at that point it is easier to just get an I2S DAC and avoid the whole thing (plus get stereo and true 16-bit output).
### Debugging the 1-T amp circuit, compliments of @msmcmickey
If you've built the amp but are not getting any sound, @msmcmickey wrote up a very good debugging sequence to check:
0. Please double-check the wiring. GPIO pins and board pins are not always the same and vary immensely between brands of ESP8266 carrier boards.
1. Is the transistor connected properly? Check the datasheet for this package style and make sure you have the leads connected properly. This package has three leads, and the lead that is by itself in the middle of the one side is the collector, not the base as you might expect it to be.
2. If connected properly, do you have ~5 volts between the collector and emitter?
3. Was the transistor possibly damaged/overheated during soldering, or by connecting it improperly? Out-of-circuit diode check voltage drop test using a multimeter from base->emitter and base->collector should be between .5 and .7 volts. If it's shorted or open or conducting in both directions, then replace it and make sure it's connected properly.
## SPDIF optical output
The proper way would be using optical TOSLINK transmitter (i.e. TOTXxxx). For testing, you can try with ~660nm red LED and resistor. Same as your basic Blink project with external LED, just that the LED will blink a bit faster.
```
____
ESP Pin -------|____|--------+
|
---
V LED
---
|
Ground ---------------------+
```
For ESP8266 with red LED (~1.9Vf drop) you need minimum 150Ohm resistor (12mA max per pin), and output pin is fixed (GPIO3/RX0).On ESP32 it is confgurable with `AudioOutputSPDIF(gpio_num)`.
## Using external SPI RAM to increase buffer
A class allows you to use a 23lc1024 SPI RAM from Microchip as input buffer. This chip connects to ESP8266 HSPI port and provides a large buffer to help avoid hiccus in playback of web streams.
The current version allows for using the standard hardware CS (GPIO15) or any other pin via software at slightly less performance. The following schematic shows one example:
![Example of SPIRAM Schematic](examples/StreamMP3FromHTTP_SPIRAM/Schema_Spiram.png)
## Notes for using SD cards and ESP8266Audio on Wemos shields
I've been told the Wemos SD card shield uses GPIO15 as the SD chip select. This needs to be changed because GPIO15 == I2SBCLK, and is driven even if you're using the NoDAC option. Once you move the CS to another pin and update your program it should work fine.
## Porting to other microcontrollers
There's no ESP8266-specific code in the AudioGenerator routines, so porting to other controllers should be relatively easy assuming they have the same endianness as the Xtensa core used. Drop me a line if you're doing this, I may be able to help point you in the right direction.
## Thanks
Thanks to the authors of StellaPlayer and libMAD for releasing their code freely, and to the maintainers and contributors to the ESP8266 Arduino port.
Also, big thanks to @tueddy for getting the initial ESP32 porting into the tree!
-Earle F. Philhower, III
earlephilhower@yahoo.com

View File

@ -0,0 +1,68 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"
#include "AudioOutputMixer.h"
// VIOLA sample taken from https://ccrma.stanford.edu/~jos/pasp/Sound_Examples.html
#include "viola.h"
AudioGeneratorWAV *wav[2];
AudioFileSourcePROGMEM *file[2];
AudioOutputI2S *out;
AudioOutputMixer *mixer;
AudioOutputMixerStub *stub[2];
void setup()
{
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
delay(1000);
Serial.printf("WAV start\n");
audioLogger = &Serial;
file[0] = new AudioFileSourcePROGMEM( viola, sizeof(viola) );
out = new AudioOutputI2S();
mixer = new AudioOutputMixer(32, out);
stub[0] = mixer->NewInput();
stub[0]->SetGain(0.3);
wav[0] = new AudioGeneratorWAV();
wav[0]->begin(file[0], stub[0]);
// Begin wav[1] later in loop()
Serial.printf("starting 1\n");
}
void loop()
{
static uint32_t start = 0;
static bool go = false;
if (!start) start = millis();
if (wav[0]->isRunning()) {
if (!wav[0]->loop()) { wav[0]->stop(); stub[0]->stop(); Serial.printf("stopping 1\n"); }
}
if (millis()-start > 3000) {
if (!go) {
Serial.printf("starting 2\n");
stub[1] = mixer->NewInput();
stub[1]->SetGain(0.4);
wav[1] = new AudioGeneratorWAV();
file[1] = new AudioFileSourcePROGMEM( viola, sizeof(viola) );
wav[1]->begin(file[1], stub[1]);
go = true;
}
if (wav[1]->isRunning()) {
if (!wav[1]->loop()) { wav[1]->stop(); stub[1]->stop(); Serial.printf("stopping 2\n");}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
#include <Arduino.h>
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"
#include "AudioFileSourcePROGMEM.h"
#include "sampleaac.h"
AudioFileSourcePROGMEM *in;
AudioGeneratorAAC *aac;
AudioOutputI2S *out;
void setup()
{
Serial.begin(115200);
audioLogger = &Serial;
in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
aac = new AudioGeneratorAAC();
out = new AudioOutputI2S();
aac->begin(in, out);
}
void loop()
{
if (aac->isRunning()) {
aac->loop();
} else {
Serial.printf("AAC done\n");
delay(1000);
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
#include <Arduino.h>
#include "AudioFileSourceSD.h"
#include "AudioOutputSPDIF.h"
#include "AudioGeneratorFLAC.h"
// For this sketch, you need connected SD card with '.flac' music files in the root
// directory. Some samples with various sampling rates are available from i.e.
// Espressif Audio Development Framework at:
// https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/audio-samples.html
//
// On ESP8266 you might need to reencode FLAC files with max '-2' compression level
// (i.e. 1152 maximum block size) or you will run out of memory. FLAC files will be
// slightly bigger but you don't loose audio quality with reencoding (lossles codec).
// You may need a fast SD card. Set this as high as it will work (40MHz max).
#define SPI_SPEED SD_SCK_MHZ(40)
// On ESP32 you can adjust the SPDIF_OUT_PIN (GPIO number).
// On ESP8266 it is fixed to GPIO3/RX0 and this setting has no effect
#define SPDIF_OUT_PIN 27
File dir;
AudioFileSourceSD *source = NULL;
AudioOutputSPDIF *output = NULL;
AudioGeneratorFLAC *decoder = NULL;
void setup() {
Serial.begin(115200);
Serial.println();
delay(1000);
audioLogger = &Serial;
source = new AudioFileSourceSD();
output = new AudioOutputSPDIF(SPDIF_OUT_PIN);
decoder = new AudioGeneratorFLAC();
// NOTE: SD.begin(...) should be called AFTER AudioOutputSPDIF()
// to takover the the SPI pins if they share some with I2S
// (i.e. D8 on Wemos D1 mini is both I2S BCK and SPI SS)
#if defined(ESP8266)
SD.begin(SS, SPI_SPEED);
#else
SD.begin();
#endif
dir = SD.open("/");
}
void loop() {
if ((decoder) && (decoder->isRunning())) {
if (!decoder->loop()) decoder->stop();
} else {
File file = dir.openNextFile();
if (file) {
if (String(file.name()).endsWith(".flac")) {
source->close();
if (source->open(file.name())) {
Serial.printf_P(PSTR("Playing '%s' from SD card...\n"), file.name());
decoder->begin(source, output);
} else {
Serial.printf_P(PSTR("Error opening '%s'\n"), file.name());
}
}
} else {
Serial.println(F("Playback form SD card done\n"));
delay(1000);
}
}
}

View File

@ -0,0 +1,33 @@
#include <Arduino.h>
#include <AudioOutputI2S.h>
#include <AudioFileSourcePROGMEM.h>
#include <AudioGeneratorFLAC.h>
#include "sample.h"
AudioOutputI2S *out;
AudioFileSourcePROGMEM *file;
AudioGeneratorFLAC *flac;
void setup()
{
Serial.begin(115200);
Serial.println("Starting up...\n");
audioLogger = &Serial;
file = new AudioFileSourcePROGMEM( sample_flac, sizeof(sample_flac) );
out = new AudioOutputI2S();
flac = new AudioGeneratorFLAC();
flac->begin(file, out);
}
void loop()
{
if (flac->isRunning()) {
if (!flac->loop()) flac->stop();
} else {
Serial.printf("FLAC done\n");
delay(1000);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
#include <Arduino.h>
#ifdef ESP32
void setup() {
Serial.begin(115200);
Serial.printf("ERROR - ESP32 does not support LittleFS\n");
}
void loop() {}
#else
#include <ESP8266WiFi.h>
#include <AudioOutputI2S.h>
#include <AudioGeneratorMIDI.h>
#include <AudioFileSourceLittleFS.h>
AudioFileSourceLittleFS *sf2;
AudioFileSourceLittleFS *mid;
AudioOutputI2S *dac;
AudioGeneratorMIDI *midi;
void setup()
{
const char *soundfont = "/1mgm.sf2";
const char *midifile = "/furelise.mid";
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
Serial.println("Starting up...\n");
audioLogger = &Serial;
sf2 = new AudioFileSourceLittleFS(soundfont);
mid = new AudioFileSourceLittleFS(midifile);
dac = new AudioOutputI2S();
midi = new AudioGeneratorMIDI();
midi->SetSoundfont(sf2);
midi->SetSampleRate(22050);
Serial.printf("BEGIN...\n");
midi->begin(mid, dac);
}
void loop()
{
if (midi->isRunning()) {
if (!midi->loop()) {
uint32_t e = millis();
midi->stop();
}
} else {
Serial.printf("MIDI done\n");
delay(1000);
}
}
#endif

Binary file not shown.

View File

@ -0,0 +1,55 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include "SPIFFS.h"
#else
#include <ESP8266WiFi.h>
#endif
#include <AudioOutputNull.h>
#include <AudioOutputI2S.h>
#include <AudioGeneratorMIDI.h>
#include <AudioFileSourceSPIFFS.h>
AudioFileSourceSPIFFS *sf2;
AudioFileSourceSPIFFS *mid;
AudioOutputI2S *dac;
AudioGeneratorMIDI *midi;
void setup()
{
const char *soundfont = "/1mgm.sf2";
const char *midifile = "/furelise.mid";
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
Serial.println("Starting up...\n");
audioLogger = &Serial;
sf2 = new AudioFileSourceSPIFFS(soundfont);
mid = new AudioFileSourceSPIFFS(midifile);
dac = new AudioOutputI2S();
midi = new AudioGeneratorMIDI();
midi->SetSoundfont(sf2);
midi->SetSampleRate(22050);
Serial.printf("BEGIN...\n");
midi->begin(mid, dac);
}
void loop()
{
if (midi->isRunning()) {
if (!midi->loop()) {
uint32_t e = millis();
midi->stop();
}
} else {
Serial.printf("MIDI done\n");
delay(1000);
}
}

Binary file not shown.

View File

@ -0,0 +1,44 @@
#include <Arduino.h>
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorMOD.h"
#include "AudioOutputI2S.h"
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
// enigma.mod sample from the mod archive: https://modarchive.org/index.php?request=view_by_moduleid&query=42146
#include "enigma.h"
AudioGeneratorMOD *mod;
AudioFileSourcePROGMEM *file;
AudioOutputI2S *out;
void setup()
{
WiFi.mode(WIFI_OFF); //WiFi.forceSleepBegin();
Serial.begin(115200);
delay(1000);
audioLogger = &Serial;
file = new AudioFileSourcePROGMEM( enigma_mod, sizeof(enigma_mod) );
// out = new AudioOutputI2S(0, 1); Uncomment this line, comment the next one to use the internal DAC channel 1 (pin25) on ESP32
out = new AudioOutputI2S();
mod = new AudioGeneratorMOD();
mod->SetBufferSize(3*1024);
mod->SetSampleRate(44100);
mod->SetStereoSeparation(32);
mod->begin(file, out);
}
void loop()
{
if (mod->isRunning()) {
if (!mod->loop()) mod->stop();
} else {
Serial.printf("MOD done\n");
delay(1000);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include "SPIFFS.h"
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceSPIFFS.h"
#include "AudioFileSourceID3.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"
// To run, set your ESP8266 build to 160MHz, and include a SPIFFS of 512KB or greater.
// Use the "Tools->ESP8266/ESP32 Sketch Data Upload" menu to write the MP3 to SPIFFS
// Then upload the sketch normally.
// pno_cs from https://ccrma.stanford.edu/~jos/pasp/Sound_Examples.html
AudioGeneratorMP3 *mp3;
AudioFileSourceSPIFFS *file;
AudioOutputI2SNoDAC *out;
AudioFileSourceID3 *id3;
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
(void)cbData;
Serial.printf("ID3 callback for: %s = '", type);
if (isUnicode) {
string += 2;
}
while (*string) {
char a = *(string++);
if (isUnicode) {
string++;
}
Serial.printf("%c", a);
}
Serial.printf("'\n");
Serial.flush();
}
void setup()
{
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
delay(1000);
SPIFFS.begin();
Serial.printf("Sample MP3 playback begins...\n");
audioLogger = &Serial;
file = new AudioFileSourceSPIFFS("/pno-cs.mp3");
id3 = new AudioFileSourceID3(file);
id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3();
mp3->begin(id3, out);
}
void loop()
{
if (mp3->isRunning()) {
if (!mp3->loop()) mp3->stop();
} else {
Serial.printf("MP3 done\n");
delay(1000);
}
}

Binary file not shown.

View File

@ -0,0 +1,99 @@
#include <Arduino.h>
#ifdef ESP32
#include "SPIFFS.h"
#endif
#include "AudioFileSourceSPIFFS.h"
#include "AudioFileSourceID3.h"
#include "AudioOutputSPDIF.h"
#include "AudioGeneratorMP3.h"
// To run, set your ESP8266 build to 160MHz, and include a SPIFFS partition
// big enough to hold your MP3 file. Find suitable MP3 file from i.e.
// https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/audio-samples.html
// and download it into 'data' directory. Use the "Tools->ESP8266/ESP32 Sketch Data Upload"
// menu to write the MP3 to SPIFFS. Then upload the sketch normally.
AudioFileSourceSPIFFS *file;
AudioFileSourceID3 *id3;
AudioOutputSPDIF *out;
AudioGeneratorMP3 *mp3;
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
(void)cbData;
Serial.printf("ID3 callback for: %s = '", type);
if (isUnicode) {
string += 2;
}
while (*string) {
char a = *(string++);
if (isUnicode) {
string++;
}
Serial.printf("%c", a);
}
Serial.printf("'\n");
Serial.flush();
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println();
audioLogger = &Serial;
SPIFFS.begin();
file = new AudioFileSourceSPIFFS();
id3 = NULL;
out = new AudioOutputSPDIF();
mp3 = new AudioGeneratorMP3();
String fileName = "";
// Find first MP3 file in SPIFF and play it
#ifdef ESP32
File dir, root = SPIFFS.open("/");
while ((dir = root.openNextFile())) {
if (String(dir.name()).endsWith(".mp3")) {
if (file->open(dir.name())) {
fileName = String(dir.name());
break;
}
}
dir = root.openNextFile();
}
#else
Dir dir = SPIFFS.openDir("");
while (dir.next()) {
if (dir.fileName().endsWith(".mp3")) {
if (file->open(dir.fileName().c_str())) {
fileName = dir.fileName();
break;
}
}
}
#endif
if (fileName.length() > 0) {
id3 = new AudioFileSourceID3(file);
id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
mp3->begin(id3, out);
Serial.printf("Playback of '%s' begins...\n", fileName.c_str());
} else {
Serial.println("Can't find .mp3 file in SPIFFS");
}
}
void loop()
{
if (mp3->isRunning()) {
if (!mp3->loop()) mp3->stop();
} else {
Serial.println("MP3 done");
delay(1000);
}
}

View File

@ -0,0 +1,41 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include "SPIFFS.h"
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceSPIFFS.h"
#include "AudioGeneratorOpus.h"
#include "AudioOutputI2S.h"
// The includes OPUS file is from Kevin MacLeod (incompetech.com), Licensed under Creative Commons: By Attribution 3.0, http://creativecommons.org/licenses/by/3.0/
AudioGeneratorOpus *opus;
AudioFileSourceSPIFFS *file;
AudioOutputI2S *out;
void setup()
{
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
delay(1000);
SPIFFS.begin();
Serial.printf("Sample Opus playback begins...\n");
audioLogger = &Serial;
file = new AudioFileSourceSPIFFS("/gs-16b-2c-44100hz.opus");
out = new AudioOutputI2S();
opus = new AudioGeneratorOpus();
opus->begin(file, out);
}
void loop()
{
if (opus->isRunning()) {
if (!opus->loop()) opus->stop();
} else {
Serial.printf("Opus done\n");
delay(1000);
}
}

View File

@ -0,0 +1,36 @@
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorRTTTL.h"
#include "AudioOutputI2S.h"
const char rudolph[] PROGMEM =
"Rudolph the Red Nosed Raindeer:d=8,o=5,b=250:g,4a,g,4e,4c6,4a,2g.,g,a,g,a,4g,4c6,2b.,4p,f,4g,f,4d,4b,4a,2g.,g,a,g,a,4g,4a,2e.,4p,g,4a,a,4e,4c6,4a,2g.,g,a,g,a,4g,4c6,2b.,4p,f,4g,f,4d,4b,4a,2g.,g,a,g,a,4g,4d6,2c.6,4p,4a,4a,4c6,4a,4g,4e,2g,4d,4e,4g,4a,4b,4b,2b,4c6,4c6,4b,4a,4g,4f,2d,g,4a,g,4e,4c6,4a,2g.,g,a,g,a,4g,4c6,2b.,4p,f,4g,f,4d,4b,4a,2g.,4g,4a,4g,4a,2g,2d6,1c.6.";
// Plenty more at: http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/MFC/RingTones.RTTTL
AudioGeneratorRTTTL *rtttl;
AudioFileSourcePROGMEM *file;
AudioOutputI2S *out;
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("RTTTL start\n");
audioLogger = &Serial;
file = new AudioFileSourcePROGMEM( rudolph, strlen_P(rudolph) );
out = new AudioOutputI2S();
rtttl = new AudioGeneratorRTTTL();
rtttl->begin(file, out);
}
void loop()
{
if (rtttl->isRunning()) {
if (!rtttl->loop()) rtttl->stop();
} else {
Serial.printf("RTTTL done\n");
delay(1000);
}
}

View File

@ -0,0 +1,42 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2SNoDAC.h"
// VIOLA sample taken from https://ccrma.stanford.edu/~jos/pasp/Sound_Examples.html
#include "viola.h"
AudioGeneratorWAV *wav;
AudioFileSourcePROGMEM *file;
AudioOutputI2SNoDAC *out;
void setup()
{
WiFi.mode(WIFI_OFF);
Serial.begin(115200);
delay(1000);
Serial.printf("WAV start\n");
audioLogger = &Serial;
file = new AudioFileSourcePROGMEM( viola, sizeof(viola) );
out = new AudioOutputI2SNoDAC();
wav = new AudioGeneratorWAV();
wav->begin(file, out);
}
void loop()
{
if (wav->isRunning()) {
if (!wav->loop()) wav->stop();
} else {
Serial.printf("WAV done\n");
delay(1000);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"
// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.
// Enter your WiFi setup here:
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
// Randomly picked URL
const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US";
AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2SNoDAC *out;
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
char s1[32], s2[64];
strncpy_P(s1, type, sizeof(s1));
s1[sizeof(s1)-1]=0;
strncpy_P(s2, string, sizeof(s2));
s2[sizeof(s2)-1]=0;
Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2);
Serial.flush();
}
// Called when there's a warning or error (like a buffer underflow or decode hiccup)
void StatusCallback(void *cbData, int code, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
// Note that the string may be in PROGMEM, so copy it to RAM for printf
char s1[64];
strncpy_P(s1, string, sizeof(s1));
s1[sizeof(s1)-1]=0;
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1);
Serial.flush();
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("Connecting to WiFi");
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Try forever
while (WiFi.status() != WL_CONNECTED) {
Serial.println("...Connecting to WiFi");
delay(1000);
}
Serial.println("Connected");
audioLogger = &Serial;
file = new AudioFileSourceICYStream(URL);
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, 2048);
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3();
mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out);
}
void loop()
{
static int lastms = 0;
if (mp3->isRunning()) {
if (millis()-lastms > 1000) {
lastms = millis();
Serial.printf("Running for %d ms...\n", lastms);
Serial.flush();
}
if (!mp3->loop()) mp3->stop();
} else {
Serial.printf("MP3 done\n");
delay(1000);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,104 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceSPIRAMBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"
// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.
// Enter your WiFi setup here:
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
// Randomly picked URL
const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am";
AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file;
AudioFileSourceSPIRAMBuffer *buff;
AudioOutputI2SNoDAC *out;
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
Serial.printf_P(PSTR("METADATA(%s) '%s' = '%s'\n"), ptr, type, string);
Serial.flush();
}
// Called when there's a warning or error (like a buffer underflow or decode hiccup)
void StatusCallback(void *cbData, int code, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
static uint32_t lastTime = 0;
static int lastCode = -99999;
uint32_t now = millis();
if ((lastCode != code) || (now - lastTime > 1000)) {
Serial.printf_P(PSTR("STATUS(%s) '%d' = '%s'\n"), ptr, code, string);
Serial.flush();
lastTime = now;
lastCode = code;
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("Connecting to WiFi");
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Try forever
while (WiFi.status() != WL_CONNECTED) {
Serial.println("...Connecting to WiFi");
delay(1000);
}
Serial.println("Connected");
audioLogger = &Serial;
file = new AudioFileSourceICYStream(URL);
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
// Initialize 23LC1024 SPI RAM buffer with chip select ion GPIO4 and ram size of 128KByte
buff = new AudioFileSourceSPIRAMBuffer(file, 4, 128*1024);
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3();
mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out);
}
void loop()
{
static int lastms = 0;
if (mp3->isRunning()) {
if (millis()-lastms > 1000) {
lastms = millis();
Serial.printf("Running for %d ms...\n", lastms);
Serial.flush();
}
if (!mp3->loop()) mp3->stop();
} else {
Serial.printf("MP3 done\n");
delay(1000);
}
}

View File

@ -0,0 +1,185 @@
// Talking Clock example, with speech taken from
// https://github.com/going-digital/Talkie/blob/master/Talkie/examples/Vocab_US_Clock/Vocab_US_Clock.ino
// Released under GPL v2
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <time.h>
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorTalkie.h"
#include "AudioOutputI2S.h"
#ifndef STASSID
#define STASSID "NOBABIES"
#define STAPSK "ElephantsAreGreat"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
long timezone = 2;
byte daysavetime = 1;
uint8_t spTHE[] PROGMEM = {0x08,0xE8,0x3E,0x55,0x01,0xC3,0x86,0x27,0xAF,0x72,0x0D,0x4D,0x97,0xD5,0xBC,0x64,0x3C,0xF2,0x5C,0x51,0xF1,0x93,0x36,0x8F,0x4F,0x59,0x2A,0x42,0x7A,0x32,0xC3,0x64,0xFF,0x3F};
uint8_t spTIME[] PROGMEM = {0x0E,0x28,0xAC,0x2D,0x01,0x5D,0xB6,0x0D,0x33,0xF3,0x54,0xB3,0x60,0xBA,0x8C,0x54,0x5C,0xCD,0x2D,0xD4,0x32,0x73,0x0F,0x8E,0x34,0x33,0xCB,0x4A,0x25,0xD4,0x25,0x83,0x2C,0x2B,0xD5,0x50,0x97,0x08,0x32,0xEC,0xD4,0xDC,0x4C,0x33,0xC8,0x70,0x73,0x0F,0x33,0xCD,0x20,0xC3,0xCB,0x43,0xDD,0x3C,0xCD,0x8C,0x20,0x77,0x89,0xF4,0x94,0xB2,0xE2,0xE2,0x35,0x22,0x5D,0xD6,0x4A,0x8A,0x96,0xCC,0x36,0x25,0x2D,0xC9,0x9A,0x7B,0xC2,0x18,0x87,0x24,0x4B,0x1C,0xC9,0x50,0x19,0x92,0x2C,0x71,0x34,0x4B,0x45,0x8A,0x8B,0xC4,0x96,0xB6,0x5A,0x29,0x2A,0x92,0x5A,0xCA,0x53,0x96,0x20,0x05,0x09,0xF5,0x92,0x5D,0xBC,0xE8,0x58,0x4A,0xDD,0xAE,0x73,0xBD,0x65,0x4B,0x8D,0x78,0xCA,0x2B,0x4E,0xD8,0xD9,0xED,0x22,0x20,0x06,0x75,0x00,0x00,0x80,0xFF,0x07};
uint8_t spIS[] PROGMEM = {0x21,0x18,0x96,0x38,0xB7,0x14,0x8D,0x60,0x3A,0xA6,0xE8,0x51,0xB4,0xDC,0x2E,0x48,0x7B,0x5A,0xF1,0x70,0x1B,0xA3,0xEC,0x09,0xC6,0xCB,0xEB,0x92,0x3D,0xA7,0x69,0x1F,0xAF,0x71,0x89,0x9C,0xA2,0xB3,0xFC,0xCA,0x35,0x72,0x9A,0xD1,0xF0,0xAB,0x12,0xB3,0x2B,0xC6,0xCD,0x4F,0xCC,0x32,0x26,0x19,0x07,0xDF,0x0B,0x8F,0xB8,0xA4,0xED,0x7C,0xCF,0x23,0x62,0x8B,0x8E,0xF1,0x23,0x0A,0x8B,0x6E,0xCB,0xCE,0xEF,0x54,0x44,0x3C,0xDC,0x08,0x60,0x0B,0x37,0x01,0x1C,0x53,0x26,0x80,0x15,0x4E,0x14,0xB0,0x54,0x2B,0x02,0xA4,0x69,0xFF,0x7F};
uint8_t spA_M_[] PROGMEM = {0xCD,0xEF,0x86,0xAB,0x57,0x6D,0x0F,0xAF,0x71,0xAD,0x49,0x55,0x3C,0xFC,0x2E,0xC5,0xB7,0x5C,0xF1,0xF2,0x87,0x66,0xDD,0x4E,0xC5,0xC3,0xEF,0x92,0xE2,0x3A,0x65,0xB7,0xA0,0x09,0xAA,0x1B,0x97,0x54,0x82,0x2E,0x28,0x77,0x5C,0x52,0x09,0x1A,0xA3,0xB8,0x76,0x49,0x25,0x68,0x8C,0x73,0xDB,0x24,0x95,0xA0,0x32,0xA9,0x6B,0xA7,0xD9,0x82,0x26,0xA9,0x76,0x42,0xD6,0x08,0xBA,0xE1,0xE8,0x0E,0x5A,0x2B,0xEA,0x9E,0x3D,0x27,0x18,0xAD,0xA8,0x07,0xF1,0x98,0x90,0x35,0xA2,0x96,0x44,0xA3,0x5D,0x66,0x8B,0x6B,0x12,0xCD,0x32,0x85,0x25,0xC9,0x81,0x2D,0xC3,0x64,0x85,0x34,0x58,0x89,0x94,0x52,0x1C,0x52,0x2F,0x35,0xDA,0xC7,0x51,0x48,0x23,0x97,0xCC,0x2C,0x97,0x2E,0xF3,0x5C,0xF3,0xA2,0x14,0xBA,0x2C,0x48,0xCE,0xCA,0x76,0xE8,0x32,0x2F,0x34,0xB2,0xDB,0x85,0xC9,0x83,0x90,0xA8,0x2C,0x57,0x26,0x8F,0x9C,0xBD,0xA2,0x53,0xD9,0xC2,0x54,0x59,0x28,0x99,0x4B,0x2C,0x5D,0xFF,0x3F};
uint8_t spP_M_[] PROGMEM = {0x0E,0x98,0x41,0x54,0x00,0x43,0xA0,0x05,0xAB,0x42,0x8E,0x1D,0xA3,0x15,0xEC,0x4E,0x58,0xF7,0x92,0x66,0x70,0x1B,0x66,0xDB,0x73,0x99,0xC1,0xEB,0x98,0xED,0xD6,0x25,0x25,0x6F,0x70,0x92,0xDD,0x64,0xD8,0xFC,0x61,0xD0,0x66,0x83,0xD6,0x0A,0x86,0x23,0xAB,0x69,0xDA,0x2B,0x18,0x9E,0x3D,0x37,0x69,0x9D,0xA8,0x07,0x71,0x9F,0xA0,0xBD,0xA2,0x16,0xD5,0x7C,0x54,0xF6,0x88,0x6B,0x54,0x8B,0x34,0x49,0x2D,0x29,0x49,0x3C,0x34,0x64,0xA5,0x24,0x1B,0x36,0xD7,0x72,0x13,0x92,0xA4,0xC4,0x2D,0xC3,0xB3,0x4B,0xA3,0x62,0x0F,0x2B,0x37,0x6E,0x8B,0x5A,0xD4,0x3D,0xDD,0x9A,0x2D,0x50,0x93,0xF6,0x4C,0xAA,0xB6,0xC4,0x85,0x3B,0xB2,0xB1,0xD8,0x93,0x20,0x4D,0x8F,0x24,0xFF,0x0F};
uint8_t spOH[] PROGMEM = {0xC6,0xC9,0x71,0x5A,0xA2,0x92,0x14,0x2F,0x6E,0x97,0x9C,0x46,0x9D,0xDC,0xB0,0x4D,0x62,0x1B,0x55,0x70,0xDD,0x55,0xBE,0x0E,0x36,0xC1,0x33,0x37,0xA9,0xA7,0x51,0x1B,0xCF,0x3C,0xA5,0x9E,0x44,0xAC,0x3C,0x7D,0x98,0x7B,0x52,0x96,0x72,0x65,0x4B,0xF6,0x1A,0xD9,0xCA,0xF5,0x91,0x2D,0xA2,0x2A,0x4B,0xF7,0xFF,0x01};
uint8_t spOCLOCK[] PROGMEM = {0x21,0x4E,0x3D,0xB8,0x2B,0x19,0xBB,0x24,0x0E,0xE5,0xEC,0x60,0xE4,0xF2,0x90,0x13,0xD4,0x2A,0x11,0x80,0x00,0x42,0x69,0x26,0x40,0xD0,0x2B,0x04,0x68,0xE0,0x4D,0x00,0x3A,0x35,0x35,0x33,0xB6,0x51,0xD9,0x64,0x34,0x82,0xB4,0x9A,0x63,0x92,0x55,0x89,0x52,0x5B,0xCA,0x2E,0x34,0x25,0x4E,0x63,0x28,0x3A,0x50,0x95,0x26,0x8D,0xE6,0xAA,0x64,0x58,0xEA,0x92,0xCE,0xC2,0x46,0x15,0x9B,0x86,0xCD,0x2A,0x2E,0x37,0x00,0x00,0x00,0x0C,0xC8,0xDD,0x05,0x01,0xB9,0x33,0x21,0xA0,0x74,0xD7,0xFF,0x07};
uint8_t spONE[] PROGMEM = {0xCC,0x67,0x75,0x42,0x59,0x5D,0x3A,0x4F,0x9D,0x36,0x63,0xB7,0x59,0xDC,0x30,0x5B,0x5C,0x23,0x61,0xF3,0xE2,0x1C,0xF1,0xF0,0x98,0xC3,0x4B,0x7D,0x39,0xCA,0x1D,0x2C,0x2F,0xB7,0x15,0xEF,0x70,0x79,0xBC,0xD2,0x46,0x7C,0x52,0xE5,0xF1,0x4A,0x6A,0xB3,0x71,0x47,0xC3,0x2D,0x39,0x34,0x4B,0x23,0x35,0xB7,0x7A,0x55,0x33,0x8F,0x59,0xDC,0xA2,0x44,0xB5,0xBC,0x66,0x72,0x8B,0x64,0xF5,0xF6,0x98,0xC1,0x4D,0x42,0xD4,0x27,0x62,0x38,0x2F,0x4A,0xB6,0x9C,0x88,0x68,0xBC,0xA6,0x95,0xF8,0x5C,0xA1,0x09,0x86,0x77,0x91,0x11,0x5B,0xFF,0x0F};
uint8_t spTWO[] PROGMEM = {0x0E,0x38,0x6E,0x25,0x00,0xA3,0x0D,0x3A,0xA0,0x37,0xC5,0xA0,0x05,0x9E,0x56,0x35,0x86,0xAA,0x5E,0x8C,0xA4,0x82,0xB2,0xD7,0x74,0x31,0x22,0x69,0xAD,0x1C,0xD3,0xC1,0xD0,0xFA,0x28,0x2B,0x2D,0x47,0xC3,0x1B,0xC2,0xC4,0xAE,0xC6,0xCD,0x9C,0x48,0x53,0x9A,0xFF,0x0F};
uint8_t spTHREE[] PROGMEM = {0x02,0xD8,0x2E,0x9C,0x01,0xDB,0xA6,0x33,0x60,0xFB,0x30,0x01,0xEC,0x20,0x12,0x8C,0xE4,0xD8,0xCA,0x32,0x96,0x73,0x63,0x41,0x39,0x89,0x98,0xC1,0x4D,0x0D,0xED,0xB0,0x2A,0x05,0x37,0x0F,0xB4,0xA5,0xAE,0x5C,0xDC,0x36,0xD0,0x83,0x2F,0x4A,0x71,0x7B,0x03,0xF7,0x38,0x59,0xCD,0xED,0x1E,0xB4,0x6B,0x14,0x35,0xB7,0x6B,0x94,0x99,0x91,0xD5,0xDC,0x26,0x48,0x77,0x4B,0x66,0x71,0x1B,0x21,0xDB,0x2D,0x8A,0xC9,0x6D,0x88,0xFC,0x26,0x28,0x3A,0xB7,0x21,0xF4,0x1F,0xA3,0x65,0xBC,0x02,0x38,0xBB,0x3D,0x8E,0xF0,0x2B,0xE2,0x08,0xB7,0x34,0xFF,0x0F};
uint8_t spFOUR[] PROGMEM = {0x0C,0x18,0xB6,0x9A,0x01,0xC3,0x75,0x09,0x60,0xD8,0x0E,0x09,0x30,0xA0,0x9B,0xB6,0xA0,0xBB,0xB0,0xAA,0x16,0x4E,0x82,0xEB,0xEA,0xA9,0xFA,0x59,0x49,0x9E,0x59,0x23,0x9A,0x27,0x3B,0x78,0x66,0xAE,0x4A,0x9C,0x9C,0xE0,0x99,0xD3,0x2A,0xBD,0x72,0x92,0xEF,0xE6,0x88,0xE4,0x45,0x4D,0x7E,0x98,0x2D,0x62,0x67,0x37,0xF9,0xA1,0x37,0xA7,0x6C,0x94,0xE4,0xC7,0x1E,0xDC,0x3C,0xA5,0x83,0x1F,0x8B,0xEB,0x52,0x0E,0x0E,0x7E,0x2E,0x4E,0xC7,0x31,0xD2,0x79,0xA5,0x3A,0x0D,0xD9,0xC4,0xFF,0x07};
uint8_t spFIVE[] PROGMEM = {0x02,0xE8,0x3E,0x8C,0x01,0xDD,0x65,0x08,0x60,0x98,0x4C,0x06,0x34,0x93,0xCE,0x80,0xE6,0xDA,0x9A,0x14,0x6B,0xAA,0x47,0xD1,0x5E,0x56,0xAA,0x6D,0x56,0xCD,0x78,0xD9,0xA9,0x1C,0x67,0x05,0x83,0xE1,0xA4,0xBA,0x38,0xEE,0x16,0x86,0x9B,0xFA,0x60,0x87,0x5B,0x18,0x6E,0xEE,0x8B,0x1D,0x6E,0x61,0xB9,0x69,0x36,0x65,0xBA,0x8D,0xE5,0xE5,0x3E,0x1C,0xE9,0x0E,0x96,0x9B,0x5B,0xAB,0x95,0x2B,0x58,0x6E,0xCE,0xE5,0x3A,0x6A,0xF3,0xB8,0x35,0x84,0x7B,0x05,0xA3,0xE3,0x36,0xEF,0x92,0x19,0xB4,0x86,0xDB,0xB4,0x69,0xB4,0xD1,0x2A,0x4E,0x65,0x9A,0x99,0xCE,0x28,0xD9,0x85,0x71,0x4C,0x18,0x6D,0x67,0x47,0xC6,0x5E,0x53,0x4A,0x9C,0xB5,0xE2,0x85,0x45,0x26,0xFE,0x7F};
uint8_t spSIX[] PROGMEM = {0x0E,0xD8,0xAE,0xDD,0x03,0x0E,0x38,0xA6,0xD2,0x01,0xD3,0xB4,0x2C,0xAD,0x6A,0x35,0x9D,0xB1,0x7D,0xDC,0xEE,0xC4,0x65,0xD7,0xF1,0x72,0x47,0x24,0xB3,0x19,0xD9,0xD9,0x05,0x70,0x40,0x49,0xEA,0x02,0x98,0xBE,0x42,0x01,0xDF,0xA4,0x69,0x40,0x00,0xDF,0x95,0xFC,0x3F};
uint8_t spSEVEN[] PROGMEM = {0x02,0xB8,0x3A,0x8C,0x01,0xDF,0xA4,0x73,0x40,0x01,0x47,0xB9,0x2F,0x33,0x3B,0x73,0x5F,0x53,0x7C,0xEC,0x9A,0xC5,0x63,0xD5,0xD1,0x75,0xAE,0x5B,0xFC,0x64,0x5C,0x35,0x87,0x91,0xF1,0x83,0x36,0xB5,0x68,0x55,0xC5,0x6F,0xDA,0x45,0x2D,0x1C,0x2D,0xB7,0x38,0x37,0x9F,0x60,0x3C,0xBC,0x9A,0x85,0xA3,0x25,0x66,0xF7,0x8A,0x57,0x1C,0xA9,0x67,0x56,0xCA,0x5E,0xF0,0xB2,0x16,0xB2,0xF1,0x89,0xCE,0x8B,0x92,0x25,0xC7,0x2B,0x33,0xCF,0x48,0xB1,0x99,0xB4,0xF3,0xFF};
uint8_t spEIGHT[] PROGMEM = {0xC3,0x6C,0x86,0xB3,0x27,0x6D,0x0F,0xA7,0x48,0x99,0x4E,0x55,0x3C,0xBC,0x22,0x65,0x36,0x4D,0xD1,0xF0,0x32,0xD3,0xBE,0x34,0xDA,0xC3,0xEB,0x82,0xE2,0xDA,0x65,0x35,0xAF,0x31,0xF2,0x6B,0x97,0x95,0xBC,0x86,0xD8,0x6F,0x82,0xA6,0x73,0x0B,0xC6,0x9E,0x72,0x99,0xCC,0xCB,0x02,0xAD,0x3C,0x9A,0x10,0x60,0xAB,0x62,0x05,0x2C,0x37,0x84,0x00,0xA9,0x73,0x00,0x00,0xFE,0x1F};
uint8_t spNINE[] PROGMEM = {0xCC,0xA1,0x26,0xBB,0x83,0x93,0x18,0xCF,0x4A,0xAD,0x2E,0x31,0xED,0x3C,0xA7,0x24,0x26,0xC3,0x54,0xF1,0x92,0x64,0x8B,0x8A,0x98,0xCB,0x2B,0x2E,0x34,0x53,0x2D,0x0E,0x2F,0x57,0xB3,0x0C,0x0D,0x3C,0xBC,0x3C,0x4C,0x4B,0xCA,0xF4,0xF0,0x72,0x0F,0x6E,0x49,0x53,0xCD,0xCB,0x53,0x2D,0x35,0x4D,0x0F,0x2F,0x0F,0xD7,0x0C,0x0D,0x3D,0xBC,0xDC,0x4D,0xD3,0xDD,0xC2,0xF0,0x72,0x52,0x4F,0x57,0x9B,0xC3,0xAB,0x89,0xBD,0x42,0x2D,0x0F,0xAF,0x5A,0xD1,0x71,0x91,0x55,0xBC,0x2C,0xC5,0x3B,0xD8,0x65,0xF2,0x82,0x94,0x18,0x4E,0x3B,0xC1,0x73,0x42,0x32,0x33,0x15,0x45,0x4F,0x79,0x52,0x6A,0x55,0xA6,0xA3,0xFF,0x07};
uint8_t spTEN[] PROGMEM = {0x0E,0xD8,0xB1,0xDD,0x01,0x3D,0xA8,0x24,0x7B,0x04,0x27,0x76,0x77,0xDC,0xEC,0xC2,0xC5,0x23,0x84,0xCD,0x72,0x9A,0x51,0xF7,0x62,0x45,0xC7,0xEB,0x4E,0x35,0x4A,0x14,0x2D,0xBF,0x45,0xB6,0x0A,0x75,0xB8,0xFC,0x16,0xD9,0x2A,0xD9,0xD6,0x0A,0x5A,0x10,0xCD,0xA2,0x48,0x23,0xA8,0x81,0x35,0x4B,0x2C,0xA7,0x20,0x69,0x0A,0xAF,0xB6,0x15,0x82,0xA4,0x29,0x3C,0xC7,0x52,0x08,0xA2,0x22,0xCF,0x68,0x4B,0x2E,0xF0,0x8A,0xBD,0xA3,0x2C,0xAB,0x40,0x1B,0xCE,0xAA,0xB2,0x6C,0x82,0x40,0x4D,0x7D,0xC2,0x89,0x88,0x8A,0x61,0xCC,0x74,0xD5,0xFF,0x0F};
uint8_t spELEVEN[] PROGMEM = {0xC3,0xCD,0x76,0x5C,0xAE,0x14,0x0F,0x37,0x9B,0x71,0xDE,0x92,0x55,0xBC,0x2C,0x27,0x70,0xD3,0x76,0xF0,0x83,0x5E,0xA3,0x5E,0x5A,0xC1,0xF7,0x61,0x58,0xA7,0x19,0x35,0x3F,0x99,0x31,0xDE,0x52,0x74,0xFC,0xA2,0x26,0x64,0x4B,0xD1,0xF1,0xAB,0xAE,0xD0,0x2D,0xC5,0xC7,0x2F,0x36,0xDD,0x27,0x15,0x0F,0x3F,0xD9,0x08,0x9F,0x62,0xE4,0xC2,0x2C,0xD4,0xD8,0xD3,0x89,0x0B,0x1B,0x57,0x11,0x0B,0x3B,0xC5,0xCF,0xD6,0xCC,0xC6,0x64,0x35,0xAF,0x18,0x73,0x1F,0xA1,0x5D,0xBC,0x62,0x45,0xB3,0x45,0x51,0xF0,0xA2,0x62,0xAB,0x4A,0x5B,0xC9,0x4B,0x8A,0x2D,0xB3,0x6C,0x06,0x2F,0x29,0xB2,0xAC,0x8A,0x18,0xBC,0x28,0xD9,0xAA,0xD2,0x92,0xF1,0xBC,0xE0,0x98,0x8C,0x48,0xCC,0x17,0x52,0xA3,0x27,0x6D,0x93,0xD0,0x4B,0x8E,0x0E,0x77,0x02,0x00,0xFF,0x0F};
uint8_t spTWELVE[] PROGMEM = {0x06,0x28,0x46,0xD3,0x01,0x25,0x06,0x13,0x20,0xBA,0x70,0x70,0xB6,0x79,0xCA,0x36,0xAE,0x28,0x38,0xE1,0x29,0xC5,0x35,0xA3,0xE6,0xC4,0x16,0x6A,0x53,0x8C,0x97,0x9B,0x72,0x86,0x4F,0x28,0x1A,0x6E,0x0A,0x59,0x36,0xAE,0x68,0xF8,0x29,0x67,0xFA,0x06,0xA3,0x16,0xC4,0x96,0xE6,0x53,0xAC,0x5A,0x9C,0x56,0x72,0x77,0x31,0x4E,0x49,0x5C,0x8D,0x5B,0x29,0x3B,0x24,0x61,0x1E,0x6C,0x9B,0x6C,0x97,0xF8,0xA7,0x34,0x19,0x92,0x4C,0x62,0x9E,0x72,0x65,0x58,0x12,0xB1,0x7E,0x09,0xD5,0x2E,0x53,0xC5,0xBA,0x36,0x6B,0xB9,0x2D,0x17,0x05,0xEE,0x9A,0x6E,0x8E,0x05,0x50,0x6C,0x19,0x07,0x18,0x50,0xBD,0x3B,0x01,0x92,0x08,0x41,0x40,0x10,0xA6,0xFF,0x0F};
uint8_t spTHIRTEEN[] PROGMEM = {0x08,0xE8,0x2C,0x15,0x01,0x43,0x07,0x13,0xE0,0x98,0xB4,0xA6,0x35,0xA9,0x1E,0xDE,0x56,0x8E,0x53,0x9C,0x7A,0xE7,0xCA,0x5E,0x76,0x8D,0x94,0xE5,0x2B,0xAB,0xD9,0xB5,0x62,0xA4,0x9C,0xE4,0xE6,0xB4,0x41,0x1E,0x7C,0xB6,0x93,0xD7,0x16,0x99,0x5A,0xCD,0x61,0x76,0x55,0xC2,0x91,0x61,0x1B,0xC0,0x01,0x5D,0x85,0x05,0xE0,0x68,0x51,0x07,0x1C,0xA9,0x64,0x80,0x1D,0x4C,0x9C,0x95,0x88,0xD4,0x04,0x3B,0x4D,0x4E,0x21,0x5C,0x93,0xA8,0x26,0xB9,0x05,0x4B,0x6E,0xA0,0xE2,0xE4,0x57,0xC2,0xB9,0xC1,0xB2,0x93,0x5F,0x09,0xD7,0x24,0xCB,0x4E,0x41,0x25,0x54,0x1D,0x62,0x3B,0x05,0x8D,0x52,0x57,0xAA,0xAD,0x10,0x24,0x26,0xE3,0xE1,0x36,0x5D,0x10,0x85,0xB4,0x97,0x85,0x72,0x41,0x14,0x52,0x5E,0x1A,0xCA,0xF9,0x91,0x6B,0x7A,0x5B,0xC4,0xE0,0x17,0x2D,0x54,0x1D,0x92,0x8C,0x1F,0x25,0x4B,0x8F,0xB2,0x16,0x41,0xA1,0x4A,0x3E,0xE6,0xFA,0xFF,0x01};
uint8_t spFOURTEEN[] PROGMEM = {0x0C,0x58,0xAE,0x5C,0x01,0xD9,0x87,0x07,0x51,0xB7,0x25,0xB3,0x8A,0x15,0x2C,0xF7,0x1C,0x35,0x87,0x4D,0xB2,0xDD,0x53,0xCE,0x28,0x2B,0xC9,0x0E,0x97,0x2D,0xBD,0x2A,0x17,0x27,0x76,0x8E,0xD2,0x9A,0x6C,0x80,0x94,0x71,0x00,0x00,0x02,0xB0,0x58,0x58,0x00,0x9E,0x0B,0x0A,0xC0,0xB2,0xCE,0xC1,0xC8,0x98,0x7A,0x52,0x95,0x24,0x2B,0x11,0xED,0x36,0xD4,0x92,0xDC,0x4C,0xB5,0xC7,0xC8,0x53,0xF1,0x2A,0xE5,0x1A,0x17,0x55,0xC5,0xAF,0x94,0xBB,0xCD,0x1C,0x26,0xBF,0x52,0x9A,0x72,0x53,0x98,0xFC,0xC2,0x68,0xD2,0x4D,0x61,0xF0,0xA3,0x90,0xB6,0xD6,0x50,0xC1,0x8F,0x42,0xDA,0x4A,0x43,0x39,0x3F,0x48,0x2D,0x6B,0x33,0xF9,0xFF};
uint8_t spFIFTEEN[] PROGMEM = {0x08,0xE8,0x2A,0x0D,0x01,0xDD,0xBA,0x31,0x60,0x6A,0xF7,0xA0,0xAE,0x54,0xAA,0x5A,0x76,0x97,0xD9,0x34,0x69,0xEF,0x32,0x1E,0x66,0xE1,0xE2,0xB3,0x43,0xA9,0x18,0x55,0x92,0x4E,0x37,0x2D,0x67,0x6F,0xDF,0xA2,0x5A,0xB6,0x04,0x30,0x55,0xA8,0x00,0x86,0x09,0xE7,0x00,0x01,0x16,0x17,0x05,0x70,0x40,0x57,0xE5,0x01,0xF8,0x21,0x34,0x00,0xD3,0x19,0x33,0x80,0x89,0x9A,0x62,0x34,0x4C,0xD5,0x49,0xAE,0x8B,0x53,0x09,0xF7,0x26,0xD9,0x6A,0x7E,0x23,0x5C,0x13,0x12,0xB3,0x04,0x9D,0x50,0x4F,0xB1,0xAD,0x14,0x15,0xC2,0xD3,0xA1,0xB6,0x42,0x94,0xA8,0x8C,0x87,0xDB,0x74,0xB1,0x70,0x59,0xE1,0x2E,0xC9,0xC5,0x81,0x5B,0x55,0xA4,0x4C,0x17,0x47,0xC1,0x6D,0xE3,0x81,0x53,0x9C,0x84,0x6A,0x46,0xD9,0x4C,0x51,0x31,0x42,0xD9,0x66,0xC9,0x44,0x85,0x29,0x6A,0x9B,0xAD,0xFF,0x07};
uint8_t spSIXTEEN[] PROGMEM = {0x0A,0x58,0x5A,0x5D,0x00,0x93,0x97,0x0B,0x60,0xA9,0x48,0x05,0x0C,0x15,0xAE,0x80,0xAD,0x3D,0x14,0x30,0x7D,0xD9,0x50,0x92,0x92,0xAC,0x0D,0xC5,0xCD,0x2A,0x82,0xAA,0x3B,0x98,0x04,0xB3,0x4A,0xC8,0x9A,0x90,0x05,0x09,0x68,0x51,0xD4,0x01,0x23,0x9F,0x1A,0x60,0xA9,0x12,0x03,0xDC,0x50,0x81,0x80,0x22,0xDC,0x20,0x00,0xCB,0x06,0x3A,0x60,0x16,0xE3,0x64,0x64,0x42,0xDD,0xCD,0x6A,0x8A,0x5D,0x28,0x75,0x07,0xA9,0x2A,0x5E,0x65,0x34,0xED,0x64,0xBB,0xF8,0x85,0xF2,0x94,0x8B,0xAD,0xE4,0x37,0x4A,0x5B,0x21,0xB6,0x52,0x50,0x19,0xAD,0xA7,0xD8,0x4A,0x41,0x14,0xDA,0x5E,0x12,0x3A,0x04,0x91,0x4B,0x7B,0x69,0xA8,0x10,0x24,0x2E,0xE5,0xA3,0x81,0x52,0x90,0x94,0x5A,0x55,0x98,0x32,0x41,0x50,0xCC,0x93,0x2E,0x47,0x85,0x89,0x1B,0x5B,0x5A,0x62,0x04,0x44,0xE3,0x02,0x80,0x80,0x64,0xDD,0xFF,0x1F};
uint8_t spSEVENTEEN[] PROGMEM = {0x02,0x98,0x3A,0x42,0x00,0x5B,0xA6,0x09,0x60,0xDB,0x52,0x06,0x1C,0x93,0x29,0x80,0xA9,0x52,0x87,0x9A,0xB5,0x99,0x4F,0xC8,0x3E,0x46,0xD6,0x5E,0x7E,0x66,0xFB,0x98,0xC5,0x5A,0xC6,0x9A,0x9C,0x63,0x15,0x6B,0x11,0x13,0x8A,0x9C,0x97,0xB9,0x9A,0x5A,0x39,0x71,0xEE,0xD2,0x29,0xC2,0xA6,0xB8,0x58,0x59,0x99,0x56,0x14,0xA3,0xE1,0x26,0x19,0x19,0xE3,0x8C,0x93,0x17,0xB4,0x46,0xB5,0x88,0x71,0x9E,0x97,0x9E,0xB1,0x2C,0xC5,0xF8,0x56,0xC4,0x58,0xA3,0x1C,0xE1,0x33,0x9D,0x13,0x41,0x8A,0x43,0x58,0xAD,0x95,0xA9,0xDB,0x36,0xC0,0xD1,0xC9,0x0E,0x58,0x4E,0x45,0x01,0x23,0xA9,0x04,0x37,0x13,0xAE,0x4D,0x65,0x52,0x82,0xCA,0xA9,0x37,0x99,0x4D,0x89,0xBA,0xC0,0xBC,0x14,0x36,0x25,0xEA,0x1C,0x73,0x52,0x1D,0x97,0xB8,0x33,0xAC,0x0E,0x75,0x9C,0xE2,0xCE,0xB0,0xDA,0xC3,0x51,0x4A,0x1A,0xA5,0xCA,0x70,0x5B,0x21,0xCE,0x4C,0x26,0xD2,0x6C,0xBA,0x38,0x71,0x2E,0x1F,0x2D,0xED,0xE2,0x24,0xB8,0xBC,0x3D,0x52,0x88,0xAB,0x50,0x8E,0xA8,0x48,0x22,0x4E,0x42,0xA0,0x26,0x55,0xFD,0x3F};
uint8_t spEIGHTEEN[] PROGMEM = {0x2E,0x9C,0xD1,0x4D,0x54,0xEC,0x2C,0xBF,0x1B,0x8A,0x99,0x70,0x7C,0xFC,0x2E,0x29,0x6F,0x52,0xF6,0xF1,0xBA,0x20,0xBF,0x36,0xD9,0xCD,0xED,0x0C,0xF3,0x27,0x64,0x17,0x73,0x2B,0xA2,0x99,0x90,0x65,0xEC,0xED,0x40,0x73,0x32,0x12,0xB1,0xAF,0x30,0x35,0x0B,0xC7,0x00,0xE0,0x80,0xAE,0xDD,0x1C,0x70,0x43,0xAA,0x03,0x86,0x51,0x36,0xC0,0x30,0x64,0xCE,0x4C,0x98,0xFB,0x5C,0x65,0x07,0xAF,0x10,0xEA,0x0B,0x66,0x1B,0xFC,0x46,0xA8,0x3E,0x09,0x4D,0x08,0x2A,0xA6,0x3E,0x67,0x36,0x21,0x2A,0x98,0x67,0x9D,0x15,0xA7,0xA8,0x60,0xEE,0xB6,0x94,0x99,0xA2,0x4A,0x78,0x22,0xC2,0xA6,0x8B,0x8C,0x8E,0xCC,0x4C,0x8A,0x2E,0x8A,0x4C,0xD3,0x57,0x03,0x87,0x28,0x71,0x09,0x1F,0x2B,0xE4,0xA2,0xC4,0xC5,0x6D,0xAD,0x54,0x88,0xB2,0x63,0xC9,0xF2,0x50,0x2E,0x8A,0x4A,0x38,0x4A,0xEC,0x88,0x28,0x08,0xE3,0x28,0x49,0xF3,0xFF};
uint8_t spNINETEEN[] PROGMEM = {0xC2,0xEA,0x8A,0x95,0x2B,0x6A,0x05,0x3F,0x71,0x71,0x5F,0x0D,0x12,0xFC,0x28,0x25,0x62,0x35,0xF0,0xF0,0xB3,0x48,0x1E,0x0F,0xC9,0xCB,0x2F,0x45,0x7C,0x2C,0x25,0x1F,0xBF,0x14,0xB3,0x2C,0xB5,0x75,0xFC,0x5A,0x5C,0xA3,0x5D,0xE1,0xF1,0x7A,0x76,0xB3,0x4E,0x45,0xC7,0xED,0x96,0x23,0x3B,0x18,0x37,0x7B,0x18,0xCC,0x09,0x51,0x13,0x4C,0xAB,0x6C,0x4C,0x4B,0x96,0xD2,0x49,0xAA,0x36,0x0B,0xC5,0xC2,0x20,0x26,0x27,0x35,0x63,0x09,0x3D,0x30,0x8B,0xF0,0x48,0x5C,0xCA,0x61,0xDD,0xCB,0xCD,0x91,0x03,0x8E,0x4B,0x76,0xC0,0xCC,0x4D,0x06,0x98,0x31,0x31,0x98,0x99,0x70,0x6D,0x2A,0xA3,0xE4,0x16,0xCA,0xBD,0xCE,0x5C,0x92,0x57,0x28,0xCF,0x09,0x69,0x2E,0x7E,0xA5,0x3C,0x63,0xA2,0x30,0x05,0x95,0xD2,0x74,0x98,0xCD,0x14,0x54,0xCA,0x53,0xA9,0x96,0x52,0x50,0x28,0x6F,0xBA,0xCB,0x0C,0x41,0x50,0xDE,0x65,0x2E,0xD3,0x05,0x89,0x4B,0x7B,0x6B,0x20,0x17,0x44,0xAE,0xED,0x23,0x81,0x52,0x90,0x85,0x73,0x57,0xD0,0x72,0x41,0xB1,0x02,0xDE,0x2E,0xDB,0x04,0x89,0x05,0x79,0xBB,0x62,0xE5,0x76,0x11,0xCA,0x61,0x0E,0xFF,0x1F};
uint8_t spTWENTY[] PROGMEM = {0x01,0x98,0xD1,0xC2,0x00,0xCD,0xA4,0x32,0x20,0x79,0x13,0x04,0x28,0xE7,0x92,0xDC,0x70,0xCC,0x5D,0xDB,0x76,0xF3,0xD2,0x32,0x0B,0x0B,0x5B,0xC3,0x2B,0xCD,0xD4,0xDD,0x23,0x35,0xAF,0x44,0xE1,0xF0,0xB0,0x6D,0x3C,0xA9,0xAD,0x3D,0x35,0x0E,0xF1,0x0C,0x8B,0x28,0xF7,0x34,0x01,0x68,0x22,0xCD,0x00,0xC7,0xA4,0x04,0xBB,0x32,0xD6,0xAC,0x56,0x9C,0xDC,0xCA,0x28,0x66,0x53,0x51,0x70,0x2B,0xA5,0xBC,0x0D,0x9A,0xC1,0xEB,0x14,0x73,0x37,0x29,0x19,0xAF,0x33,0x8C,0x3B,0xA7,0x24,0xBC,0x42,0xB0,0xB7,0x59,0x09,0x09,0x3C,0x96,0xE9,0xF4,0x58,0xFF,0x0F};
uint8_t spTHIRTY[] PROGMEM = {0x08,0x98,0xD6,0x15,0x01,0x43,0xBB,0x0A,0x20,0x1B,0x8B,0xE5,0x16,0xA3,0x1E,0xB6,0xB6,0x96,0x97,0x3C,0x57,0xD4,0x2A,0x5E,0x7E,0x4E,0xD8,0xE1,0x6B,0x7B,0xF8,0x39,0x63,0x0D,0x9F,0x95,0xE1,0xE7,0x4C,0x76,0xBC,0x91,0x5B,0x90,0x13,0xC6,0x68,0x57,0x4E,0x41,0x8B,0x10,0x5E,0x1D,0xA9,0x44,0xD3,0xBA,0x47,0xB8,0xDD,0xE4,0x35,0x86,0x11,0x93,0x94,0x92,0x5F,0x29,0xC7,0x4C,0x30,0x0C,0x41,0xC5,0x1C,0x3B,0x2E,0xD3,0x05,0x15,0x53,0x6C,0x07,0x4D,0x15,0x14,0x8C,0xB5,0xC9,0x6A,0x44,0x90,0x10,0x4E,0x9A,0xB6,0x21,0x81,0x23,0x3A,0x91,0x91,0xE8,0xFF,0x01};
uint8_t spFOURTY[] PROGMEM = {0x04,0x18,0xB6,0x4C,0x00,0xC3,0x56,0x30,0xA0,0xE8,0xF4,0xA0,0x98,0x99,0x62,0x91,0xAE,0x83,0x6B,0x77,0x89,0x78,0x3B,0x09,0xAE,0xBD,0xA6,0x1E,0x63,0x3B,0x79,0x7E,0x71,0x5A,0x8F,0x95,0xE6,0xA5,0x4A,0x69,0xB9,0x4E,0x8A,0x5F,0x12,0x56,0xE4,0x58,0x69,0xE1,0x36,0xA1,0x69,0x2E,0x2B,0xF9,0x95,0x93,0x55,0x17,0xED,0xE4,0x37,0xC6,0xBA,0x93,0xB2,0x92,0xDF,0x19,0xD9,0x6E,0xC8,0x0A,0xFE,0x60,0xE8,0x37,0x21,0xC9,0xF9,0x8D,0x61,0x5F,0x32,0x13,0xE7,0x17,0x4C,0xD3,0xC6,0xB1,0x94,0x97,0x10,0x8F,0x8B,0xAD,0x11,0x7E,0xA1,0x9A,0x26,0x92,0xF6,0xFF,0x01};
uint8_t spFIFTY[] PROGMEM = {0x08,0xE8,0x2E,0x84,0x00,0x23,0x84,0x13,0x60,0x38,0x95,0xA5,0x0F,0xCF,0xE2,0x79,0x8A,0x8F,0x37,0x02,0xB3,0xD5,0x2A,0x6E,0x5E,0x93,0x94,0x79,0x45,0xD9,0x05,0x5D,0x0A,0xB9,0x97,0x63,0x02,0x74,0xA7,0x82,0x80,0xEE,0xC3,0x10,0xD0,0x7D,0x28,0x03,0x6E,0x14,0x06,0x70,0xE6,0x0A,0xC9,0x9A,0x4E,0x37,0xD9,0x95,0x51,0xCE,0xBA,0xA2,0x14,0x0C,0x81,0x36,0x1B,0xB2,0x5C,0x30,0x38,0xFA,0x9C,0xC9,0x32,0x41,0xA7,0x18,0x3B,0xA2,0x48,0x04,0x05,0x51,0x4F,0x91,0x6D,0x12,0x04,0x20,0x9B,0x61,0x89,0xFF,0x1F};
uint8_t spGOOD[] PROGMEM = {0x0A,0x28,0xCD,0x34,0x20,0xD9,0x1A,0x45,0x74,0xE4,0x66,0x24,0xAD,0xBA,0xB1,0x8C,0x9B,0x91,0xA5,0x64,0xE6,0x98,0x21,0x16,0x0B,0x96,0x9B,0x4C,0xE5,0xFF,0x01};
uint8_t spMORNING[] PROGMEM = {0xCE,0x08,0x52,0x2A,0x35,0x5D,0x39,0x53,0x29,0x5B,0xB7,0x0A,0x15,0x0C,0xEE,0x2A,0x42,0x56,0x66,0xD2,0x55,0x2E,0x37,0x2F,0xD9,0x45,0xB3,0xD3,0xC5,0xCA,0x6D,0x27,0xD5,0xEE,0x50,0xF5,0x50,0x94,0x14,0x77,0x2D,0xD8,0x5D,0x49,0x92,0xFD,0xB1,0x64,0x2F,0xA9,0x49,0x0C,0x93,0x4B,0xAD,0x19,0x17,0x3E,0x66,0x1E,0xF1,0xA2,0x5B,0x84,0xE2,0x29,0x8F,0x8B,0x72,0x10,0xB5,0xB1,0x2E,0x4B,0xD4,0x45,0x89,0x4A,0xEC,0x5C,0x95,0x14,0x2B,0x8A,0x9C,0x34,0x52,0x5D,0xBC,0xCC,0xB5,0x3B,0x49,0x69,0x89,0x87,0xC1,0x98,0x56,0x3A,0x21,0x2B,0x82,0x67,0xCC,0x5C,0x85,0xB5,0x4A,0x8A,0xF6,0x64,0xA9,0x96,0xC4,0x69,0x3C,0x52,0x81,0x58,0x1C,0x97,0xF6,0x0E,0x1B,0xCC,0x0D,0x42,0x32,0xAA,0x65,0x12,0x67,0xD4,0x6A,0x61,0x52,0xFC,0xFF};
uint8_t spAFTERNOON[] PROGMEM = {0xC7,0xCE,0xCE,0x3A,0xCB,0x58,0x1F,0x3B,0x07,0x9D,0x28,0x71,0xB4,0xAC,0x9C,0x74,0x5A,0x42,0x55,0x33,0xB2,0x93,0x0A,0x09,0xD4,0xC5,0x9A,0xD6,0x44,0x45,0xE3,0x38,0x60,0x9A,0x32,0x05,0xF4,0x18,0x01,0x09,0xD8,0xA9,0xC2,0x00,0x5E,0xCA,0x24,0xD5,0x5B,0x9D,0x4A,0x95,0xEA,0x34,0xEE,0x63,0x92,0x5C,0x4D,0xD0,0xA4,0xEE,0x58,0x0C,0xB9,0x4D,0xCD,0x42,0xA2,0x3A,0x24,0x37,0x25,0x8A,0xA8,0x8E,0xA0,0x53,0xE4,0x28,0x23,0x26,0x13,0x72,0x91,0xA2,0x76,0xBB,0x72,0x38,0x45,0x0A,0x46,0x63,0xCA,0x69,0x27,0x39,0x58,0xB1,0x8D,0x60,0x1C,0x34,0x1B,0x34,0xC3,0x55,0x8E,0x73,0x45,0x2D,0x4F,0x4A,0x3A,0x26,0x10,0xA1,0xCA,0x2D,0xE9,0x98,0x24,0x0A,0x1E,0x6D,0x97,0x29,0xD2,0xCC,0x71,0xA2,0xDC,0x86,0xC8,0x12,0xA7,0x8E,0x08,0x85,0x22,0x8D,0x9C,0x43,0xA7,0x12,0xB2,0x2E,0x50,0x09,0xEF,0x51,0xC5,0xBA,0x28,0x58,0xAD,0xDB,0xE1,0xFF,0x03};
uint8_t spEVENING[] PROGMEM = {0xCD,0x6D,0x98,0x73,0x47,0x65,0x0D,0x6D,0x10,0xB2,0x5D,0x93,0x35,0x94,0xC1,0xD0,0x76,0x4D,0x66,0x93,0xA7,0x04,0xBD,0x71,0xD9,0x45,0xAE,0x92,0xD5,0xAC,0x53,0x07,0x6D,0xA5,0x76,0x63,0x51,0x92,0xD4,0xA1,0x83,0xD4,0xCB,0xB2,0x51,0x88,0xCD,0xF5,0x50,0x45,0xCE,0xA2,0x2E,0x27,0x28,0x54,0x15,0x37,0x0A,0xCF,0x75,0x61,0x5D,0xA2,0xC4,0xB5,0xC7,0x44,0x55,0x8A,0x0B,0xA3,0x6E,0x17,0x95,0x21,0xA9,0x0C,0x37,0xCD,0x15,0xBA,0xD4,0x2B,0x6F,0xB3,0x54,0xE4,0xD2,0xC8,0x64,0xBC,0x4C,0x91,0x49,0x12,0xE7,0xB2,0xB1,0xD0,0x22,0x0D,0x9C,0xDD,0xAB,0x62,0xA9,0x38,0x53,0x11,0xA9,0x74,0x2C,0xD2,0xCA,0x59,0x34,0xA3,0xE5,0xFF,0x03};
uint8_t spPAUSE1[] PROGMEM = {0x00,0x00,0x00,0x00,0xFF,0x0F};
void sayTime(int hour, int minutes, AudioGeneratorTalkie *talkie)
{
bool pm = (hour >= 12);
uint8_t *spHour[] = { spTWELVE, spONE, spTWO, spTHREE, spFOUR, spFIVE, spSIX,
spSEVEN, spEIGHT, spNINE, spTEN, spELEVEN };
size_t spHourLen[] = { sizeof(spTWELVE), sizeof(spONE), sizeof(spTWO),
sizeof(spTHREE), sizeof(spFOUR), sizeof(spFIVE),
sizeof(spSIX), sizeof(spSEVEN), sizeof(spEIGHT),
sizeof(spNINE), sizeof(spTEN), sizeof(spELEVEN) };
uint8_t *spMinDec[] = { spOH, spTEN, spTWENTY, spTHIRTY, spFOURTY, spFIFTY };
size_t spMinDecLen[] = { sizeof(spOH), sizeof(spTEN), sizeof(spTWENTY),
sizeof(spTHIRTY), sizeof(spFOURTY), sizeof(spFIFTY) };
uint8_t *spMinSpecial[] = { spELEVEN, spTWELVE, spTHIRTEEN, spFOURTEEN,
spFIFTEEN, spSIXTEEN, spSEVENTEEN, spEIGHTEEN,
spNINETEEN };
size_t spMinSpecialLen[] = { sizeof(spELEVEN), sizeof(spTWELVE),
sizeof(spTHIRTEEN), sizeof(spFOURTEEN),
sizeof(spFIFTEEN), sizeof(spSIXTEEN),
sizeof(spSEVENTEEN), sizeof(spEIGHTEEN),
sizeof(spNINETEEN) };
uint8_t *spMinLow[] = { spONE, spTWO, spTHREE, spFOUR, spFIVE, spSIX,
spSEVEN, spEIGHT, spNINE };
size_t spMinLowLen[] = { sizeof(spONE), sizeof(spTWO), sizeof(spTHREE),
sizeof(spFOUR), sizeof(spFIVE), sizeof(spSIX),
sizeof(spSEVEN), sizeof(spEIGHT), sizeof(spNINE) };
talkie->say(spTHE, sizeof(spTHE));
talkie->say(spTIME, sizeof(spTIME));
talkie->say(spIS, sizeof(spIS));
hour = hour % 12;
talkie->say(spHour[hour], spHourLen[hour]);
if (minutes==0) {
talkie->say(spOCLOCK, sizeof(spOCLOCK));
} else if (minutes<=10 || minutes >=20) {
talkie->say(spMinDec[minutes / 10], spMinDecLen[minutes /10]);
if (minutes % 10) {
talkie->say(spMinLow[(minutes % 10) - 1], spMinLowLen[(minutes % 10) - 1]);
}
} else {
talkie->say(spMinSpecial[minutes - 11], spMinSpecialLen[minutes - 11]);
}
if (pm) {
talkie->say(spP_M_, sizeof(spP_M_));
} else {
talkie->say(spA_M_, sizeof(spA_M_));
}
}
AudioGeneratorTalkie *talkie;
AudioOutputI2S *out;
bool GetLocalTime(struct tm * info, uint32_t ms) {
uint32_t count = ms / 10;
time_t now;
time(&now);
localtime_r(&now, info);
if (info->tm_year > (2016 - 1900)) {
return true;
}
while (count--) {
delay(10);
time(&now);
localtime_r(&now, info);
if (info->tm_year > (2016 - 1900)) {
return true;
}
}
return false;
}
void setup()
{
Serial.begin(115200);
delay(1000);
// We start by connecting to a WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("Contacting Time Server");
configTime(3600 * timezone, daysavetime * 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
struct tm tmstruct ;
do {
tmstruct.tm_year = 0;
Serial.printf(".");
GetLocalTime(&tmstruct, 5000);
delay(100);
} while (tmstruct.tm_year < 100);
audioLogger = &Serial;
out = new AudioOutputI2S();
talkie = new AudioGeneratorTalkie();
talkie->begin(nullptr, out);
}
void loop()
{
struct tm tmstruct ;
tmstruct.tm_year = 0;
GetLocalTime(&tmstruct, 5000);
Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n",
tmstruct.tm_year + 1900, tmstruct.tm_mon + 1,
tmstruct.tm_mday, tmstruct.tm_hour,
tmstruct.tm_min, tmstruct.tm_sec);
sayTime(tmstruct.tm_hour, tmstruct.tm_min, talkie);
delay(1000);
}

View File

@ -0,0 +1,441 @@
/*
WebRadio Example
Very simple HTML app to control web streaming
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"
#include <EEPROM.h>
// Custom web server that doesn't need much RAM
#include "web.h"
// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.
// Enter your WiFi setup here:
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
WiFiServer server(80);
AudioGenerator *decoder = NULL;
AudioFileSourceICYStream *file = NULL;
AudioFileSourceBuffer *buff = NULL;
AudioOutputI2S *out = NULL;
int volume = 100;
char title[64];
char url[96];
char status[64];
bool newUrl = false;
bool isAAC = false;
int retryms = 0;
typedef struct {
char url[96];
bool isAAC;
int16_t volume;
int16_t checksum;
} Settings;
// C++11 multiline string constants are neato...
static const char HEAD[] PROGMEM = R"KEWL(
<head>
<title>ESP8266 Web Radio</title>
<script type="text/javascript">
function updateTitle() {
var x = new XMLHttpRequest();
x.open("GET", "title");
x.onload = function() { document.getElementById("titlespan").innerHTML=x.responseText; setTimeout(updateTitle, 5000); }
x.onerror = function() { setTimeout(updateTitle, 5000); }
x.send();
}
setTimeout(updateTitle, 1000);
function showValue(n) {
document.getElementById("volspan").innerHTML=n;
var x = new XMLHttpRequest();
x.open("GET", "setvol?vol="+n);
x.send();
}
function updateStatus() {var x = new XMLHttpRequest();
x.open("GET", "status");
x.onload = function() { document.getElementById("statusspan").innerHTML=x.responseText; setTimeout(updateStatus, 5000); }
x.onerror = function() { setTimeout(updateStatus, 5000); }
x.send();
}
setTimeout(updateStatus, 2000);
</script>
</head>)KEWL";
static const char BODY[] PROGMEM = R"KEWL(
<body>
ESP8266 Web Radio!
<hr>
Currently Playing: <span id="titlespan">%s</span><br>
Volume: <input type="range" name="vol" min="1" max="150" steps="10" value="%d" onchange="showValue(this.value)"/> <span id="volspan">%d</span>%%
<hr>
Status: <span id="statusspan">%s</span>
<hr>
<form action="changeurl" method="GET">
Current URL: %s<br>
Change URL: <input type="text" name="url">
<select name="type"><option value="mp3">MP3</option><option value="aac">AAC</option></select>
<input type="submit" value="Change"></form>
<form action="stop" method="POST"><input type="submit" value="Stop"></form>
</body>)KEWL";
void HandleIndex(WiFiClient *client)
{
char buff[sizeof(BODY) + sizeof(title) + sizeof(status) + sizeof(url) + 3*2];
Serial.printf_P(PSTR("Sending INDEX...Free mem=%d\n"), ESP.getFreeHeap());
WebHeaders(client, NULL);
WebPrintf(client, DOCTYPE);
client->write_P( PSTR("<html>"), 6 );
client->write_P( HEAD, strlen_P(HEAD) );
sprintf_P(buff, BODY, title, volume, volume, status, url);
client->write(buff, strlen(buff) );
client->write_P( PSTR("</html>"), 7 );
Serial.printf_P(PSTR("Sent INDEX...Free mem=%d\n"), ESP.getFreeHeap());
}
void HandleStatus(WiFiClient *client)
{
WebHeaders(client, NULL);
client->write(status, strlen(status));
}
void HandleTitle(WiFiClient *client)
{
WebHeaders(client, NULL);
client->write(title, strlen(title));
}
void HandleVolume(WiFiClient *client, char *params)
{
char *namePtr;
char *valPtr;
while (ParseParam(&params, &namePtr, &valPtr)) {
ParamInt("vol", volume);
}
Serial.printf_P(PSTR("Set volume: %d\n"), volume);
out->SetGain(((float)volume)/100.0);
RedirectToIndex(client);
}
void HandleChangeURL(WiFiClient *client, char *params)
{
char *namePtr;
char *valPtr;
char newURL[sizeof(url)];
char newType[4];
newURL[0] = 0;
newType[0] = 0;
while (ParseParam(&params, &namePtr, &valPtr)) {
ParamText("url", newURL);
ParamText("type", newType);
}
if (newURL[0] && newType[0]) {
newUrl = true;
strncpy(url, newURL, sizeof(url)-1);
url[sizeof(url)-1] = 0;
if (!strcmp_P(newType, PSTR("aac"))) {
isAAC = true;
} else {
isAAC = false;
}
strcpy_P(status, PSTR("Changing URL..."));
Serial.printf_P(PSTR("Changed URL to: %s(%s)\n"), url, newType);
RedirectToIndex(client);
} else {
WebError(client, 404, NULL, false);
}
}
void RedirectToIndex(WiFiClient *client)
{
WebError(client, 301, PSTR("Location: /\r\n"), true);
}
void StopPlaying()
{
if (decoder) {
decoder->stop();
delete decoder;
decoder = NULL;
}
if (buff) {
buff->close();
delete buff;
buff = NULL;
}
if (file) {
file->close();
delete file;
file = NULL;
}
strcpy_P(status, PSTR("Stopped"));
strcpy_P(title, PSTR("Stopped"));
}
void HandleStop(WiFiClient *client)
{
Serial.printf_P(PSTR("HandleStop()\n"));
StopPlaying();
RedirectToIndex(client);
}
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *str)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
(void) ptr;
if (strstr_P(type, PSTR("Title"))) {
strncpy(title, str, sizeof(title));
title[sizeof(title)-1] = 0;
} else {
// Who knows what to do? Not me!
}
}
void StatusCallback(void *cbData, int code, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) code;
(void) ptr;
strncpy_P(status, string, sizeof(status)-1);
status[sizeof(status)-1] = 0;
}
#ifdef ESP8266
const int preallocateBufferSize = 5*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
#else
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
#endif
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
void setup()
{
// First, preallocate all the memory needed for the buffering and codecs, never to be freed
preallocateBuffer = malloc(preallocateBufferSize);
preallocateCodec = malloc(preallocateCodecSize);
if (!preallocateBuffer || !preallocateCodec) {
Serial.begin(115200);
Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
while (1) delay(1000); // Infinite halt
}
Serial.begin(115200);
delay(1000);
Serial.printf_P(PSTR("Connecting to WiFi\n"));
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Try forever
while (WiFi.status() != WL_CONNECTED) {
Serial.printf_P(PSTR("...Connecting to WiFi\n"));
delay(1000);
}
Serial.printf_P(PSTR("Connected\n"));
Serial.printf_P(PSTR("Go to http://"));
Serial.print(WiFi.localIP());
Serial.printf_P(PSTR("/ to control the web radio.\n"));
server.begin();
strcpy_P(url, PSTR("none"));
strcpy_P(status, PSTR("OK"));
strcpy_P(title, PSTR("Idle"));
audioLogger = &Serial;
file = NULL;
buff = NULL;
out = new AudioOutputI2S();
decoder = NULL;
LoadSettings();
}
void StartNewURL()
{
Serial.printf_P(PSTR("Changing URL to: %s, vol=%d\n"), url, volume);
newUrl = false;
// Stop and free existing ones
Serial.printf_P(PSTR("Before stop...Free mem=%d\n"), ESP.getFreeHeap());
StopPlaying();
Serial.printf_P(PSTR("After stop...Free mem=%d\n"), ESP.getFreeHeap());
SaveSettings();
Serial.printf_P(PSTR("Saved settings\n"));
file = new AudioFileSourceICYStream(url);
Serial.printf_P(PSTR("created icystream\n"));
file->RegisterMetadataCB(MDCallback, NULL);
buff = new AudioFileSourceBuffer(file, preallocateBuffer, preallocateBufferSize);
Serial.printf_P(PSTR("created buffer\n"));
buff->RegisterStatusCB(StatusCallback, NULL);
decoder = isAAC ? (AudioGenerator*) new AudioGeneratorAAC(preallocateCodec, preallocateCodecSize) : (AudioGenerator*) new AudioGeneratorMP3(preallocateCodec, preallocateCodecSize);
Serial.printf_P(PSTR("created decoder\n"));
decoder->RegisterStatusCB(StatusCallback, NULL);
Serial.printf_P("Decoder start...\n");
decoder->begin(buff, out);
out->SetGain(((float)volume)/100.0);
if (!decoder->isRunning()) {
Serial.printf_P(PSTR("Can't connect to URL"));
StopPlaying();
strcpy_P(status, PSTR("Unable to connect to URL"));
retryms = millis() + 2000;
}
Serial.printf_P("Done start new URL\n");
}
void LoadSettings()
{
// Restore from EEPROM, check the checksum matches
Settings s;
uint8_t *ptr = reinterpret_cast<uint8_t *>(&s);
EEPROM.begin(sizeof(s));
for (size_t i=0; i<sizeof(s); i++) {
ptr[i] = EEPROM.read(i);
}
EEPROM.end();
int16_t sum = 0x1234;
for (size_t i=0; i<sizeof(url); i++) sum += s.url[i];
sum += s.isAAC;
sum += s.volume;
if (s.checksum == sum) {
strcpy(url, s.url);
isAAC = s.isAAC;
volume = s.volume;
Serial.printf_P(PSTR("Resuming stream from EEPROM: %s, type=%s, vol=%d\n"), url, isAAC?"AAC":"MP3", volume);
newUrl = true;
}
}
void SaveSettings()
{
// Store in "EEPROM" to restart automatically
Settings s;
memset(&s, 0, sizeof(s));
strcpy(s.url, url);
s.isAAC = isAAC;
s.volume = volume;
s.checksum = 0x1234;
for (size_t i=0; i<sizeof(url); i++) s.checksum += s.url[i];
s.checksum += s.isAAC;
s.checksum += s.volume;
uint8_t *ptr = reinterpret_cast<uint8_t *>(&s);
EEPROM.begin(sizeof(s));
for (size_t i=0; i<sizeof(s); i++) {
EEPROM.write(i, ptr[i]);
}
EEPROM.commit();
EEPROM.end();
}
void PumpDecoder()
{
if (decoder && decoder->isRunning()) {
strcpy_P(status, PSTR("Playing")); // By default we're OK unless the decoder says otherwise
if (!decoder->loop()) {
Serial.printf_P(PSTR("Stopping decoder\n"));
StopPlaying();
retryms = millis() + 2000;
}
}
}
void loop()
{
static int lastms = 0;
if (millis()-lastms > 1000) {
lastms = millis();
Serial.printf_P(PSTR("Running for %d seconds%c...Free mem=%d\n"), lastms/1000, !decoder?' ':(decoder->isRunning()?'*':' '), ESP.getFreeHeap());
}
if (retryms && millis()-retryms>0) {
retryms = 0;
newUrl = true;
}
if (newUrl) {
StartNewURL();
}
PumpDecoder();
char *reqUrl;
char *params;
WiFiClient client = server.available();
PumpDecoder();
char reqBuff[384];
if (client && WebReadRequest(&client, reqBuff, 384, &reqUrl, &params)) {
PumpDecoder();
if (IsIndexHTML(reqUrl)) {
HandleIndex(&client);
} else if (!strcmp_P(reqUrl, PSTR("stop"))) {
HandleStop(&client);
} else if (!strcmp_P(reqUrl, PSTR("status"))) {
HandleStatus(&client);
} else if (!strcmp_P(reqUrl, PSTR("title"))) {
HandleTitle(&client);
} else if (!strcmp_P(reqUrl, PSTR("setvol"))) {
HandleVolume(&client, params);
} else if (!strcmp_P(reqUrl, PSTR("changeurl"))) {
HandleChangeURL(&client, params);
} else {
WebError(&client, 404, NULL, false);
}
// web clients hate when door is violently shut
while (client.available()) {
PumpDecoder();
client.read();
}
}
PumpDecoder();
if (client) {
client.flush();
client.stop();
}
}

View File

@ -0,0 +1,314 @@
/*
PsychoPlug
ESP8266 based remote outlet with standalone timer and MQTT integration
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "web.h"
void WebPrintError(WiFiClient *client, int code)
{
switch(code) {
case 301: WebPrintf(client, "301 Moved Permanently"); break;
case 400: WebPrintf(client, "400 Bad Request"); break;
case 401: WebPrintf(client, "401 Unauthorized"); break;
case 404: WebPrintf(client, "404 Not Found"); break;
case 405: WebPrintf(client, "405 Method Not Allowed"); break;
default: WebPrintf(client, "500 Server Error"); break;
}
}
void WebError(WiFiClient *client, int code, const char *headers, bool usePMEM)
{
WebPrintf(client, "HTTP/1.1 %d\r\n", code);
WebPrintf(client, "Server: PsychoPlug\r\n");
WebPrintf(client, "Content-type: text/html\r\n");
WebPrintf(client, "Cache-Control: no-cache, no-store, must-revalidate\r\n");
WebPrintf(client, "Pragma: no-cache\r\n");
WebPrintf(client, "Expires: 0\r\n");
WebPrintf(client, "Connection: close\r\n");
if (headers) {
if (!usePMEM) {
WebPrintf(client, "%s", headers);
} else {
WebPrintfPSTR(client, headers);
}
}
WebPrintf(client, "\r\n\r\n");
WebPrintf(client, DOCTYPE);
WebPrintf(client, "<html><head><title>");
WebPrintError(client, code);
WebPrintf(client, "</title>" ENCODING "</head>\n");
WebPrintf(client, "<body><h1>");
WebPrintError(client, code);
WebPrintf(client, "</h1></body></html>\r\n");
}
void WebHeaders(WiFiClient *client, PGM_P /*const char **/headers)
{
WebPrintf(client, "HTTP/1.1 200 OK\r\n");
WebPrintf(client, "Server: PsychoPlug\r\n");
WebPrintf(client, "Content-type: text/html\r\n");
WebPrintf(client, "Cache-Control: no-cache, no-store, must-revalidate\r\n");
WebPrintf(client, "Pragma: no-cache\r\n");
WebPrintf(client, "Connection: close\r\n");
WebPrintf(client, "Expires: 0\r\n");
if (headers) {
WebPrintfPSTR(client, headers);
}
WebPrintf(client, "\r\n");
}
// In-place decoder, overwrites source with decoded values. Needs 0-termination on input
// Try and keep memory needs low, speed not critical
static uint8_t b64lut(uint8_t i)
{
if (i >= 'A' && i <= 'Z') return i - 'A';
if (i >= 'a' && i <= 'z') return i - 'a' + 26;
if (i >= '0' && i <= '9') return i - '0' + 52;
if (i == '-') return 62;
if (i == '_') return 63;
else return 64;// sentinel
}
void Base64Decode(char *str)
{
char *dest;
dest = str;
if (strlen(str)%4) return; // Not multiple of 4 == error
while (*str) {
uint8_t a = b64lut(*(str++));
uint8_t b = b64lut(*(str++));
uint8_t c = b64lut(*(str++));
uint8_t d = b64lut(*(str++));
*(dest++) = (a << 2) | ((b & 0x30) >> 4);
if (c == 64) break;
*(dest++) = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
if (d == 64) break;
*(dest++) = ((c & 0x03) << 6) | d;
}
*dest = 0; // Terminate the string
}
void URLDecode(char *ptr)
{
while (*ptr) {
if (*ptr == '+') {
*ptr = ' ';
} else if (*ptr == '%') {
if (*(ptr+1) && *(ptr+2)) {
byte a = *(ptr + 1);
byte b = *(ptr + 2);
if (a>='0' && a<='9') a -= '0';
else if (a>='a' && a<='f') a = a - 'a' + 10;
else if (a>='A' && a<='F') a = a - 'A' + 10;
if (b>='0' && b<='9') b -= '0';
else if (b>='a' && b<='f') b = b - 'a' + 10;
else if (b>='A' && b<='F') b = b - 'A' + 10;
*ptr = ((a&0x0f)<<4) | (b&0x0f);
// Safe strcpy the rest of the string back
char *p1 = ptr + 1;
char *p2 = ptr + 3;
while (*p2) { *p1 = *p2; p1++; p2++; }
*p1 = 0;
}
// OTW this is a bad encoding, just pass unchanged
}
ptr++;
}
}
// Parse HTTP request
bool WebReadRequest(WiFiClient *client, char *reqBuff, int reqBuffLen, char **urlStr, char **paramStr)
{
static char NUL = 0; // Get around writable strings...
*urlStr = NULL;
*paramStr = NULL;
unsigned long timeoutMS = millis() + 5000; // Max delay before we timeout
while (!client->available() && millis() < timeoutMS) { delay(10); }
if (!client->available()) {
return false;
}
int wlen = client->readBytesUntil('\r', reqBuff, reqBuffLen-1);
reqBuff[wlen] = 0;
// Delete HTTP version (well, anything after the 2nd space)
char *ptr = reqBuff;
while (*ptr && *ptr!=' ') ptr++;
if (*ptr) ptr++;
while (*ptr && *ptr!=' ') ptr++;
*ptr = 0;
URLDecode(reqBuff);
char *url;
char *qp;
if (!memcmp_P(reqBuff, PSTR("GET "), 4)) {
client->flush(); // Don't need anything here...
// Break into URL and form data
url = reqBuff+4;
while (*url && *url=='/') url++; // Strip off leading /s
qp = strchr(url, '?');
if (qp) {
*qp = 0; // End URL
qp++;
} else {
qp = &NUL;
}
} else if (!memcmp_P(reqBuff, PSTR("POST "), 5)) {
uint8_t newline;
client->read(&newline, 1); // Get rid of \n
url = reqBuff+5;
while (*url && *url=='/') url++; // Strip off leading /s
qp = strchr(url, '?');
if (qp) *qp = 0; // End URL @ ?
// In a POST the params are in the body
int sizeleft = reqBuffLen - strlen(reqBuff) - 1;
qp = reqBuff + strlen(reqBuff) + 1;
int wlen = client->readBytesUntil('\r', qp, sizeleft-1);
qp[wlen] = 0;
client->flush();
URLDecode(qp);
} else {
// Not a GET or POST, error
WebError(client, 405, PSTR("Allow: GET, POST"));
return false;
}
if (urlStr) *urlStr = url;
if (paramStr) *paramStr = qp;
return true;
}
// Scan out and update a pointeinto the param string, returning the name and value or false if done
bool ParseParam(char **paramStr, char **name, char **value)
{
char *data = *paramStr;
if (*data==0) return false;
char *namePtr = data;
while ((*data != 0) && (*data != '=') && (*data != '&')) data++;
if (*data) { *data = 0; data++; }
char *valPtr = data;
if (*data == '=') data++;
while ((*data != 0) && (*data != '=') && (*data != '&')) data++;
if (*data) { *data = 0; data++;}
*paramStr = data;
*name = namePtr;
*value = valPtr;
return true;
}
bool IsIndexHTML(const char *url)
{
if (!url) return false;
if (*url==0 || !strcmp_P(url, PSTR("/")) || !strcmp_P(url, PSTR("/index.html")) || !strcmp_P(url, PSTR("index.html"))) return true;
else return false;
}
void WebFormText(WiFiClient *client, /*const char **/ PGM_P label, const char *name, const char *value, bool enabled)
{
WebPrintfPSTR(client, label);
WebPrintf(client, ": <input type=\"text\" name=\"%s\" id=\"%s\" value=\"%s\" %s><br>\n", name, name, value, !enabled?"disabled":"");
}
void WebFormText(WiFiClient *client, /*const char **/ PGM_P label, const char *name, const int value, bool enabled)
{
WebPrintfPSTR(client, label);
WebPrintf(client, ": <input type=\"text\" name=\"%s\" id=\"%s\" value=\"%d\" %s><br>\n", name, name, value, !enabled?"disabled":"");
}
void WebFormCheckbox(WiFiClient *client, /*const char **/ PGM_P label, const char *name, bool checked, bool enabled)
{
WebPrintf(client, "<input type=\"checkbox\" name=\"%s\" id=\"%s\" %s %s> ", name, name, checked?"checked":"", !enabled?"disabled":"");
WebPrintfPSTR(client, label);
WebPrintf(client, "<br>\n");
}
void WebFormCheckboxDisabler(WiFiClient *client, PGM_P /*const char **/label, const char *name, bool invert, bool checked, bool enabled, const char *ids[])
{
WebPrintf(client,"<input type=\"checkbox\" name=\"%s\" id=\"%s\" onclick=\"", name,name);
if (invert) WebPrintf(client, "var x = true; if (this.checked) { x = false; }\n")
else WebPrintf(client, "var x = false; if (this.checked) { x = true; }\n");
for (byte i=0; ids[i][0]; i++ ) {
WebPrintf(client, "document.getElementById('%s').disabled = x;\n", ids[i]);
}
WebPrintf(client, "\" %s %s> ", checked?"checked":"", !enabled?"disabled":"")
WebPrintfPSTR(client, label);
WebPrintf(client, "<br>\n");
}
// Scan an integer from a string, place it into dest, and then return # of bytes scanned
int ParseInt(char *src, int *dest)
{
byte count = 0;
bool neg = false;
int res = 0;
if (!src) return 0;
if (src[0] == '-') {neg = true; src++; count++;}
while (*src && (*src>='0') && (*src<='9')) {
res = res * 10;
res += *src - '0';
src++;
count++;
}
if (neg) res *= -1;
if (dest) *dest = res;
return count;
}
void Read4Int(char *str, byte *p)
{
int i;
str += ParseInt(str, &i); p[0] = i; if (*str) str++;
str += ParseInt(str, &i); p[1] = i; if (*str) str++;
str += ParseInt(str, &i); p[2] = i; if (*str) str++;
str += ParseInt(str, &i); p[3] = i;
}

View File

@ -0,0 +1,64 @@
/*
PsychoPlug
ESP8266 based remote outlet with standalone timer and MQTT integration
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _web_h
#define _web_h
// Global way of writing out dynamic HTML to socket
// snprintf guarantees a null termination
#define WebPrintf(c, fmt, ...) { char webBuff[192]; snprintf_P(webBuff, sizeof(webBuff), PSTR(fmt), ## __VA_ARGS__); (c)->print(webBuff); delay(10);}
#define WebPrintfPSTR(c, fmt, ...) { char webBuff[192]; snprintf_P(webBuff, sizeof(webBuff), (fmt), ## __VA_ARGS__); (c)->print(webBuff); delay(10);}
// Common HTTP header bits
#define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
#define ENCODING "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"
// Web header creation
void WebPrintError(WiFiClient *client, int code); // Sends only the error code string and a description
void WebError(WiFiClient *client, int code, const char *headers, bool usePMEM = true); // Sends whole HTTP error headers
void WebHeaders(WiFiClient *client, PGM_P /*const char **/headers); // Send success headers
// Web decoding utilities
void Base64Decode(char *str); // In-place B64 decode
void URLDecode(char *ptr); // In-place URL decode
// GET/POST parsing
bool WebReadRequest(WiFiClient *client, char *reqBuff, int reqBuffLen, char **urlStr, char **paramStr);
bool ParseParam(char **paramStr, char **name, char **value); // Get next name/parameter from a param string
bool IsIndexHTML(const char *url); // Is this meant to be index.html (/, index.htm, etc.)
// HTML FORM generation
void WebFormText(WiFiClient *client, /*const char **/ PGM_P label, const char *name, const char *value, bool enabled);
void WebFormText(WiFiClient *client, /*const char **/ PGM_P label, const char *name, const int value, bool enabled);
void WebFormCheckbox(WiFiClient *client, /*const char **/ PGM_P label, const char *name, bool checked, bool enabled);
void WebFormCheckboxDisabler(WiFiClient *client, PGM_P /*const char **/label, const char *name, bool invert, bool checked, bool enabled, const char *ids[]);
// HTML FORM parsing
int ParseInt(char *src, int *dest);
void Read4Int(char *str, byte *p);
#define ParamText(name, dest) { if (!strcmp(namePtr, (name))) strlcpy((dest), valPtr, sizeof(dest)); }
#define ParamCheckbox(name, dest) { if (!strcmp(namePtr, (name))) (dest) = !strcmp("on", valPtr); }
#define ParamInt(name, dest) { if (!strcmp(namePtr, (name))) ParseInt(valPtr, &dest); }
#define Param4Int(name, dest) { if (!strcmp(namePtr, (name))) Read4Int(valPtr, (dest)); }
#endif

31
lib/ESP8266Audio/keywords.txt Executable file
View File

@ -0,0 +1,31 @@
AudioFileSource KEYWORD1
AudioFileSourceSPIFFS KEYWORD1
AudioFileSourceLittleFS KEYWORD1
AudioFileSourceFS KEYWORD1
AudioFileSourcePROGMEM KEYWORD1
AudioFileSourceHTTPStream KEYWORD1
AudioFileSourceICYStream KEYWORD1
AudioFileSourceID3 KEYWORD1
AudioFileSourceSD KEYWORD1
AudioFileSourceBuffer KEYWORD1
AudioFileSourceSPIRAMBuffer KEYWORD1
AudioGenerator KEYWORD1
AudioGeneratorAAC KEYWORD1
AudioGeneratorFLAC KEYWORD1
AudioGeneratorMOD KEYWORD1
AudioGeneratorMIDI KEYWORD1
AudioGeneratorMP3 KEYWORD1
AudioGeneratorOpus KEYWORD1
AudioGeneratorRTTTL KEYWORD1
AudioGeneratorTalkie KEYWORD1
AudioGeneratorWAV KEYWORD1
AudioOutput KEYWORD1
AudioOutputI2S KEYWORD1
AudioOutputI2SNoDAC KEYWORD1
AudioOutputNull KEYWORD1
AudioOutputBuffer KEYWORD1
AudioOutputSerialWAV KEYWORD1
AudioOutputSPIFFSWAV KEYWORD1
AudioOutputMixer KEYWORD1
AudioOutputMixerStub KEYWORD1
AudioOutputSPDIF KEYWORD1

26
lib/ESP8266Audio/library.json Executable file
View File

@ -0,0 +1,26 @@
{
"name": "ESP8266Audio",
"description": "Audio file format and I2S DAC library",
"keywords": "ESP8266, ESP32, MP3, AAC, WAV, MOD, FLAC, RTTTL, MIDI, I2S, DAC, Delta-Sigma, TTS",
"authors": [
{
"name": "Earle F. Philhower, III",
"email": "earlephilhower@yahoo.com",
"url": "https://github.com/earlephilhower/ESP8266Audio",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/earlephilhower/ESP8266Audio"
},
"version": "1.5.0",
"homepage": "https://github.com/earlephilhower/ESP8266Audio",
"dependencies": {
"SPI": "1.0"
},
"frameworks": "Arduino",
"examples": [
"examples/*/*.ino"
]
}

View File

@ -0,0 +1,9 @@
name=ESP8266Audio
version=1.5.0
author=Earle F. Philhower, III
maintainer=Earle F. Philhower, III
sentence=Audio file and I2S sound playing routines.
paragraph=Decode compressed MP3, AAC, FLAC, Screamtracker MOD, MIDI, RTTL, TI Talkie, and WAV and play on an I2S DAC or a software-driven delta-sigma DAC and 1-transistor amplifier.
category=Signal Input/Output
url=https://github.com/earlephilhower/ESP8266Audio
architectures=esp8266,esp32

View File

@ -0,0 +1,51 @@
/*
AudioFileSource
Base class of an input "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCE_H
#define _AUDIOFILESOURCE_H
#include <Arduino.h>
#include "AudioStatus.h"
class AudioFileSource
{
public:
AudioFileSource() {};
virtual ~AudioFileSource() {};
virtual bool open(const char *filename) { (void)filename; return false; };
virtual uint32_t read(void *data, uint32_t len) { (void)data; (void)len; return 0; };
virtual uint32_t readNonBlock(void *data, uint32_t len) { return read(data, len); };
virtual bool seek(int32_t pos, int dir) { (void)pos; (void)dir; return false; };
virtual bool close() { return false; };
virtual bool isOpen() { return false; };
virtual uint32_t getSize() { return 0; };
virtual uint32_t getPos() { return 0; };
virtual bool loop() { return true; };
public:
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
protected:
AudioStatus cb;
};
#endif

View File

@ -0,0 +1,190 @@
/*
AudioFileSourceBuffer
Double-buffered file source using system RAM
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "AudioFileSourceBuffer.h"
#pragma GCC optimize ("O3")
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, uint32_t buffSizeBytes)
{
buffSize = buffSizeBytes;
buffer = (uint8_t*)malloc(sizeof(uint8_t) * buffSize);
if (!buffer) audioLogger->printf_P(PSTR("Unable to allocate AudioFileSourceBuffer::buffer[]\n"));
deallocateBuffer = true;
writePtr = 0;
readPtr = 0;
src = source;
length = 0;
filled = false;
}
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, void *inBuff, uint32_t buffSizeBytes)
{
buffSize = buffSizeBytes;
buffer = (uint8_t*)inBuff;
deallocateBuffer = false;
writePtr = 0;
readPtr = 0;
src = source;
length = 0;
filled = false;
}
AudioFileSourceBuffer::~AudioFileSourceBuffer()
{
if (deallocateBuffer) free(buffer);
buffer = NULL;
}
bool AudioFileSourceBuffer::seek(int32_t pos, int dir)
{
if(dir == SEEK_CUR && (readPtr+pos) < length) {
readPtr += pos;
return true;
} else {
// Invalidate
readPtr = 0;
writePtr = 0;
length = 0;
return src->seek(pos, dir);
}
}
bool AudioFileSourceBuffer::close()
{
if (deallocateBuffer) free(buffer);
buffer = NULL;
return src->close();
}
bool AudioFileSourceBuffer::isOpen()
{
return src->isOpen();
}
uint32_t AudioFileSourceBuffer::getSize()
{
return src->getSize();
}
uint32_t AudioFileSourceBuffer::getPos()
{
return src->getPos();
}
uint32_t AudioFileSourceBuffer::getFillLevel()
{
return length;
}
uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len)
{
if (!buffer) return src->read(data, len);
uint32_t bytes = 0;
if (!filled) {
// Fill up completely before returning any data at all
cb.st(STATUS_FILLING, PSTR("Refilling buffer"));
length = src->read(buffer, buffSize);
writePtr = length % buffSize;
filled = true;
}
// Pull from buffer until we've got none left or we've satisfied the request
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
uint32_t toReadFromBuffer = (len < length) ? len : length;
if ( (toReadFromBuffer > 0) && (readPtr >= writePtr) ) {
uint32_t toReadToEnd = (toReadFromBuffer < (uint32_t)(buffSize - readPtr)) ? toReadFromBuffer : (buffSize - readPtr);
memcpy(ptr, &buffer[readPtr], toReadToEnd);
readPtr = (readPtr + toReadToEnd) % buffSize;
len -= toReadToEnd;
length -= toReadToEnd;
ptr += toReadToEnd;
bytes += toReadToEnd;
toReadFromBuffer -= toReadToEnd;
}
if (toReadFromBuffer > 0) { // We know RP < WP at this point
memcpy(ptr, &buffer[readPtr], toReadFromBuffer);
readPtr = (readPtr + toReadFromBuffer) % buffSize;
len -= toReadFromBuffer;
length -= toReadFromBuffer;
ptr += toReadFromBuffer;
bytes += toReadFromBuffer;
toReadFromBuffer -= toReadFromBuffer;
}
if (len) {
// Still need more, try direct read from src
bytes += src->read(ptr, len);
// We're out of buffered data, need to force a complete refill. Thanks, @armSeb
readPtr = 0;
writePtr = 0;
length = 0;
filled = false;
cb.st(STATUS_UNDERFLOW, PSTR("Buffer underflow"));
}
fill();
return bytes;
}
void AudioFileSourceBuffer::fill()
{
if (!buffer) return;
if (length < buffSize) {
// Now try and opportunistically fill the buffer
if (readPtr > writePtr) {
if (readPtr == writePtr+1) return;
uint32_t bytesAvailMid = readPtr - writePtr - 1;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailMid);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
return;
}
if (buffSize > writePtr) {
uint32_t bytesAvailEnd = buffSize - writePtr;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailEnd);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
if (cnt != (int)bytesAvailEnd) return;
}
if (readPtr > 1) {
uint32_t bytesAvailStart = readPtr - 1;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailStart);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
}
}
}
bool AudioFileSourceBuffer::loop()
{
if (!src->loop()) return false;
fill();
return true;
}

View File

@ -0,0 +1,62 @@
/*
AudioFileSourceBuffer
Double-buffered input file using system RAM
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEBUFFER_H
#define _AUDIOFILESOURCEBUFFER_H
#include "AudioFileSource.h"
class AudioFileSourceBuffer : public AudioFileSource
{
public:
AudioFileSourceBuffer(AudioFileSource *in, uint32_t bufferBytes);
AudioFileSourceBuffer(AudioFileSource *in, void *buffer, uint32_t bufferBytes); // Pre-allocated buffer by app
virtual ~AudioFileSourceBuffer() override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
virtual bool loop() override;
virtual uint32_t getFillLevel();
enum { STATUS_FILLING=2, STATUS_UNDERFLOW };
private:
virtual void fill();
private:
AudioFileSource *src;
uint32_t buffSize;
uint8_t *buffer;
bool deallocateBuffer;
uint32_t writePtr;
uint32_t readPtr;
uint32_t length;
bool filled;
};
#endif

View File

@ -0,0 +1,64 @@
/*
AudioFileSourceFS
Input Arduion "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEFATFS_H
#define _AUDIOFILESOURCEFATFS_H
#ifdef ESP32
#include <Arduino.h>
#include <FS.h>
#include <FFat.h>
#include "AudioFileSource.h"
#include "AudioFileSourceFS.h"
/*
AudioFileSource for FAT filesystem.
*/
class AudioFileSourceFATFS : public AudioFileSourceFS
{
public:
AudioFileSourceFATFS() : AudioFileSourceFS(FFat) {};
AudioFileSourceFATFS(const char *filename) : AudioFileSourceFS(FFat) {
// We call open() ourselves because calling AudioFileSourceFS(FFat, filename)
// would call the parent open() and we do not want that
open(filename);
};
virtual bool open(const char *filename) override {
// make sure that the FATFS filesystem has been mounted
if (!FFat.begin()) {
audioLogger->printf_P(PSTR("Unable to initialize FATFS filesystem\n"));
return false;
} else {
// now that the fielsystem has been mounted, we can call the regular parent open() function
return AudioFileSourceFS::open(filename);
}
};
// Others are inherited from base
};
#endif
#endif

View File

@ -0,0 +1,73 @@
/*
AudioFileSourceFS
Input "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioFileSourceFS.h"
#ifdef ESP32
#include "SPIFFS.h"
#endif
AudioFileSourceFS::AudioFileSourceFS(FS &fs, const char *filename)
{
filesystem = &fs;
open(filename);
}
bool AudioFileSourceFS::open(const char *filename)
{
#ifndef ESP32
filesystem->begin();
#endif
f = filesystem->open(filename, "r");
return f;
}
AudioFileSourceFS::~AudioFileSourceFS()
{
if (f) f.close();
}
uint32_t AudioFileSourceFS::read(void *data, uint32_t len)
{
return f.read(reinterpret_cast<uint8_t*>(data), len);
}
bool AudioFileSourceFS::seek(int32_t pos, int dir)
{
return f.seek(pos, (dir==SEEK_SET)?SeekSet:(dir==SEEK_CUR)?SeekCur:SeekEnd);
}
bool AudioFileSourceFS::close()
{
f.close();
return true;
}
bool AudioFileSourceFS::isOpen()
{
return f?true:false;
}
uint32_t AudioFileSourceFS::getSize()
{
if (!f) return 0;
return f.size();
}

View File

@ -0,0 +1,51 @@
/*
AudioFileSourceFS
Input Arduion "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEFS_H
#define _AUDIOFILESOURCEFS_H
#include <Arduino.h>
#include <FS.h>
#include "AudioFileSource.h"
class AudioFileSourceFS : public AudioFileSource
{
public:
AudioFileSourceFS(FS &fs) { filesystem = &fs; }
AudioFileSourceFS(FS &fs, const char *filename);
virtual ~AudioFileSourceFS() override;
virtual bool open(const char *filename) override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override { if (!f) return 0; else return f.position(); };
private:
FS *filesystem;
File f;
};
#endif

View File

@ -0,0 +1,154 @@
/*
AudioFileSourceHTTPStream
Streaming HTTP source
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioFileSourceHTTPStream.h"
AudioFileSourceHTTPStream::AudioFileSourceHTTPStream()
{
pos = 0;
reconnectTries = 0;
saveURL[0] = 0;
}
AudioFileSourceHTTPStream::AudioFileSourceHTTPStream(const char *url)
{
saveURL[0] = 0;
reconnectTries = 0;
open(url);
}
bool AudioFileSourceHTTPStream::open(const char *url)
{
pos = 0;
http.begin(client, url);
http.setReuse(true);
#ifndef ESP32
http.setFollowRedirects(true);
#endif
int code = http.GET();
if (code != HTTP_CODE_OK) {
http.end();
cb.st(STATUS_HTTPFAIL, PSTR("Can't open HTTP request"));
return false;
}
size = http.getSize();
strncpy(saveURL, url, sizeof(saveURL));
saveURL[sizeof(saveURL)-1] = 0;
return true;
}
AudioFileSourceHTTPStream::~AudioFileSourceHTTPStream()
{
http.end();
}
uint32_t AudioFileSourceHTTPStream::read(void *data, uint32_t len)
{
if (data==NULL) {
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::read passed NULL data\n"));
return 0;
}
return readInternal(data, len, false);
}
uint32_t AudioFileSourceHTTPStream::readNonBlock(void *data, uint32_t len)
{
if (data==NULL) {
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::readNonBlock passed NULL data\n"));
return 0;
}
return readInternal(data, len, true);
}
uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool nonBlock)
{
retry:
if (!http.connected()) {
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
http.end();
for (int i = 0; i < reconnectTries; i++) {
char buff[32];
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
cb.st(STATUS_RECONNECTING, buff);
delay(reconnectDelayMs);
if (open(saveURL)) {
cb.st(STATUS_RECONNECTED, PSTR("Stream reconnected"));
break;
}
}
if (!http.connected()) {
cb.st(STATUS_DISCONNECTED, PSTR("Unable to reconnect"));
return 0;
}
}
if ((size > 0) && (pos >= size)) return 0;
WiFiClient *stream = http.getStreamPtr();
// Can't read past EOF...
if ( (size > 0) && (len > (uint32_t)(pos - size)) ) len = pos - size;
if (!nonBlock) {
int start = millis();
while ((stream->available() < (int)len) && (millis() - start < 500)) yield();
}
size_t avail = stream->available();
if (!nonBlock && !avail) {
cb.st(STATUS_NODATA, PSTR("No stream data available"));
http.end();
goto retry;
}
if (avail == 0) return 0;
if (avail < len) len = avail;
int read = stream->read(reinterpret_cast<uint8_t*>(data), len);
pos += read;
return read;
}
bool AudioFileSourceHTTPStream::seek(int32_t pos, int dir)
{
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::seek not implemented!"));
(void) pos;
(void) dir;
return false;
}
bool AudioFileSourceHTTPStream::close()
{
http.end();
return true;
}
bool AudioFileSourceHTTPStream::isOpen()
{
return http.connected();
}
uint32_t AudioFileSourceHTTPStream::getSize()
{
return size;
}
uint32_t AudioFileSourceHTTPStream::getPos()
{
return pos;
}

View File

@ -0,0 +1,66 @@
/*
AudioFileSourceHTTPStream
Connect to a HTTP based streaming service
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEHTTPSTREAM_H
#define _AUDIOFILESOURCEHTTPSTREAM_H
#include <Arduino.h>
#ifdef ESP32
#include <HTTPClient.h>
#else
#include <WiFiClient.h>
#include <ESP8266HTTPClient.h>
#endif
#include "AudioFileSource.h"
class AudioFileSourceHTTPStream : public AudioFileSource
{
friend class AudioFileSourceICYStream;
public:
AudioFileSourceHTTPStream();
AudioFileSourceHTTPStream(const char *url);
virtual ~AudioFileSourceHTTPStream() override;
virtual bool open(const char *url) override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual uint32_t readNonBlock(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; }
enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA };
private:
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock);
WiFiClient client;
HTTPClient http;
int pos;
int size;
int reconnectTries;
int reconnectDelayMs;
char saveURL[128];
};
#endif

View File

@ -0,0 +1,213 @@
/*
AudioFileSourceICYStream
Streaming Shoutcast ICY source
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include "AudioFileSourceICYStream.h"
#include <string.h>
AudioFileSourceICYStream::AudioFileSourceICYStream()
{
pos = 0;
reconnectTries = 0;
saveURL[0] = 0;
}
AudioFileSourceICYStream::AudioFileSourceICYStream(const char *url)
{
saveURL[0] = 0;
reconnectTries = 0;
open(url);
}
bool AudioFileSourceICYStream::open(const char *url)
{
static const char *hdr[] = { "icy-metaint", "icy-name", "icy-genre", "icy-br" };
pos = 0;
http.begin(client, url);
http.addHeader("Icy-MetaData", "1");
http.collectHeaders( hdr, 4 );
http.setReuse(true);
int code = http.GET();
if (code != HTTP_CODE_OK) {
http.end();
cb.st(STATUS_HTTPFAIL, PSTR("Can't open HTTP request"));
return false;
}
if (http.hasHeader(hdr[0])) {
String ret = http.header(hdr[0]);
icyMetaInt = ret.toInt();
} else {
icyMetaInt = 0;
}
if (http.hasHeader(hdr[1])) {
String ret = http.header(hdr[1]);
// cb.md("SiteName", false, ret.c_str());
}
if (http.hasHeader(hdr[2])) {
String ret = http.header(hdr[2]);
// cb.md("Genre", false, ret.c_str());
}
if (http.hasHeader(hdr[3])) {
String ret = http.header(hdr[3]);
// cb.md("Bitrate", false, ret.c_str());
}
icyByteCount = 0;
size = http.getSize();
strncpy(saveURL, url, sizeof(saveURL));
saveURL[sizeof(saveURL)-1] = 0;
return true;
}
AudioFileSourceICYStream::~AudioFileSourceICYStream()
{
http.end();
}
uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool nonBlock)
{
retry:
if (!http.connected()) {
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
http.end();
for (int i = 0; i < reconnectTries; i++) {
char buff[32];
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
cb.st(STATUS_RECONNECTING, buff);
delay(reconnectDelayMs);
if (open(saveURL)) {
cb.st(STATUS_RECONNECTED, PSTR("Stream reconnected"));
break;
}
}
if (!http.connected()) {
cb.st(STATUS_DISCONNECTED, PSTR("Unable to reconnect"));
return 0;
}
}
if ((size > 0) && (pos >= size)) return 0;
WiFiClient *stream = http.getStreamPtr();
// Can't read past EOF...
if ( (size > 0) && (len > (uint32_t)(pos - size)) ) len = pos - size;
if (!nonBlock) {
int start = millis();
while ((stream->available() < (int)len) && (millis() - start < 500)) yield();
}
size_t avail = stream->available();
if (!nonBlock && !avail) {
cb.st(STATUS_NODATA, PSTR("No stream data available"));
http.end();
goto retry;
}
if (avail == 0) return 0;
if (avail < len) len = avail;
int read = 0;
int ret = 0;
// If the read would hit an ICY block, split it up...
if (((int)(icyByteCount + len) > (int)icyMetaInt) && (icyMetaInt > 0)) {
int beforeIcy = icyMetaInt - icyByteCount;
if (beforeIcy > 0) {
ret = stream->read(reinterpret_cast<uint8_t*>(data), beforeIcy);
if (ret < 0) ret = 0;
read += ret;
pos += ret;
len -= ret;
data = (void *)(reinterpret_cast<char*>(data) + ret);
icyByteCount += ret;
if (ret != beforeIcy) return read; // Partial read
}
// ICY MD handling
int mdSize;
uint8_t c;
int mdret = stream->read(&c, 1);
if (mdret==0) return read;
mdSize = c * 16;
if ((mdret == 1) && (mdSize > 0)) {
// This is going to get ugly fast.
char icyBuff[256 + 16 + 1];
char *readInto = icyBuff + 16;
memset(icyBuff, 0, 16); // Ensure no residual matches occur
while (mdSize) {
int toRead = mdSize > 256 ? 256 : mdSize;
int ret = stream->read((uint8_t*)readInto, toRead);
if (ret < 0) return read;
if (ret == 0) { delay(1); continue; }
mdSize -= ret;
// At this point we have 0...15 = last 15 chars read from prior read plus new data
int end = 16 + ret; // The last byte of valid data
char *header = (char *)memmem((void*)icyBuff, end, (void*)"StreamTitle=", 12);
if (!header) {
// No match, so move the last 16 bytes back to the start and continue
memmove(icyBuff, icyBuff+end-16, 16);
delay(1);
continue;
}
// Found header, now move it to the front
int lastValidByte = end - (header -icyBuff) + 1;
memmove(icyBuff, header, lastValidByte);
// Now fill the buffer to the end with read data
while (mdSize && lastValidByte < 255) {
int toRead = mdSize > (256 - lastValidByte) ? (256 - lastValidByte) : mdSize;
ret = stream->read((uint8_t*)icyBuff + lastValidByte, toRead);
if (ret==-1) return read; // error
if (ret == 0) { delay(1); continue; }
mdSize -= ret;
lastValidByte += ret;
}
// Buffer now contains StreamTitle=....., parse it
char *p = icyBuff+12;
if (*p=='\'' || *p== '"' ) {
char closing[] = { *p, ';', '\0' };
char *psz = strstr( p+1, closing );
if( !psz ) psz = strchr( &icyBuff[13], ';' );
if( psz ) *psz = '\0';
p++;
} else {
char *psz = strchr( p, ';' );
if( psz ) *psz = '\0';
}
cb.md("StreamTitle", false, p);
// Now skip rest of MD block
while (mdSize) {
int toRead = mdSize > 256 ? 256 : mdSize;
ret = stream->read((uint8_t*)icyBuff, toRead);
if (ret < 0) return read;
if (ret == 0) { delay(1); continue; }
mdSize -= ret;
}
}
}
icyByteCount = 0;
}
ret = stream->read(reinterpret_cast<uint8_t*>(data), len);
if (ret < 0) ret = 0;
read += ret;
pos += ret;
icyByteCount += ret;
return read;
}

View File

@ -0,0 +1,49 @@
/*
AudioFileSourceHTTPStream
Connect to a HTTP based streaming service
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEICYSTREAM_H
#define _AUDIOFILESOURCEICYSTREAM_H
#include <Arduino.h>
#ifdef ESP32
#include <HTTPClient.h>
#else
#include <ESP8266HTTPClient.h>
#endif
#include "AudioFileSourceHTTPStream.h"
class AudioFileSourceICYStream : public AudioFileSourceHTTPStream
{
public:
AudioFileSourceICYStream();
AudioFileSourceICYStream(const char *url);
virtual ~AudioFileSourceICYStream() override;
virtual bool open(const char *url) override;
private:
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock) override;
int icyMetaInt;
int icyByteCount;
};
#endif

View File

@ -0,0 +1,265 @@
/*
AudioFileSourceID3
ID3 filter that extracts any ID3 fields and sends to CB function
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioFileSourceID3.h"
// Handle unsync operation in ID3 with custom class
class AudioFileSourceUnsync : public AudioFileSource
{
public:
AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync);
virtual ~AudioFileSourceUnsync() override;
virtual uint32_t read(void *data, uint32_t len) override;
int getByte();
bool eof();
private:
AudioFileSource *src;
int remaining;
bool unsync;
int savedByte;
};
AudioFileSourceUnsync::AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync)
{
this->src = src;
this->remaining = len;
this->unsync = unsync;
this->savedByte = -1;
}
AudioFileSourceUnsync::~AudioFileSourceUnsync()
{
}
uint32_t AudioFileSourceUnsync::read(void *data, uint32_t len)
{
uint32_t bytes = 0;
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
// This is only used during ID3 parsing, so no need to optimize here...
while (len--) {
int b = getByte();
if (b >= 0) {
*(ptr++) = (uint8_t)b;
bytes++;
}
}
return bytes;
}
int AudioFileSourceUnsync::getByte()
{
// If we're not unsync, just read.
if (!unsync) {
uint8_t c;
if (!remaining) return -1;
remaining--;
if (1 != src->read(&c, 1)) return -1;
return c;
}
// If we've saved a pre-read character, return it immediately
if (savedByte >= 0) {
int s = savedByte;
savedByte = -1;
return s;
}
if (remaining <= 0) {
return -1;
} else if (remaining == 1) {
remaining--;
uint8_t c;
if (1 != src->read(&c, 1)) return -1;
else return c;
} else {
uint8_t c;
remaining--;
if (1 != src->read(&c, 1)) return -1;
if (c != 0xff) {
return c;
}
// Saw 0xff, check next byte. If 0 then eat it, OTW return the 0xff
uint8_t d;
remaining--;
if (1 != src->read(&d, 1)) return c;
if (d != 0x00) {
savedByte = d;
}
return c;
}
}
bool AudioFileSourceUnsync::eof()
{
if (remaining<=0) return true;
else return false;
}
AudioFileSourceID3::AudioFileSourceID3(AudioFileSource *src)
{
this->src = src;
this->checked = false;
}
AudioFileSourceID3::~AudioFileSourceID3()
{
}
uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
{
int rev = 0;
if (checked) {
return src->read(data, len);
}
checked = true;
// <10 bytes initial read, not enough space to check header
if (len<10) return src->read(data, len);
uint8_t *buff = reinterpret_cast<uint8_t*>(data);
int ret = src->read(data, 10);
if (ret<10) return ret;
if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) {
return 10 + src->read(buff+10, len-10);
}
rev = buff[3];
bool unsync = false;
bool exthdr = false;
switch(rev) {
case 2:
unsync = (buff[5] & 0x80);
exthdr = false;
break;
case 3:
case 4:
unsync = (buff[5] & 0x80);
exthdr = (buff[5] & 0x40);
break;
};
int id3Size = buff[6];
id3Size = id3Size << 7;
id3Size |= buff[7];
id3Size = id3Size << 7;
id3Size |= buff[8];
id3Size = id3Size << 7;
id3Size |= buff[9];
// Every read from now may be unsync'd
AudioFileSourceUnsync id3(src, id3Size, unsync);
if (exthdr) {
int ehsz = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
for (int j=0; j<ehsz-4; j++) id3.getByte(); // Throw it away
}
do {
unsigned char frameid[4];
int framesize;
bool compressed;
frameid[0] = id3.getByte();
frameid[1] = id3.getByte();
frameid[2] = id3.getByte();
if (rev==2) frameid[3] = 0;
else frameid[3] = id3.getByte();
if (frameid[0]==0 && frameid[1]==0 && frameid[2]==0 && frameid[3]==0) {
// We're in padding
while (!id3.eof()) {
id3.getByte();
}
} else {
if (rev==2) {
framesize = (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
compressed = false;
} else {
framesize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
id3.getByte(); // skip 1st flag
compressed = id3.getByte()&0x80;
}
if (compressed) {
int decompsize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
// TODO - add libz decompression, for now ignore this one...
(void)decompsize;
for (int j=0; j<framesize; j++)
id3.getByte();
}
// Read the value and send to callback
char value[64];
uint16_t i;
bool isUnicode = (id3.getByte()==1) ? true : false;
for (i=0; i<framesize-1; i++) {
if (i<sizeof(value)-1) value[i] = id3.getByte();
else (void)id3.getByte();
}
value[i<sizeof(value)-1?i:sizeof(value)-1] = 0; // Terminate the string...
if ( (frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && frameid[3] == 'B' ) ||
(frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && rev==2) ) {
cb.md("Album", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='I' && frameid[2]=='T' && frameid[3] == '2') ||
(frameid[0]=='T' && frameid[1]=='T' && frameid[2]=='2' && rev==2) ) {
cb.md("Title", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='E' && frameid[3] == '1') ||
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='1' && rev==2) ) {
cb.md("Performer", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') ||
(frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) {
cb.md("Year", isUnicode, value);
}
}
} while (!id3.eof());
// All ID3 processing done, return to main caller
return src->read(data, len);
}
bool AudioFileSourceID3::seek(int32_t pos, int dir)
{
return src->seek(pos, dir);
}
bool AudioFileSourceID3::close()
{
return src->close();
}
bool AudioFileSourceID3::isOpen()
{
return src->isOpen();
}
uint32_t AudioFileSourceID3::getSize()
{
return src->getSize();
}
uint32_t AudioFileSourceID3::getPos()
{
return src->getPos();
}

View File

@ -0,0 +1,48 @@
/*
AudioFileSourceID3
ID3 filter that extracts any ID3 fields and sends to CB function
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEID3_H
#define _AUDIOFILESOURCEID3_H
#include <Arduino.h>
#include "AudioFileSource.h"
class AudioFileSourceID3 : public AudioFileSource
{
public:
AudioFileSourceID3(AudioFileSource *src);
virtual ~AudioFileSourceID3() override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
private:
AudioFileSource *src;
bool checked;
};
#endif

View File

@ -0,0 +1,43 @@
/*
AudioFileSourceFS
Input Arduion "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCESPIFFS_H
#define _AUDIOFILESOURCESPIFFS_H
#ifndef ESP32 // No LittleFS there, yet
#include <Arduino.h>
#include <LittleFS.h>
#include "AudioFileSource.h"
#include "AudioFileSourceFS.h"
class AudioFileSourceLittleFS : public AudioFileSourceFS
{
public:
AudioFileSourceLittleFS() : AudioFileSourceFS(LittleFS) { };
AudioFileSourceLittleFS(const char *filename) : AudioFileSourceFS(LittleFS, filename) {};
// Others are inherited from base
};
#endif
#endif

View File

@ -0,0 +1,99 @@
/*
AudioFileSourcePROGMEM
Store a "file" as a PROGMEM array and use it as audio source data
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioFileSourcePROGMEM.h"
AudioFileSourcePROGMEM::AudioFileSourcePROGMEM()
{
opened = false;
progmemData = NULL;
progmemLen = 0;
filePointer = 0;
}
AudioFileSourcePROGMEM::AudioFileSourcePROGMEM(const void *data, uint32_t len)
{
open(data, len);
}
AudioFileSourcePROGMEM::~AudioFileSourcePROGMEM()
{
}
bool AudioFileSourcePROGMEM::open(const void *data, uint32_t len)
{
if (!data || !len) return false;
opened = true;
progmemData = data;
progmemLen = len;
filePointer = 0;
return true;
}
uint32_t AudioFileSourcePROGMEM::getSize()
{
if (!opened) return 0;
return progmemLen;
}
bool AudioFileSourcePROGMEM::isOpen()
{
return opened;
}
bool AudioFileSourcePROGMEM::close()
{
opened = false;
progmemData = NULL;
progmemLen = 0;
filePointer = 0;
return true;
}
bool AudioFileSourcePROGMEM::seek(int32_t pos, int dir)
{
if (!opened) return false;
uint32_t newPtr;
switch (dir) {
case SEEK_SET: newPtr = pos; break;
case SEEK_CUR: newPtr = filePointer + pos; break;
case SEEK_END: newPtr = progmemLen - pos; break;
default: return false;
}
if (newPtr > progmemLen) return false;
filePointer = newPtr;
return true;
}
uint32_t AudioFileSourcePROGMEM::read(void *data, uint32_t len)
{
if (!opened) return 0;
if (filePointer >= progmemLen) return 0;
uint32_t toRead = progmemLen - filePointer;
if (toRead > len) toRead = len;
memcpy_P(data, reinterpret_cast<const uint8_t*>(progmemData)+filePointer, toRead);
filePointer += toRead;
return toRead;
}

View File

@ -0,0 +1,49 @@
/*
AudioFileSourcePROGMEM
Store a "file" as a PROGMEM array and use it as audio source data
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEPROGMEM_H
#define _AUDIOFILESOURCEPROGMEM_H
#include "AudioFileSource.h"
class AudioFileSourcePROGMEM : public AudioFileSource
{
public:
AudioFileSourcePROGMEM();
AudioFileSourcePROGMEM(const void *data, uint32_t len);
virtual ~AudioFileSourcePROGMEM() override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override { if (!opened) return 0; else return filePointer; };
bool open(const void *data, uint32_t len);
private:
bool opened;
const void *progmemData;
uint32_t progmemLen;
uint32_t filePointer;
};
#endif

View File

@ -0,0 +1,78 @@
/*
AudioFileSourceSPIFFS
Input SD card "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioFileSourceSD.h"
AudioFileSourceSD::AudioFileSourceSD()
{
}
AudioFileSourceSD::AudioFileSourceSD(const char *filename)
{
open(filename);
}
bool AudioFileSourceSD::open(const char *filename)
{
f = SD.open(filename, FILE_READ);
return f;
}
AudioFileSourceSD::~AudioFileSourceSD()
{
if (f) f.close();
}
uint32_t AudioFileSourceSD::read(void *data, uint32_t len)
{
return f.read(reinterpret_cast<uint8_t*>(data), len);
}
bool AudioFileSourceSD::seek(int32_t pos, int dir)
{
if (!f) return false;
if (dir==SEEK_SET) return f.seek(pos);
else if (dir==SEEK_CUR) return f.seek(f.position() + pos);
else if (dir==SEEK_END) return f.seek(f.size() + pos);
return false;
}
bool AudioFileSourceSD::close()
{
f.close();
return true;
}
bool AudioFileSourceSD::isOpen()
{
return f?true:false;
}
uint32_t AudioFileSourceSD::getSize()
{
if (!f) return 0;
return f.size();
}
uint32_t AudioFileSourceSD::getPos()
{
if (!f) return 0;
return f.position();
}

View File

@ -0,0 +1,52 @@
/*
AudioFileSourceSPIFFS
Input SD card "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCESD_H
#define _AUDIOFILESOURCESD_H
#include "AudioFileSource.h"
#ifdef ESP8266
#include <SdFat.h>
#include <SDFS.h>
#endif
#include <SD.h>
class AudioFileSourceSD : public AudioFileSource
{
public:
AudioFileSourceSD();
AudioFileSourceSD(const char *filename);
virtual ~AudioFileSourceSD() override;
virtual bool open(const char *filename) override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
private:
File f;
};
#endif

View File

@ -0,0 +1,40 @@
/*
AudioFileSourceFS
Input Arduion "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCESPIFFS_H
#define _AUDIOFILESOURCESPIFFS_H
#include <Arduino.h>
#include <FS.h>
#include "AudioFileSource.h"
#include "AudioFileSourceFS.h"
class AudioFileSourceSPIFFS : public AudioFileSourceFS
{
public:
AudioFileSourceSPIFFS() : AudioFileSourceFS(SPIFFS) { };
AudioFileSourceSPIFFS(const char *filename) : AudioFileSourceFS(SPIFFS, filename) {};
// Others are inherited from base
};
#endif

View File

@ -0,0 +1,167 @@
/*
AudioFileSourceSPIRAMBuffer
Buffered file source in external SPI RAM
Copyright (C) 2017 Sebastien Decourriere
Based on AudioFileSourceBuffer class from Earle F. Philhower, III
Copyright (C) 2020 Earle F. Philhower, III
Rewritten for speed and functionality
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "AudioFileSourceSPIRAMBuffer.h"
#pragma GCC optimize ("O3")
AudioFileSourceSPIRAMBuffer::AudioFileSourceSPIRAMBuffer(AudioFileSource *source, uint8_t csPin, uint32_t buffSizeBytes)
{
ram.begin(40, csPin);
ramSize = buffSizeBytes;
writePtr = 0;
readPtr = 0;
filled = false;
src = source;
audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize);
}
AudioFileSourceSPIRAMBuffer::~AudioFileSourceSPIRAMBuffer()
{
ram.end();
}
bool AudioFileSourceSPIRAMBuffer::seek(int32_t pos, int dir)
{
// Invalidate
readPtr = 0;
writePtr = 0;
filled = false;
return src->seek(pos, dir);
}
bool AudioFileSourceSPIRAMBuffer::close()
{
return src->close();
}
bool AudioFileSourceSPIRAMBuffer::isOpen()
{
return src->isOpen();
}
uint32_t AudioFileSourceSPIRAMBuffer::getSize()
{
return src->getSize();
}
uint32_t AudioFileSourceSPIRAMBuffer::getPos()
{
return src->getPos() - (writePtr - readPtr);
}
uint32_t AudioFileSourceSPIRAMBuffer::read(void *data, uint32_t len)
{
uint32_t bytes = 0;
// Check if the buffer isn't empty, otherwise we try to fill completely
if (!filled) {
cb.st(999, PSTR("Filling buffer..."));
uint8_t buffer[256];
writePtr = 0;
readPtr = 0;
// Fill up completely before returning any data at all
do {
int toRead = std::min(ramSize - (writePtr - readPtr), sizeof(buffer));
int length = src->read(buffer, toRead);
if (length > 0) {
#ifdef FAKERAM
for (size_t i=0; i<length; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
#else
ram.writeBytes(writePtr % ramSize, buffer, length);
#endif
writePtr += length;
} else {
// EOF, break out of read loop
break;
}
} while ((writePtr - readPtr) < ramSize);
filled = true;
cb.st(999, PSTR("Buffer filled..."));
}
// Read up to the entire buffer from RAM
uint32_t toReadFromBuffer = std::min(len, writePtr - readPtr);
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
if (toReadFromBuffer > 0) {
#ifdef FAKERAM
for (size_t i=0; i<toReadFromBuffer; i++) ptr[i] = fakeRAM[(i+readPtr)%ramSize];
#else
ram.readBytes(readPtr % ramSize, ptr, toReadFromBuffer);
#endif
readPtr += toReadFromBuffer;
ptr += toReadFromBuffer;
bytes += toReadFromBuffer;
len -= toReadFromBuffer;
}
// If len>0 there is no data left in buffer and we try to read more directly from source.
// Then, we trigger a complete buffer refill
if (len) {
bytes += src->read(data, len);
filled = false;
}
return bytes;
}
void AudioFileSourceSPIRAMBuffer::fill()
{
// Make sure the buffer is pre-filled before make partial fill.
if (!filled) return;
for (auto i=0; i<5; i++) {
// Make sure there is at least buffer size free in RAM
uint8_t buffer[128];
if ((ramSize - (writePtr - readPtr)) < sizeof(buffer)) {
return;
}
int cnt = src->readNonBlock(buffer, sizeof(buffer));
if (cnt) {
#ifdef FAKERAM
for (size_t i=0; i<cnt; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
#else
ram.writeBytes(writePtr % ramSize, buffer, cnt);
#endif
writePtr += cnt;
}
}
}
bool AudioFileSourceSPIRAMBuffer::loop()
{
static uint32_t last = 0;
if (!src->loop()) return false;
fill();
if ((ESP.getCycleCount() - last) > microsecondsToClockCycles(1000000)) {
last = ESP.getCycleCount();
char str[65];
memset(str, '#', 64);
str[64] = 0;
str[((writePtr - readPtr) * 64)/ramSize] = 0;
cb.st(((writePtr - readPtr) * 100)/ramSize, str);
}
return true;
}

View File

@ -0,0 +1,65 @@
/*
AudioFileSourceSPIRAMBuffer
Buffered file source in external SPI RAM
Copyright (C) 2017 Sebastien Decourriere
Based on AudioFileSourceBuffer class from Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCESPIRAMBUFFER_H
#define _AUDIOFILESOURCESPIRAMBUFFER_H
#include "AudioFileSource.h"
#include <SPI.h>
#include "spiram-fast.h"
//#define FAKERAM
// #define SPIBUF_DEBUG
class AudioFileSourceSPIRAMBuffer : public AudioFileSource
{
public:
#ifdef FAKERAM
AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 2048);
#else
AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 128*1024);
#endif
virtual ~AudioFileSourceSPIRAMBuffer() override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
virtual bool loop() override;
private:
virtual void fill();
private:
AudioFileSource *src;
ESP8266SPIRAM ram;
size_t ramSize;
size_t writePtr;
size_t readPtr;
bool filled;
#ifdef FAKERAM
char fakeRAM[2048];
#endif
};
#endif

View File

@ -0,0 +1,98 @@
/*
AudioFileSourceSTDIO
Input STDIO "file" to be used by AudioGenerator
Only for hot-based testing
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifndef ARDUINO
#include <time.h>
#include "AudioFileSourceSTDIO.h"
AudioFileSourceSTDIO::AudioFileSourceSTDIO()
{
f = NULL;
srand(time(NULL));
}
AudioFileSourceSTDIO::AudioFileSourceSTDIO(const char *filename)
{
open(filename);
}
bool AudioFileSourceSTDIO::open(const char *filename)
{
f = fopen(filename, "rb");
return f;
}
AudioFileSourceSTDIO::~AudioFileSourceSTDIO()
{
if (f) fclose(f);
f = NULL;
}
uint32_t AudioFileSourceSTDIO::read(void *data, uint32_t len)
{
// if (rand() % 100 == 69) { // Give 0 data 1%
// printf("0 read\n");
// len = 0;
// } else if (rand() % 100 == 1) { // Give short reads 1%
// printf("0 read\n");
// len = 0;
// }
int ret = fread(reinterpret_cast<uint8_t*>(data), 1, len, f);
// if (ret && rand() % 100 < 5 ) {
// // We're really mean...throw bad data in the mix
// printf("bad data\n");
// for (int i=0; i<100; i++)
// *(reinterpret_cast<uint8_t*>(data) + (rand() % ret)) = rand();
// }
return ret;
}
bool AudioFileSourceSTDIO::seek(int32_t pos, int dir)
{
return fseek(f, pos, dir) == 0;
}
bool AudioFileSourceSTDIO::close()
{
fclose(f);
f = NULL;
return true;
}
bool AudioFileSourceSTDIO::isOpen()
{
return f?true:false;
}
uint32_t AudioFileSourceSTDIO::getSize()
{
if (!f) return 0;
uint32_t p = ftell(f);
fseek(f, 0, SEEK_END);
uint32_t len = ftell(f);
fseek(f, p, SEEK_SET);
return len;
}
#endif

View File

@ -0,0 +1,53 @@
/*
AudioFileSourceSTDIO
Input SPIFFS "file" to be used by AudioGenerator
Only for host-based testing, not Arduino
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCESTDIO_H
#define _AUDIOFILESOURCESTDIO_H
#include <Arduino.h>
#ifndef ARDUINO
#include "AudioFileSource.h"
class AudioFileSourceSTDIO : public AudioFileSource
{
public:
AudioFileSourceSTDIO();
AudioFileSourceSTDIO(const char *filename);
virtual ~AudioFileSourceSTDIO() override;
virtual bool open(const char *filename) override;
virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;
virtual bool close() override;
virtual bool isOpen() override;
virtual uint32_t getSize() override;
virtual uint32_t getPos() override { if (!f) return 0; else return (uint32_t)ftell(f); };
private:
FILE *f;
};
#endif // !ARDUINO
#endif

View File

@ -0,0 +1,61 @@
#include <Arduino.h>
#include "AudioFileStream.h"
AudioFileStream::AudioFileStream(AudioFileSource *source, int definedLen)
{
src = source;
len = definedLen;
ptr = 0;
saved = -1;
}
AudioFileStream::~AudioFileStream()
{
// If there's a defined len, read until we're empty
if (len) {
while (ptr++ < len) (void)read();
}
}
int AudioFileStream::available()
{
if (saved >= 0) return 1;
else if (len) return ptr - len;
else if (src->getSize()) return (src->getPos() - src->getSize());
else return 1;
}
int AudioFileStream::read()
{
uint8_t c;
int r;
if (ptr >= len) return -1;
ptr++;
if (saved >= 0) {
c = (uint8_t)saved;
saved = -1;
r = 1;
} else {
r = src->read(&c, 1);
}
if (r != 1) return -1;
return (int)c;
}
int AudioFileStream::peek()
{
uint8_t c;
if ((ptr+1) >= len) return -1;
if (saved >= 0) return saved;
int r = src->read(&c, 1);
if (r<1) return -1;
saved = c;
return saved;
}
void AudioFileStream::flush()
{
/* noop? */
}

View File

@ -0,0 +1,48 @@
/*
AudioFileStream
Convert an AudioFileSource* to a Stream*
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef AUDIOFILESTREAM_H
#define AUDIOFILESTREAM_H
#include <Arduino.h>
#include "AudioFileSource.h"
class AudioFileStream : public Stream
{
public:
AudioFileStream(AudioFileSource *source, int definedLen);
virtual ~AudioFileStream();
public:
// Stream interface - see the Arduino library documentation.
virtual int available() override;
virtual int read() override;
virtual int peek() override;
virtual void flush() override;
virtual size_t write(uint8_t x) override { (void)x; return 0; };
private:
AudioFileSource *src;
int saved;
int len;
int ptr;
};
#endif

View File

@ -0,0 +1,53 @@
/*
AudioGenerator
Base class of an audio output generator
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATOR_H
#define _AUDIOGENERATOR_H
#include <Arduino.h>
#include "AudioStatus.h"
#include "AudioFileSource.h"
#include "AudioOutput.h"
class AudioGenerator
{
public:
AudioGenerator() { lastSample[0] = 0; lastSample[1] = 0; };
virtual ~AudioGenerator() {};
virtual bool begin(AudioFileSource *source, AudioOutput *output) { (void)source; (void)output; return false; };
virtual bool loop() { return false; };
virtual bool stop() { return false; };
virtual bool isRunning() { return false;};
public:
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
protected:
bool running;
AudioFileSource *file;
AudioOutput *output;
int16_t lastSample[2];
protected:
AudioStatus cb;
};
#endif

View File

@ -0,0 +1,214 @@
/*
AudioGeneratorAAC
Audio output generator using the Helix AAC decoder
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#pragma GCC optimize ("O3")
#include "AudioGeneratorAAC.h"
AudioGeneratorAAC::AudioGeneratorAAC()
{
preallocateSpace = NULL;
preallocateSize = 0;
running = false;
file = NULL;
output = NULL;
buff = (uint8_t*)malloc(buffLen);
outSample = (int16_t*)malloc(1024 * 2 * sizeof(uint16_t));
if (!buff || !outSample) {
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
Serial.flush();
}
hAACDecoder = AACInitDecoder();
if (!hAACDecoder) {
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
Serial.flush();
}
buffValid = 0;
lastFrameEnd = 0;
validSamples = 0;
curSample = 0;
lastRate = 0;
lastChannels = 0;
}
AudioGeneratorAAC::AudioGeneratorAAC(void *preallocateData, int preallocateSz)
{
preallocateSpace = preallocateData;
preallocateSize = preallocateSz;
running = false;
file = NULL;
output = NULL;
uint8_t *p = (uint8_t*)preallocateSpace;
buff = (uint8_t*) p;
p += (buffLen + 7) & ~7;
outSample = (int16_t*) p;
p += (1024 * 2 * sizeof(int16_t) + 7) & ~7;
int used = p - (uint8_t*)preallocateSpace;
int availSpace = preallocateSize - used;
if (availSpace < 0 ) {
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
}
hAACDecoder = AACInitDecoderPre(p, availSpace);
if (!hAACDecoder) {
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
Serial.flush();
}
buffValid = 0;
lastFrameEnd = 0;
validSamples = 0;
curSample = 0;
lastRate = 0;
lastChannels = 0;
}
AudioGeneratorAAC::~AudioGeneratorAAC()
{
if (!preallocateSpace) {
AACFreeDecoder(hAACDecoder);
free(buff);
free(outSample);
}
}
bool AudioGeneratorAAC::stop()
{
running = false;
output->stop();
return file->close();
}
bool AudioGeneratorAAC::isRunning()
{
return running;
}
bool AudioGeneratorAAC::FillBufferWithValidFrame()
{
buff[0] = 0; // Destroy any existing sync word @ 0
int nextSync;
do {
nextSync = AACFindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
if (nextSync >= 0) nextSync += lastFrameEnd;
lastFrameEnd = 0;
if (nextSync == -1) {
if (buffValid && buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
buff[0] = 0xff;
buffValid = file->read(buff+1, buffLen-1);
if (buffValid==0) return false; // No data available, EOF
} else { // Try a whole new buffer
buffValid = file->read(buff, buffLen-1);
if (buffValid==0) return false; // No data available, EOF
}
}
} while (nextSync == -1);
// Move the frame to start at offset 0 in the buffer
buffValid -= nextSync; // Throw out prior to nextSync
memmove(buff, buff+nextSync, buffValid);
// We have a sync word at 0 now, try and fill remainder of buffer
buffValid += file->read(buff + buffValid, buffLen - buffValid);
return true;
}
bool AudioGeneratorAAC::loop()
{
if (!running) goto done; // Nothing to do here!
// If we've got data, try and pump it out...
while (validSamples) {
lastSample[0] = outSample[curSample*2];
lastSample[1] = outSample[curSample*2 + 1];
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
validSamples--;
curSample++;
}
// No samples available, need to decode a new frame
if (FillBufferWithValidFrame()) {
// buff[0] start of frame, decode it...
unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
int bytesLeft = buffValid;
int ret = AACDecode(hAACDecoder, &inBuff, &bytesLeft, outSample);
if (ret) {
// Error, skip the frame...
char buff[48];
sprintf_P(buff, PSTR("AAC decode error %d"), ret);
cb.st(ret, buff);
} else {
lastFrameEnd = buffValid - bytesLeft;
AACFrameInfo fi;
AACGetLastFrameInfo(hAACDecoder, &fi);
if ((int)fi.sampRateOut != (int)lastRate) {
output->SetRate(fi.sampRateOut);
lastRate = fi.sampRateOut;
}
if (fi.nChans != lastChannels) {
output->SetChannels(fi.nChans);
lastChannels = fi.nChans;
}
curSample = 0;
validSamples = fi.outputSamps / lastChannels;
}
} else {
running = false; // No more data, we're done here...
}
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorAAC::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) return false; // Error
output->begin();
// AAC always comes out at 16 bits
output->SetBitsPerSample(16);
memset(buff, 0, buffLen);
memset(outSample, 0, 1024*2*sizeof(int16_t));
running = true;
return true;
}

View File

@ -0,0 +1,64 @@
/*
AudioGeneratorAAC
Audio output generator using the Helix AAC decoder
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORAAC_H
#define _AUDIOGENERATORAAC_H
#include "AudioGenerator.h"
#include "libhelix-aac/aacdec.h"
class AudioGeneratorAAC : public AudioGenerator
{
public:
AudioGeneratorAAC();
AudioGeneratorAAC(void *preallocateData, int preallocateSize);
virtual ~AudioGeneratorAAC() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
void *preallocateSpace;
int preallocateSize;
// Helix AAC decoder
HAACDecoder hAACDecoder;
// Input buffering
const int buffLen = 1600;
uint8_t *buff; //[1600]; // File buffer required to store at least a whole compressed frame
int16_t buffValid;
int16_t lastFrameEnd;
bool FillBufferWithValidFrame(); // Read until we get a valid syncword and min(feof, 2048) butes in the buffer
// Output buffering
int16_t *outSample; //[1024 * 2]; // Interleaved L/R
int16_t validSamples;
int16_t curSample;
// Each frame may change this if they're very strange, I guess
unsigned int lastRate;
int lastChannels;
};
#endif

View File

@ -0,0 +1,199 @@
/*
AudioGeneratorFLAC
Audio output generator that plays FLAC audio files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <AudioGeneratorFLAC.h>
AudioGeneratorFLAC::AudioGeneratorFLAC()
{
flac = NULL;
channels = 0;
sampleRate = 0;
bitsPerSample = 0;
buff[0] = NULL;
buff[1] = NULL;
buffPtr = 0;
buffLen = 0;
running = false;
}
AudioGeneratorFLAC::~AudioGeneratorFLAC()
{
if (flac)
FLAC__stream_decoder_delete(flac);
flac = NULL;
}
bool AudioGeneratorFLAC::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) return false; // Error
flac = FLAC__stream_decoder_new();
if (!flac) return false;
(void)FLAC__stream_decoder_set_md5_checking(flac, false);
FLAC__StreamDecoderInitStatus ret = FLAC__stream_decoder_init_stream(flac, _read_cb, _seek_cb, _tell_cb, _length_cb, _eof_cb, _write_cb, _metadata_cb, _error_cb, reinterpret_cast<void*>(this) );
if (ret != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
FLAC__stream_decoder_delete(flac);
flac = NULL;
return false;
}
output->begin();
running = true;
return true;
}
bool AudioGeneratorFLAC::loop()
{
FLAC__bool ret;
if (!running) goto done;
if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample
do {
if (buffPtr == buffLen) {
ret = FLAC__stream_decoder_process_single(flac);
if (!ret) {
running = false;
goto done;
} else {
// We might be done...
if (FLAC__stream_decoder_get_state(flac)==FLAC__STREAM_DECODER_END_OF_STREAM) {
running = false;
goto done;
}
unsigned newsr = FLAC__stream_decoder_get_sample_rate(flac);
unsigned newch = FLAC__stream_decoder_get_channels(flac);
unsigned newbps = FLAC__stream_decoder_get_bits_per_sample(flac);
if (newsr != sampleRate) output->SetRate(sampleRate = newsr);
if (newch != channels) output->SetChannels(channels = newch);
if (newbps != bitsPerSample) output->SetBitsPerSample( bitsPerSample = newbps);
}
}
// Check for some weird case where above didn't give any data
if (buffPtr == buffLen) {
goto done; // At some point the flac better error and we'll return
}
if (bitsPerSample <= 16) {
lastSample[AudioOutput::LEFTCHANNEL] = buff[0][buffPtr] & 0xffff;
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = buff[1][buffPtr] & 0xffff;
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
} else if (bitsPerSample <= 24) {
lastSample[AudioOutput::LEFTCHANNEL] = (buff[0][buffPtr]>>8) & 0xffff;
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = (buff[1][buffPtr]>>8) & 0xffff;
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
} else {
lastSample[AudioOutput::LEFTCHANNEL] = (buff[0][buffPtr]>>16) & 0xffff;
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = (buff[1][buffPtr]>>16) & 0xffff;
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
}
buffPtr++;
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorFLAC::stop()
{
if (flac)
FLAC__stream_decoder_delete(flac);
flac = NULL;
running = false;
output->stop();
return true;
}
bool AudioGeneratorFLAC::isRunning()
{
return running;
}
FLAC__StreamDecoderReadStatus AudioGeneratorFLAC::read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes)
{
(void) decoder;
if (*bytes==0) return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
*bytes = file->read(buffer, sizeof(FLAC__byte) * (*bytes));
if (*bytes==0) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
FLAC__StreamDecoderSeekStatus AudioGeneratorFLAC::seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset)
{
(void) decoder;
if (!file->seek((int32_t)absolute_byte_offset, 0)) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
FLAC__StreamDecoderTellStatus AudioGeneratorFLAC::tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset)
{
(void) decoder;
*absolute_byte_offset = file->getPos();
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
FLAC__StreamDecoderLengthStatus AudioGeneratorFLAC::length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length)
{
(void) decoder;
*stream_length = file->getSize();
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
FLAC__bool AudioGeneratorFLAC::eof_cb(const FLAC__StreamDecoder *decoder)
{
(void) decoder;
if (file->getPos() >= file->getSize()) return true;
return false;
}
FLAC__StreamDecoderWriteStatus AudioGeneratorFLAC::write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[])
{
(void) decoder;
// Hackish warning here. FLAC sends the buffer but doesn't free it until the next call to decode_frame, so we stash
// the pointers here and use it in our loop() instead of memcpy()'ing into yet another buffer.
buffLen = frame->header.blocksize;
buff[0] = buffer[0];
if (frame->header.channels>1) buff[1] = buffer[1];
else buff[1] = buffer[0];
buffPtr = 0;
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
void AudioGeneratorFLAC::metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata)
{
(void) decoder;
(void) metadata;
audioLogger->printf_P(PSTR("Metadata\n"));
}
char AudioGeneratorFLAC::error_cb_str[64];
void AudioGeneratorFLAC::error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status)
{
(void) decoder;
strncpy_P(error_cb_str, FLAC__StreamDecoderErrorStatusString[status], 64);
cb.st((int)status, error_cb_str);
}

View File

@ -0,0 +1,89 @@
/*
AudioGeneratorFLAC
Audio output generator that plays FLAC audio files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORFLAC_H
#define _AUDIOGENERATORFLAC_H
#include <AudioGenerator.h>
extern "C" {
#include "libflac/FLAC/stream_decoder.h"
};
class AudioGeneratorFLAC : public AudioGenerator
{
public:
AudioGeneratorFLAC();
virtual ~AudioGeneratorFLAC() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
// FLAC info
uint16_t channels;
uint32_t sampleRate;
uint16_t bitsPerSample;
// We need to buffer some data in-RAM to avoid doing 1000s of small reads
const int *buff[2];
uint16_t buffPtr;
uint16_t buffLen;
FLAC__StreamDecoder *flac;
// FLAC callbacks, need static functions to bounce into c++ from c
static FLAC__StreamDecoderReadStatus _read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->read_cb(decoder, buffer, bytes);
};
static FLAC__StreamDecoderSeekStatus _seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->seek_cb(decoder, absolute_byte_offset);
};
static FLAC__StreamDecoderTellStatus _tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->tell_cb(decoder, absolute_byte_offset);
};
static FLAC__StreamDecoderLengthStatus _length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->length_cb(decoder, stream_length);
};
static FLAC__bool _eof_cb(const FLAC__StreamDecoder *decoder, void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->eof_cb(decoder);
};
static FLAC__StreamDecoderWriteStatus _write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) {
return static_cast<AudioGeneratorFLAC*>(client_data)->write_cb(decoder, frame, buffer);
};
static void _metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
static_cast<AudioGeneratorFLAC*>(client_data)->metadata_cb(decoder, metadata);
};
static void _error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
static_cast<AudioGeneratorFLAC*>(client_data)->error_cb(decoder, status);
};
// Actual FLAC callbacks
FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes);
FLAC__StreamDecoderSeekStatus seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset);
FLAC__StreamDecoderTellStatus tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset);
FLAC__StreamDecoderLengthStatus length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length);
FLAC__bool eof_cb(const FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
void metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata);
static char error_cb_str[64];
void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status);
};
#endif

View File

@ -0,0 +1,639 @@
/*
AudioGeneratorMIDI
Audio output generator that plays MIDI files using a SF2 SoundFont
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
/*
The MIDI processing engine is a heavily modified version of MIDITONES,
by Len Shustek, https://github.com/LenShustek/miditones .
Whereas MIDITONES original simply parsed a file beforehand to a byte
stream to be played by another program, this does the parsing and
playback in real-time.
Here's his original header/readme w/MIT license, which is subsumed by the
GPL license of the ESP8266Audio project.
*/
/***************************************************************************
MIDITONES: Convert a MIDI file into a simple bytestream of notes
-------------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2011,2013,2015,2016, Len Shustek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**************************************************************************/
#include "AudioGeneratorMIDI.h"
#pragma GCC optimize ("O3")
#define TSF_NO_STDIO
#define TSF_IMPLEMENTATION
#include "libtinysoundfont/tsf.h"
/**************** utility routines **********************/
/* announce a fatal MIDI file format error */
void AudioGeneratorMIDI::midi_error(const char *msg, int curpos)
{
cb.st(curpos, msg);
#if 0
int ptr;
audioLogger->printf("---> MIDI file error at position %04X (%d): %s\n", (uint16_t) curpos, (uint16_t) curpos, msg);
/* print some bytes surrounding the error */
ptr = curpos - 16;
if (ptr < 0) ptr = 0;
buffer.seek( buffer.data, ptr );
for (int i = 0; i < 32; i++) {
char c;
buffer.read (buffer.data, &c, 1);
audioLogger->printf((ptr + i) == curpos ? " [%02X] " : "%02X ", (int) c & 0xff);
}
audioLogger->printf("\n");
#endif
running = false;
}
/* check that we have a specified number of bytes left in the buffer */
void AudioGeneratorMIDI::chk_bufdata (int ptr, unsigned long int len) {
if ((unsigned) (ptr + len) > buflen)
midi_error ("data missing", ptr);
}
/* fetch big-endian numbers */
uint16_t AudioGeneratorMIDI::rev_short (uint16_t val) {
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
}
uint32_t AudioGeneratorMIDI::rev_long (uint32_t val) {
return (((rev_short ((uint16_t) val) & 0xffff) << 16) |
(rev_short ((uint16_t) (val >> 16)) & 0xffff));
}
/************** process the MIDI file header *****************/
void AudioGeneratorMIDI::process_header (void) {
struct midi_header hdr;
unsigned int time_division;
chk_bufdata (hdrptr, sizeof (struct midi_header));
buffer.seek (buffer.data, hdrptr);
buffer.read (buffer.data, &hdr, sizeof (hdr));
if (!charcmp ((char *) hdr.MThd, "MThd"))
midi_error ("Missing 'MThd'", hdrptr);
num_tracks = rev_short (hdr.number_of_tracks);
time_division = rev_short (hdr.time_division);
if (time_division < 0x8000)
ticks_per_beat = time_division;
else
ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ *(time_division & 0xff); /* ticks/SMTE frame */
hdrptr += rev_long (hdr.header_size) + 8; /* point past header to track header, presumably. */
return;
}
/**************** Process a MIDI track header *******************/
void AudioGeneratorMIDI::start_track (int tracknum) {
struct track_header hdr;
unsigned long tracklen;
chk_bufdata (hdrptr, sizeof (struct track_header));
buffer.seek (buffer.data, hdrptr);
buffer.read (buffer.data, &hdr, sizeof (hdr));
if (!charcmp ((char *) (hdr.MTrk), "MTrk"))
midi_error ("Missing 'MTrk'", hdrptr);
tracklen = rev_long (hdr.track_size);
hdrptr += sizeof (struct track_header); /* point past header */
chk_bufdata (hdrptr, tracklen);
track[tracknum].trkptr = hdrptr;
hdrptr += tracklen; /* point to the start of the next track */
track[tracknum].trkend = hdrptr; /* the point past the end of the track */
}
unsigned char AudioGeneratorMIDI::buffer_byte (int offset) {
unsigned char c;
buffer.seek (buffer.data, offset);
buffer.read (buffer.data, &c, 1);
return c;
}
unsigned short AudioGeneratorMIDI::buffer_short (int offset) {
unsigned short s;
buffer.seek (buffer.data, offset);
buffer.read (buffer.data, &s, sizeof (short));
return s;
}
unsigned int AudioGeneratorMIDI::buffer_int32 (int offset) {
uint32_t i;
buffer.seek (buffer.data, offset);
buffer.read (buffer.data, &i, sizeof (i));
return i;
}
/* Get a MIDI-style variable-length integer */
unsigned long AudioGeneratorMIDI::get_varlen (int *ptr) {
/* Get a 1-4 byte variable-length value and adjust the pointer past it.
These are a succession of 7-bit values with a MSB bit of zero marking the end */
unsigned long val;
int i, byte;
val = 0;
for (i = 0; i < 4; ++i) {
byte = buffer_byte ((*ptr)++);
val = (val << 7) | (byte & 0x7f);
if (!(byte & 0x80))
return val;
}
return val;
}
/*************** Process the MIDI track data ***************************/
/* Skip in the track for the next "note on", "note off" or "set tempo" command,
then record that information in the track status block and return. */
void AudioGeneratorMIDI::find_note (int tracknum) {
unsigned long int delta_time;
int event, chan;
int note, velocity, controller, pressure, pitchbend, instrument;
int meta_cmd, meta_length;
unsigned long int sysex_length;
struct track_status *t;
const char *tag;
/* process events */
t = &track[tracknum]; /* our track status structure */
while (t->trkptr < t->trkend) {
delta_time = get_varlen (&t->trkptr);
t->time += delta_time;
if (buffer_byte (t->trkptr) < 0x80)
event = t->last_event; /* using "running status": same event as before */
else { /* otherwise get new "status" (event type) */
event = buffer_byte (t->trkptr++);
}
if (event == 0xff) { /* meta-event */
meta_cmd = buffer_byte (t->trkptr++);
meta_length = get_varlen(&t->trkptr);
switch (meta_cmd) {
case 0x00:
break;
case 0x01:
tag = "description";
goto show_text;
case 0x02:
tag = "copyright";
goto show_text;
case 0x03:
tag = "track name";
goto show_text;
case 0x04:
tag = "instrument name";
goto show_text;
case 0x05:
tag = "lyric";
goto show_text;
case 0x06:
tag = "marked point";
goto show_text;
case 0x07:
tag = "cue point";
show_text:
break;
case 0x20:
break;
case 0x2f:
break;
case 0x51: /* tempo: 3 byte big-endian integer! */
t->cmd = CMD_TEMPO;
t->tempo = rev_long (buffer_int32 (t->trkptr - 1)) & 0xffffffL;
t->trkptr += meta_length;
return;
case 0x54:
break;
case 0x58:
break;
case 0x59:
break;
case 0x7f:
tag = "sequencer data";
goto show_hex;
default: /* unknown meta command */
tag = "???";
show_hex:
break;
}
t->trkptr += meta_length;
}
else if (event < 0x80)
midi_error ("Unknown MIDI event type", t->trkptr);
else {
if (event < 0xf0)
t->last_event = event; // remember "running status" if not meta or sysex event
chan = event & 0xf;
t->chan = chan;
switch (event >> 4) {
case 0x8:
t->note = buffer_byte (t->trkptr++);
velocity = buffer_byte (t->trkptr++);
note_off:
t->cmd = CMD_STOPNOTE;
return; /* stop processing and return */
case 0x9:
t->note = buffer_byte (t->trkptr++);
velocity = buffer_byte (t->trkptr++);
if (velocity == 0) /* some scores use note-on with zero velocity for off! */
goto note_off;
t->velocity = velocity;
t->cmd = CMD_PLAYNOTE;
return; /* stop processing and return */
case 0xa:
note = buffer_byte (t->trkptr++);
velocity = buffer_byte (t->trkptr++);
break;
case 0xb:
controller = buffer_byte (t->trkptr++);
velocity = buffer_byte (t->trkptr++);
break;
case 0xc:
instrument = buffer_byte (t->trkptr++);
midi_chan_instrument[chan] = instrument; // record new instrument for this channel
break;
case 0xd:
pressure = buffer_byte (t->trkptr++);
break;
case 0xe:
pitchbend = buffer_byte (t->trkptr) | (buffer_byte (t->trkptr + 1) << 7);
t->trkptr += 2;
break;
case 0xf:
sysex_length = get_varlen (&t->trkptr);
t->trkptr += sysex_length;
break;
default:
midi_error ("Unknown MIDI command", t->trkptr);
}
}
}
t->cmd = CMD_TRACKDONE; /* no more notes to process */
++tracks_done;
// Remove unused warnings..maybe some day we'll look at these
(void)note;
(void)controller;
(void)pressure;
(void)pitchbend;
(void)tag;
}
// Open file, parse headers, get ready tio process MIDI
void AudioGeneratorMIDI::PrepareMIDI(AudioFileSource *src)
{
MakeStreamFromAFS(src, &afsMIDI);
tsf_stream_wrap_cached(&afsMIDI, 32, 64, &buffer);
buflen = buffer.size (buffer.data);
/* process the MIDI file header */
hdrptr = buffer.tell (buffer.data); /* pointer to file and track headers */
process_header ();
printf (" Processing %d tracks.\n", num_tracks);
if (num_tracks > MAX_TRACKS)
midi_error ("Too many tracks", buffer.tell (buffer.data));
/* initialize processing of all the tracks */
for (tracknum = 0; tracknum < num_tracks; ++tracknum) {
start_track (tracknum); /* process the track header */
find_note (tracknum); /* position to the first note on/off */
}
notes_skipped = 0;
tracknum = 0;
earliest_tracknum = 0;
earliest_time = 0;
}
// Parses the note on/offs until we are ready to render some more samples. Then return the
// total number of samples to render before we need to be called again
int AudioGeneratorMIDI::PlayMIDI()
{
/* Continue processing all tracks, in an order based on the simulated time.
This is not unlike multiway merging used for tape sorting algoritms in the 50's! */
do { /* while there are still track notes to process */
static struct track_status *trk;
static struct tonegen_status *tg;
static int tgnum;
static int count_tracks;
static unsigned long delta_time, delta_msec;
/* Find the track with the earliest event time,
and output a delay command if time has advanced.
A potential improvement: If there are multiple tracks with the same time,
first do the ones with STOPNOTE as the next command, if any. That would
help avoid running out of tone generators. In practice, though, most MIDI
files do all the STOPNOTEs first anyway, so it won't have much effect.
*/
earliest_time = 0x7fffffff;
/* Usually we start with the track after the one we did last time (tracknum),
so that if we run out of tone generators, we have been fair to all the tracks.
The alternate "strategy1" says we always start with track 0, which means
that we favor early tracks over later ones when there aren't enough tone generators.
*/
count_tracks = num_tracks;
do {
if (++tracknum >= num_tracks)
tracknum = 0;
trk = &track[tracknum];
if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) {
earliest_time = trk->time;
earliest_tracknum = tracknum;
}
} while (--count_tracks);
tracknum = earliest_tracknum; /* the track we picked */
trk = &track[tracknum];
if (earliest_time < timenow)
midi_error ("INTERNAL: time went backwards", trk->trkptr);
/* If time has advanced, output a "delay" command */
delta_time = earliest_time - timenow;
if (delta_time) {
/* Convert ticks to milliseconds based on the current tempo */
unsigned long long temp;
temp = ((unsigned long long) delta_time * tempo) / ticks_per_beat;
delta_msec = temp / 1000; // get around LCC compiler bug
if (delta_msec > 0x7fff)
midi_error ("INTERNAL: time delta too big", trk->trkptr);
int samples = (((int) delta_msec) * freq) / 1000;
timenow = earliest_time;
return samples;
}
timenow = earliest_time;
/* If this track event is "set tempo", just change the global tempo.
That affects how we generate "delay" commands. */
if (trk->cmd == CMD_TEMPO) {
tempo = trk->tempo;
find_note (tracknum);
}
/* If this track event is "stop note", process it and all subsequent "stop notes" for this track
that are happening at the same time. Doing so frees up as many tone generators as possible. */
else if (trk->cmd == CMD_STOPNOTE)
do {
// stop a note
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */
tg = &tonegen[tgnum];
if (tg->playing && tg->track == tracknum && tg->note == trk->note) {
tsf_note_off (g_tsf, tg->instrument, tg->note);
tg->playing = false;
trk->tonegens[tgnum] = false;
}
}
find_note (tracknum); // use up the note
} while (trk->cmd == CMD_STOPNOTE && trk->time == timenow);
/* If this track event is "start note", process only it.
Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */
else if (trk->cmd == CMD_PLAYNOTE) {
bool foundgen = false;
/* if not, then try for any free tone generator */
if (!foundgen)
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) {
tg = &tonegen[tgnum];
if (!tg->playing) {
foundgen = true;
break;
}
}
if (foundgen) {
if (tgnum + 1 > num_tonegens_used)
num_tonegens_used = tgnum + 1;
tg->playing = true;
tg->track = tracknum;
tg->note = trk->note;
trk->tonegens[tgnum] = true;
trk->preferred_tonegen = tgnum;
if (tg->instrument != midi_chan_instrument[trk->chan]) { /* new instrument for this generator */
tg->instrument = midi_chan_instrument[trk->chan];
}
tsf_note_on (g_tsf, tg->instrument, tg->note, trk->velocity / 127.0); // velocity = 0...127
} else {
++notes_skipped;
}
find_note (tracknum); // use up the note
}
}
while (tracks_done < num_tracks);
return -1; // EOF
}
void AudioGeneratorMIDI::StopMIDI()
{
buffer.close(buffer.data);
tsf_close(g_tsf);
printf (" %s %d tone generators were used.\n",
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used);
if (notes_skipped)
printf
(" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped);
printf (" Done.\n");
}
bool AudioGeneratorMIDI::begin(AudioFileSource *src, AudioOutput *out)
{
// Clear out status variables
for (int i=0; i<MAX_TONEGENS; i++) memset(&tonegen[i], 0, sizeof(struct tonegen_status));
for (int i=0; i<MAX_TRACKS; i++) memset(&track[i], 0, sizeof(struct track_status));
memset(midi_chan_instrument, 0, sizeof(midi_chan_instrument));
g_tsf = tsf_load(&afsSF2);
if (!g_tsf) return false;
tsf_set_output (g_tsf, TSF_MONO, freq, -10 /* dB gain -10 */ );
if (!out->SetRate( freq )) return false;
if (!out->SetBitsPerSample( 16 )) return false;
if (!out->SetChannels( 1 )) return false;
if (!out->begin()) return false;
output = out;
file = src;
running = true;
PrepareMIDI(src);
samplesToPlay = 0;
numSamplesRendered = 0;
sentSamplesRendered = 0;
sawEOF = false;
return running;
}
bool AudioGeneratorMIDI::loop()
{
static int c = 0;
if (!running) goto done; // Nothing to do here!
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
// Try and stuff the buffer one sample at a time
do {
c++;
if (c%44100 == 0) yield();
play:
if (sentSamplesRendered < numSamplesRendered) {
lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[sentSamplesRendered];
lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[sentSamplesRendered];
sentSamplesRendered++;
} else if (samplesToPlay) {
numSamplesRendered = sizeof(samplesRendered)/sizeof(samplesRendered[0]);
if ((int)samplesToPlay < (int)(sizeof(samplesRendered)/sizeof(samplesRendered[0]))) numSamplesRendered = samplesToPlay;
tsf_render_short_fast(g_tsf, samplesRendered, numSamplesRendered, 0);
lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[0];
lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[0];
sentSamplesRendered = 1;
samplesToPlay -= numSamplesRendered;
} else {
numSamplesRendered = 0;
sentSamplesRendered = 0;
if (sawEOF) {
running = false;
} else {
samplesToPlay = PlayMIDI();
if (samplesToPlay == -1) {
sawEOF = true;
samplesToPlay = freq / 2;
}
goto play;
}
}
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorMIDI::stop()
{
StopMIDI();
output->stop();
return true;
}
int AudioGeneratorMIDI::afs_read(void *data, void *ptr, unsigned int size)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->read(ptr, size);
}
int AudioGeneratorMIDI::afs_tell(void *data)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->getPos();
}
int AudioGeneratorMIDI::afs_skip(void *data, unsigned int count)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->seek(count, SEEK_CUR);
}
int AudioGeneratorMIDI::afs_seek(void *data, unsigned int pos)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->seek(pos, SEEK_SET);
}
int AudioGeneratorMIDI::afs_close(void *data)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->close();
}
int AudioGeneratorMIDI::afs_size(void *data)
{
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
return s->getSize();
}
void AudioGeneratorMIDI::MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs)
{
afs->data = reinterpret_cast<void*>(src);
afs->read = &afs_read;
afs->tell = &afs_tell;
afs->skip = &afs_skip;
afs->seek = &afs_seek;
afs->close = &afs_close;
afs->size = &afs_size;
}

View File

@ -0,0 +1,181 @@
/*
AudioGeneratorMIDI
Audio output generator that plays MIDI files using a SF2 SoundFont
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORMIDI_H
#define _AUDIOGENERATORMIDI_H
#include "AudioGenerator.h"
#define TSF_NO_STDIO
#include "libtinysoundfont/tsf.h"
class AudioGeneratorMIDI : public AudioGenerator
{
public:
AudioGeneratorMIDI() { freq=44100; running = false; };
virtual ~AudioGeneratorMIDI() override {};
bool SetSoundfont(AudioFileSource *newsf2) {
if (isRunning()) return false;
sf2 = newsf2;
MakeStreamFromAFS(sf2, &afsSF2);
return true;
}
bool SetSampleRate(int newfreq) {
if (isRunning()) return false;
freq = newfreq;
return true;
}
virtual bool begin(AudioFileSource *mid, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override { return running; };
private:
int freq;
tsf *g_tsf;
struct tsf_stream buffer;
struct tsf_stream afsMIDI;
struct tsf_stream afsSF2;
AudioFileSource *sf2;
AudioFileSource *midi;
protected:
struct midi_header {
int8_t MThd[4];
uint32_t header_size;
uint16_t format_type;
uint16_t number_of_tracks;
uint16_t time_division;
};
struct track_header {
int8_t MTrk[4];
uint32_t track_size;
};
enum { MAX_TONEGENS = 32, /* max tone generators: tones we can play simultaneously */
MAX_TRACKS = 24
}; /* max number of MIDI tracks we will process */
int hdrptr;
unsigned long buflen;
int num_tracks;
int tracks_done = 0;
int num_tonegens = MAX_TONEGENS;
int num_tonegens_used = 0;
unsigned int ticks_per_beat = 240;
unsigned long timenow = 0;
unsigned long tempo; /* current tempo in usec/qnote */
// State needed for PlayMID()
int notes_skipped = 0;
int tracknum = 0;
int earliest_tracknum = 0;
unsigned long earliest_time = 0;
struct tonegen_status { /* current status of a tone generator */
bool playing; /* is it playing? */
char track; /* if so, which track is the note from? */
char note; /* what note is playing? */
char instrument; /* what instrument? */
} tonegen[MAX_TONEGENS];
struct track_status { /* current processing point of a MIDI track */
int trkptr; /* ptr to the next note change */
int trkend; /* ptr past the end of the track */
unsigned long time; /* what time we're at in the score */
unsigned long tempo; /* the tempo last set, in usec per qnote */
unsigned int preferred_tonegen; /* for strategy2, try to use this generator */
unsigned char cmd; /* CMD_xxxx next to do */
unsigned char note; /* for which note */
unsigned char chan; /* from which channel it was */
unsigned char velocity; /* the current volume */
unsigned char last_event; /* the last event, for MIDI's "running status" */
bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */
} track[MAX_TRACKS];
int midi_chan_instrument[16]; /* which instrument is currently being played on each channel */
/* output bytestream commands, which are also stored in track_status.cmd */
enum { CMD_PLAYNOTE = 0x90, /* play a note: low nibble is generator #, note is next byte */
CMD_STOPNOTE = 0x80, /* stop a note: low nibble is generator # */
CMD_INSTRUMENT = 0xc0, /* change instrument; low nibble is generator #, instrument is next byte */
CMD_RESTART = 0xe0, /* restart the score from the beginning */
CMD_STOP = 0xf0, /* stop playing */
CMD_TEMPO = 0xFE, /* tempo in usec per quarter note ("beat") */
CMD_TRACKDONE = 0xFF
}; /* no more data left in this track */
/* portable string length */
int strlength (const char *str) {
int i;
for (i = 0; str[i] != '\0'; ++i);
return i;
}
/* match a constant character sequence */
int charcmp (const char *buf, const char *match) {
int len, i;
len = strlength (match);
for (i = 0; i < len; ++i)
if (buf[i] != match[i])
return 0;
return 1;
}
unsigned char buffer_byte (int offset);
unsigned short buffer_short (int offset);
unsigned int buffer_int32 (int offset);
void midi_error (const char *msg, int curpos);
void chk_bufdata (int ptr, unsigned long int len);
uint16_t rev_short (uint16_t val);
uint32_t rev_long (uint32_t val);
void process_header (void);
void start_track (int tracknum);
unsigned long get_varlen (int *ptr);
void find_note (int tracknum);
void PrepareMIDI(AudioFileSource *src);
int PlayMIDI();
void StopMIDI();
// tsf_stream <-> AudioFileSource
static int afs_read(void *data, void *ptr, unsigned int size);
static int afs_tell(void *data);
static int afs_skip(void *data, unsigned int count);
static int afs_seek(void *data, unsigned int pos);
static int afs_close(void *data);
static int afs_size(void *data);
void MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs);
int samplesToPlay;
bool sawEOF;
int numSamplesRendered;
int sentSamplesRendered ;
short samplesRendered[256];
};
#endif

View File

@ -0,0 +1,876 @@
/*
AudioGeneratorMOD
Audio output generator that plays Amiga MOD tracker files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#define PGM_READ_UNALIGNED 0
#include "AudioGeneratorMOD.h"
/*
Ported/hacked out from STELLARPLAYER by Ronen K.
http://mobile4dev.blogspot.com/2012/11/stellaris-launchpad-mod-player.html
A version exists in GitHub at https://github.com/steveway/stellarplayer
and also at https://github.com/MikesModz/StellarPlayer
Both which were themselves a port of the PIC32 MOD player
https://www.youtube.com/watch?v=i3Yl0TISQBE (seems to no longer be available.)
Most changes involved reducing memory usage by changing data structures,
moving constants to PROGMEM and minor tweaks to allow non pow2 buffer sizes.
*/
#pragma GCC optimize ("O3")
#define NOTE(r, c) (Player.currentPattern.note8[r][c]==NONOTE8?NONOTE:8*Player.currentPattern.note8[r][c])
#ifndef min
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
#endif
AudioGeneratorMOD::AudioGeneratorMOD()
{
sampleRate = 44100;
fatBufferSize = 6 * 1024;
stereoSeparation = 32;
mixerTick = 0;
usePAL = false;
UpdateAmiga();
running = false;
file = NULL;
output = NULL;
}
AudioGeneratorMOD::~AudioGeneratorMOD()
{
// Free any remaining buffers
for (int i = 0; i < CHANNELS; i++) {
FatBuffer.channels[i] = NULL;
}
}
bool AudioGeneratorMOD::stop()
{
// We may be stopping because of allocation failures, so always deallocate
for (int i = 0; i < CHANNELS; i++) {
free(FatBuffer.channels[i]);
FatBuffer.channels[i] = NULL;
}
if (file) file->close();
running = false;
output->stop();
return true;
}
bool AudioGeneratorMOD::loop()
{
if (!running) goto done; // Easy-peasy
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // FIFO full, wait...
// Now advance enough times to fill the i2s buffer
do {
if (mixerTick == 0) {
running = RunPlayer();
if (!running) {
stop();
goto done;
}
mixerTick = Player.samplesPerTick;
}
GetSample( lastSample );
mixerTick--;
} while (output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
// We'll be left with one sample still in our buffer because it couldn't fit in the FIFO
return running;
}
bool AudioGeneratorMOD::begin(AudioFileSource *source, AudioOutput *out)
{
if (running) stop();
if (!source) return false;
file = source;
if (!out) return false;
output = out;
if (!file->isOpen()) return false; // Can't read the file!
// Set the output values properly
if (!output->SetRate(sampleRate)) return false;
if (!output->SetBitsPerSample(16)) return false;
if (!output->SetChannels(2)) return false;
if (!output->begin()) return false;
UpdateAmiga();
for (int i = 0; i < CHANNELS; i++) {
FatBuffer.channels[i] = reinterpret_cast<uint8_t*>(malloc(fatBufferSize));
if (!FatBuffer.channels[i]) {
stop();
return false;
}
}
if (!LoadMOD()) {
stop();
return false;
}
running = true;
return true;
}
// Sorted Amiga periods
static const uint16_t amigaPeriods[296] PROGMEM = {
907, 900, 894, 887, 881, 875, 868, 862, // -8 to -1
856, 850, 844, 838, 832, 826, 820, 814, // C-1 to +7
808, 802, 796, 791, 785, 779, 774, 768, // C#1 to +7
762, 757, 752, 746, 741, 736, 730, 725, // D-1 to +7
720, 715, 709, 704, 699, 694, 689, 684, // D#1 to +7
678, 675, 670, 665, 660, 655, 651, 646, // E-1 to +7
640, 636, 632, 628, 623, 619, 614, 610, // F-1 to +7
604, 601, 597, 592, 588, 584, 580, 575, // F#1 to +7
570, 567, 563, 559, 555, 551, 547, 543, // G-1 to +7
538, 535, 532, 528, 524, 520, 516, 513, // G#1 to +7
508, 505, 502, 498, 494, 491, 487, 484, // A-1 to +7
480, 477, 474, 470, 467, 463, 460, 457, // A#1 to +7
453, 450, 447, 444, 441, 437, 434, 431, // B-1 to +7
428, 425, 422, 419, 416, 413, 410, 407, // C-2 to +7
404, 401, 398, 395, 392, 390, 387, 384, // C#2 to +7
381, 379, 376, 373, 370, 368, 365, 363, // D-2 to +7
360, 357, 355, 352, 350, 347, 345, 342, // D#2 to +7
339, 337, 335, 332, 330, 328, 325, 323, // E-2 to +7
320, 318, 316, 314, 312, 309, 307, 305, // F-2 to +7
302, 300, 298, 296, 294, 292, 290, 288, // F#2 to +7
285, 284, 282, 280, 278, 276, 274, 272, // G-2 to +7
269, 268, 266, 264, 262, 260, 258, 256, // G#2 to +7
254, 253, 251, 249, 247, 245, 244, 242, // A-2 to +7
240, 238, 237, 235, 233, 232, 230, 228, // A#2 to +7
226, 225, 223, 222, 220, 219, 217, 216, // B-2 to +7
214, 212, 211, 209, 208, 206, 205, 203, // C-3 to +7
202, 200, 199, 198, 196, 195, 193, 192, // C#3 to +7
190, 189, 188, 187, 185, 184, 183, 181, // D-3 to +7
180, 179, 177, 176, 175, 174, 172, 171, // D#3 to +7
170, 169, 167, 166, 165, 164, 163, 161, // E-3 to +7
160, 159, 158, 157, 156, 155, 154, 152, // F-3 to +7
151, 150, 149, 148, 147, 146, 145, 144, // F#3 to +7
143, 142, 141, 140, 139, 138, 137, 136, // G-3 to +7
135, 134, 133, 132, 131, 130, 129, 128, // G#3 to +7
127, 126, 125, 125, 123, 123, 122, 121, // A-3 to +7
120, 119, 118, 118, 117, 116, 115, 114, // A#3 to +7
113, 113, 112, 111, 110, 109, 109, 108 // B-3 to +7
};
#define ReadAmigaPeriods(a) (uint16_t)pgm_read_word(amigaPeriods + (a))
static const uint8_t sine[64] PROGMEM = {
0, 24, 49, 74, 97, 120, 141, 161,
180, 197, 212, 224, 235, 244, 250, 253,
255, 253, 250, 244, 235, 224, 212, 197,
180, 161, 141, 120, 97, 74, 49, 24
};
#define ReadSine(a) pgm_read_byte(sine + (a))
static inline uint16_t MakeWord(uint8_t h, uint8_t l) { return h << 8 | l; }
bool AudioGeneratorMOD::LoadHeader()
{
uint8_t i;
uint8_t temp[4];
uint8_t junk[22];
if (20 != file->read(/*Mod.name*/junk, 20)) return false; // Skip MOD name
for (i = 0; i < SAMPLES; i++) {
if (22 != file->read(junk /*Mod.samples[i].name*/, 22)) return false; // Skip sample name
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].length = MakeWord(temp[0], temp[1]) * 2;
if (1 != file->read(reinterpret_cast<uint8_t*>(&Mod.samples[i].fineTune), 1)) return false;
if (Mod.samples[i].fineTune > 7) Mod.samples[i].fineTune -= 16;
if (1 != file->read(&Mod.samples[i].volume, 1)) return false;
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].loopBegin = MakeWord(temp[0], temp[1]) * 2;
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].loopLength = MakeWord(temp[0], temp[1]) * 2;
if (Mod.samples[i].loopBegin + Mod.samples[i].loopLength > Mod.samples[i].length)
Mod.samples[i].loopLength = Mod.samples[i].length - Mod.samples[i].loopBegin;
}
if (1 != file->read(&Mod.songLength, 1)) return false;
if (1 != file->read(temp, 1)) return false; // Discard this byte
Mod.numberOfPatterns = 0;
for (i = 0; i < 128; i++) {
if (1 != file->read(&Mod.order[i], 1)) return false;
if (Mod.order[i] > Mod.numberOfPatterns)
Mod.numberOfPatterns = Mod.order[i];
}
Mod.numberOfPatterns++;
// Offset 1080
if (4 != file->read(temp, 4)) return false;;
if (!strncmp(reinterpret_cast<const char*>(temp + 1), "CHN", 3))
Mod.numberOfChannels = temp[0] - '0';
else if (!strncmp(reinterpret_cast<const char*>(temp + 2), "CH", 2))
Mod.numberOfChannels = (temp[0] - '0') * 10 + temp[1] - '0';
else
Mod.numberOfChannels = 4;
return true;
}
void AudioGeneratorMOD::LoadSamples()
{
uint8_t i;
uint32_t fileOffset = 1084 + Mod.numberOfPatterns * ROWS * Mod.numberOfChannels * 4 - 1;
for (i = 0; i < SAMPLES; i++) {
if (Mod.samples[i].length) {
Mixer.sampleBegin[i] = fileOffset;
Mixer.sampleEnd[i] = fileOffset + Mod.samples[i].length;
if (Mod.samples[i].loopLength > 2) {
Mixer.sampleloopBegin[i] = fileOffset + Mod.samples[i].loopBegin;
Mixer.sampleLoopLength[i] = Mod.samples[i].loopLength;
Mixer.sampleLoopEnd[i] = Mixer.sampleloopBegin[i] + Mixer.sampleLoopLength[i];
} else {
Mixer.sampleloopBegin[i] = 0;
Mixer.sampleLoopLength[i] = 0;
Mixer.sampleLoopEnd[i] = 0;
}
fileOffset += Mod.samples[i].length;
}
}
}
bool AudioGeneratorMOD::LoadPattern(uint8_t pattern)
{
uint8_t row;
uint8_t channel;
uint8_t i;
uint8_t temp[4];
uint16_t amigaPeriod;
if (!file->seek(1084 + pattern * ROWS * Mod.numberOfChannels * 4, SEEK_SET)) return false;
for (row = 0; row < ROWS; row++) {
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (4 != file->read(temp, 4)) return false;
Player.currentPattern.sampleNumber[row][channel] = (temp[0] & 0xF0) + (temp[2] >> 4);
amigaPeriod = ((temp[0] & 0xF) << 8) + temp[1];
// Player.currentPattern.note[row][channel] = NONOTE;
Player.currentPattern.note8[row][channel] = NONOTE8;
for (i = 1; i < 37; i++)
if (amigaPeriod > ReadAmigaPeriods(i * 8) - 3 &&
amigaPeriod < ReadAmigaPeriods(i * 8) + 3)
Player.currentPattern.note8[row][channel] = i;
Player.currentPattern.effectNumber[row][channel] = temp[2] & 0xF;
Player.currentPattern.effectParameter[row][channel] = temp[3];
}
}
return true;
}
void AudioGeneratorMOD::Portamento(uint8_t channel)
{
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel]) {
Player.lastAmigaPeriod[channel] += Player.portamentoSpeed[channel];
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel])
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
}
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel]) {
Player.lastAmigaPeriod[channel] -= Player.portamentoSpeed[channel];
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel])
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
}
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
}
void AudioGeneratorMOD::Vibrato(uint8_t channel)
{
uint16_t delta;
uint16_t temp;
temp = Player.vibratoPos[channel] & 31;
switch (Player.waveControl[channel] & 3) {
case 0:
delta = ReadSine(temp);
break;
case 1:
temp <<= 3;
if (Player.vibratoPos[channel] < 0)
temp = 255 - temp;
delta = temp;
break;
case 2:
delta = 255;
break;
case 3:
delta = rand() & 255;
break;
}
delta *= Player.vibratoDepth[channel];
delta >>= 7;
if (Player.vibratoPos[channel] >= 0)
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] + delta);
else
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] - delta);
Player.vibratoPos[channel] += Player.vibratoSpeed[channel];
if (Player.vibratoPos[channel] > 31) Player.vibratoPos[channel] -= 64;
}
void AudioGeneratorMOD::Tremolo(uint8_t channel)
{
uint16_t delta;
uint16_t temp;
temp = Player.tremoloPos[channel] & 31;
switch (Player.waveControl[channel] & 3) {
case 0:
delta = ReadSine(temp);
break;
case 1:
temp <<= 3;
if (Player.tremoloPos[channel] < 0)
temp = 255 - temp;
delta = temp;
break;
case 2:
delta = 255;
break;
case 3:
delta = rand() & 255;
break;
}
delta *= Player.tremoloDepth[channel];
delta >>= 6;
if (Player.tremoloPos[channel] >= 0) {
if (Player.volume[channel] + delta > 64) delta = 64 - Player.volume[channel];
Mixer.channelVolume[channel] = Player.volume[channel] + delta;
} else {
if (Player.volume[channel] - delta < 0) delta = Player.volume[channel];
Mixer.channelVolume[channel] = Player.volume[channel] - delta;
}
Player.tremoloPos[channel] += Player.tremoloSpeed[channel];
if (Player.tremoloPos[channel] > 31) Player.tremoloPos[channel] -= 64;
}
bool AudioGeneratorMOD::ProcessRow()
{
bool jumpFlag;
bool breakFlag;
uint8_t channel;
uint8_t sampleNumber;
uint16_t note;
uint8_t effectNumber;
uint8_t effectParameter;
uint8_t effectParameterX;
uint8_t effectParameterY;
uint16_t sampleOffset;
if (!running) return false;
Player.lastRow = Player.row++;
jumpFlag = false;
breakFlag = false;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
note = NOTE(Player.lastRow, channel);
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
effectParameterX = effectParameter >> 4;
effectParameterY = effectParameter & 0xF;
sampleOffset = 0;
if (sampleNumber) {
Player.lastSampleNumber[channel] = sampleNumber - 1;
if (!(effectParameter == 0xE && effectParameterX == NOTEDELAY))
Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
}
if (note != NONOTE) {
Player.lastNote[channel] = note;
Player.amigaPeriod[channel] = ReadAmigaPeriods(note + Mod.samples[Player.lastSampleNumber[channel]].fineTune);
if (effectNumber != TONEPORTAMENTO && effectNumber != PORTAMENTOVOLUMESLIDE)
Player.lastAmigaPeriod[channel] = Player.amigaPeriod[channel];
if (!(Player.waveControl[channel] & 0x80)) Player.vibratoPos[channel] = 0;
if (!(Player.waveControl[channel] & 0x08)) Player.tremoloPos[channel] = 0;
}
switch (effectNumber) {
case TONEPORTAMENTO:
if (effectParameter) Player.portamentoSpeed[channel] = effectParameter;
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
note = NONOTE;
break;
case VIBRATO:
if (effectParameterX) Player.vibratoSpeed[channel] = effectParameterX;
if (effectParameterY) Player.vibratoDepth[channel] = effectParameterY;
break;
case PORTAMENTOVOLUMESLIDE:
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
note = NONOTE;
break;
case TREMOLO:
if (effectParameterX) Player.tremoloSpeed[channel] = effectParameterX;
if (effectParameterY) Player.tremoloDepth[channel] = effectParameterY;
break;
case SETCHANNELPANNING:
Mixer.channelPanning[channel] = effectParameter >> 1;
break;
case SETSAMPLEOFFSET:
sampleOffset = effectParameter << 8;
if (sampleOffset > Mod.samples[Player.lastSampleNumber[channel]].length)
sampleOffset = Mod.samples[Player.lastSampleNumber[channel]].length;
break;
case JUMPTOORDER:
Player.orderIndex = effectParameter;
if (Player.orderIndex >= Mod.songLength)
Player.orderIndex = 0;
Player.row = 0;
jumpFlag = true;
break;
case SETVOLUME:
if (effectParameter > 64) Player.volume[channel] = 64;
else Player.volume[channel] = effectParameter;
break;
case BREAKPATTERNTOROW:
Player.row = effectParameterX * 10 + effectParameterY;
if (Player.row >= ROWS)
Player.row = 0;
if (!jumpFlag && !breakFlag) {
Player.orderIndex++;
if (Player.orderIndex >= Mod.songLength)
Player.orderIndex = 0;
}
breakFlag = true;
break;
case 0xE:
switch (effectParameterX) {
case FINEPORTAMENTOUP:
Player.lastAmigaPeriod[channel] -= effectParameterY;
break;
case FINEPORTAMENTODOWN:
Player.lastAmigaPeriod[channel] += effectParameterY;
break;
case SETVIBRATOWAVEFORM:
Player.waveControl[channel] &= 0xF0;
Player.waveControl[channel] |= effectParameterY;
break;
case SETFINETUNE:
Mod.samples[Player.lastSampleNumber[channel]].fineTune = effectParameterY;
if (Mod.samples[Player.lastSampleNumber[channel]].fineTune > 7)
Mod.samples[Player.lastSampleNumber[channel]].fineTune -= 16;
break;
case PATTERNLOOP:
if (effectParameterY) {
if (Player.patternLoopCount[channel])
Player.patternLoopCount[channel]--;
else
Player.patternLoopCount[channel] = effectParameterY;
if (Player.patternLoopCount[channel])
Player.row = Player.patternLoopRow[channel] - 1;
} else
Player.patternLoopRow[channel] = Player.row;
break;
case SETTREMOLOWAVEFORM:
Player.waveControl[channel] &= 0xF;
Player.waveControl[channel] |= effectParameterY << 4;
break;
case FINEVOLUMESLIDEUP:
Player.volume[channel] += effectParameterY;
if (Player.volume[channel] > 64) Player.volume[channel] = 64;
break;
case FINEVOLUMESLIDEDOWN:
Player.volume[channel] -= effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
break;
case NOTECUT:
note = NONOTE;
break;
case PATTERNDELAY:
Player.patternDelay = effectParameterY;
break;
case INVERTLOOP:
break;
}
break;
case SETSPEED:
if (effectParameter < 0x20)
Player.speed = effectParameter;
else
Player.samplesPerTick = sampleRate / (2 * effectParameter / 5);
break;
}
if (note != NONOTE || (Player.lastAmigaPeriod[channel] &&
effectNumber != VIBRATO && effectNumber != VIBRATOVOLUMESLIDE &&
!(effectNumber == 0xE && effectParameterX == NOTEDELAY)))
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
if (note != NONOTE)
Mixer.channelSampleOffset[channel] = sampleOffset << DIVIDER;
if (sampleNumber)
Mixer.channelSampleNumber[channel] = Player.lastSampleNumber[channel];
if (effectNumber != TREMOLO)
Mixer.channelVolume[channel] = Player.volume[channel];
}
return true;
}
bool AudioGeneratorMOD::ProcessTick()
{
uint8_t channel;
uint8_t sampleNumber;
uint16_t note;
uint8_t effectNumber;
uint8_t effectParameter;
uint8_t effectParameterX;
uint8_t effectParameterY;
uint16_t tempNote;
if (!running) return false;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (Player.lastAmigaPeriod[channel]) {
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
// note = Player.currentPattern.note[Player.lastRow][channel];
note = NOTE(Player.lastRow, channel);
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
effectParameterX = effectParameter >> 4;
effectParameterY = effectParameter & 0xF;
switch (effectNumber) {
case ARPEGGIO:
if (effectParameter)
switch (Player.tick % 3) {
case 0:
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case 1:
tempNote = Player.lastNote[channel] + effectParameterX * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
break;
case 2:
tempNote = Player.lastNote[channel] + effectParameterY * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
break;
}
break;
case PORTAMENTOUP:
Player.lastAmigaPeriod[channel] -= effectParameter;
if (Player.lastAmigaPeriod[channel] < 113) Player.lastAmigaPeriod[channel] = 113;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case PORTAMENTODOWN:
Player.lastAmigaPeriod[channel] += effectParameter;
if (Player.lastAmigaPeriod[channel] > 856) Player.lastAmigaPeriod[channel] = 856;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case TONEPORTAMENTO:
Portamento(channel);
break;
case VIBRATO:
Vibrato(channel);
break;
case PORTAMENTOVOLUMESLIDE:
Portamento(channel);
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case VIBRATOVOLUMESLIDE:
Vibrato(channel);
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case TREMOLO:
Tremolo(channel);
break;
case VOLUMESLIDE:
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case 0xE:
switch (effectParameterX) {
case RETRIGGERNOTE:
if (!effectParameterY) break;
if (!(Player.tick % effectParameterY)) {
Mixer.channelSampleOffset[channel] = 0;
}
break;
case NOTECUT:
if (Player.tick == effectParameterY)
Mixer.channelVolume[channel] = Player.volume[channel] = 0;
break;
case NOTEDELAY:
if (Player.tick == effectParameterY) {
if (sampleNumber) Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
if (note != NONOTE) Mixer.channelSampleOffset[channel] = 0;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
Mixer.channelVolume[channel] = Player.volume[channel];
}
break;
}
break;
}
}
}
return true;
}
bool AudioGeneratorMOD::RunPlayer()
{
if (!running) return false;
if (Player.tick == Player.speed) {
Player.tick = 0;
if (Player.row == ROWS) {
Player.orderIndex++;
if (Player.orderIndex == Mod.songLength)
{
//Player.orderIndex = 0;
// No loop, just say we're done!
return false;
}
Player.row = 0;
}
if (Player.patternDelay) {
Player.patternDelay--;
} else {
if (Player.orderIndex != Player.oldOrderIndex)
if (!LoadPattern(Mod.order[Player.orderIndex])) return false;
Player.oldOrderIndex = Player.orderIndex;
if (!ProcessRow()) return false;
}
} else {
if (!ProcessTick()) return false;
}
Player.tick++;
return true;
}
void AudioGeneratorMOD::GetSample(int16_t sample[2])
{
int16_t sumL;
int16_t sumR;
uint8_t channel;
uint32_t samplePointer;
int8_t current;
int8_t next;
int16_t out;
if (!running) return;
sumL = 0;
sumR = 0;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (!Mixer.channelFrequency[channel] ||
!Mod.samples[Mixer.channelSampleNumber[channel]].length) continue;
Mixer.channelSampleOffset[channel] += Mixer.channelFrequency[channel];
if (!Mixer.channelVolume[channel]) continue;
samplePointer = Mixer.sampleBegin[Mixer.channelSampleNumber[channel]] +
(Mixer.channelSampleOffset[channel] >> DIVIDER);
if (Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]]) {
if (samplePointer >= Mixer.sampleLoopEnd[Mixer.channelSampleNumber[channel]]) {
Mixer.channelSampleOffset[channel] -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]] << DIVIDER;
samplePointer -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]];
}
} else {
if (samplePointer >= Mixer.sampleEnd[Mixer.channelSampleNumber[channel]]) {
Mixer.channelFrequency[channel] = 0;
samplePointer = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]];
}
}
if (samplePointer < FatBuffer.samplePointer[channel] ||
samplePointer >= FatBuffer.samplePointer[channel] + fatBufferSize - 1 ||
Mixer.channelSampleNumber[channel] != FatBuffer.channelSampleNumber[channel]) {
uint16_t toRead = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]] - samplePointer + 1;
if (toRead > fatBufferSize) toRead = fatBufferSize;
if (!file->seek(samplePointer, SEEK_SET)) {
stop();
return;
}
if (toRead != file->read(FatBuffer.channels[channel], toRead)) {
stop();
return;
}
FatBuffer.samplePointer[channel] = samplePointer;
FatBuffer.channelSampleNumber[channel] = Mixer.channelSampleNumber[channel];
}
current = FatBuffer.channels[channel][(samplePointer - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
next = FatBuffer.channels[channel][(samplePointer + 1 - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
out = current;
// Integer linear interpolation
out += (next - current) * (Mixer.channelSampleOffset[channel] & ((1 << DIVIDER) - 1)) >> DIVIDER;
// Upscale to BITDEPTH
out <<= BITDEPTH - 8;
// Channel volume
out = out * Mixer.channelVolume[channel] >> 6;
// Channel panning
sumL += out * min(128 - Mixer.channelPanning[channel], 64) >> 6;
sumR += out * min(Mixer.channelPanning[channel], 64) >> 6;
}
// Downscale to BITDEPTH
sumL /= Mod.numberOfChannels;
sumR /= Mod.numberOfChannels;
// Fill the sound buffer with unsigned values
sample[AudioOutput::LEFTCHANNEL] = sumL + (1 << (BITDEPTH - 1));
sample[AudioOutput::RIGHTCHANNEL] = sumR + (1 << (BITDEPTH - 1));
}
bool AudioGeneratorMOD::LoadMOD()
{
uint8_t channel;
if (!LoadHeader()) return false;
LoadSamples();
Player.amiga = AMIGA;
Player.samplesPerTick = sampleRate / (2 * 125 / 5); // Hz = 2 * BPM / 5
Player.speed = 6;
Player.tick = Player.speed;
Player.row = 0;
Player.orderIndex = 0;
Player.oldOrderIndex = 0xFF;
Player.patternDelay = 0;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
Player.patternLoopCount[channel] = 0;
Player.patternLoopRow[channel] = 0;
Player.lastAmigaPeriod[channel] = 0;
Player.waveControl[channel] = 0;
Player.vibratoSpeed[channel] = 0;
Player.vibratoDepth[channel] = 0;
Player.vibratoPos[channel] = 0;
Player.tremoloSpeed[channel] = 0;
Player.tremoloDepth[channel] = 0;
Player.tremoloPos[channel] = 0;
FatBuffer.samplePointer[channel] = 0;
FatBuffer.channelSampleNumber[channel] = 0xFF;
Mixer.channelSampleOffset[channel] = 0;
Mixer.channelFrequency[channel] = 0;
Mixer.channelVolume[channel] = 0;
switch (channel % 4) {
case 0:
case 3:
Mixer.channelPanning[channel] = stereoSeparation;
break;
default:
Mixer.channelPanning[channel] = 128 - stereoSeparation;
}
}
return true;
}

View File

@ -0,0 +1,168 @@
/*
AudioGeneratorMOD
Audio output generator that plays Amiga MOD tracker files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORMOD_H
#define _AUDIOGENERATORMOD_H
#include "AudioGenerator.h"
class AudioGeneratorMOD : public AudioGenerator
{
public:
AudioGeneratorMOD();
virtual ~AudioGeneratorMOD() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override { return running; }
bool SetSampleRate(int hz) { if (running || (hz < 1) || (hz > 96000) ) return false; sampleRate = hz; return true; }
bool SetBufferSize(int sz) { if (running || (sz < 1) ) return false; fatBufferSize = sz; return true; }
bool SetStereoSeparation(int sep) { if (running || (sep<0) || (sep>64)) return false; stereoSeparation = sep; return true; }
bool SetPAL(bool use) { if (running) return false; usePAL = use; return true; }
protected:
bool LoadMOD();
bool LoadHeader();
void GetSample(int16_t sample[2]);
bool RunPlayer();
void LoadSamples();
bool LoadPattern(uint8_t pattern);
bool ProcessTick();
bool ProcessRow();
void Tremolo(uint8_t channel);
void Portamento(uint8_t channel);
void Vibrato(uint8_t channel);
protected:
int mixerTick;
enum {BITDEPTH = 15};
int sampleRate;
int fatBufferSize; //(6*1024) // File system buffers per-CHANNEL (i.e. total mem required is 4 * FATBUFFERSIZE)
enum {DIVIDER = 10}; // Fixed-point mantissa used for integer arithmetic
int stereoSeparation; //STEREOSEPARATION = 32; // 0 (max) to 64 (mono)
bool usePAL;
// Hz = 7093789 / (amigaPeriod * 2) for PAL
// Hz = 7159091 / (amigaPeriod * 2) for NTSC
int AMIGA;
void UpdateAmiga() { AMIGA = ((usePAL?7159091:7093789) / 2 / sampleRate << DIVIDER); }
enum {ROWS = 64, SAMPLES = 31, CHANNELS = 4, NONOTE = 0xFFFF, NONOTE8 = 0xff };
typedef struct Sample {
uint16_t length;
int8_t fineTune;
uint8_t volume;
uint16_t loopBegin;
uint16_t loopLength;
} Sample;
typedef struct mod {
Sample samples[SAMPLES];
uint8_t songLength;
uint8_t numberOfPatterns;
uint8_t order[128];
uint8_t numberOfChannels;
} mod;
// Save 256 bytes by storing raw note values, unpack with macro NOTE
typedef struct Pattern {
uint8_t sampleNumber[ROWS][CHANNELS];
uint8_t note8[ROWS][CHANNELS];
uint8_t effectNumber[ROWS][CHANNELS];
uint8_t effectParameter[ROWS][CHANNELS];
} Pattern;
typedef struct player {
Pattern currentPattern;
uint32_t amiga;
uint16_t samplesPerTick;
uint8_t speed;
uint8_t tick;
uint8_t row;
uint8_t lastRow;
uint8_t orderIndex;
uint8_t oldOrderIndex;
uint8_t patternDelay;
uint8_t patternLoopCount[CHANNELS];
uint8_t patternLoopRow[CHANNELS];
uint8_t lastSampleNumber[CHANNELS];
int8_t volume[CHANNELS];
uint16_t lastNote[CHANNELS];
uint16_t amigaPeriod[CHANNELS];
int16_t lastAmigaPeriod[CHANNELS];
uint16_t portamentoNote[CHANNELS];
uint8_t portamentoSpeed[CHANNELS];
uint8_t waveControl[CHANNELS];
uint8_t vibratoSpeed[CHANNELS];
uint8_t vibratoDepth[CHANNELS];
int8_t vibratoPos[CHANNELS];
uint8_t tremoloSpeed[CHANNELS];
uint8_t tremoloDepth[CHANNELS];
int8_t tremoloPos[CHANNELS];
} player;
typedef struct mixer {
uint32_t sampleBegin[SAMPLES];
uint32_t sampleEnd[SAMPLES];
uint32_t sampleloopBegin[SAMPLES];
uint16_t sampleLoopLength[SAMPLES];
uint32_t sampleLoopEnd[SAMPLES];
uint8_t channelSampleNumber[CHANNELS];
uint32_t channelSampleOffset[CHANNELS];
uint16_t channelFrequency[CHANNELS];
uint8_t channelVolume[CHANNELS];
uint8_t channelPanning[CHANNELS];
} mixer;
typedef struct fatBuffer {
uint8_t *channels[CHANNELS]; // Make dynamically allocated [FATBUFFERSIZE];
uint32_t samplePointer[CHANNELS];
uint8_t channelSampleNumber[CHANNELS];
} fatBuffer;
// Effects
typedef enum { ARPEGGIO = 0, PORTAMENTOUP, PORTAMENTODOWN, TONEPORTAMENTO, VIBRATO, PORTAMENTOVOLUMESLIDE,
VIBRATOVOLUMESLIDE, TREMOLO, SETCHANNELPANNING, SETSAMPLEOFFSET, VOLUMESLIDE, JUMPTOORDER,
SETVOLUME, BREAKPATTERNTOROW, ESUBSET, SETSPEED } EffectsValues;
// 0xE subset
typedef enum { SETFILTER = 0, FINEPORTAMENTOUP, FINEPORTAMENTODOWN, GLISSANDOCONTROL, SETVIBRATOWAVEFORM,
SETFINETUNE, PATTERNLOOP, SETTREMOLOWAVEFORM, SUBEFFECT8, RETRIGGERNOTE, FINEVOLUMESLIDEUP,
FINEVOLUMESLIDEDOWN, NOTECUT, NOTEDELAY, PATTERNDELAY, INVERTLOOP } Effect08Subvalues;
// Our state lives here...
player Player;
mod Mod;
mixer Mixer;
fatBuffer FatBuffer;
};
#endif

View File

@ -0,0 +1,353 @@
/*
AudioGeneratorMP3
Wrap libmad MP3 library to play audio
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorMP3.h"
AudioGeneratorMP3::AudioGeneratorMP3()
{
running = false;
file = NULL;
output = NULL;
buff = NULL;
nsCountMax = 1152/32;
madInitted = false;
preallocateSpace = NULL;
preallocateSize = 0;
}
AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size)
{
running = false;
file = NULL;
output = NULL;
buff = NULL;
nsCountMax = 1152/32;
madInitted = false;
preallocateSpace = space;
preallocateSize = size;
}
AudioGeneratorMP3::~AudioGeneratorMP3()
{
if (!preallocateSpace) {
free(buff);
free(synth);
free(frame);
free(stream);
}
}
bool AudioGeneratorMP3::stop()
{
if (madInitted) {
mad_synth_finish(synth);
mad_frame_finish(frame);
mad_stream_finish(stream);
madInitted = false;
}
if (!preallocateSpace) {
free(buff);
free(synth);
free(frame);
free(stream);
}
buff = NULL;
synth = NULL;
frame = NULL;
stream = NULL;
running = false;
output->stop();
return file->close();
}
bool AudioGeneratorMP3::isRunning()
{
return running;
}
enum mad_flow AudioGeneratorMP3::ErrorToFlow()
{
char err[64];
char errLine[128];
// Special case - eat "lost sync @ byte 0" as it always occurs and is not really correct....it never had sync!
if ((lastReadPos==0) && (stream->error==MAD_ERROR_LOSTSYNC)) return MAD_FLOW_CONTINUE;
strcpy_P(err, mad_stream_errorstr(stream));
snprintf_P(errLine, sizeof(errLine), PSTR("Decoding error '%s' at byte offset %d"),
err, (stream->this_frame - buff) + lastReadPos);
yield(); // Something bad happened anyway, ensure WiFi gets some time, too
cb.st(stream->error, errLine);
return MAD_FLOW_CONTINUE;
}
enum mad_flow AudioGeneratorMP3::Input()
{
int unused = 0;
if (stream->next_frame) {
unused = lastBuffLen - (stream->next_frame - buff);
memmove(buff, stream->next_frame, unused);
stream->next_frame = NULL;
}
if (unused == lastBuffLen) {
// Something wicked this way came, throw it all out and try again
unused = 0;
}
lastReadPos = file->getPos() - unused;
int len = buffLen - unused;
len = file->read(buff + unused, len);
if ((len == 0) && (unused == 0)) {
// Can't read any from the file, and we don't have anything left. It's done....
return MAD_FLOW_STOP;
}
lastBuffLen = len + unused;
mad_stream_buffer(stream, buff, lastBuffLen);
return MAD_FLOW_CONTINUE;
}
bool AudioGeneratorMP3::DecodeNextFrame()
{
if (mad_frame_decode(frame, stream) == -1) {
ErrorToFlow(); // Always returns CONTINUE
return false;
}
nsCountMax = MAD_NSBSAMPLES(&frame->header);
return true;
}
bool AudioGeneratorMP3::GetOneSample(int16_t sample[2])
{
if (synth->pcm.samplerate != lastRate) {
output->SetRate(synth->pcm.samplerate);
lastRate = synth->pcm.samplerate;
}
if (synth->pcm.channels != lastChannels) {
output->SetChannels(synth->pcm.channels);
lastChannels = synth->pcm.channels;
}
// If we're here, we have one decoded frame and sent 0 or more samples out
if (samplePtr < synth->pcm.length) {
sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr];
sample[AudioOutput::RIGHTCHANNEL] = synth->pcm.samples[1][samplePtr];
samplePtr++;
} else {
samplePtr = 0;
switch ( mad_synth_frame_onens(synth, frame, nsCount++) ) {
case MAD_FLOW_STOP:
case MAD_FLOW_BREAK: audioLogger->printf_P(PSTR("msf1ns failed\n"));
return false; // Either way we're done
default:
break; // Do nothing
}
// for IGNORE and CONTINUE, just play what we have now
sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr];
sample[AudioOutput::RIGHTCHANNEL] = synth->pcm.samples[1][samplePtr];
samplePtr++;
}
return true;
}
bool AudioGeneratorMP3::loop()
{
if (!running) goto done; // Nothing to do here!
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
// Try and stuff the buffer one sample at a time
do
{
// Decode next frame if we're beyond the existing generated data
if ( (samplePtr >= synth->pcm.length) && (nsCount >= nsCountMax) ) {
retry:
if (Input() == MAD_FLOW_STOP) {
return false;
}
if (!DecodeNextFrame()) {
goto retry;
}
samplePtr = 9999;
nsCount = 0;
}
if (!GetOneSample(lastSample)) {
audioLogger->printf_P(PSTR("G1S failed\n"));
running = false;
goto done;
}
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) {
audioLogger->printf_P(PSTR("MP3 source file not open\n"));
return false; // Error
}
output->SetBitsPerSample(16); // Constant for MP3 decoder
output->SetChannels(2);
if (!output->begin()) return false;
// Where we are in generating one frame's data, set to invalid so we will run loop on first getsample()
samplePtr = 9999;
nsCount = 9999;
lastRate = 0;
lastChannels = 0;
lastReadPos = 0;
lastBuffLen = 0;
// Allocate all large memory chunks
if (preallocateSpace) {
uint8_t *p = reinterpret_cast<uint8_t *>(preallocateSpace);
buff = reinterpret_cast<unsigned char *>(p);
p += (buffLen+7) & ~7;
stream = reinterpret_cast<struct mad_stream *>(p);
p += (sizeof(struct mad_stream)+7) & ~7;
frame = reinterpret_cast<struct mad_frame *>(p);
p += (sizeof(struct mad_frame)+7) & ~7;
synth = reinterpret_cast<struct mad_synth *>(p);
p += (sizeof(struct mad_synth)+7) & ~7;
int neededBytes = p - reinterpret_cast<uint8_t *>(preallocateSpace);
if (neededBytes > preallocateSize) {
audioLogger->printf_P("OOM error in MP3: Want %d bytes, have %d bytes preallocated.\n", neededBytes, preallocateSize);
return false;
}
} else {
buff = reinterpret_cast<unsigned char *>(malloc(buffLen));
stream = reinterpret_cast<struct mad_stream *>(malloc(sizeof(struct mad_stream)));
frame = reinterpret_cast<struct mad_frame *>(malloc(sizeof(struct mad_frame)));
synth = reinterpret_cast<struct mad_synth *>(malloc(sizeof(struct mad_synth)));
if (!buff || !stream || !frame || !synth) {
free(buff);
free(stream);
free(frame);
free(synth);
buff = NULL;
stream = NULL;
frame = NULL;
synth = NULL;
uint32_t size = buffLen + sizeof(struct mad_stream) + sizeof(struct mad_frame) + sizeof(struct mad_synth);
audioLogger->printf_P("OOM error in MP3: Want %d bytes\n", size);
return false;
}
}
mad_stream_init(stream);
mad_frame_init(frame);
mad_synth_init(synth);
synth->pcm.length = 0;
mad_stream_options(stream, 0); // TODO - add options support
madInitted = true;
running = true;
return true;
}
// The following are helper routines for use in libmad to check stack/heap free
// and to determine if there's enough stack space to allocate some blocks there
// instead of precious heap.
#undef stack
extern "C" {
#ifdef ESP32
//TODO - add ESP32 checks
void stack(const char *s, const char *t, int i)
{
}
int stackfree()
{
return 8192;
}
#elif defined(ESP8266)
#include <cont.h>
extern cont_t g_cont;
void stack(const char *s, const char *t, int i)
{
(void) t;
(void) i;
register uint32_t *sp asm("a1");
int freestack = 4 * (sp - g_cont.stack);
int freeheap = ESP.getFreeHeap();
if ((freestack < 512) || (freeheap < 5120)) {
static int laststack, lastheap;
if (laststack!=freestack|| lastheap !=freeheap) {
audioLogger->printf_P(PSTR("%s: FREESTACK=%d, FREEHEAP=%d\n"), s, /*t, i,*/ freestack, /*cont_get_free_stack(&g_cont),*/ freeheap);
}
if (freestack < 256) {
audioLogger->printf_P(PSTR("out of stack!\n"));
}
if (freeheap < 1024) {
audioLogger->printf_P(PSTR("out of heap!\n"));
}
Serial.flush();
laststack = freestack;
lastheap = freeheap;
}
}
int stackfree()
{
register uint32_t *sp asm("a1");
int freestack = 4 * (sp - g_cont.stack);
return freestack;
}
#else
void stack(const char *s, const char *t, int i)
{
(void) s;
(void) t;
(void) i;
}
int stackfree()
{
return 8192;
}
#endif
}

View File

@ -0,0 +1,68 @@
/*
AudioGeneratorMP3
Wrap libmad MP3 library to play audio
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORMP3_H
#define _AUDIOGENERATORMP3_H
#include "AudioGenerator.h"
#include "libmad/config.h"
#include "libmad/mad.h"
class AudioGeneratorMP3 : public AudioGenerator
{
public:
AudioGeneratorMP3();
AudioGeneratorMP3(void *preallocateSpace, int preallocateSize);
virtual ~AudioGeneratorMP3() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
void *preallocateSpace;
int preallocateSize;
const int buffLen = 0x600; // Slightly larger than largest MP3 frame
unsigned char *buff;
int lastReadPos;
int lastBuffLen;
unsigned int lastRate;
int lastChannels;
// Decoding bits
bool madInitted;
struct mad_stream *stream;
struct mad_frame *frame;
struct mad_synth *synth;
int samplePtr;
int nsCount;
int nsCountMax;
// The internal helpers
enum mad_flow ErrorToFlow();
enum mad_flow Input();
bool DecodeNextFrame();
bool GetOneSample(int16_t sample[2]);
};
#endif

View File

@ -0,0 +1,162 @@
/*
AudioGeneratorMP3
Audio output generator using the Helix MP3 decoder
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#pragma GCC optimize ("O3")
#include "AudioGeneratorMP3a.h"
AudioGeneratorMP3a::AudioGeneratorMP3a()
{
running = false;
file = NULL;
output = NULL;
hMP3Decoder = MP3InitDecoder();
if (!hMP3Decoder) {
audioLogger->printf_P(PSTR("Out of memory error! hMP3Decoder==NULL\n"));
Serial.flush();
}
// For sanity's sake...
memset(buff, 0, sizeof(buff));
memset(outSample, 0, sizeof(outSample));
buffValid = 0;
lastFrameEnd = 0;
validSamples = 0;
curSample = 0;
lastRate = 0;
lastChannels = 0;
}
AudioGeneratorMP3a::~AudioGeneratorMP3a()
{
MP3FreeDecoder(hMP3Decoder);
}
bool AudioGeneratorMP3a::stop()
{
if (!running) return true;
running = false;
output->stop();
return file->close();
}
bool AudioGeneratorMP3a::isRunning()
{
return running;
}
bool AudioGeneratorMP3a::FillBufferWithValidFrame()
{
buff[0] = 0; // Destroy any existing sync word @ 0
int nextSync;
do {
nextSync = MP3FindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
if (nextSync >= 0) nextSync += lastFrameEnd;
lastFrameEnd = 0;
if (nextSync == -1) {
if (buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
buff[0] = 0xff;
buffValid = file->read(buff+1, sizeof(buff)-1);
if (buffValid==0) return false; // No data available, EOF
} else { // Try a whole new buffer
buffValid = file->read(buff, sizeof(buff));
if (buffValid==0) return false; // No data available, EOF
}
}
} while (nextSync == -1);
// Move the frame to start at offset 0 in the buffer
buffValid -= nextSync; // Throw out prior to nextSync
memmove(buff, buff+nextSync, buffValid);
// We have a sync word at 0 now, try and fill remainder of buffer
buffValid += file->read(buff + buffValid, sizeof(buff) - buffValid);
return true;
}
bool AudioGeneratorMP3a::loop()
{
if (!running) goto done; // Nothing to do here!
// If we've got data, try and pump it out...
while (validSamples) {
lastSample[0] = outSample[curSample*2];
lastSample[1] = outSample[curSample*2 + 1];
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
validSamples--;
curSample++;
}
// No samples available, need to decode a new frame
if (FillBufferWithValidFrame()) {
// buff[0] start of frame, decode it...
unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
int bytesLeft = buffValid;
int ret = MP3Decode(hMP3Decoder, &inBuff, &bytesLeft, outSample, 0);
if (ret) {
// Error, skip the frame...
char buff[48];
sprintf(buff, "MP3 decode error %d", ret);
cb.st(ret, buff);
} else {
lastFrameEnd = buffValid - bytesLeft;
MP3FrameInfo fi;
MP3GetLastFrameInfo(hMP3Decoder, &fi);
if ((int)fi.samprate!= (int)lastRate) {
output->SetRate(fi.samprate);
lastRate = fi.samprate;
}
if (fi.nChans != lastChannels) {
output->SetChannels(fi.nChans);
lastChannels = fi.nChans;
}
curSample = 0;
validSamples = fi.outputSamps / lastChannels;
}
} else {
running = false; // No more data, we're done here...
}
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorMP3a::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) return false; // Error
output->begin();
// AAC always comes out at 16 bits
output->SetBitsPerSample(16);
running = true;
return true;
}

View File

@ -0,0 +1,57 @@
/*
AudioGeneratorMP3
Audio output generator using the Helix MP3 decoder
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORMP3A_H
#define _AUDIOGENERATORMP3A_H
#include "AudioGenerator.h"
#include "libhelix-mp3/mp3dec.h"
class AudioGeneratorMP3a : public AudioGenerator
{
public:
AudioGeneratorMP3a();
virtual ~AudioGeneratorMP3a() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
// Helix MP3 decoder
HMP3Decoder hMP3Decoder;
// Input buffering
uint8_t buff[1600]; // File buffer required to store at least a whole compressed frame
int16_t buffValid;
int16_t lastFrameEnd;
bool FillBufferWithValidFrame(); // Read until we get a valid syncword and min(feof, 2048) butes in the buffer
// Output buffering
int16_t outSample[1152 * 2]; // Interleaved L/R
int16_t validSamples;
int16_t curSample;
// Each frame may change this if they're very strange, I guess
unsigned int lastRate;
int lastChannels;
};
#endif

View File

@ -0,0 +1,142 @@
/*
AudioGeneratorOpus
Audio output generator that plays Opus audio files
Copyright (C) 2020 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <AudioGeneratorOpus.h>
AudioGeneratorOpus::AudioGeneratorOpus()
{
of = nullptr;
buff = nullptr;
buffPtr = 0;
buffLen = 0;
running = false;
}
AudioGeneratorOpus::~AudioGeneratorOpus()
{
if (of) op_free(of);
of = nullptr;
free(buff);
buff = nullptr;
}
#define OPUS_BUFF 1024
bool AudioGeneratorOpus::begin(AudioFileSource *source, AudioOutput *output)
{
buff = (int16_t*)malloc(OPUS_BUFF * sizeof(int16_t));
if (!buff) return false;
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) return false; // Error
of = op_open_callbacks((void*)this, &cb, nullptr, 0, nullptr);
if (!of) return false;
prev_li = -1;
lastSample[0] = 0;
lastSample[1] = 0;
buffPtr = 0;
buffLen = 0;
output->begin();
// These are fixed by Opus
output->SetRate(48000);
output->SetBitsPerSample(16);
output->SetChannels(2);
running = true;
return true;
}
bool AudioGeneratorOpus::loop()
{
if (!running) goto done;
if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample
do {
if (buffPtr == buffLen) {
int ret = op_read_stereo(of, (opus_int16 *)buff, OPUS_BUFF);
if (ret == OP_HOLE) {
// fprintf(stderr,"\nHole detected! Corrupt file segment?\n");
continue;
} else if (ret <= 0) {
running = false;
goto done;
}
buffPtr = 0;
buffLen = ret * 2;
}
lastSample[AudioOutput::LEFTCHANNEL] = buff[buffPtr] & 0xffff;
lastSample[AudioOutput::RIGHTCHANNEL] = buff[buffPtr+1] & 0xffff;
buffPtr += 2;
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorOpus::stop()
{
if (of) op_free(of);
of = nullptr;
free(buff);
buff = nullptr;
running = false;
output->stop();
return true;
}
bool AudioGeneratorOpus::isRunning()
{
return running;
}
int AudioGeneratorOpus::read_cb(unsigned char *_ptr, int _nbytes) {
if (_nbytes == 0) return 0;
_nbytes = file->read(_ptr, _nbytes);
if (_nbytes == 0) return -1;
return _nbytes;
}
int AudioGeneratorOpus::seek_cb(opus_int64 _offset, int _whence) {
if (!file->seek((int32_t)_offset, _whence)) return -1;
return 0;
}
opus_int64 AudioGeneratorOpus::tell_cb() {
return file->getPos();
}
int AudioGeneratorOpus::close_cb() {
// NO OP, we close in main loop
return 0;
}

View File

@ -0,0 +1,70 @@
/*
AudioGeneratorOpus
Audio output generator that plays Opus audio files
Copyright (C) 2020 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATOROPUS_H
#define _AUDIOGENERATOROPUS_H
#include <AudioGenerator.h>
//#include "libopus/opus.h"
#include "opusfile/opusfile.h"
class AudioGeneratorOpus : public AudioGenerator
{
public:
AudioGeneratorOpus();
virtual ~AudioGeneratorOpus() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
// Opus callbacks, need static functions to bounce into C++ from C
static int OPUS_read(void *_stream, unsigned char *_ptr, int _nbytes) {
return static_cast<AudioGeneratorOpus*>(_stream)->read_cb(_ptr, _nbytes);
}
static int OPUS_seek(void *_stream, opus_int64 _offset, int _whence) {
return static_cast<AudioGeneratorOpus*>(_stream)->seek_cb(_offset, _whence);
}
static opus_int64 OPUS_tell(void *_stream) {
return static_cast<AudioGeneratorOpus*>(_stream)->tell_cb();
}
static int OPUS_close(void *_stream) {
return static_cast<AudioGeneratorOpus*>(_stream)->close_cb();
}
// Actual Opus callbacks
int read_cb(unsigned char *_ptr, int _nbytes);
int seek_cb(opus_int64 _offset, int _whence);
opus_int64 tell_cb();
int close_cb();
private:
OpusFileCallbacks cb = {OPUS_read, OPUS_seek, OPUS_tell, OPUS_close};
OggOpusFile *of;
int prev_li; // To detect changes in streams
int16_t *buff;
uint32_t buffPtr;
uint32_t buffLen;
};
#endif

View File

@ -0,0 +1,292 @@
/*
AudioGeneratorRTTTL
Audio output generator that plays RTTTL (Nokia ringtone)
Based on the Rtttl Arduino library by James BM, https://github.com/spicajames/Rtttl
Based on the gist from Daniel Hall https://gist.github.com/smarthall/1618800
Copyright (C) 2018 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorRTTTL.h"
AudioGeneratorRTTTL::AudioGeneratorRTTTL()
{
running = false;
file = NULL;
output = NULL;
rate = 22050;
buff = nullptr;
ptr = 0;
}
AudioGeneratorRTTTL::~AudioGeneratorRTTTL()
{
free(buff);
}
bool AudioGeneratorRTTTL::stop()
{
if (!running) return true;
running = false;
output->stop();
return file->close();
}
bool AudioGeneratorRTTTL::isRunning()
{
return running;
}
bool AudioGeneratorRTTTL::loop()
{
if (!running) goto done; // Nothing to do here!
// Load the next note, if we've hit the end of the last one
if (samplesSent == ttlSamples) {
if (!GetNextNote()) {
running = false;
goto done;
}
samplesSent = 0;
}
// Try and send out the remainder of the existing note, one per loop()
if (ttlSamplesPerWaveFP10 == 0) { // Mute
int16_t mute[2] = {0, 0};
while ((samplesSent < ttlSamples) && output->ConsumeSample(mute)) {
samplesSent++;
}
} else {
while (samplesSent < ttlSamples) {
int samplesSentFP10 = samplesSent << 10;
int rem = samplesSentFP10 % ttlSamplesPerWaveFP10;
int16_t val = (rem > ttlSamplesPerWaveFP10/2) ? 8192:-8192;
int16_t s[2] = { val, val };
if (!output->ConsumeSample(s)) goto done;
samplesSent++;
}
}
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorRTTTL::SkipWhitespace()
{
while ((ptr < len) && (buff[ptr] == ' ')) ptr++;
return ptr < len;
}
bool AudioGeneratorRTTTL::ReadInt(int *dest)
{
if (ptr >= len) return false;
SkipWhitespace();
if (ptr >= len) return false;
if ((buff[ptr] <'0') || (buff[ptr] > '9')) return false;
int t = 0;
while ((buff[ptr] >= '0') && (buff[ptr] <='9')) {
t = (t * 10) + (buff[ptr] - '0');
ptr++;
}
*dest = t;
return true;
}
bool AudioGeneratorRTTTL::ParseHeader()
{
// Skip the title
while ((ptr < len) && (buff[ptr] != ':')) ptr++;
if (ptr >= len) return false;
if (buff[ptr++] != ':') return false;
if (!SkipWhitespace()) return false;
if ((buff[ptr] != 'd') && (buff[ptr] != 'D')) return false;
ptr++;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != '=') return false;
if (!ReadInt(&defaultDuration)) return false;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != ',') return false;
if (!SkipWhitespace()) return false;
if ((buff[ptr] != 'o') && (buff[ptr] != 'O')) return false;
ptr++;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != '=') return false;
if (!ReadInt(&defaultOctave)) return false;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != ',') return false;
int bpm;
if (!SkipWhitespace()) return false;
if ((buff[ptr] != 'b') && (buff[ptr] != 'B')) return false;
ptr++;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != '=') return false;
if (!ReadInt(&bpm)) return false;
if (!SkipWhitespace()) return false;
if (buff[ptr++] != ':') return false;
wholeNoteMS = (60 * 1000 * 4) / bpm;
return true;
}
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
static int notes[49] = { 0,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7 };
bool AudioGeneratorRTTTL::GetNextNote()
{
int dur, note, scale;
if (ptr >= len) return false;
if (!ReadInt(&dur)) {
dur = defaultDuration;
}
dur = wholeNoteMS / dur;
if (ptr >= len) return false;
note = 0;
switch (buff[ptr++]) {
case 'c': case 'C': note = 1; break;
case 'd': case 'D': note = 3; break;
case 'e': case 'E': note = 5; break;
case 'f': case 'F': note = 6; break;
case 'g': case 'G': note = 8; break;
case 'a': case 'A': note = 10; break;
case 'b': case 'B': note = 12; break;
case 'p': case 'P': note = 0; break;
default: return false;
}
if ((ptr < len) && (buff[ptr] == '#')) {
ptr++;
note++;
}
if ((ptr < len) && (buff[ptr] == '.')) {
ptr++;
dur += dur / 2;
}
if (!ReadInt(&scale)) {
scale = defaultOctave;
}
// Eat any trailing whitespace and comma
SkipWhitespace();
if ((ptr < len) && (buff[ptr]==',')) {
ptr++;
}
if (scale < 4) scale = 4;
if (scale > 7) scale = 7;
if (note) {
int freq = notes[(scale - 4) * 12 + note];
// Convert from frequency in Hz to high and low samples in fixed point
ttlSamplesPerWaveFP10 = (rate << 10) / freq;
} else {
ttlSamplesPerWaveFP10 = 0;
}
ttlSamples = (rate * dur ) / 1000;
//audioLogger->printf("%d %d %d %d %d\n", dur, note, scale, ttlSamplesPerWaveFP10, ttlSamples );
return true;
}
bool AudioGeneratorRTTTL::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) return false;
file = source;
if (!output) return false;
this->output = output;
if (!file->isOpen()) return false; // Error
len = file->getSize();
buff = (char *)malloc(len);
if (!buff) return false;
if (file->read(buff, len) != (uint32_t)len) return false;
ptr = 0;
samplesSent = 0;
ttlSamples = 0;
if (!ParseHeader()) return false;
if (!output->SetRate( rate )) return false;
if (!output->SetBitsPerSample( 16 )) return false;
if (!output->SetChannels( 2 )) return false;
if (!output->begin()) return false;
running = true;
return true;
}

View File

@ -0,0 +1,66 @@
/*
AudioGeneratorRTTTL
Audio output generator that plays RTTTL (Nokia ringtones)
Based on the Rtttl Arduino library by James BM, https://github.com/spicajames/Rtttl
Based on the gist from Daniel Hall https://gist.github.com/smarthall/1618800
Copyright (C) 2018 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORRTTTL_H
#define _AUDIOGENERATORRTTTL_H
#include "AudioGenerator.h"
class AudioGeneratorRTTTL : public AudioGenerator
{
public:
AudioGeneratorRTTTL();
virtual ~AudioGeneratorRTTTL() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
void SetRate(uint16_t hz) { rate = hz; }
private:
bool SkipWhitespace();
bool ReadInt(int *dest);
bool ParseHeader();
bool GetNextNote();
protected:
uint16_t rate;
// We copy the entire tiny song to a buffer for easier access
char *buff;
int len;
int ptr;
// Song-global settings
int defaultDuration;
int defaultOctave;
int wholeNoteMS;
// The note we're currently playing
int ttlSamplesPerWaveFP10;
int ttlSamples;
int samplesSent;
};
#endif

View File

@ -0,0 +1,302 @@
/*
AudioGeneratorTalkie
Audio output generator that speaks using the LPC code in old TI speech chips
Output is locked at 8khz as that's that the hardcoded LPC coefficients are built around
Based on the Talkie Arduino library by Peter Knight, https://github.com/going-digital/Talkie
Copyright (C) 2020 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorTalkie.h"
AudioGeneratorTalkie::AudioGeneratorTalkie()
{
running = false;
lastFrame = false;
file = nullptr;
output = nullptr;
buff = nullptr;
}
AudioGeneratorTalkie::~AudioGeneratorTalkie()
{
free(buff);
}
bool AudioGeneratorTalkie::say(const uint8_t *data, size_t len, bool async) {
// Finish saying anything in the pipe
while (running) {
loop();
delay(0);
}
buff = (uint8_t*)realloc(buff, len);
if (!buff) return false;
memcpy_P(buff, data, len);
// Reset the interpreter to the start of the stream
ptrAddr = buff;
ptrBit = 0;
frameLeft = 0;
lastFrame = false;
running = true;
if (!async) {
// Finish saying anything in the pipe
while (running) {
loop();
delay(0);
}
}
return true;
}
bool AudioGeneratorTalkie::begin(AudioFileSource *source, AudioOutput *output)
{
if (!output) return false;
this->output = output;
if (source) {
file = source;
if (!file->isOpen()) return false; // Error
auto len = file->getSize();
uint8_t *temp = (uint8_t *)malloc(len);
if (!temp) return false;
if (file->read(temp, len) != (uint32_t)len) return false;
say(temp, len);
free(temp);
} else {
// Reset the interpreter to the start of the stream
ptrAddr = buff;
ptrBit = 0;
frameLeft = 0;
running = false;
}
if (!output->SetRate( 8000 )) return false;
if (!output->SetBitsPerSample( 16 )) return false;
if (!output->SetChannels( 2 )) return false;
if (!output->begin()) return false;
return true;
}
bool AudioGeneratorTalkie::stop()
{
if (!running) return true;
running = false;
output->stop();
return file ? file->close() : true;
}
bool AudioGeneratorTalkie::isRunning()
{
return running;
}
bool AudioGeneratorTalkie::loop()
{
if (!running) goto done; // Nothing to do here!
if (!frameLeft) {
if (lastFrame) {
running = false;
goto done;
}
lastFrame = genOneFrame();
}
if (frameLeft) {
for ( ; frameLeft; frameLeft--) {
auto res = genOneSample();
int16_t r[2] = {res, res};
if (!output->ConsumeSample(r)) break;
}
}
done:
if (file) file->loop();
output->loop();
return running;
}
// The ROMs used with the TI speech were serial, not byte wide.
// Here's a handy routine to flip ROM data which is usually reversed.
uint8_t AudioGeneratorTalkie::rev(uint8_t a)
{
// 76543210
a = (a>>4) | (a<<4); // Swap in groups of 4
// 32107654
a = ((a & 0xcc)>>2) | ((a & 0x33)<<2); // Swap in groups of 2
// 10325476
a = ((a & 0xaa)>>1) | ((a & 0x55)<<1); // Swap bit pairs
// 01234567
return a;
}
uint8_t AudioGeneratorTalkie::getBits(uint8_t bits) {
uint8_t value;
uint16_t data;
data = rev(ptrAddr[0])<<8;
if (ptrBit+bits > 8) {
data |= rev(ptrAddr[1]);
}
data <<= ptrBit;
value = data >> (16-bits);
ptrBit += bits;
if (ptrBit >= 8) {
ptrBit -= 8;
ptrAddr++;
}
return value;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
// Constant LPC coefficient tables
static const uint8_t tmsEnergy[0x10] = {0x00,0x02,0x03,0x04,0x05,0x07,0x0a,0x0f,0x14,0x20,0x29,0x39,0x51,0x72,0xa1,0xff};
static const uint8_t tmsPeriod[0x40] = {0x00,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2D,0x2F,0x31,0x33,0x35,0x36,0x39,0x3B,0x3D,0x3F,0x42,0x45,0x47,0x49,0x4D,0x4F,0x51,0x55,0x57,0x5C,0x5F,0x63,0x66,0x6A,0x6E,0x73,0x77,0x7B,0x80,0x85,0x8A,0x8F,0x95,0x9A,0xA0};
static const int16_t tmsK1[0x20] = {0x82C0,0x8380,0x83C0,0x8440,0x84C0,0x8540,0x8600,0x8780,0x8880,0x8980,0x8AC0,0x8C00,0x8D40,0x8F00,0x90C0,0x92C0,0x9900,0xA140,0xAB80,0xB840,0xC740,0xD8C0,0xEBC0,0x0000,0x1440,0x2740,0x38C0,0x47C0,0x5480,0x5EC0,0x6700,0x6D40};
static const int16_t tmsK2[0x20] = {0xAE00,0xB480,0xBB80,0xC340,0xCB80,0xD440,0xDDC0,0xE780,0xF180,0xFBC0,0x0600,0x1040,0x1A40,0x2400,0x2D40,0x3600,0x3E40,0x45C0,0x4CC0,0x5300,0x5880,0x5DC0,0x6240,0x6640,0x69C0,0x6CC0,0x6F80,0x71C0,0x73C0,0x7580,0x7700,0x7E80};
static const int8_t tmsK3[0x10] = {0x92,0x9F,0xAD,0xBA,0xC8,0xD5,0xE3,0xF0,0xFE,0x0B,0x19,0x26,0x34,0x41,0x4F,0x5C};
static const int8_t tmsK4[0x10] = {0xAE,0xBC,0xCA,0xD8,0xE6,0xF4,0x01,0x0F,0x1D,0x2B,0x39,0x47,0x55,0x63,0x71,0x7E};
static const int8_t tmsK5[0x10] = {0xAE,0xBA,0xC5,0xD1,0xDD,0xE8,0xF4,0xFF,0x0B,0x17,0x22,0x2E,0x39,0x45,0x51,0x5C};
static const int8_t tmsK6[0x10] = {0xC0,0xCB,0xD6,0xE1,0xEC,0xF7,0x03,0x0E,0x19,0x24,0x2F,0x3A,0x45,0x50,0x5B,0x66};
static const int8_t tmsK7[0x10] = {0xB3,0xBF,0xCB,0xD7,0xE3,0xEF,0xFB,0x07,0x13,0x1F,0x2B,0x37,0x43,0x4F,0x5A,0x66};
static const int8_t tmsK8[0x08] = {0xC0,0xD8,0xF0,0x07,0x1F,0x37,0x4F,0x66};
static const int8_t tmsK9[0x08] = {0xC0,0xD4,0xE8,0xFC,0x10,0x25,0x39,0x4D};
static const int8_t tmsK10[0x08] = {0xCD,0xDF,0xF1,0x04,0x16,0x20,0x3B,0x4D};
// The chirp we active the filter using
static const int8_t chirp[] = {0x00,0x2a,0xd4,0x32,0xb2,0x12,0x25,0x14,0x02,0xe1,0xc5,0x02,0x5f,0x5a,0x05,0x0f,0x26,0xfc,0xa5,0xa5,0xd6,0xdd,0xdc,0xfc,0x25,0x2b,0x22,0x21,0x0f,0xff,0xf8,0xee,0xed,0xef,0xf7,0xf6,0xfa,0x00,0x03,0x02,0x01};
#pragma GCC diagnostic pop
bool AudioGeneratorTalkie::genOneFrame() {
uint8_t energy;
uint8_t repeat;
// Read speech data, processing the variable size frames.
energy = getBits(4);
if (energy == 0) {
// Energy = 0: rest frame
synthEnergy = 0;
} else if (energy == 0xf) {
// Energy = 15: stop frame. Silence the synthesiser.
synthEnergy = 0;
synthK1 = 0;
synthK2 = 0;
synthK3 = 0;
synthK4 = 0;
synthK5 = 0;
synthK6 = 0;
synthK7 = 0;
synthK8 = 0;
synthK9 = 0;
synthK10 = 0;
} else {
synthEnergy = tmsEnergy[energy];
repeat = getBits(1);
synthPeriod = tmsPeriod[getBits(6)];
// A repeat frame uses the last coefficients
if (!repeat) {
// All frames use the first 4 coefficients
synthK1 = tmsK1[getBits(5)];
synthK2 = tmsK2[getBits(5)];
synthK3 = tmsK3[getBits(4)];
synthK4 = tmsK4[getBits(4)];
if (synthPeriod) {
// Voiced frames use 6 extra coefficients.
synthK5 = tmsK5[getBits(4)];
synthK6 = tmsK6[getBits(4)];
synthK7 = tmsK7[getBits(4)];
synthK8 = tmsK8[getBits(3)];
synthK9 = tmsK9[getBits(3)];
synthK10 = tmsK10[getBits(3)];
}
}
}
frameLeft = 8000 / 40;
return (energy == 0xf); // Last frame will return true
}
int16_t AudioGeneratorTalkie::genOneSample()
{
static uint8_t periodCounter;
static int16_t x0,x1,x2,x3,x4,x5,x6,x7,x8,x9;
int16_t u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10;
if (synthPeriod) {
// Voiced source
if (periodCounter < synthPeriod) {
periodCounter++;
} else {
periodCounter = 0;
}
if (periodCounter < sizeof(chirp)) {
u10 = ((chirp[periodCounter]) * (uint32_t) synthEnergy) >> 8;
} else {
u10 = 0;
}
} else {
// Unvoiced source
static uint16_t synthRand = 1;
synthRand = (synthRand >> 1) ^ ((synthRand & 1) ? 0xB800 : 0);
u10 = (synthRand & 1) ? synthEnergy : -synthEnergy;
}
// Lattice filter forward path
u9 = u10 - (((int16_t)synthK10*x9) >> 7);
u8 = u9 - (((int16_t)synthK9*x8) >> 7);
u7 = u8 - (((int16_t)synthK8*x7) >> 7);
u6 = u7 - (((int16_t)synthK7*x6) >> 7);
u5 = u6 - (((int16_t)synthK6*x5) >> 7);
u4 = u5 - (((int16_t)synthK5*x4) >> 7);
u3 = u4 - (((int16_t)synthK4*x3) >> 7);
u2 = u3 - (((int16_t)synthK3*x2) >> 7);
u1 = u2 - (((int32_t)synthK2*x1) >> 15);
u0 = u1 - (((int32_t)synthK1*x0) >> 15);
// Output clamp
if (u0 > 511) u0 = 511;
if (u0 < -512) u0 = -512;
// Lattice filter reverse path
x9 = x8 + (((int16_t)synthK9*u8) >> 7);
x8 = x7 + (((int16_t)synthK8*u7) >> 7);
x7 = x6 + (((int16_t)synthK7*u6) >> 7);
x6 = x5 + (((int16_t)synthK6*u5) >> 7);
x5 = x4 + (((int16_t)synthK5*u4) >> 7);
x4 = x3 + (((int16_t)synthK4*u3) >> 7);
x3 = x2 + (((int16_t)synthK3*u2) >> 7);
x2 = x1 + (((int32_t)synthK2*u1) >> 15);
x1 = x0 + (((int32_t)synthK1*u0) >> 15);
x0 = u0;
uint16_t v = u0; // 10 bits
v <<= 6; // Now full 16
return v;
}

View File

@ -0,0 +1,66 @@
/*
AudioGeneratorTalkie
Audio output generator that speaks using the LPC code in old TI speech chips
Output is locked at 8khz as that's that the hardcoded LPC coefficients are built around
Based on the Talkie Arduino library by Peter Knight, https://github.com/going-digital/Talkie
Copyright (C) 2020 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORTALKIE_H
#define _AUDIOGENERATORTALKIE_H
#include "AudioGenerator.h"
class AudioGeneratorTalkie : public AudioGenerator
{
public:
AudioGeneratorTalkie();
virtual ~AudioGeneratorTalkie() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
bool say(const uint8_t *data, size_t len, bool async = false);
protected:
// The data stream we're playing
uint8_t *buff;
// Codeword stream handlers
uint8_t *ptrAddr;
uint8_t ptrBit;
bool lastFrame;
bool genOneFrame(); // Fill up one frame's worth of data, returns if this is the last frame
int16_t genOneSample(); // Generate one sample of a frame
// Utilities
uint8_t rev(uint8_t a);
uint8_t getBits(uint8_t bits);
// Synthesizer state
uint8_t synthPeriod;
uint16_t synthEnergy;
int16_t synthK1, synthK2;
int8_t synthK3, synthK4, synthK5, synthK6, synthK7, synthK8, synthK9, synthK10;
int frameLeft;
};
#endif

View File

@ -0,0 +1,316 @@
/*
AudioGeneratorWAV
Audio output generator that reads 8 and 16-bit WAV files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorWAV.h"
AudioGeneratorWAV::AudioGeneratorWAV()
{
running = false;
file = NULL;
output = NULL;
buffSize = 128;
buff = NULL;
buffPtr = 0;
buffLen = 0;
}
AudioGeneratorWAV::~AudioGeneratorWAV()
{
free(buff);
buff = NULL;
}
bool AudioGeneratorWAV::stop()
{
if (!running) return true;
running = false;
free(buff);
buff = NULL;
output->stop();
return file->close();
}
bool AudioGeneratorWAV::isRunning()
{
return running;
}
// Handle buffered reading, reload each time we run out of data
bool AudioGeneratorWAV::GetBufferedData(int bytes, void *dest)
{
if (!running) return false; // Nothing to do here!
uint8_t *p = reinterpret_cast<uint8_t*>(dest);
while (bytes--) {
// Potentially load next batch of data...
if (buffPtr >= buffLen) {
buffPtr = 0;
uint32_t toRead = availBytes > buffSize ? buffSize : availBytes;
buffLen = file->read( buff, toRead );
availBytes -= buffLen;
}
if (buffPtr >= buffLen)
return false; // No data left!
*(p++) = buff[buffPtr++];
}
return true;
}
bool AudioGeneratorWAV::loop()
{
if (!running) goto done; // Nothing to do here!
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
// Try and stuff the buffer one sample at a time
do
{
if (bitsPerSample == 8) {
uint8_t l, r;
if (!GetBufferedData(1, &l)) stop();
if (channels == 2) {
if (!GetBufferedData(1, &r)) stop();
} else {
r = 0;
}
lastSample[AudioOutput::LEFTCHANNEL] = l;
lastSample[AudioOutput::RIGHTCHANNEL] = r;
} else if (bitsPerSample == 16) {
if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) stop();
if (channels == 2) {
if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) stop();
} else {
lastSample[AudioOutput::RIGHTCHANNEL] = 0;
}
}
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorWAV::ReadWAVInfo()
{
uint32_t u32;
uint16_t u16;
int toSkip;
// WAV specification document:
// https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
// Header == "RIFF"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 != 0x46464952) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: %08X \n"), (uint32_t) u32);
return false;
}
// Skip ChunkSize
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
// Format == "WAVE"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 != 0x45564157) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid WAVE header, got: %08X \n"), (uint32_t) u32);
return false;
}
// there might be JUNK or PAD - ignore it by continuing reading until we get to "fmt "
while (1) {
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 0x20746d66) break; // 'fmt '
};
// subchunk size
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 16) { toSkip = 0; }
else if (u32 == 18) { toSkip = 18 - 16; }
else if (u32 == 40) { toSkip = 40 - 16; }
else {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, appears not to be standard PCM \n"));
return false;
} // we only do standard PCM
// AudioFormat
if (!ReadU16(&u16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u16 != 1) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, AudioFormat appears not to be standard PCM \n"));
return false;
} // we only do standard PCM
// NumChannels
if (!ReadU16(&channels)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if ((channels<1) || (channels>2)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only mono and stereo are supported \n"));
return false;
} // Mono or stereo support only
// SampleRate
if (!ReadU32(&sampleRate)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (sampleRate < 1) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, unknown sample rate \n"));
return false;
} // Weird rate, punt. Will need to check w/DAC to see if supported
// Ignore byterate and blockalign
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (!ReadU16(&u16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
// Bits per sample
if (!ReadU16(&bitsPerSample)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if ((bitsPerSample!=8) && (bitsPerSample != 16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only 8 or 16 bits is supported \n"));
return false;
} // Only 8 or 16 bits
// Skip any extra header
while (toSkip) {
uint8_t ign;
if (!ReadU8(&ign)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
toSkip--;
}
// look for data subchunk
do {
// id == "data"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 0x61746164) break; // "data"
// Skip size, read until end of chunk
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if(!file->seek(u32, SEEK_CUR)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data, seek failed\n"));
return false;
}
} while (1);
if (!file->isOpen()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, file is not open\n"));
return false;
};
// Skip size, read until end of file...
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
availBytes = u32;
// Now set up the buffer or fail
buff = reinterpret_cast<uint8_t *>(malloc(buffSize));
if (!buff) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, failed to set up buffer \n"));
return false;
};
buffPtr = 0;
buffLen = 0;
return true;
}
bool AudioGeneratorWAV::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed: invalid source\n"));
return false;
}
file = source;
if (!output) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: invalid output\n"));
return false;
}
this->output = output;
if (!file->isOpen()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: file not open\n"));
return false;
} // Error
if (!ReadWAVInfo()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed during ReadWAVInfo\n"));
return false;
}
if (!output->SetRate( sampleRate )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetRate in output\n"));
return false;
}
if (!output->SetBitsPerSample( bitsPerSample )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetBitsPerSample in output\n"));
return false;
}
if (!output->SetChannels( channels )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetChannels in output\n"));
return false;
}
if (!output->begin()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: output's begin did not return true\n"));
return false;
}
running = true;
return true;
}

View File

@ -0,0 +1,61 @@
/*
AudioGeneratorWAV
Audio output generator that reads 8 and 16-bit WAV files
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOGENERATORWAV_H
#define _AUDIOGENERATORWAV_H
#include "AudioGenerator.h"
class AudioGeneratorWAV : public AudioGenerator
{
public:
AudioGeneratorWAV();
virtual ~AudioGeneratorWAV() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
void SetBufferSize(int sz) { buffSize = sz; }
private:
bool ReadU32(uint32_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 4); }
bool ReadU16(uint16_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 2); }
bool ReadU8(uint8_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 1); }
bool GetBufferedData(int bytes, void *dest);
bool ReadWAVInfo();
protected:
// WAV info
uint16_t channels;
uint32_t sampleRate;
uint16_t bitsPerSample;
uint32_t availBytes;
// We need to buffer some data in-RAM to avoid doing 1000s of small reads
uint32_t buffSize;
uint8_t *buff;
uint16_t buffPtr;
uint16_t buffLen;
};
#endif

View File

@ -0,0 +1,5 @@
#include "AudioLogger.h"
DevNullOut silencedLogger;
Print* audioLogger = &silencedLogger;

View File

@ -0,0 +1,19 @@
#include <Arduino.h>
#ifndef _AUDIOLOGGER_H
#define _AUDIOLOGGER_H
class DevNullOut: public Print
{
public:
virtual size_t write(uint8_t) { return 1; }
};
extern DevNullOut silencedLogger;
// Global `audioLogger` is initialized to &silencedLogger
// It can be initialized anytime to &Serial or any other Print:: derivative instance.
extern Print* audioLogger;
#endif

View File

@ -0,0 +1,85 @@
/*
AudioOutput
Base class of an audio output player
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUT_H
#define _AUDIOOUTPUT_H
#include <Arduino.h>
#include "AudioStatus.h"
class AudioOutput
{
public:
AudioOutput() { };
virtual ~AudioOutput() {};
virtual bool SetRate(int hz) { hertz = hz; return true; }
virtual bool SetBitsPerSample(int bits) { bps = bits; return true; }
virtual bool SetChannels(int chan) { channels = chan; return true; }
virtual bool SetGain(float f) { if (f>4.0) f = 4.0; if (f<0.0) f=0.0; gainF2P6 = (uint8_t)(f*(1<<6)); return true; }
virtual bool begin() { return false; };
typedef enum { LEFTCHANNEL=0, RIGHTCHANNEL=1 } SampleIndex;
virtual bool ConsumeSample(int16_t sample[2]) { (void)sample; return false; }
virtual uint16_t ConsumeSamples(int16_t *samples, uint16_t count)
{
for (uint16_t i=0; i<count; i++) {
if (!ConsumeSample(samples)) return i;
samples += 2;
}
return count;
}
virtual bool stop() { return false; }
virtual void flush() { return; }
virtual bool loop() { return true; }
public:
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
protected:
void MakeSampleStereo16(int16_t sample[2]) {
// Mono to "stereo" conversion
if (channels == 1)
sample[RIGHTCHANNEL] = sample[LEFTCHANNEL];
if (bps == 8) {
// Upsample from unsigned 8 bits to signed 16 bits
sample[LEFTCHANNEL] = (((int16_t)(sample[LEFTCHANNEL]&0xff)) - 128) << 8;
sample[RIGHTCHANNEL] = (((int16_t)(sample[RIGHTCHANNEL]&0xff)) - 128) << 8;
}
};
inline int16_t Amplify(int16_t s) {
int32_t v = (s * gainF2P6)>>6;
if (v < -32767) return -32767;
else if (v > 32767) return 32767;
else return (int16_t)(v&0xffff);
}
protected:
uint16_t hertz;
uint8_t bps;
uint8_t channels;
uint8_t gainF2P6; // Fixed point 2.6
protected:
AudioStatus cb;
};
#endif

View File

@ -0,0 +1,89 @@
/*
AudioOutputBuffer
Adds additional bufferspace to the output chain
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "AudioOutputBuffer.h"
AudioOutputBuffer::AudioOutputBuffer(int buffSizeSamples, AudioOutput *dest)
{
buffSize = buffSizeSamples;
leftSample = (int16_t*)malloc(sizeof(int16_t) * buffSize);
rightSample = (int16_t*)malloc(sizeof(int16_t) * buffSize);
writePtr = 0;
readPtr = 0;
sink = dest;
}
AudioOutputBuffer::~AudioOutputBuffer()
{
free(leftSample);
free(rightSample);
}
bool AudioOutputBuffer::SetRate(int hz)
{
return sink->SetRate(hz);
}
bool AudioOutputBuffer::SetBitsPerSample(int bits)
{
return sink->SetBitsPerSample(bits);
}
bool AudioOutputBuffer::SetChannels(int channels)
{
return sink->SetChannels(channels);
}
bool AudioOutputBuffer::begin()
{
filled = false;
return sink->begin();
}
bool AudioOutputBuffer::ConsumeSample(int16_t sample[2])
{
// First, try and fill I2S...
if (filled) {
while (readPtr != writePtr) {
int16_t s[2] = {leftSample[readPtr], rightSample[readPtr]};
if (!sink->ConsumeSample(s)) break; // Can't stuff any more in I2S...
readPtr = (readPtr + 1) % buffSize;
}
}
// Now, do we have space for a new sample?
int nextWritePtr = (writePtr + 1) % buffSize;
if (nextWritePtr == readPtr) {
filled = true;
return false;
}
leftSample[writePtr] = sample[LEFTCHANNEL];
rightSample[writePtr] = sample[RIGHTCHANNEL];
writePtr = nextWritePtr;
return true;
}
bool AudioOutputBuffer::stop()
{
return sink->stop();
}

View File

@ -0,0 +1,49 @@
/*
AudioOutputBuffer
Adds additional bufferspace to the output chain
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTBUFFER_H
#define _AUDIOOUTPUTBUFFER_H
#include "AudioOutput.h"
class AudioOutputBuffer : public AudioOutput
{
public:
AudioOutputBuffer(int bufferSizeSamples, AudioOutput *dest);
virtual ~AudioOutputBuffer() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;
protected:
AudioOutput *sink;
int buffSize;
int16_t *leftSample;
int16_t *rightSample;
int writePtr;
int readPtr;
bool filled;
};
#endif

View File

@ -0,0 +1,112 @@
/*
AudioOutputFilter
Implements a user-defined FIR on a passthrough
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "AudioOutputFilterDecimate.h"
AudioOutputFilterDecimate::AudioOutputFilterDecimate(uint8_t taps, const int16_t *tap, int num, int den, AudioOutput *sink)
{
this->sink = sink;
// The filter state. Passed in TAPS must be available throughout object lifetime
this->taps = taps;
this->tap = (int16_t*)malloc(sizeof(int16_t) * taps);
memcpy_P(this->tap, tap, sizeof(int16_t) * taps);
this->hist[0] = (int16_t*)malloc(sizeof(int16_t) * taps);
memset(this->hist[0], 0, sizeof(int16_t) * taps);
this->hist[1] = (int16_t*)malloc(sizeof(int16_t) * taps);
memset(this->hist[1], 0, sizeof(int16_t) * taps);
this->idx = 0;
// Decimator numerator and denominator with an error signal. Not great, but fast and simple
this->num = num;
this->den = den;
this->err = 0;
}
AudioOutputFilterDecimate::~AudioOutputFilterDecimate()
{
free(hist[1]);
free(hist[0]);
free(tap);
}
bool AudioOutputFilterDecimate::SetRate(int hz)
{
// Modify input frequency to account for decimation
hz *= den;
hz /= num;
return sink->SetRate(hz);
}
bool AudioOutputFilterDecimate::SetBitsPerSample(int bits)
{
return sink->SetBitsPerSample(bits);
}
bool AudioOutputFilterDecimate::SetChannels(int channels)
{
return sink->SetChannels(channels);
}
bool AudioOutputFilterDecimate::SetGain(float gain)
{
return sink->SetGain(gain);
}
bool AudioOutputFilterDecimate::begin()
{
return sink->begin();
}
bool AudioOutputFilterDecimate::ConsumeSample(int16_t sample[2])
{
// Store the data samples in history always
hist[LEFTCHANNEL][idx] = sample[LEFTCHANNEL];
hist[RIGHTCHANNEL][idx] = sample[RIGHTCHANNEL];
idx++;
if (idx == taps) idx = 0;
// Only output if the error signal says we're ready to decimate. This simplistic way might give some aliasing noise
err += num;
if (err >= den) {
err -= den;
// Need to output a sample, so actually calculate the filter at this point in time
// Smarter might actually shift the history by the fractional remainder or take two filters and interpolate
int32_t accL = 0;
int32_t accR = 0;
int index = idx;
for (size_t i=0; i < taps; i++) {
index = index != 0 ? index-1 : taps-1;
accL += (int32_t)hist[LEFTCHANNEL][index] * tap[i];
accR += (int32_t)hist[RIGHTCHANNEL][index] * tap[i];
};
int16_t out[2];
out[LEFTCHANNEL] = accL >> 16;
out[RIGHTCHANNEL] = accR >> 16;
return sink->ConsumeSample(out);
}
return true; // Nothing to do here...
}
bool AudioOutputFilterDecimate::stop()
{
return sink->stop();
}

View File

@ -0,0 +1,51 @@
/*
AudioOutputFilterDecimate
Implements a user-defined FIR on a passthrough w/rational decimation
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTFILTERDECIMATE_H
#define _AUDIOOUTPUTFILTERDECIMATE_H
#include "AudioOutput.h"
class AudioOutputFilterDecimate : public AudioOutput
{
public:
AudioOutputFilterDecimate(uint8_t taps, const int16_t *tap, int num, int den, AudioOutput *sink);
virtual ~AudioOutputFilterDecimate() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int chan) override;
virtual bool SetGain(float f) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;
protected:
AudioOutput *sink;
uint8_t taps;
int16_t *tap;
int16_t *hist[2];
int idx;
int num;
int den;
int err;
};
#endif

View File

@ -0,0 +1,223 @@
/*
AudioOutputI2S
Base class for I2S interface port
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifdef ESP32
#include "driver/i2s.h"
#else
#include <i2s.h>
#endif
#include "AudioOutputI2S.h"
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
{
this->portNo = port;
this->i2sOn = false;
this->dma_buf_count = dma_buf_count;
if (output_mode != EXTERNAL_I2S && output_mode != INTERNAL_DAC && output_mode != INTERNAL_PDM) {
output_mode = EXTERNAL_I2S;
}
this->output_mode = output_mode;
#ifdef ESP32
if (!i2sOn) {
if (use_apll == APLL_AUTO) {
// don't use audio pll on buggy rev0 chips
use_apll = APLL_DISABLE;
esp_chip_info_t out_info;
esp_chip_info(&out_info);
if(out_info.revision > 0) {
use_apll = APLL_ENABLE;
}
}
i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
if (output_mode == INTERNAL_DAC) {
mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN);
} else if (output_mode == INTERNAL_PDM) {
mode = (i2s_mode_t)(mode | I2S_MODE_PDM);
}
i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
if (output_mode == INTERNAL_DAC) {
comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB;
}
i2s_config_t i2s_config_dac = {
.mode = mode,
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = comm_fmt,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
.dma_buf_count = dma_buf_count,
.dma_buf_len = 64,
.use_apll = use_apll // Use audio PLL
};
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) {
audioLogger->println("ERROR: Unable to install I2S drives\n");
}
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) {
i2s_set_pin((i2s_port_t)portNo, NULL);
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
} else {
SetPinout(26, 25, 22);
}
i2s_zero_dma_buffer((i2s_port_t)portNo);
}
#else
(void) dma_buf_count;
(void) use_apll;
if (!i2sOn) {
orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U);
orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U);
i2s_begin();
}
#endif
i2sOn = true;
mono = false;
bps = 16;
channels = 2;
SetGain(1.0);
SetRate(44100); // Default
}
AudioOutputI2S::~AudioOutputI2S()
{
#ifdef ESP32
if (i2sOn) {
audioLogger->printf("UNINSTALL I2S\n");
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
}
#else
if (i2sOn) i2s_end();
#endif
i2sOn = false;
}
bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout)
{
#ifdef ESP32
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) return false; // Not allowed
i2s_pin_config_t pins = {
.bck_io_num = bclk,
.ws_io_num = wclk,
.data_out_num = dout,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_set_pin((i2s_port_t)portNo, &pins);
return true;
#else
(void) bclk;
(void) wclk;
(void) dout;
return false;
#endif
}
bool AudioOutputI2S::SetRate(int hz)
{
// TODO - have a list of allowable rates from constructor, check them
this->hertz = hz;
#ifdef ESP32
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
#else
i2s_set_rate(AdjustI2SRate(hz));
#endif
return true;
}
bool AudioOutputI2S::SetBitsPerSample(int bits)
{
if ( (bits != 16) && (bits != 8) ) return false;
this->bps = bits;
return true;
}
bool AudioOutputI2S::SetChannels(int channels)
{
if ( (channels < 1) || (channels > 2) ) return false;
this->channels = channels;
return true;
}
bool AudioOutputI2S::SetOutputModeMono(bool mono)
{
this->mono = mono;
return true;
}
bool AudioOutputI2S::begin()
{
return true;
}
bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
{
int16_t ms[2];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16( ms );
if (this->mono) {
// Average the two samples and overwrite
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
}
#ifdef ESP32
uint32_t s32;
if (output_mode == INTERNAL_DAC) {
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r<<16) | (l&0xffff);
} else {
s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
}
return i2s_write_bytes((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), 0);
#else
uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true
#endif
}
void AudioOutputI2S::flush() {
#ifdef ESP32
// makes sure that all stored DMA samples are consumed / played
int buffersize = 64 * this->dma_buf_count;
int16_t samples[2] = {0x0,0x0};
for (int i=0;i<buffersize; i++) {
while (!ConsumeSample(samples)) {
delay(10);
}
}
#endif
}
bool AudioOutputI2S::stop()
{
#ifdef ESP32
i2s_zero_dma_buffer((i2s_port_t)portNo);
#endif
return true;
}

View File

@ -0,0 +1,58 @@
/*
AudioOutputI2S
Base class for an I2S output port
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTI2S_H
#define _AUDIOOUTPUTI2S_H
#include "AudioOutput.h"
class AudioOutputI2S : public AudioOutput
{
public:
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
virtual ~AudioOutputI2S() override;
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual void flush() override;
virtual bool stop() override;
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
protected:
virtual int AdjustI2SRate(int hz) { return hz; }
uint8_t portNo;
int output_mode;
bool mono;
bool i2sOn;
int dma_buf_count;
// We can restore the old values and free up these pins when in NoDAC mode
uint32_t orig_bck;
uint32_t orig_ws;
};
#endif

View File

@ -0,0 +1,108 @@
/*
AudioOutputI2SNoDAC
Audio player using SW delta-sigma to generate "analog" on I2S data
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifdef ESP32
#include "driver/i2s.h"
#else
#include <i2s.h>
#endif
#include "AudioOutputI2SNoDAC.h"
AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false)
{
SetOversampling(32);
lastSamp = 0;
cumErr = 0;
#ifndef ESP32
WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck);
WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws);
#endif
}
AudioOutputI2SNoDAC::~AudioOutputI2SNoDAC()
{
}
bool AudioOutputI2SNoDAC::SetOversampling(int os) {
if (os % 32) return false; // Only Nx32 oversampling supported
if (os > 256) return false; // Don't be silly now!
if (os < 32) return false; // Nothing under 32 allowed
oversample = os;
return SetRate(hertz);
}
void AudioOutputI2SNoDAC::DeltaSigma(int16_t sample[2], uint32_t dsBuff[8])
{
// Not shift 8 because addition takes care of one mult x 2
int32_t sum = (((int32_t)sample[0]) + ((int32_t)sample[1])) >> 1;
fixed24p8_t newSamp = ( (int32_t)Amplify(sum) ) << 8;
int oversample32 = oversample / 32;
// How much the comparison signal changes each oversample step
fixed24p8_t diffPerStep = (newSamp - lastSamp) >> (4 + oversample32);
// Don't need lastSamp anymore, store this one for next round
lastSamp = newSamp;
for (int j = 0; j < oversample32; j++) {
uint32_t bits = 0; // The bits we convert the sample into, MSB to go on the wire first
for (int i = 32; i > 0; i--) {
bits = bits << 1;
if (cumErr < 0) {
bits |= 1;
cumErr += fixedPosValue - newSamp;
} else {
// Bits[0] = 0 handled already by left shift
cumErr -= fixedPosValue + newSamp;
}
newSamp += diffPerStep; // Move the reference signal towards destination
}
dsBuff[j] = bits;
}
}
bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2])
{
int16_t ms[2];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16( ms );
// Make delta-sigma filled buffer
uint32_t dsBuff[8];
DeltaSigma(ms, dsBuff);
// Either send complete pulse stream or nothing
#ifdef ESP32
if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0))
return false;
#else
if (!i2s_write_sample_nb(dsBuff[0])) return false; // No room at the inn
// At this point we've sent in first of possibly 8 32-bits, need to send
// remaining ones even if they block.
for (int i = 32; i < oversample; i+=32)
i2s_write_sample( dsBuff[i / 32]);
#endif
return true;
}

View File

@ -0,0 +1,46 @@
/*
AudioOutputI2SNoDAC
Audio player using SW delta-sigma to generate "analog" on I2S data
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTI2SNODAC_H
#define _AUDIOOUTPUTI2SNODAC_H
#include "AudioOutputI2S.h"
class AudioOutputI2SNoDAC : public AudioOutputI2S
{
public:
AudioOutputI2SNoDAC(int port = 0);
virtual ~AudioOutputI2SNoDAC() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
bool SetOversampling(int os);
protected:
virtual int AdjustI2SRate(int hz) override { return hz * oversample/32; }
uint8_t oversample;
void DeltaSigma(int16_t sample[2], uint32_t dsBuff[4]);
typedef int32_t fixed24p8_t;
enum {fixedPosValue=0x007fff00}; /* 24.8 of max-signed-int */
fixed24p8_t lastSamp; // Last sample value
fixed24p8_t cumErr; // Running cumulative error since time began
};
#endif

View File

@ -0,0 +1,244 @@
/*
AudioOutputMixer
Simple mixer which can combine multiple inputs to a single output stream
Copyright (C) 2018 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "AudioOutputMixer.h"
AudioOutputMixerStub::AudioOutputMixerStub(AudioOutputMixer *sink, int id) : AudioOutput()
{
this->id = id;
this->parent = sink;
SetGain(1.0);
}
AudioOutputMixerStub::~AudioOutputMixerStub()
{
parent->RemoveInput(id);
}
bool AudioOutputMixerStub::SetRate(int hz)
{
return parent->SetRate(hz, id);
}
bool AudioOutputMixerStub::SetBitsPerSample(int bits)
{
return parent->SetBitsPerSample(bits, id);
}
bool AudioOutputMixerStub::SetChannels(int channels)
{
return parent->SetChannels(channels, id);
}
bool AudioOutputMixerStub::begin()
{
return parent->begin(id);
}
bool AudioOutputMixerStub::ConsumeSample(int16_t sample[2])
{
int16_t amp[2];
amp[LEFTCHANNEL] = Amplify(sample[LEFTCHANNEL]);
amp[RIGHTCHANNEL] = Amplify(sample[RIGHTCHANNEL]);
return parent->ConsumeSample(amp, id);
}
bool AudioOutputMixerStub::stop()
{
return parent->stop(id);
}
AudioOutputMixer::AudioOutputMixer(int buffSizeSamples, AudioOutput *dest) : AudioOutput()
{
buffSize = buffSizeSamples;
leftAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
rightAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
for (int i=0; i<maxStubs; i++) {
stubAllocated[i] = false;
stubRunning[i] = false;
writePtr[i] = 0;
}
readPtr = 0;
sink = dest;
sinkStarted = false;
}
AudioOutputMixer::~AudioOutputMixer()
{
free(leftAccum);
free(rightAccum);
}
// Most "standard" interfaces should fail, only MixerStub should be able to talk to us
bool AudioOutputMixer::SetRate(int hz)
{
(void) hz;
return false;
}
bool AudioOutputMixer::SetBitsPerSample(int bits)
{
(void) bits;
return false;
}
bool AudioOutputMixer::SetChannels(int channels)
{
(void) channels;
return false;
}
bool AudioOutputMixer::ConsumeSample(int16_t sample[2])
{
(void) sample;
return false;
}
bool AudioOutputMixer::begin()
{
return false;
}
bool AudioOutputMixer::stop()
{
return false;
}
// TODO - actually ensure all samples are same speed, size, channels, rate
bool AudioOutputMixer::SetRate(int hz, int id)
{
(void) id;
return sink->SetRate(hz);
}
bool AudioOutputMixer::SetBitsPerSample(int bits, int id)
{
(void) id;
return sink->SetBitsPerSample(bits);
}
bool AudioOutputMixer::SetChannels(int channels, int id)
{
(void) id;
return sink->SetChannels(channels);
}
bool AudioOutputMixer::begin(int id)
{
stubRunning[id] = true;
if (!sinkStarted) {
sinkStarted = true;
return sink->begin();
} else {
return true;
}
}
AudioOutputMixerStub *AudioOutputMixer::NewInput()
{
for (int i=0; i<maxStubs; i++) {
if (!stubAllocated[i]) {
stubAllocated[i] = true;
stubRunning[i] = false;
writePtr[i] = readPtr; // TODO - should it be 1 before readPtr?
AudioOutputMixerStub *stub = new AudioOutputMixerStub(this, i);
return stub;
}
}
return nullptr;
}
void AudioOutputMixer::RemoveInput(int id)
{
stubAllocated[id] = false;
stubRunning[id] = false;
}
bool AudioOutputMixer::loop()
{
// First, try and fill I2S...
// This is not optimal, but algorithmically should work fine
bool avail;
do {
avail = true;
for (int i=0; i<maxStubs && avail; i++) {
if (stubRunning[i] && writePtr[i] == readPtr) {
avail = false; // The read pointer is touching an active writer, can't advance
}
}
if (avail) {
int16_t s[2];
if (leftAccum[readPtr] > 32767) {
s[LEFTCHANNEL] = 32767;
} else if (leftAccum[readPtr] < -32767) {
s[LEFTCHANNEL] = -32767;
} else {
s[LEFTCHANNEL] = leftAccum[readPtr];
}
if (rightAccum[readPtr] > 32767) {
s[RIGHTCHANNEL] = 32767;
} else if (rightAccum[readPtr] < -32767) {
s[RIGHTCHANNEL] = -32767;
} else {
s[RIGHTCHANNEL] = rightAccum[readPtr];
}
// s[LEFTCHANNEL] = Amplify(s[LEFTCHANNEL]);
// s[RIGHTCHANNEL] = Amplify(s[RIGHTCHANNEL]);
if (!sink->ConsumeSample(s)) {
break; // Can't stuff any more in I2S...
}
// Clear the accums and advance the pointer to next potential sample
leftAccum[readPtr] = 0;
rightAccum[readPtr] = 0;
readPtr = (readPtr + 1) % buffSize;
}
} while (avail);
return true;
}
bool AudioOutputMixer::ConsumeSample(int16_t sample[2], int id)
{
loop(); // Send any pre-existing, completed I2S data we can fit
// Now, do we have space for a new sample?
int nextWritePtr = (writePtr[id] + 1) % buffSize;
if (nextWritePtr == readPtr) {
return false;
}
leftAccum[writePtr[id]] += sample[LEFTCHANNEL];
rightAccum[writePtr[id]] += sample[RIGHTCHANNEL];
writePtr[id] = nextWritePtr;
return true;
}
bool AudioOutputMixer::stop(int id)
{
stubRunning[id] = false;
return true;
}

View File

@ -0,0 +1,88 @@
/*
AudioOutputMixer
Simple mixer which can combine multiple inputs to a single output stream
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTMIXER_H
#define _AUDIOOUTPUTMIXER_H
#include "AudioOutput.h"
class AudioOutputMixer;
// The output stub exported by the mixer for use by the generator
class AudioOutputMixerStub : public AudioOutput
{
public:
AudioOutputMixerStub(AudioOutputMixer *sink, int id);
virtual ~AudioOutputMixerStub() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;
protected:
AudioOutputMixer *parent;
int id;
};
// Single mixer object per output
class AudioOutputMixer : public AudioOutput
{
public:
AudioOutputMixer(int samples, AudioOutput *sink);
virtual ~AudioOutputMixer() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;
virtual bool loop() override; // Send all existing samples we can to I2S
AudioOutputMixerStub *NewInput(); // Get a new stub to pass to a generator
// Stub called functions
friend class AudioOutputMixerStub;
private:
void RemoveInput(int id);
bool SetRate(int hz, int id);
bool SetBitsPerSample(int bits, int id);
bool SetChannels(int channels, int id);
bool begin(int id);
bool ConsumeSample(int16_t sample[2], int id);
bool stop(int id);
protected:
enum { maxStubs = 8 };
AudioOutput *sink;
bool sinkStarted;
int16_t buffSize;
int32_t *leftAccum;
int32_t *rightAccum;
bool stubAllocated[maxStubs];
bool stubRunning[maxStubs];
int16_t writePtr[maxStubs]; // Array of pointers for allocated stubs
int16_t readPtr;
};
#endif

View File

@ -0,0 +1,45 @@
/*
AudioOutput
Base class of an audio output player
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTNULL_H
#define _AUDIOOUTPUTNULL_H
#include "AudioOutput.h"
class AudioOutputNull : public AudioOutput
{
public:
AudioOutputNull() {};
~AudioOutputNull() {};
virtual bool begin() { samples = 0; startms = millis(); return true; }
virtual bool ConsumeSample(int16_t sample[2]) { (void)sample; samples++; return true; }
virtual bool stop() { endms = millis(); return true; };
unsigned long GetMilliseconds() { return endms - startms; }
int GetSamples() { return samples; }
int GetFrequency() { return hertz; }
protected:
unsigned long startms;
unsigned long endms;
int samples;
};
#endif

View File

@ -0,0 +1,288 @@
/*
AudioOutputSPDIF
S/PDIF output via I2S
Needs transciever from CMOS level to either optical or coaxial interface
See: https://www.epanorama.net/documents/audio/spdif.html
Original idea and sources:
Forum thread dicussing implementation
https://forum.pjrc.com/threads/28639-S-pdif
Teensy Audio Library
https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
Adapted for ESP8266Audio
NOTE: This module operates I2S at 4x sampling rate, as it needs to
send out each bit as two output symbols, packed into
32-bit words. Even for mono sound, S/PDIF is specified minimum
for 2 channels, each as 32-bits sub-frame. This drains I2S
buffers 4x more quickly so you may need 4x bigger output
buffers than usual, configurable with 'dma_buf_count'
constructor parameter.
Copyright (C) 2020 Ivan Kostoski
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#if defined(ESP32)
#include "driver/i2s.h"
#include "soc/rtc.h"
#elif defined(ESP8266)
#include "driver/SinglePinI2SDriver.h"
#endif
#include "AudioOutputSPDIF.h"
// BMC (Biphase Mark Coded) values (bit order reversed, i.e. LSB first)
static const uint16_t spdif_bmclookup[256] PROGMEM = {
0xcccc, 0x4ccc, 0x2ccc, 0xaccc, 0x34cc, 0xb4cc, 0xd4cc, 0x54cc,
0x32cc, 0xb2cc, 0xd2cc, 0x52cc, 0xcacc, 0x4acc, 0x2acc, 0xaacc,
0x334c, 0xb34c, 0xd34c, 0x534c, 0xcb4c, 0x4b4c, 0x2b4c, 0xab4c,
0xcd4c, 0x4d4c, 0x2d4c, 0xad4c, 0x354c, 0xb54c, 0xd54c, 0x554c,
0x332c, 0xb32c, 0xd32c, 0x532c, 0xcb2c, 0x4b2c, 0x2b2c, 0xab2c,
0xcd2c, 0x4d2c, 0x2d2c, 0xad2c, 0x352c, 0xb52c, 0xd52c, 0x552c,
0xccac, 0x4cac, 0x2cac, 0xacac, 0x34ac, 0xb4ac, 0xd4ac, 0x54ac,
0x32ac, 0xb2ac, 0xd2ac, 0x52ac, 0xcaac, 0x4aac, 0x2aac, 0xaaac,
0x3334, 0xb334, 0xd334, 0x5334, 0xcb34, 0x4b34, 0x2b34, 0xab34,
0xcd34, 0x4d34, 0x2d34, 0xad34, 0x3534, 0xb534, 0xd534, 0x5534,
0xccb4, 0x4cb4, 0x2cb4, 0xacb4, 0x34b4, 0xb4b4, 0xd4b4, 0x54b4,
0x32b4, 0xb2b4, 0xd2b4, 0x52b4, 0xcab4, 0x4ab4, 0x2ab4, 0xaab4,
0xccd4, 0x4cd4, 0x2cd4, 0xacd4, 0x34d4, 0xb4d4, 0xd4d4, 0x54d4,
0x32d4, 0xb2d4, 0xd2d4, 0x52d4, 0xcad4, 0x4ad4, 0x2ad4, 0xaad4,
0x3354, 0xb354, 0xd354, 0x5354, 0xcb54, 0x4b54, 0x2b54, 0xab54,
0xcd54, 0x4d54, 0x2d54, 0xad54, 0x3554, 0xb554, 0xd554, 0x5554,
0x3332, 0xb332, 0xd332, 0x5332, 0xcb32, 0x4b32, 0x2b32, 0xab32,
0xcd32, 0x4d32, 0x2d32, 0xad32, 0x3532, 0xb532, 0xd532, 0x5532,
0xccb2, 0x4cb2, 0x2cb2, 0xacb2, 0x34b2, 0xb4b2, 0xd4b2, 0x54b2,
0x32b2, 0xb2b2, 0xd2b2, 0x52b2, 0xcab2, 0x4ab2, 0x2ab2, 0xaab2,
0xccd2, 0x4cd2, 0x2cd2, 0xacd2, 0x34d2, 0xb4d2, 0xd4d2, 0x54d2,
0x32d2, 0xb2d2, 0xd2d2, 0x52d2, 0xcad2, 0x4ad2, 0x2ad2, 0xaad2,
0x3352, 0xb352, 0xd352, 0x5352, 0xcb52, 0x4b52, 0x2b52, 0xab52,
0xcd52, 0x4d52, 0x2d52, 0xad52, 0x3552, 0xb552, 0xd552, 0x5552,
0xccca, 0x4cca, 0x2cca, 0xacca, 0x34ca, 0xb4ca, 0xd4ca, 0x54ca,
0x32ca, 0xb2ca, 0xd2ca, 0x52ca, 0xcaca, 0x4aca, 0x2aca, 0xaaca,
0x334a, 0xb34a, 0xd34a, 0x534a, 0xcb4a, 0x4b4a, 0x2b4a, 0xab4a,
0xcd4a, 0x4d4a, 0x2d4a, 0xad4a, 0x354a, 0xb54a, 0xd54a, 0x554a,
0x332a, 0xb32a, 0xd32a, 0x532a, 0xcb2a, 0x4b2a, 0x2b2a, 0xab2a,
0xcd2a, 0x4d2a, 0x2d2a, 0xad2a, 0x352a, 0xb52a, 0xd52a, 0x552a,
0xccaa, 0x4caa, 0x2caa, 0xacaa, 0x34aa, 0xb4aa, 0xd4aa, 0x54aa,
0x32aa, 0xb2aa, 0xd2aa, 0x52aa, 0xcaaa, 0x4aaa, 0x2aaa, 0xaaaa
};
AudioOutputSPDIF::AudioOutputSPDIF(int dout_pin, int port, int dma_buf_count)
{
this->portNo = port;
#if defined(ESP32)
// Configure ESP32 I2S to roughly compatible to ESP8266 peripheral
i2s_config_t i2s_config_spdif = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 88200, // 2 x sampling_rate
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32bit words
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Right than left
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
.dma_buf_count = dma_buf_count,
.dma_buf_len = DMA_BUF_SIZE_DEFAULT, // bigger buffers, reduces interrupts
.use_apll = true // Audio PLL is needed for low clock jitter
};
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_spdif, 0, NULL) != ESP_OK) {
audioLogger->println(F("ERROR: Unable to install I2S drivers"));
return;
}
i2s_zero_dma_buffer((i2s_port_t)portNo);
SetPinout(I2S_PIN_NO_CHANGE, I2S_PIN_NO_CHANGE, dout_pin);
rate_multiplier = 2; // 2x32bit words
#elif defined(ESP8266)
(void) dout_pin;
if (!I2SDriver.begin(dma_buf_count, DMA_BUF_SIZE_DEFAULT)) {
audioLogger->println(F("ERROR: Unable to start I2S driver"));
return;
}
rate_multiplier = 4; // 4x16 bit words
#endif
i2sOn = true;
mono = false;
bps = 16;
channels = 2;
frame_num = 0;
SetGain(1.0);
hertz = 0;
SetRate(44100);
}
AudioOutputSPDIF::~AudioOutputSPDIF()
{
#if defined(ESP32)
if (i2sOn) {
i2s_stop((i2s_port_t)this->portNo);
audioLogger->printf("UNINSTALL I2S\n");
i2s_driver_uninstall((i2s_port_t)this->portNo); //stop & destroy i2s driver
}
#elif defined(ESP8266)
if (i2sOn) I2SDriver.stop();
#endif
i2sOn = false;
}
bool AudioOutputSPDIF::SetPinout(int bclk, int wclk, int dout)
{
#if defined(ESP32)
i2s_pin_config_t pins = {
.bck_io_num = bclk,
.ws_io_num = wclk,
.data_out_num = dout,
.data_in_num = I2S_PIN_NO_CHANGE
};
if (i2s_set_pin((i2s_port_t)portNo, &pins) != ESP_OK) {
audioLogger->println("ERROR setting up S/PDIF I2S pins\n");
return false;
}
return true;
#else
(void) bclk;
(void) wclk;
(void) dout;
return false;
#endif
}
bool AudioOutputSPDIF::SetRate(int hz)
{
if (!i2sOn) return false;
if (hz < 32000) return false;
if (hz == this->hertz) return true;
this->hertz = hz;
int adjustedHz = AdjustI2SRate(hz);
#if defined(ESP32)
if (i2s_set_sample_rates((i2s_port_t)portNo, adjustedHz) == ESP_OK) {
if (adjustedHz == 88200) {
// Manually fix the APLL rate for 44100.
// See: https://github.com/espressif/esp-idf/issues/2634
// sdm0 = 28, sdm1 = 8, sdm2 = 5, odir = 0 -> 88199.977
rtc_clk_apll_enable(1, 28, 8, 5, 0);
}
} else {
audioLogger->println("ERROR changing S/PDIF sample rate");
}
#elif defined(ESP8266)
I2SDriver.setRate(adjustedHz);
audioLogger->printf_P(PSTR("S/PDIF rate set: %.3f\n"), I2SDriver.getActualRate()/4);
#endif
return true;
}
bool AudioOutputSPDIF::SetBitsPerSample(int bits)
{
if ( (bits != 16) && (bits != 8) ) return false;
this->bps = bits;
return true;
}
bool AudioOutputSPDIF::SetChannels(int channels)
{
if ( (channels < 1) || (channels > 2) ) return false;
this->channels = channels;
return true;
}
bool AudioOutputSPDIF::SetOutputModeMono(bool mono)
{
this->mono = mono;
// Just use the left channel for mono
if (mono) SetChannels(1);
return true;
}
bool AudioOutputSPDIF::begin()
{
return true;
}
bool AudioOutputSPDIF::ConsumeSample(int16_t sample[2])
{
if (!i2sOn) return true; // Sink the data
int16_t ms[2];
uint16_t hi, lo, aux;
uint32_t buf[4];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16(ms);
// S/PDIF encoding:
// http://www.hardwarebook.info/S/PDIF
// Original sources: Teensy Audio Library
// https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
//
// Order of bits, before BMC encoding, from the definition of SPDIF format
// PPPP AAAA SSSS SSSS SSSS SSSS SSSS VUCP
// are sent rearanged as
// VUCP PPPP AAAA 0000 SSSS SSSS SSSS SSSS
// This requires a bit less shifting as 16 sample bits align and can be
// BMC encoded with two table lookups (and at the same time flipped to LSB first).
// There is no separate word-clock, so hopefully the receiver won't notice.
uint16_t sample_left = Amplify(ms[LEFTCHANNEL]);
// BMC encode and flip left channel bits
hi = pgm_read_word(&spdif_bmclookup[(uint8_t)(sample_left >> 8)]);
lo = pgm_read_word(&spdif_bmclookup[(uint8_t)sample_left]);
// Low word is inverted depending on first bit of high word
lo ^= (~((int16_t)hi) >> 16);
buf[0] = ((uint32_t)lo << 16) | hi;
// Fixed 4 bits auxillary-audio-databits, the first used as parity
// Depending on first bit of low word, invert the bits
aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17);
// Send 'B' preamble only for the first frame of data-block
if (frame_num == 0) {
buf[1] = VUCP_PREAMBLE_B | aux;
} else {
buf[1] = VUCP_PREAMBLE_M | aux;
}
uint16_t sample_right = Amplify(ms[RIGHTCHANNEL]);
// BMC encode right channel, similar as above
hi = pgm_read_word(&spdif_bmclookup[(uint8_t)(sample_right >> 8)]);
lo = pgm_read_word(&spdif_bmclookup[(uint8_t)sample_right]);
lo ^= (~((int16_t)hi) >> 16);
buf[2] = ((uint32_t)lo << 16) | hi;
aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17);
buf[3] = VUCP_PREAMBLE_W | aux;
#if defined(ESP32)
// Assume DMA buffers are multiples of 16 bytes. Either we write all bytes or none.
uint32_t bytes_written;
esp_err_t ret = i2s_write((i2s_port_t)portNo, (const char*)&buf, 8 * channels, &bytes_written, 0);
// If we didn't write all bytes, return false early and do not increment frame_num
if ((ret != ESP_OK) || (bytes_written != (8 * channels))) return false;
#elif defined(ESP8266)
if (!I2SDriver.writeInterleaved(buf)) return false;
#endif
// Increment and rotate frame number
if (++frame_num > 191) frame_num = 0;
return true;
}
bool AudioOutputSPDIF::stop()
{
#if defined(ESP32)
i2s_zero_dma_buffer((i2s_port_t)portNo);
#elif defined(ESP8266)
I2SDriver.stop();
#endif
frame_num = 0;
return true;
}

View File

@ -0,0 +1,76 @@
/*
AudioOutputSPDIF
S/PDIF output via I2S
Needs transciever from CMOS level to either optical or coaxial interface
See: https://www.epanorama.net/documents/audio/spdif.html
Original idea and sources:
Forum thread dicussing implementation
https://forum.pjrc.com/threads/28639-S-pdif
Teensy Audio Library
https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
Adapted for ESP8266Audio
Copyright (C) 2020 Ivan Kostoski
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTSPDIF_H
#define _AUDIOOUTPUTSPDIF_H
#include "AudioOutput.h"
#if defined(ESP32)
#define SPDIF_OUT_PIN_DEFAULT 27
#define DMA_BUF_COUNT_DEFAULT 8
#define DMA_BUF_SIZE_DEFAULT 256
#elif defined(ESP8266)
#define SPDIF_OUT_PIN_DEFAULT 3
#define DMA_BUF_COUNT_DEFAULT 32
#define DMA_BUF_SIZE_DEFAULT 64
#endif
class AudioOutputSPDIF : public AudioOutput
{
public:
AudioOutputSPDIF(int dout_pin=SPDIF_OUT_PIN_DEFAULT, int port=0, int dma_buf_count = DMA_BUF_COUNT_DEFAULT);
virtual ~AudioOutputSPDIF() override;
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
const uint32_t VUCP_PREAMBLE_B = 0xCCE80000; // 11001100 11101000
const uint32_t VUCP_PREAMBLE_M = 0xCCE20000; // 11001100 11100010
const uint32_t VUCP_PREAMBLE_W = 0xCCE40000; // 11001100 11100100
protected:
virtual inline int AdjustI2SRate(int hz) { return rate_multiplier * hz; }
uint8_t portNo;
bool mono;
bool i2sOn;
uint8_t frame_num;
uint8_t rate_multiplier;
};
#endif // _AUDIOOUTPUTSPDIF_H

View File

@ -0,0 +1,113 @@
/*
AudioOutputSPIFFSWAV
Writes a WAV file to the SPIFFS filesystem
Copyright (C) 2017 Earle F. Philhower, III
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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include <FS.h>
#ifdef ESP32
#include "SPIFFS.h"
#endif
#include "AudioOutputSPIFFSWAV.h"
static const uint8_t wavHeaderTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around
0x52, 0x49, 0x46, 0x46, 0xff, 0xff, 0xff, 0xff, 0x57, 0x41, 0x56, 0x45,
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x22, 0x56, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00,
0x64, 0x61, 0x74, 0x61, 0xff, 0xff, 0xff, 0xff };
void AudioOutputSPIFFSWAV::SetFilename(const char *name)
{
if (filename) free(filename);
filename = strdup(name);
}
bool AudioOutputSPIFFSWAV::begin()
{
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
memset(wavHeader, 0, sizeof(wavHeader));
if (f) return false; // Already open!
SPIFFS.remove(filename);
f = SPIFFS.open(filename, "w+");
if (!f) return false;
// We'll fix the header up when we close the file
f.write(wavHeader, sizeof(wavHeader));
return true;
}
bool AudioOutputSPIFFSWAV::ConsumeSample(int16_t sample[2])
{
for (int i=0; i<channels; i++) {
if (bps == 8) {
uint8_t l = sample[i] & 0xff;
f.write(&l, sizeof(l));
} else {
uint8_t l = sample[i] & 0xff;
uint8_t h = (sample[i] >> 8) & 0xff;
f.write(&l, sizeof(l));
f.write(&h, sizeof(h));
}
}
return true;
}
bool AudioOutputSPIFFSWAV::stop()
{
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
memcpy_P(wavHeader, wavHeaderTemplate, sizeof(wavHeaderTemplate));
int chunksize = f.size() - 8;
wavHeader[4] = chunksize & 0xff;
wavHeader[5] = (chunksize>>8)&0xff;
wavHeader[6] = (chunksize>>16)&0xff;
wavHeader[7] = (chunksize>>24)&0xff;
wavHeader[22] = channels & 0xff;
wavHeader[23] = 0;
wavHeader[24] = hertz & 0xff;
wavHeader[25] = (hertz >> 8) & 0xff;
wavHeader[26] = (hertz >> 16) & 0xff;
wavHeader[27] = (hertz >> 24) & 0xff;
int byteRate = hertz * bps * channels / 8;
wavHeader[28] = byteRate & 0xff;
wavHeader[29] = (byteRate >> 8) & 0xff;
wavHeader[30] = (byteRate >> 16) & 0xff;
wavHeader[31] = (byteRate >> 24) & 0xff;
wavHeader[32] = channels * bps / 8;
wavHeader[33] = 0;
wavHeader[34] = bps;
wavHeader[35] = 0;
int datasize = f.size() - sizeof(wavHeader);
wavHeader[40] = datasize & 0xff;
wavHeader[41] = (datasize>>8)&0xff;
wavHeader[42] = (datasize>>16)&0xff;
wavHeader[43] = (datasize>>24)&0xff;
// Write real header out
f.seek(0, SeekSet);
f.write(wavHeader, sizeof(wavHeader));
f.close();
return true;
}

Some files were not shown because too many files have changed in this diff Show More