diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ef8a9a2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+cmake-build-*
+compile_commands.json
+build
+.idea
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..2dc2b39
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,70 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.17)
+
+PROJECT(XMIDIX VERSION 1.0 LANGUAGES CXX)
+
+SET(CMAKE_INCLUDE_CURRENT_DIR ON)
+SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+SET(CMAKE_AUTOUIC ON)
+SET(CMAKE_AUTOMOC ON)
+SET(CMAKE_AUTORCC ON)
+
+SET(CMAKE_CXX_STANDARD 20)
+SET(CMAKE_CXX_STANDARD_REQUIRED ON)
+SET(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
+
+FIND_PACKAGE(Qt5 COMPONENTS Widgets REQUIRED)
+FIND_PACKAGE(Threads REQUIRED)
+FIND_PACKAGE(spdlog REQUIRED)
+FIND_PACKAGE(fmt REQUIRED)
+
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(ALSA REQUIRED alsa)
+
+INCLUDE(cmake/Midifile.cmake)
+
+SET(PROJECT_SOURCES
+ xmidix.cc
+ xmidix_player.cc
+ xmidix_player.h
+ xmidix_file.cc
+ xmidix_file.h
+ xmidix_seq.cc
+ xmidix_seq.h
+ xmidix_config.cc
+ xmidix_config.h
+ xmidix_playlist.cc
+ xmidix_playlist.h
+ resources/resources.qrc
+ xmidix_vu.cc
+ xmidix_vu.h
+ xmidix_piano.cc
+ xmidix_piano.h
+ )
+
+ADD_EXECUTABLE(xmidix ${PROJECT_SOURCES})
+
+TARGET_INCLUDE_DIRECTORIES(xmidix PRIVATE
+ ${midifile_INCLUDE_DIRS}
+ ${ALSA_INCLUDE_DIRS}
+ )
+
+TARGET_LINK_LIBRARIES(xmidix PRIVATE
+ Qt5::Widgets
+ Threads::Threads
+ spdlog::spdlog
+ fmt::fmt
+ midifile
+ ${ALSA_LIBRARIES}
+ )
+
+INSTALL(TARGETS xmidix DESTINATION bin)
+INSTALL(FILES resources/xmidix.desktop DESTINATION share/applications)
+
+SET(CPACK_PACKAGE_CONTACT EMAILBEN145@gmail.com)
+SET(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
+SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "hardware MIDI player")
+SET(CPACK_DEBIAN_PACKAGE_SECTION sound)
+SET(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+INCLUDE(CPack)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+ .
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..268630c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+XMIDIX
+=========
+XMIDIX is a MIDI hardware device player for the X Window System not written in Rust.
+dust off the SC-55, load up some MIDIs and hear the music in stunning fidelity the way the artist intended.
+
+there are plenty of GUIs for playing MIDIs on softsynths but not too many for hardsynths.
+XMIDIX was written to fill that gap.
+
+![SCREENSHOT](./resources/shot.png)
+
+## REQUIREMENTS
+XMIDIX requires the following libraries:
+ * Qt5
+ * spdlog
+ * fmt
+ * asound2
+
+## KEYBOARD SHORTCUTS
+ * `SPACE` - play/pause
+ * `CTRL + o` - load files
+ * `CTRL + SPACE` - stop
+ * `CTRL + p` - configuration
+ * `CTRL + LEFT` - previous
+ * `CTRL + RIGHT` - next
+
+## TROUBLESHOOTING
+### THERE IS NO SOUND
+XMIDIX is a MIDI hardware device player so unless you have MIDI hardware
+hooked up to your computer, nothing will happen. if you would like to use this
+software with a softsynth, the author recommends [Fluidsynth](https://github.com/FluidSynth/fluidsynth)
+with the [Windows](https://musical-artifacts.com/artifacts/713) soundfont.
+
+### IT SOUNDS WEIRD AFTER I SEEK
+you have likely skipped over control messages which set up the instruments, tempo, etc.
+use sparingly.
+
+### I WOULD LIKE TO RECOMMEND THIS TO MY FRIENDS AND FAMILY BUT I DON’T KNOW HOW TO PRONOUNCE THE NAME
+XMIDIX must not be spoken out loud but, should you choose to go against the author's wishes,
+"X MIDI 10" or "10 MIDI 10" is acceptable.
+
+### I FOUND A BUG
+nice.
+
+### I DON’T HAVE ANYTHING TO LISTEN TO
+start [here](https://archive.org/details/FALCOM_MIDI)
+
+### DOES THIS WORK ON WINDOWS
+the software was written for Linux but some users on the forum
+have had luck with [getting it running on Windows](https://goatse.cx/).
+
+## ACKNOWLEDGEMENTS
+XMIDIX uses the [Midifile](https://github.com/craigsapp/midifile) library for SMF parsing.
diff --git a/cmake/Midifile.cmake b/cmake/Midifile.cmake
new file mode 100644
index 0000000..08a0c9c
--- /dev/null
+++ b/cmake/Midifile.cmake
@@ -0,0 +1,11 @@
+SET(midifile_INCLUDE_DIRS
+ ${PROJECT_SOURCE_DIR}/midifile/include)
+
+FILE(GLOB midifile_SOURCES
+ ${PROJECT_SOURCE_DIR}/midifile/src/*.cpp)
+
+ADD_LIBRARY(midifile STATIC
+ ${midifile_SOURCES})
+
+TARGET_INCLUDE_DIRECTORIES(midifile PRIVATE
+ ${midifile_INCLUDE_DIRS})
diff --git a/cmake/XMIDIXED.cmake b/cmake/XMIDIXED.cmake
new file mode 100644
index 0000000..696eeac
--- /dev/null
+++ b/cmake/XMIDIXED.cmake
@@ -0,0 +1,22 @@
+SET(XMIDIXED_SOURCES
+ xmidixed.cc
+ xmidix_file.cc
+ pw/midifile.c
+ )
+
+ADD_EXECUTABLE(xmidixed
+ ${XMIDIXED_SOURCES}
+ )
+
+TARGET_INCLUDE_DIRECTORIES(xmidixed PRIVATE
+ ${ALSA_INCLUDE_DIRS}
+ ${SMF_INCLUDE_DIRS}
+ )
+
+TARGET_LINK_LIBRARIES(xmidixed PRIVATE
+ Qt5::Widgets
+ spdlog::spdlog
+ fmt::fmt
+ ${ALSA_LIBRARIES}
+ ${SMF_LIBRARIES}
+ )
diff --git a/midifile/LICENSE.txt b/midifile/LICENSE.txt
new file mode 100644
index 0000000..cc3340a
--- /dev/null
+++ b/midifile/LICENSE.txt
@@ -0,0 +1,23 @@
+Copyright (c) 1999-2018, Craig Stuart Sapp
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/midifile/include/Binasc.h b/midifile/include/Binasc.h
new file mode 100644
index 0000000..6aa57f0
--- /dev/null
+++ b/midifile/include/Binasc.h
@@ -0,0 +1,159 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program.
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/include/Binasc.h
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// description: Interface to convert bytes between binary and ASCII forms.
+//
+
+#ifndef _BINASC_H_INCLUDED
+#define _BINASC_H_INCLUDED
+
+#include
+#include
+#include
+#include
+#include /* needed for MinGW */
+
+namespace smf {
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned long ulong;
+
+class Binasc {
+
+ public:
+ Binasc (void);
+
+ ~Binasc ();
+
+ // functions for setting options:
+ int setLineLength (int length);
+ int getLineLength (void);
+ int setLineBytes (int length);
+ int getLineBytes (void);
+ void setComments (int state);
+ void setCommentsOn (void);
+ void setCommentsOff (void);
+ int getComments (void);
+ void setBytes (int state);
+ void setBytesOn (void);
+ void setBytesOff (void);
+ int getBytes (void);
+ void setMidi (int state);
+ void setMidiOn (void);
+ void setMidiOff (void);
+ int getMidi (void);
+
+ // functions for converting into a binary file:
+ int writeToBinary (const std::string& outfile,
+ const std::string& infile);
+ int writeToBinary (const std::string& outfile,
+ std::istream& input);
+ int writeToBinary (std::ostream& out,
+ const std::string& infile);
+ int writeToBinary (std::ostream& out,
+ std::istream& input);
+
+ // functions for converting into an ASCII file with hex bytes:
+ int readFromBinary (const std::string&
+ outfile,
+ const std::string& infile);
+ int readFromBinary (const std::string& outfile,
+ std::istream& input);
+ int readFromBinary (std::ostream& out,
+ const std::string& infile);
+ int readFromBinary (std::ostream& out,
+ std::istream& input);
+
+ // static functions for writing ordered bytes:
+ static std::ostream& writeLittleEndianUShort (std::ostream& out,
+ ushort value);
+ static std::ostream& writeBigEndianUShort (std::ostream& out,
+ ushort value);
+ static std::ostream& writeLittleEndianShort (std::ostream& out,
+ short value);
+ static std::ostream& writeBigEndianShort (std::ostream& out,
+ short value);
+ static std::ostream& writeLittleEndianULong (std::ostream& out,
+ ulong value);
+ static std::ostream& writeBigEndianULong (std::ostream& out,
+ ulong value);
+ static std::ostream& writeLittleEndianLong (std::ostream& out,
+ long value);
+ static std::ostream& writeBigEndianLong (std::ostream& out,
+ long value);
+ static std::ostream& writeLittleEndianFloat (std::ostream& out,
+ float value);
+ static std::ostream& writeBigEndianFloat (std::ostream& out,
+ float value);
+ static std::ostream& writeLittleEndianDouble (std::ostream& out,
+ double value);
+ static std::ostream& writeBigEndianDouble (std::ostream& out,
+ double value);
+
+ static std::string keyToPitchName (int key);
+
+ protected:
+ int m_bytesQ; // option for printing hex bytes in ASCII output.
+ int m_commentsQ; // option for printing comments in ASCII output.
+ int m_midiQ; // output ASCII data as parsed MIDI file.
+ int m_maxLineLength;// number of character in ASCII output on a line.
+ int m_maxLineBytes; // number of hex bytes in ASCII output on a line.
+
+ private:
+ // helper functions for reading ASCII content to conver to binary:
+ int processLine (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processAsciiWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processStringWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processBinaryWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processDecimalWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processHexWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processVlvWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processMidiPitchBendWord(std::ostream& out,
+ const std::string& input,
+ int lineNum);
+ int processMidiTempoWord (std::ostream& out,
+ const std::string& input,
+ int lineNum);
+
+ // helper functions for reading binary content to convert to ASCII:
+ int outputStyleAscii (std::ostream& out, std::istream& input);
+ int outputStyleBinary (std::ostream& out, std::istream& input);
+ int outputStyleBoth (std::ostream& out, std::istream& input);
+ int outputStyleMidi (std::ostream& out, std::istream& input);
+
+ // MIDI parsing helper functions:
+ int readMidiEvent (std::ostream& out, std::istream& infile,
+ int& trackbytes, int& command);
+ int getVLV (std::istream& infile, int& trackbytes);
+ int getWord (std::string& word, const std::string& input,
+ const std::string& terminators, int index);
+
+};
+
+} // end of namespace smf
+
+#endif /* _BINASC_H_INCLUDED */
+
+
+
diff --git a/midifile/include/MidiEvent.h b/midifile/include/MidiEvent.h
new file mode 100644
index 0000000..39f141e
--- /dev/null
+++ b/midifile/include/MidiEvent.h
@@ -0,0 +1,71 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 21:47:39 PST 2015
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/include/MidiEvent.h
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class which stores a MidiMessage and a timestamp
+// for the MidiFile class.
+//
+
+#ifndef _MIDIEVENT_H_INCLUDED
+#define _MIDIEVENT_H_INCLUDED
+
+#include "MidiMessage.h"
+#include
+
+namespace smf {
+
+class MidiEvent : public MidiMessage {
+ public:
+ MidiEvent (void);
+ MidiEvent (int command);
+ MidiEvent (int command, int param1);
+ MidiEvent (int command, int param1, int param2);
+ MidiEvent (const MidiMessage& message);
+ MidiEvent (const MidiEvent& mfevent);
+ MidiEvent (int aTime, int aTrack,
+ std::vector& message);
+
+ ~MidiEvent ();
+
+ MidiEvent& operator= (const MidiEvent& mfevent);
+ MidiEvent& operator= (const MidiMessage& message);
+ MidiEvent& operator= (const std::vector& bytes);
+ MidiEvent& operator= (const std::vector& bytes);
+ MidiEvent& operator= (const std::vector& bytes);
+
+ void clearVariables (void);
+
+ // functions related to event linking (note-ons to note-offs).
+ void unlinkEvent (void);
+ void unlinkEvents (void);
+ void linkEvent (MidiEvent* mev);
+ void linkEvents (MidiEvent* mev);
+ void linkEvent (MidiEvent& mev);
+ void linkEvents (MidiEvent& mev);
+ int isLinked (void) const;
+ MidiEvent* getLinkedEvent (void);
+ const MidiEvent* getLinkedEvent (void) const;
+ int getTickDuration (void) const;
+ double getDurationInSeconds (void) const;
+
+ int tick; // delta or absolute MIDI ticks
+ int track; // [original] track number of event in MIDI file
+ double seconds; // calculated time in sec. (after doTimeAnalysis())
+ int seq; // sorting sequence number of event
+
+ private:
+ MidiEvent* m_eventlink; // used to match note-ons and note-offs
+
+};
+
+} // end of namespace smf
+
+#endif /* _MIDIEVENT_H_INCLUDED */
+
+
+
diff --git a/midifile/include/MidiEventList.h b/midifile/include/MidiEventList.h
new file mode 100644
index 0000000..e3bfb13
--- /dev/null
+++ b/midifile/include/MidiEventList.h
@@ -0,0 +1,80 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 21:55:38 PST 2015
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/include/MidiEventList.h
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class that stores a MidiEvents for a MidiFile track.
+//
+
+#ifndef _MIDIEVENTLIST_H_INCLUDED
+#define _MIDIEVENTLIST_H_INCLUDED
+
+#include "MidiEvent.h"
+#include
+
+namespace smf {
+
+class MidiEventList {
+ public:
+ MidiEventList (void);
+ MidiEventList (const MidiEventList& other);
+ MidiEventList (MidiEventList&& other);
+
+ ~MidiEventList ();
+
+ MidiEventList& operator= (MidiEventList& other);
+ MidiEvent& operator[] (int index);
+ const MidiEvent& operator[] (int index) const;
+
+ MidiEvent& back (void);
+ const MidiEvent& back (void) const;
+ MidiEvent& last (void);
+ const MidiEvent& last (void) const;
+ MidiEvent& getEvent (int index);
+ const MidiEvent& getEvent (int index) const;
+ void clear (void);
+ void reserve (int rsize);
+ int getEventCount (void) const;
+ int getSize (void) const;
+ int size (void) const;
+ void removeEmpties (void);
+ int linkNotePairs (void);
+ int linkEventPairs (void);
+ void clearLinks (void);
+ void clearSequence (void);
+ int markSequence (int sequence = 1);
+
+ int push (MidiEvent& event);
+ int push_back (MidiEvent& event);
+ int append (MidiEvent& event);
+
+ // careful when using these, intended for internal use in MidiFile class:
+ void detach (void);
+ int push_back_no_copy (MidiEvent* event);
+
+ // access to the list of MidiEvents for sorting with an external function:
+ MidiEvent** data (void);
+
+ protected:
+ std::vector list;
+
+ private:
+ void sort (void);
+
+ // MidiFile class calls sort()
+ friend class MidiFile;
+};
+
+
+int eventcompare(const void* a, const void* b);
+
+} // end of namespace smf
+
+#endif /* _MIDIEVENTLIST_H_INCLUDED */
+
+
+
diff --git a/midifile/include/MidiFile.h b/midifile/include/MidiFile.h
new file mode 100644
index 0000000..91c9cc6
--- /dev/null
+++ b/midifile/include/MidiFile.h
@@ -0,0 +1,322 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Fri Nov 26 14:12:01 PST 1999
+// Last Modified: Mon Jan 18 20:54:04 PST 2021 Added readSmf().
+// Filename: midifile/include/MidiFile.h
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class that can read/write Standard MIDI files.
+// MIDI data is stored by track in an array.
+//
+
+#ifndef _MIDIFILE_H_INCLUDED
+#define _MIDIFILE_H_INCLUDED
+
+#include "MidiEventList.h"
+
+#include
+#include
+#include
+#include
+
+#define TIME_STATE_DELTA 0
+#define TIME_STATE_ABSOLUTE 1
+
+#define TRACK_STATE_SPLIT 0
+#define TRACK_STATE_JOINED 1
+
+namespace smf {
+
+class _TickTime {
+ public:
+ int tick;
+ double seconds;
+};
+
+
+class MidiFile {
+ public:
+ MidiFile (void);
+ MidiFile (const std::string& filename);
+ MidiFile (std::istream& input);
+ MidiFile (const MidiFile& other);
+ MidiFile (MidiFile&& other);
+
+ ~MidiFile ();
+
+ MidiFile& operator= (const MidiFile& other);
+ MidiFile& operator= (MidiFile&& other);
+
+ // Reading/writing functions:
+
+ // Auto-detected SMF or ASCII-encoded SMF (decoded with Binasc class):
+ bool read (const std::string& filename);
+ bool read (std::istream& instream);
+ bool readBase64 (const std::string& base64data);
+ bool readBase64 (std::istream& instream);
+
+ // Only allow Standard MIDI File input:
+ bool readSmf (const std::string& filename);
+ bool readSmf (std::istream& instream);
+
+ bool write (const std::string& filename);
+ bool write (std::ostream& out);
+ bool writeBase64 (const std::string& out, int width = 0);
+ bool writeBase64 (std::ostream& out, int width = 0);
+ std::string getBase64 (int width = 0);
+ bool writeHex (const std::string& filename, int width = 25);
+ bool writeHex (std::ostream& out, int width = 25);
+ bool writeBinasc (const std::string& filename);
+ bool writeBinasc (std::ostream& out);
+ bool writeBinascWithComments (const std::string& filename);
+ bool writeBinascWithComments (std::ostream& out);
+ bool status (void) const;
+
+ // track-related functions:
+ const MidiEventList& operator[] (int aTrack) const;
+ MidiEventList& operator[] (int aTrack);
+ int getTrackCount (void) const;
+ int getNumTracks (void) const;
+ int size (void) const;
+ void removeEmpties (void);
+
+ // tick-related functions:
+ void makeDeltaTicks (void);
+ void deltaTicks (void);
+ void makeAbsoluteTicks (void);
+ void absoluteTicks (void);
+ int getTickState (void) const;
+ bool isDeltaTicks (void) const;
+ bool isAbsoluteTicks (void) const;
+
+ // join/split track functionality:
+ void joinTracks (void);
+ void splitTracks (void);
+ void splitTracksByChannel (void);
+ int getTrackState (void) const;
+ int hasJoinedTracks (void) const;
+ int hasSplitTracks (void) const;
+ int getSplitTrack (int track, int index) const;
+ int getSplitTrack (int index) const;
+
+ // track sorting funcionality:
+ void sortTrack (int track);
+ void sortTracks (void);
+ void markSequence (void);
+ void markSequence (int track, int sequence = 1);
+ void clearSequence (void);
+ void clearSequence (int track);
+
+ // track manipulation functionality:
+ int addTrack (void);
+ int addTrack (int count);
+ int addTracks (int count);
+ void deleteTrack (int aTrack);
+ void mergeTracks (int aTrack1, int aTrack2);
+ int getTrackCountAsType1 (void);
+
+ // ticks-per-quarter related functions:
+ void setMillisecondTicks (void);
+ int getTicksPerQuarterNote (void) const;
+ int getTPQ (void) const;
+ void setTicksPerQuarterNote (int ticks);
+ void setTPQ (int ticks);
+
+ // physical-time analysis functions:
+ void doTimeAnalysis (void);
+ double getTimeInSeconds (int aTrack, int anIndex);
+ double getTimeInSeconds (int tickvalue);
+ double getAbsoluteTickTime (double starttime);
+ int getFileDurationInTicks (void);
+ double getFileDurationInQuarters (void);
+ double getFileDurationInSeconds (void);
+
+ // note-analysis functions:
+ int linkNotePairs (void);
+ int linkEventPairs (void);
+ void clearLinks (void);
+
+ // filename functions:
+ void setFilename (const std::string& aname);
+ const char* getFilename (void) const;
+
+ // event functionality:
+ MidiEvent* addEvent (int aTrack, int aTick,
+ std::vector& midiData);
+ MidiEvent* addEvent (MidiEvent& mfevent);
+ MidiEvent* addEvent (int aTrack, MidiEvent& mfevent);
+ MidiEvent& getEvent (int aTrack, int anIndex);
+ const MidiEvent& getEvent (int aTrack, int anIndex) const;
+ int getEventCount (int aTrack) const;
+ int getNumEvents (int aTrack) const;
+ void allocateEvents (int track, int aSize);
+ void erase (void);
+ void clear (void);
+ void clear_no_deallocate (void);
+
+ // MIDI message adding convenience functions:
+ MidiEvent* addNoteOn (int aTrack, int aTick,
+ int aChannel, int key,
+ int vel);
+ MidiEvent* addNoteOff (int aTrack, int aTick,
+ int aChannel, int key,
+ int vel);
+ MidiEvent* addNoteOff (int aTrack, int aTick,
+ int aChannel, int key);
+ MidiEvent* addController (int aTrack, int aTick,
+ int aChannel, int num,
+ int value);
+ MidiEvent* addPatchChange (int aTrack, int aTick,
+ int aChannel, int patchnum);
+ MidiEvent* addTimbre (int aTrack, int aTick,
+ int aChannel, int patchnum);
+ MidiEvent* addPitchBend (int aTrack, int aTick,
+ int aChannel, double amount);
+
+ // RPN settings:
+ void setPitchBendRange (int aTrack, int aTick,
+ int aChannel, double range);
+
+ // Controller message adding convenience functions:
+ MidiEvent* addSustain (int aTrack, int aTick,
+ int aChannel, int value);
+ MidiEvent* addSustainPedal (int aTrack, int aTick,
+ int aChannel, int value);
+ MidiEvent* addSustainOn (int aTrack, int aTick,
+ int aChannel);
+ MidiEvent* addSustainPedalOn (int aTrack, int aTick,
+ int aChannel);
+ MidiEvent* addSustainOff (int aTrack, int aTick,
+ int aChannel);
+ MidiEvent* addSustainPedalOff (int aTrack, int aTick,
+ int aChannel);
+
+ // Meta-event adding convenience functions:
+ MidiEvent* addMetaEvent (int aTrack, int aTick,
+ int aType,
+ std::vector& metaData);
+ MidiEvent* addMetaEvent (int aTrack, int aTick,
+ int aType,
+ const std::string& metaData);
+ MidiEvent* addText (int aTrack, int aTick,
+ const std::string& text);
+ MidiEvent* addCopyright (int aTrack, int aTick,
+ const std::string& text);
+ MidiEvent* addTrackName (int aTrack, int aTick,
+ const std::string& name);
+ MidiEvent* addInstrumentName (int aTrack, int aTick,
+ const std::string& name);
+ MidiEvent* addLyric (int aTrack, int aTick,
+ const std::string& text);
+ MidiEvent* addMarker (int aTrack, int aTick,
+ const std::string& text);
+ MidiEvent* addCue (int aTrack, int aTick,
+ const std::string& text);
+ MidiEvent* addTempo (int aTrack, int aTick,
+ double aTempo);
+ MidiEvent* addTimeSignature (int aTrack, int aTick,
+ int top, int bottom,
+ int clocksPerClick = 24,
+ int num32dsPerQuarter = 8);
+ MidiEvent* addCompoundTimeSignature(int aTrack, int aTick,
+ int top, int bottom,
+ int clocksPerClick = 36,
+ int num32dsPerQuarter = 8);
+
+ uchar readByte (std::istream& input);
+
+ // static functions:
+ static ushort readLittleEndian2Bytes (std::istream& input);
+ static ulong readLittleEndian4Bytes (std::istream& input);
+ static std::ostream& writeLittleEndianUShort (std::ostream& out,
+ ushort value);
+ static std::ostream& writeBigEndianUShort (std::ostream& out,
+ ushort value);
+ static std::ostream& writeLittleEndianShort (std::ostream& out,
+ short value);
+ static std::ostream& writeBigEndianShort (std::ostream& out,
+ short value);
+ static std::ostream& writeLittleEndianULong (std::ostream& out,
+ ulong value);
+ static std::ostream& writeBigEndianULong (std::ostream& out,
+ ulong value);
+ static std::ostream& writeLittleEndianLong (std::ostream& out,
+ long value);
+ static std::ostream& writeBigEndianLong (std::ostream& out,
+ long value);
+ static std::ostream& writeLittleEndianFloat (std::ostream& out,
+ float value);
+ static std::ostream& writeBigEndianFloat (std::ostream& out,
+ float value);
+ static std::ostream& writeLittleEndianDouble (std::ostream& out,
+ double value);
+ static std::ostream& writeBigEndianDouble (std::ostream& out,
+ double value);
+
+ protected:
+ // m_events == Lists of MidiEvents for each MIDI file track.
+ std::vector m_events;
+
+ // m_ticksPerQuarterNote == A value for the MIDI file header
+ // which represents the number of ticks in a quarter note
+ // that are used as units for the delta times for MIDI events
+ // in MIDI file track data.
+ int m_ticksPerQuarterNote = 120;
+
+ // m_theTrackState == state variable for whether the tracks
+ // are joined or split.
+ int m_theTrackState = TRACK_STATE_SPLIT;
+
+ // m_theTimeState == state variable for whether the MidiEvent::tick
+ // variable contain absolute ticks since the start of the file's
+ // time, or delta ticks since the last MIDI event in the track.
+ int m_theTimeState = TIME_STATE_ABSOLUTE;
+
+ // m_readFileName == the filename of the last file read into
+ // the object.
+ std::string m_readFileName;
+
+ // m_timemapvalid ==
+ bool m_timemapvalid = false;
+
+ // m_timemap ==
+ std::vector<_TickTime> m_timemap;
+
+ // m_rwstatus == True if last read was successful, false if a problem.
+ bool m_rwstatus = true;
+
+ // m_linkedEventQ == True if link analysis has been done.
+ bool m_linkedEventsQ = false;
+
+ private:
+ int extractMidiData (std::istream& inputfile,
+ std::vector& array,
+ uchar& runningCommand);
+ ulong readVLValue (std::istream& inputfile);
+ ulong unpackVLV (uchar a = 0, uchar b = 0,
+ uchar c = 0, uchar d = 0,
+ uchar e = 0);
+ void writeVLValue (long aValue,
+ std::vector& data);
+ int makeVLV (uchar *buffer, int number);
+ static int ticksearch (const void* A, const void* B);
+ static int secondsearch (const void* A, const void* B);
+ void buildTimeMap (void);
+ double linearTickInterpolationAtSecond (double seconds);
+ double linearSecondInterpolationAtTick (int ticktime);
+ std::string base64Encode (const std::string &input);
+ std::string base64Decode (const std::string &input);
+ static const std::string encodeLookup;
+ static const std::vector decodeLookup;
+};
+
+} // end of namespace smf
+
+std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile);
+
+#endif /* _MIDIFILE_H_INCLUDED */
+
+
+
diff --git a/midifile/include/MidiMessage.h b/midifile/include/MidiMessage.h
new file mode 100644
index 0000000..56e1548
--- /dev/null
+++ b/midifile/include/MidiMessage.h
@@ -0,0 +1,176 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 20:36:32 PST 2015
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/include/MidiMessage.h
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: Storage for bytes of a MIDI message for use in MidiFile
+// class.
+//
+
+#ifndef _MIDIMESSAGE_H_INCLUDED
+#define _MIDIMESSAGE_H_INCLUDED
+
+#include
+#include
+
+namespace smf {
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned long ulong;
+
+class MidiMessage : public std::vector {
+
+ public:
+ MidiMessage (void);
+ MidiMessage (int command);
+ MidiMessage (int command, int p1);
+ MidiMessage (int command, int p1, int p2);
+ MidiMessage (const MidiMessage& message);
+ MidiMessage (const std::vector& message);
+ MidiMessage (const std::vector& message);
+ MidiMessage (const std::vector& message);
+
+ ~MidiMessage ();
+
+ MidiMessage& operator= (const MidiMessage& message);
+ MidiMessage& operator= (const std::vector& bytes);
+ MidiMessage& operator= (const std::vector& bytes);
+ MidiMessage& operator= (const std::vector& bytes);
+
+ void sortTrack (void);
+ void sortTrackWithSequence(void);
+
+ // data access convenience functions (returns -1 if not present):
+ int getP0 (void) const;
+ int getP1 (void) const;
+ int getP2 (void) const;
+ int getP3 (void) const;
+ void setP0 (int value);
+ void setP1 (int value);
+ void setP2 (int value);
+ void setP3 (int value);
+
+ int getSize (void) const;
+ void setSize (int asize);
+ int setSizeToCommand (void);
+ int resizeToCommand (void);
+
+ // note-message convenience functions:
+ int getKeyNumber (void) const;
+ int getVelocity (void) const;
+ void setKeyNumber (int value);
+ void setVelocity (int value);
+ void setSpelling (int base7, int accidental);
+ void getSpelling (int& base7, int& accidental);
+
+ // controller-message convenience functions:
+ int getControllerNumber (void) const;
+ int getControllerValue (void) const;
+
+ int getCommandNibble (void) const;
+ int getCommandByte (void) const;
+ int getChannelNibble (void) const;
+ int getChannel (void) const;
+
+ void setCommandByte (int value);
+ void setCommand (int value);
+ void setCommand (int value, int p1);
+ void setCommand (int value, int p1, int p2);
+ void setCommandNibble (int value);
+ void setChannelNibble (int value);
+ void setChannel (int value);
+ void setParameters (int p1, int p2);
+ void setParameters (int p1);
+ void setMessage (const std::vector& message);
+ void setMessage (const std::vector& message);
+ void setMessage (const std::vector& message);
+
+ // message-type convenience functions:
+ bool isMetaMessage (void) const;
+ bool isMeta (void) const;
+ bool isNote (void) const;
+ bool isNoteOff (void) const;
+ bool isNoteOn (void) const;
+ bool isAftertouch (void) const;
+ bool isController (void) const;
+ bool isSustain (void) const; // controller 64
+ bool isSustainOn (void) const;
+ bool isSustainOff (void) const;
+ bool isSoft (void) const; // controller 67
+ bool isSoftOn (void) const;
+ bool isSoftOff (void) const;
+ bool isPatchChange (void) const;
+ bool isTimbre (void) const;
+ bool isPressure (void) const;
+ bool isPitchbend (void) const;
+ bool isEmpty (void) const; // see MidiFile::removeEmpties()
+
+ // helper functions to create various MidiMessages:
+ void makeNoteOn (int channel, int key, int velocity);
+ void makeNoteOff (int channel, int key, int velocity);
+ void makeNoteOff (int channel, int key);
+ void makeNoteOff (void);
+ void makePatchChange (int channel, int patchnum);
+ void makeTimbre (int channel, int patchnum);
+ void makeController (int channel, int num, int value);
+
+ // helper functions to create various continuous controller messages:
+ void makeSustain (int channel, int value);
+ void makeSustainPedal (int channel, int value);
+ void makeSustainOn (int channel);
+ void makeSustainPedalOn (int channel);
+ void makeSustainOff (int channel);
+ void makeSustainPedalOff (int channel);
+
+ // meta-message creation and helper functions:
+ void makeMetaMessage (int mnum, const std::string& data);
+ void makeText (const std::string& name);
+ void makeCopyright (const std::string& text);
+ void makeTrackName (const std::string& name);
+ void makeInstrumentName (const std::string& name);
+ void makeLyric (const std::string& text);
+ void makeMarker (const std::string& text);
+ void makeCue (const std::string& text);
+ void makeTimeSignature (int top, int bottom,
+ int clocksPerClick = 24,
+ int num32dsPerQuarter = 8);
+
+ void makeTempo (double tempo) { setTempo(tempo); }
+ int getTempoMicro (void) const;
+ int getTempoMicroseconds (void) const;
+ double getTempoSeconds (void) const;
+ double getTempoBPM (void) const;
+ double getTempoTPS (int tpq) const;
+ double getTempoSPT (int tpq) const;
+
+ int getMetaType (void) const;
+ bool isText (void) const;
+ bool isCopyright (void) const;
+ bool isTrackName (void) const;
+ bool isInstrumentName (void) const;
+ bool isLyricText (void) const;
+ bool isMarkerText (void) const;
+ bool isTempo (void) const;
+ bool isTimeSignature (void) const;
+ bool isKeySignature (void) const;
+ bool isEndOfTrack (void) const;
+
+ std::string getMetaContent (void);
+ void setMetaContent (const std::string& content);
+ void setTempo (double tempo);
+ void setTempoMicroseconds (int microseconds);
+ void setMetaTempo (double tempo);
+
+};
+
+} // end of namespace smf
+
+#endif /* _MIDIMESSAGE_H_INCLUDED */
+
+
+
diff --git a/midifile/include/Options.h b/midifile/include/Options.h
new file mode 100644
index 0000000..a3d2e3d
--- /dev/null
+++ b/midifile/include/Options.h
@@ -0,0 +1,156 @@
+//
+// Copyright 1998-2018 by Craig Stuart Sapp, All Rights Reserved.
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sun Apr 5 13:07:18 PDT 1998
+// Last Modified: Mon Jan 18 18:25:23 PST 2021 Some cleanup
+// Filename: midifile/include/Options.h
+// Web Address: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: Interface for command-line options.
+//
+
+#ifndef _OPTIONS_H_INCLUDED
+#define _OPTIONS_H_INCLUDED
+
+#include
+#include
+#include
+#include
+
+#define OPTION_TYPE_unknown '\0'
+#define OPTION_TYPE_boolean 'b'
+#define OPTION_TYPE_char 'c'
+#define OPTION_TYPE_double 'd'
+#define OPTION_TYPE_float 'f'
+#define OPTION_TYPE_int 'i'
+#define OPTION_TYPE_string 's'
+
+namespace smf {
+
+class Option_register {
+ public:
+ Option_register (void);
+ Option_register (const std::string& aDefinition,
+ char aType,
+ const std::string& aDefaultOption);
+ Option_register (const std::string& aDefinition,
+ char aType,
+ const std::string& aDefaultOption,
+ const std::string& aModifiedOption);
+
+ ~Option_register ();
+
+ void clearModified (void);
+ const std::string& getDefinition (void);
+ const std::string& getDefault (void);
+ const std::string& getOption (void);
+ const std::string& getModified (void);
+ const std::string& getDescription (void);
+ bool isModified (void);
+ char getType (void);
+ void reset (void);
+ void setDefault (const std::string& aString);
+ void setDefinition (const std::string& aString);
+ void setDescription (const std::string& aString);
+ void setModified (const std::string& aString);
+ void setType (char aType);
+ std::ostream& print (std::ostream& out);
+
+ protected:
+ std::string m_definition;
+ std::string m_description;
+ std::string m_defaultOption;
+ std::string m_modifiedOption;
+ bool m_modifiedQ;
+ char m_type;
+
+};
+
+
+
+class Options {
+ public:
+ Options (void);
+ Options (int argc, char** argv);
+
+ ~Options ();
+
+ int argc (void) const;
+ const std::vector& argv (void) const;
+ int define (const std::string& aDefinition);
+ int define (const std::string& aDefinition,
+ const std::string& description);
+ const std::string& getArg (int index);
+ const std::string& getArgument (int index);
+ int getArgCount (void);
+ int getArgumentCount (void);
+ const std::vector& getArgList (void);
+ const std::vector& getArgumentList (void);
+ bool getBoolean (const std::string& optionName);
+ std::string getCommand (void);
+ const std::string& getCommandLine (void);
+ std::string getDefinition (const std::string& optionName);
+ double getDouble (const std::string& optionName);
+ char getFlag (void);
+ char getChar (const std::string& optionName);
+ float getFloat (const std::string& optionName);
+ int getInt (const std::string& optionName);
+ int getInteger (const std::string& optionName);
+ std::string getString (const std::string& optionName);
+ char getType (const std::string& optionName);
+ int optionsArg (void);
+ std::ostream& print (std::ostream& out);
+ std::ostream& printOptionList (std::ostream& out);
+ std::ostream& printOptionListBooleanState(std::ostream& out);
+ void process (int error_check = 1, int suppress = 0);
+ void process (int argc, char** argv, int error_check = 1,
+ int suppress = 0);
+ void reset (void);
+ void xverify (int argc, char** argv,
+ int error_check = 1,
+ int suppress = 0);
+ void xverify (int error_check = 1, int suppress = 0);
+ void setFlag (char aFlag);
+ void setModified (const std::string& optionName,
+ const std::string& optionValue);
+ void setOptions (int argc, char** argv);
+ void appendOptions (int argc, char** argv);
+ void appendOptions (const std::string& strang);
+ void appendOptions (const std::vector& argv);
+ std::ostream& printRegister (std::ostream& out);
+ bool isDefined (const std::string& name);
+
+ protected:
+ int m_options_error_check; // verify command
+ int m_oargc;
+ std::vector m_oargv;
+ std::string m_commandString;
+ char m_optionFlag;
+ std::vector m_argument;
+
+ std::vector m_optionRegister;
+ std::map m_optionList;
+
+ bool m_processedQ;
+ bool m_suppressQ; // prevent --options
+ bool m_optionsArgument; // --options present
+
+ std::vector m_extraArgv;
+ std::vector m_extraArgv_strings;
+
+ private:
+ int getRegIndex (const std::string& optionName);
+ int optionQ (const std::string& aString, int& argp);
+ int storeOption (int gargp, int& position, int& running);
+
+};
+
+} // end of namespace smf
+
+
+#endif /* _OPTIONS_H_INCLUDED */
+
+
+
diff --git a/midifile/src/Binasc.cpp b/midifile/src/Binasc.cpp
new file mode 100644
index 0000000..d74457a
--- /dev/null
+++ b/midifile/src/Binasc.cpp
@@ -0,0 +1,1969 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program.
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/src/Binasc.cpp
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// description: Interface to convert bytes between binary and ASCII forms.
+//
+
+#include "Binasc.h"
+
+#include
+#include
+
+
+namespace smf {
+
+//////////////////////////////
+//
+// Binasc::Binasc -- Constructor: set the default option values.
+//
+
+Binasc::Binasc(void) {
+ m_bytesQ = 1; // for printing HEX bytes when converting to ASCII
+ m_commentsQ = 0; // for printing text comments when converting to ASCII
+ m_midiQ = 0; // for printing ASCII as parsed MIDI file.
+ m_maxLineLength = 75;
+ m_maxLineBytes = 25;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::~Binasc -- Destructor.
+//
+
+Binasc::~Binasc() {
+ // do nothing
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::setLineLength -- Set the maximum length of a line when converting
+// binary content into ASCII bytes. If the input size is less than one,
+// set to the default value of 75 characters per line.
+//
+
+int Binasc::setLineLength(int length) {
+ if (length < 1) {
+ m_maxLineLength = 75;
+ } else {
+ m_maxLineLength = length;
+ }
+ return m_maxLineLength;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::getLineLength -- Set the maximum length of a line when converting
+// binary content into ASCII bytes.
+//
+
+int Binasc::getLineLength(void) {
+ return m_maxLineLength;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::setLineBytes -- Set the maximum number of hex bytes in ASCII output.
+// If the input size is less than one, set to the default value of 25
+// hex bytes per line.
+//
+
+int Binasc::setLineBytes(int length) {
+ if (length < 1) {
+ m_maxLineBytes = 25;
+ } else {
+ m_maxLineBytes = length;
+ }
+ return m_maxLineBytes;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::getLineBytes -- Get the maximum number of hex bytes in ASCII output.
+//
+
+int Binasc::getLineBytes(void) {
+ return m_maxLineLength;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::setComments -- Display or not display printable characters
+// as comments when converting binary files to ASCII byte codes.
+//
+
+void Binasc::setComments(int state) {
+ m_commentsQ = state ? 1 : 0;
+}
+
+
+void Binasc::setCommentsOn(void) {
+ setComments(true);
+}
+
+
+void Binasc::setCommentsOff(void) {
+ setComments(false);
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::getComments -- Get the comment display style for
+// showing comments in ASCII output;
+//
+
+int Binasc::getComments(void) {
+ return m_commentsQ;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::setBytes -- Display or not display hex codes (only
+// print ASCII printable characters).
+//
+
+void Binasc::setBytes(int state) {
+ m_bytesQ = state ? 1 : 0;
+}
+
+
+void Binasc::setBytesOn(void) {
+ setBytes(true);
+}
+
+
+void Binasc::setBytesOff(void) {
+ setBytes(false);
+}
+
+
+//////////////////////////////
+//
+// Binasc::getBytes -- Get hex byte display status.
+//
+
+int Binasc::getBytes(void) {
+ return m_bytesQ;
+}
+
+
+//////////////////////////////
+//
+// Binasc::setMidi -- Display or not display parsed MIDI data.
+//
+
+void Binasc::setMidi(int state) {
+ m_midiQ = state ? 1 : 0;
+}
+
+
+void Binasc::setMidiOn(void) {
+ setMidi(true);
+}
+
+
+void Binasc::setMidiOff(void) {
+ setMidi(false);
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::getMidi -- Get the MIDI file printing style option state.
+//
+
+int Binasc::getMidi(void) {
+ return m_midiQ;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeToBinary -- Convert an ASCII representation of bytes into
+// the binary file that it describes. Returns 0 if there was a problem
+// otherwise returns 1.
+//
+
+int Binasc::writeToBinary(const std::string& outfile,
+ const std::string& infile) {
+ std::ifstream input;
+ input.open(infile.c_str());
+ if (!input.is_open()) {
+ std::cerr << "Cannot open " << infile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ std::ofstream output;
+ output.open(outfile.c_str());
+ if (!output.is_open()) {
+ std::cerr << "Cannot open " << outfile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = writeToBinary(output, input);
+ input.close();
+ output.close();
+ return status;
+}
+
+
+int Binasc::writeToBinary(const std::string& outfile, std::istream& input) {
+ std::ofstream output;
+ output.open(outfile.c_str());
+ if (!output.is_open()) {
+ std::cerr << "Cannot open " << outfile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = writeToBinary(output, input);
+ output.close();
+ return status;
+}
+
+
+int Binasc::writeToBinary(std::ostream& out, const std::string& infile) {
+ std::ifstream input;
+ input.open(infile.c_str());
+ if (!input.is_open()) {
+ std::cerr << "Cannot open " << infile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = writeToBinary(out, input);
+ input.close();
+ return status;
+}
+
+
+int Binasc::writeToBinary(std::ostream& out, std::istream& input) {
+ std::string inputLine;
+ inputLine.reserve(8196);
+ int lineNum = 0; // current line number
+ getline(input, inputLine, '\n');
+ lineNum++;
+ while (!input.eof()) {
+ int status = processLine(out, inputLine, lineNum);
+ if (!status) {
+ return 0;
+ }
+ getline(input, inputLine, '\n');
+ lineNum++;
+ }
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::readFromBinary -- convert an ASCII representation of bytes into
+// the binary file that it describes.
+//
+
+int Binasc::readFromBinary(const std::string& outfile, const std::string& infile) {
+ std::ifstream input;
+ input.open(infile.c_str());
+ if (!input.is_open()) {
+ std::cerr << "Cannot open " << infile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ std::ofstream output;
+ output.open(outfile.c_str());
+ if (!output.is_open()) {
+ std::cerr << "Cannot open " << outfile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = readFromBinary(output, input);
+ input.close();
+ output.close();
+ return status;
+}
+
+
+int Binasc::readFromBinary(const std::string& outfile, std::istream& input) {
+ std::ofstream output;
+ output.open(outfile.c_str());
+ if (!output.is_open()) {
+ std::cerr << "Cannot open " << outfile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = readFromBinary(output, input);
+ output.close();
+ return status;
+}
+
+
+int Binasc::readFromBinary(std::ostream& out, const std::string& infile) {
+ std::ifstream input;
+ input.open(infile.c_str());
+ if (!input.is_open()) {
+ std::cerr << "Cannot open " << infile
+ << " for reading in binasc." << std::endl;
+ return 0;
+ }
+
+ int status = readFromBinary(out, input);
+ input.close();
+ return status;
+}
+
+
+int Binasc::readFromBinary(std::ostream& out, std::istream& input) {
+ int status;
+ if (m_midiQ) {
+ status = outputStyleMidi(out, input);
+ } else if (!m_bytesQ) {
+ status = outputStyleAscii(out, input);
+ } else if (m_bytesQ && m_commentsQ) {
+ status = outputStyleBoth(out, input);
+ } else {
+ status = outputStyleBinary(out, input);
+ }
+ return status;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// protected functions --
+//
+
+//////////////////////////////
+//
+// Binasc::outputStyleAscii -- read an input file and output bytes in ascii
+// form, not displaying any blank lines. Output words are not
+// broken unless they are longer than 75 characters.
+//
+
+int Binasc::outputStyleAscii(std::ostream& out, std::istream& input) {
+ uchar outputWord[256] = {0}; // storage for current word
+ int index = 0; // current length of word
+ int lineCount = 0; // current length of line
+ int type = 0; // 0=space, 1=printable
+ uchar ch; // current input byte
+
+ ch = static_cast(input.get());
+ while (!input.eof()) {
+ int lastType = type;
+ type = (isprint(ch) && !isspace(ch)) ? 1 : 0;
+
+ if ((type == 1) && (lastType == 0)) {
+ // start of a new word. check where to put old word
+ if (index + lineCount >= m_maxLineLength) { // put on next line
+ outputWord[index] = '\0';
+ out << '\n' << outputWord;
+ lineCount = index;
+ index = 0;
+ } else { // put on current line
+ outputWord[index] = '\0';
+ if (lineCount != 0) {
+ out << ' ';
+ lineCount++;
+ }
+ out << outputWord;
+ lineCount += index;
+ index = 0;
+ }
+ }
+ if (type == 1) {
+ outputWord[index++] = ch;
+ }
+ ch = static_cast(input.get());
+ }
+
+ if (index != 0) {
+ out << std::endl;
+ }
+
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::outputStyleBinary -- read an input binary file and output bytes
+// in ascii form, hexadecimal numbers only.
+//
+
+int Binasc::outputStyleBinary(std::ostream& out, std::istream& input) {
+ int currentByte = 0; // current byte output in line
+ uchar ch; // current input byte
+
+ ch = static_cast(input.get());
+ if (input.eof()) {
+ std::cerr << "End of the file right away!" << std::endl;
+ return 0;
+ }
+
+ while (!input.eof()) {
+ if (ch < 0x10) {
+ out << '0';
+ }
+ out << std::hex << (int)ch << ' ';
+ currentByte++;
+ if (currentByte >= m_maxLineBytes) {
+ out << '\n';
+ currentByte = 0;
+ }
+ ch = static_cast(input.get());
+ }
+
+ if (currentByte != 0) {
+ out << std::endl;
+ }
+
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::outputStyleBoth -- read an input file and output bytes in ASCII
+// form with both hexadecimal numbers and ascii representation
+//
+
+int Binasc::outputStyleBoth(std::ostream& out, std::istream& input) {
+ uchar asciiLine[256] = {0}; // storage for output line
+ int currentByte = 0; // current byte output in line
+ int index = 0; // current character in asciiLine
+ uchar ch; // current input byte
+
+ ch = static_cast(input.get());
+ while (!input.eof()) {
+ if (index == 0) {
+ asciiLine[index++] = ';';
+ out << ' ';
+ }
+ if (ch < 0x10) {
+ out << '0';
+ }
+ out << std::hex << (int)ch << ' ';
+ currentByte++;
+
+ asciiLine[index++] = ' ';
+ if (isprint(ch)) {
+ asciiLine[index++] = ch;
+ } else {
+ asciiLine[index++] = ' ';
+ }
+ asciiLine[index++] = ' ';
+
+ if (currentByte >= m_maxLineBytes) {
+ out << '\n';
+ asciiLine[index] = '\0';
+ out << asciiLine << "\n\n";
+ currentByte = 0;
+ index = 0;
+ }
+ ch = static_cast(input.get());
+ }
+
+ if (currentByte != 0) {
+ out << '\n';
+ asciiLine[index] = '\0';
+ out << asciiLine << '\n' << std::endl;
+ }
+
+ return 1;
+}
+
+
+
+///////////////////////////////
+//
+// Binasc::processLine -- Read a line of input and output any specified bytes.
+//
+
+int Binasc::processLine(std::ostream& out, const std::string& input,
+ int lineCount) {
+ int status = 1;
+ int i = 0;
+ int length = (int)input.size();
+ std::string word;
+ while (i 2)) {
+ status = processBinaryWord(out, word, lineCount);
+ } else {
+ status = processHexWord(out, word, lineCount);
+ }
+ }
+
+ if (status == 0) {
+ return 0;
+ }
+
+ }
+
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::getWord -- extract a sub string, stopping at any of the given
+// terminator characters.
+//
+
+int Binasc::getWord(std::string& word, const std::string& input,
+ const std::string& terminators, int index) {
+ word.resize(0);
+ int i = index;
+ int escape = 0;
+ int ecount = 0;
+ if (terminators.find('"') != std::string::npos) {
+ escape = 1;
+ }
+ while (i < (int)input.size()) {
+ if (escape && input[i] == '\"') {
+ ecount++;
+ i++;
+ if (ecount >= 2) {
+ break;
+ }
+ }
+ if (escape && (i<(int)input.size()-1) && (input[i] == '\\')
+ && (input[i+1] == '"')) {
+ word.push_back(input[i+1]);
+ i += 2;
+ } else if (terminators.find(input[i]) == std::string::npos) {
+ word.push_back(input[i]);
+ i++;
+ } else {
+ i++;
+ return i;
+ }
+ }
+ return i;
+}
+
+
+
+///////////////////////////////
+//
+// Binasc::getVLV -- read a Variable-Length Value from the file
+//
+
+int Binasc::getVLV(std::istream& infile, int& trackbytes) {
+ int output = 0;
+ uchar ch = 0;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ output = (output << 7) | (0x7f & ch);
+ while (ch >= 0x80) {
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ output = (output << 7) | (0x7f & ch);
+ }
+ return output;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::readMidiEvent -- Read a delta time and then a MIDI message
+// (or meta message). Returns 1 if not end-of-track meta message;
+// 0 otherwise.
+//
+
+int Binasc::readMidiEvent(std::ostream& out, std::istream& infile,
+ int& trackbytes, int& command) {
+
+ // Read and print Variable Length Value for delta ticks
+ int vlv = getVLV(infile, trackbytes);
+
+ std::stringstream output;
+
+ output << "v" << std::dec << vlv << "\t";
+
+ std::string comment;
+
+ int status = 1;
+ uchar ch = 0;
+ char byte1, byte2;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ if (ch < 0x80) {
+ // running status: command byte is previous one in data stream
+ output << " ";
+ } else {
+ // midi command byte
+ output << std::hex << (int)ch;
+ command = ch;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ }
+ byte1 = ch;
+ switch (command & 0xf0) {
+ case 0x80: // note-off: 2 bytes
+ output << " '" << std::dec << (int)byte1;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ byte2 = ch;
+ output << " '" << std::dec << (int)byte2;
+ if (m_commentsQ) {
+ comment += "note-off " + keyToPitchName(byte1);
+ }
+ break;
+ case 0x90: // note-on: 2 bytes
+ output << " '" << std::dec << (int)byte1;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ byte2 = ch;
+ output << " '" << std::dec << (int)byte2;
+ if (m_commentsQ) {
+ if (byte2 == 0) {
+ comment += "note-off " + keyToPitchName(byte1);
+ } else {
+ comment += "note-on " + keyToPitchName(byte1);
+ }
+ }
+ break;
+ case 0xA0: // aftertouch: 2 bytes
+ output << " '" << std::dec << (int)byte1;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ byte2 = ch;
+ output << " '" << std::dec << (int)byte2;
+ if (m_commentsQ) {
+ comment += "after-touch";
+ }
+ break;
+ case 0xB0: // continuous controller: 2 bytes
+ output << " '" << std::dec << (int)byte1;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ byte2 = ch;
+ output << " '" << std::dec << (int)byte2;
+ if (m_commentsQ) {
+ comment += "controller";
+ }
+ break;
+ case 0xE0: // pitch-bend: 2 bytes
+ output << " '" << std::dec << (int)byte1;
+ infile.read((char*)&ch, 1);
+ trackbytes++;
+ byte2 = ch;
+ output << " '" << std::dec << (int)byte2;
+ if (m_commentsQ) {
+ comment += "pitch-bend";
+ }
+ break;
+ case 0xC0: // patch change: 1 bytes
+ output << " '" << std::dec << (int)byte1;
+ if (m_commentsQ) {
+ output << "\t";
+ comment += "patch-change";
+ }
+ break;
+ case 0xD0: // channel pressure: 1 bytes
+ output << " '" << std::dec << (int)byte1;
+ if (m_commentsQ) {
+ comment += "channel pressure";
+ }
+ break;
+ case 0xF0: // various system bytes: variable bytes
+ switch (command) {
+ case 0xf0:
+ break;
+ case 0xf7:
+ // Read the first byte which is either 0xf0 or 0xf7.
+ // Then a VLV byte count for the number of bytes
+ // that remain in the message will follow.
+ // Then read that number of bytes.
+ {
+ infile.putback(byte1);
+ trackbytes--;
+ int length = getVLV(infile, trackbytes);
+ output << " v" << std::dec << length;
+ for (int i=0; i 0) {
+ tempout << "\t\t\t; unknown header bytes";
+ tempout << std::endl;
+ }
+
+ for (i=0; i 127 || tempLong < -128) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "Decimal number out of range from -128 to 127" << std::endl;
+ return 0;
+ }
+ char charOutput = (char)tempLong;
+ out << charOutput;
+ return 1;
+ } else {
+ ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]);
+ uchar ucharOutput = (uchar)tempLong;
+ if (tempLong > 255) { // || (tempLong < 0)) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "Decimal number out of range from 0 to 255" << std::endl;
+ return 0;
+ }
+ out << ucharOutput;
+ return 1;
+ }
+ }
+
+ // left with an integer number with a specified number of bytes
+ switch (byteCount) {
+ case 1:
+ if (signIndex != -1) {
+ long tempLong = atoi(&word[quoteIndex + 1]);
+ char charOutput = (char)tempLong;
+ out << charOutput;
+ return 1;
+ } else {
+ ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]);
+ uchar ucharOutput = (uchar)tempLong;
+ out << ucharOutput;
+ return 1;
+ }
+ break;
+ case 2:
+ if (signIndex != -1) {
+ long tempLong = atoi(&word[quoteIndex + 1]);
+ short shortOutput = (short)tempLong;
+ if (endianIndex == -1) {
+ writeBigEndianShort(out, shortOutput);
+ } else {
+ writeLittleEndianShort(out, shortOutput);
+ }
+ return 1;
+ } else {
+ ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]);
+ ushort ushortOutput = (ushort)tempLong;
+ if (endianIndex == -1) {
+ writeBigEndianUShort(out, ushortOutput);
+ } else {
+ writeLittleEndianUShort(out, ushortOutput);
+ }
+ return 1;
+ }
+ break;
+ case 3:
+ {
+ if (signIndex != -1) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "negative decimal numbers cannot be stored in 3 bytes"
+ << std::endl;
+ return 0;
+ }
+ ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]);
+ uchar byte1 = (uchar)((tempLong & 0x00ff0000) >> 16);
+ uchar byte2 = (uchar)((tempLong & 0x0000ff00) >> 8);
+ uchar byte3 = (uchar)((tempLong & 0x000000ff));
+ if (endianIndex == -1) {
+ out << byte1;
+ out << byte2;
+ out << byte3;
+ } else {
+ out << byte3;
+ out << byte2;
+ out << byte1;
+ }
+ return 1;
+ }
+ break;
+ case 4:
+ if (signIndex != -1) {
+ long tempLong = atoi(&word[quoteIndex + 1]);
+ if (endianIndex == -1) {
+ writeBigEndianLong(out, tempLong);
+ } else {
+ writeLittleEndianLong(out, tempLong);
+ }
+ return 1;
+ } else {
+ ulong tempuLong = (ulong)atoi(&word[quoteIndex + 1]);
+ if (endianIndex == -1) {
+ writeBigEndianULong(out, tempuLong);
+ } else {
+ writeLittleEndianULong(out, tempuLong);
+ }
+ return 1;
+ }
+ break;
+ default:
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "invalid byte count specification for decimal number" << std::endl;
+ return 0;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::processHexWord -- interprets a hexadecimal word and converts into
+// its binary byte form.
+//
+
+int Binasc::processHexWord(std::ostream& out, const std::string& word,
+ int lineNum) {
+ int length = (int)word.size();
+ uchar outputByte;
+
+ if (length > 2) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl;
+ std::cerr << "Size of hexadecimal number is too large. Max is ff." << std::endl;
+ return 0;
+ }
+
+ if (!isxdigit(word[0]) || (length == 2 && !isxdigit(word[1]))) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl;
+ std::cerr << "Invalid character in hexadecimal number." << std::endl;
+ return 0;
+ }
+
+ outputByte = (uchar)strtol(word.c_str(), (char**)NULL, 16);
+ out << outputByte;
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::processStringWord -- interprets a binary word into
+// its constituent byte
+//
+
+int Binasc::processStringWord(std::ostream& out, const std::string& word,
+ int /* lineNum */) {
+ out << word;
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::processAsciiWord -- interprets a binary word into
+// its constituent byte
+//
+
+int Binasc::processAsciiWord(std::ostream& out, const std::string& word,
+ int lineNum) {
+ int length = (int)word.size();
+ uchar outputByte;
+
+ if (word[0] != '+') {
+ std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl;
+ std::cerr << "character byte must start with \'+\' sign: " << std::endl;
+ return 0;
+ }
+
+ if (length > 2) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl;
+ std::cerr << "character byte word is too long -- specify only one character"
+ << std::endl;
+ return 0;
+ }
+
+ if (length == 2) {
+ outputByte = (uchar)word[1];
+ } else {
+ outputByte = ' ';
+ }
+ out << outputByte;
+ return 1;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::processBinaryWord -- interprets a binary word into
+// its constituent byte
+//
+
+int Binasc::processBinaryWord(std::ostream& out, const std::string& word,
+ int lineNum) {
+ int length = (int)word.size(); // length of ascii binary number
+ int commaIndex = -1; // index location of comma in number
+ int leftDigits = -1; // number of digits to left of comma
+ int rightDigits = -1; // number of digits to right of comma
+ int i = 0;
+
+ // make sure that all characters are valid
+ for (i=0; i 8) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "too many digits in binary number" << std::endl;
+ return 0;
+ }
+ // if there is a comma, then there cannot be more than 4 digits on a side
+ if (leftDigits > 4) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "too many digits to left of comma" << std::endl;
+ return 0;
+ }
+ if (rightDigits > 4) {
+ std::cerr << "Error on line " << lineNum << " at token: " << word
+ << std::endl;
+ std::cerr << "too many digits to right of comma" << std::endl;
+ return 0;
+ }
+
+ // OK, we have a valid binary number, so calculate the byte
+
+ uchar output = 0;
+
+ // if no comma in binary number
+ if (commaIndex == -1) {
+ for (i=0; i> 28) & 0x7f;
+ byte[1] = (value >> 21) & 0x7f;
+ byte[2] = (value >> 14) & 0x7f;
+ byte[3] = (value >> 7) & 0x7f;
+ byte[4] = (value >> 0) & 0x7f;
+
+ int i;
+ int flag = 0;
+ for (i=0; i<4; i++) {
+ if (byte[i] != 0) {
+ flag = 1;
+ }
+ if (flag) {
+ byte[i] |= 0x80;
+ }
+ }
+
+ for (i=0; i<5; i++) {
+ if (byte[i] >= 0x80 || i == 4) {
+ out << byte[i];
+ }
+ }
+
+ return 1;
+}
+
+
+
+////////////////////////////
+//
+// Binasc::processMidiTempoWord -- convert a floating point tempo into
+// a three-byte number of microseconds per beat per minute value.
+//
+
+int Binasc::processMidiTempoWord(std::ostream& out, const std::string& word,
+ int lineNum) {
+ if (word.size() < 2) {
+ std::cerr << "Error on line: " << lineNum
+ << ": 't' needs to be followed immediately by "
+ << "a floating-point number" << std::endl;
+ return 0;
+ }
+ if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-'
+ || word[1] == '+')) {
+ std::cerr << "Error on line: " << lineNum
+ << ": 't' needs to be followed immediately by "
+ << "a floating-point number" << std::endl;
+ return 0;
+ }
+ double value = strtod(&word[1], NULL);
+
+ if (value < 0.0) {
+ value = -value;
+ }
+
+ int intval = int(60.0 * 1000000.0 / value + 0.5);
+
+ uchar byte0 = intval & 0xff;
+ uchar byte1 = (intval >> 8) & 0xff;
+ uchar byte2 = (intval >> 16) & 0xff;
+ out << byte2 << byte1 << byte0;
+ return 1;
+}
+
+
+
+////////////////////////////
+//
+// Binasc::processMidiPitchBendWord -- convert a floating point number in
+// the range from +1.0 to -1.0 into a 14-point integer with -1.0 mapping
+// to 0 and +1.0 mapping to 2^15-1. This integer will be packed into
+// two bytes, with the LSB coming first and containing the bottom
+// 7-bits of the 14-bit value, then the MSB coming second and containing
+// the top 7-bits of the 14-bit value.
+
+int Binasc::processMidiPitchBendWord(std::ostream& out, const std::string& word,
+ int lineNum) {
+ if (word.size() < 2) {
+ std::cerr << "Error on line: " << lineNum
+ << ": 'p' needs to be followed immediately by "
+ << "a floating-point number" << std::endl;
+ return 0;
+ }
+ if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-'
+ || word[1] == '+')) {
+ std::cerr << "Error on line: " << lineNum
+ << ": 'p' needs to be followed immediately by "
+ << "a floating-point number" << std::endl;
+ return 0;
+ }
+ double value = strtod(&word[1], NULL);
+
+ if (value > 1.0) {
+ value = 1.0;
+ }
+ if (value < -1.0) {
+ value = -1.0;
+ }
+
+ int intval = (int)(((1 << 13)-0.5) * (value + 1.0) + 0.5);
+ uchar LSB = intval & 0x7f;
+ uchar MSB = (intval >> 7) & 0x7f;
+ out << LSB << MSB;
+ return 1;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Ordered byte writing functions --
+//
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianUShort --
+//
+
+std::ostream& Binasc::writeLittleEndianUShort(std::ostream& out, ushort value) {
+ union { char bytes[2]; ushort us; } data;
+ data.us = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeBigEndianUShort --
+//
+
+std::ostream& Binasc::writeBigEndianUShort(std::ostream& out, ushort value) {
+ union { char bytes[2]; ushort us; } data;
+ data.us = value;
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianShort --
+//
+
+std::ostream& Binasc::writeLittleEndianShort(std::ostream& out, short value) {
+ union { char bytes[2]; short s; } data;
+ data.s = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// writeBigEndianShort --
+//
+
+std::ostream& Binasc::writeBigEndianShort(std::ostream& out, short value) {
+ union { char bytes[2]; short s; } data;
+ data.s = value;
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianULong --
+//
+
+std::ostream& Binasc::writeLittleEndianULong(std::ostream& out, ulong value) {
+ union { char bytes[4]; ulong ul; } data;
+ data.ul = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeBigEndianULong --
+//
+
+std::ostream& Binasc::writeBigEndianULong(std::ostream& out, ulong value) {
+ union { char bytes[4]; long ul; } data;
+ data.ul = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianLong --
+//
+
+std::ostream& Binasc::writeLittleEndianLong(std::ostream& out, long value) {
+ union { char bytes[4]; long l; } data;
+ data.l = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeBigEndianLong --
+//
+
+std::ostream& Binasc::writeBigEndianLong(std::ostream& out, long value) {
+ union { char bytes[4]; long l; } data;
+ data.l = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeBigEndianFloat --
+//
+
+std::ostream& Binasc::writeBigEndianFloat(std::ostream& out, float value) {
+ union { char bytes[4]; float f; } data;
+ data.f = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianFloat --
+//
+
+std::ostream& Binasc::writeLittleEndianFloat(std::ostream& out, float value) {
+ union { char bytes[4]; float f; } data;
+ data.f = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeBigEndianDouble --
+//
+
+std::ostream& Binasc::writeBigEndianDouble(std::ostream& out, double value) {
+ union { char bytes[8]; double d; } data;
+ data.d = value;
+ out << data.bytes[7];
+ out << data.bytes[6];
+ out << data.bytes[5];
+ out << data.bytes[4];
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Binasc::writeLittleEndianDouble --
+//
+
+std::ostream& Binasc::writeLittleEndianDouble(std::ostream& out, double value) {
+ union { char bytes[8]; double d; } data;
+ data.d = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ out << data.bytes[4];
+ out << data.bytes[5];
+ out << data.bytes[6];
+ out << data.bytes[7];
+ return out;
+}
+
+
+} // end namespace smf
+
+
+
diff --git a/midifile/src/MidiEvent.cpp b/midifile/src/MidiEvent.cpp
new file mode 100644
index 0000000..945e3c5
--- /dev/null
+++ b/midifile/src/MidiEvent.cpp
@@ -0,0 +1,284 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 21:40:14 PST 2015
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/src/MidiEvent.cpp
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class which stores a MidiMessage and a timestamp
+// for the MidiFile class.
+//
+
+#include "MidiEvent.h"
+
+#include
+
+
+namespace smf {
+
+//////////////////////////////
+//
+// MidiEvent::MidiEvent -- Constructor classes
+//
+
+MidiEvent::MidiEvent(void) : MidiMessage() {
+ clearVariables();
+}
+
+
+MidiEvent::MidiEvent(int command) : MidiMessage(command) {
+ clearVariables();
+}
+
+
+MidiEvent::MidiEvent(int command, int p1) : MidiMessage(command, p1) {
+ clearVariables();
+}
+
+
+MidiEvent::MidiEvent(int command, int p1, int p2)
+ : MidiMessage(command, p1, p2) {
+ clearVariables();
+}
+
+
+MidiEvent::MidiEvent(int aTime, int aTrack, vector& message)
+ : MidiMessage(message) {
+ track = aTrack;
+ tick = aTime;
+ seconds = 0.0;
+ seq = 0;
+ m_eventlink = NULL;
+}
+
+
+MidiEvent::MidiEvent(const MidiEvent& mfevent) : MidiMessage() {
+ track = mfevent.track;
+ tick = mfevent.tick;
+ seconds = mfevent.seconds;
+ seq = mfevent.seq;
+ m_eventlink = NULL;
+
+ this->resize(mfevent.size());
+ for (int i=0; i<(int)this->size(); i++) {
+ (*this)[i] = mfevent[i];
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::~MidiEvent -- MidiFile Event destructor
+//
+
+MidiEvent::~MidiEvent() {
+ track = -1;
+ tick = -1;
+ seconds = -1.0;
+ seq = -1;
+ this->resize(0);
+ m_eventlink = NULL;
+}
+
+
+//////////////////////////////
+//
+// MidiEvent::clearVariables -- Clear everything except MidiMessage data.
+//
+
+void MidiEvent::clearVariables(void) {
+ track = 0;
+ tick = 0;
+ seconds = 0.0;
+ seq = 0;
+ m_eventlink = NULL;
+}
+
+
+//////////////////////////////
+//
+// MidiEvent::operator= -- Copy the contents of another MidiEvent.
+//
+
+MidiEvent& MidiEvent::operator=(const MidiEvent& mfevent) {
+ if (this == &mfevent) {
+ return *this;
+ }
+ tick = mfevent.tick;
+ track = mfevent.track;
+ seconds = mfevent.seconds;
+ seq = mfevent.seq;
+ m_eventlink = NULL;
+ this->resize(mfevent.size());
+ for (int i=0; i<(int)this->size(); i++) {
+ (*this)[i] = mfevent[i];
+ }
+ return *this;
+}
+
+
+MidiEvent& MidiEvent::operator=(const MidiMessage& message) {
+ if (this == &message) {
+ return *this;
+ }
+ clearVariables();
+ this->resize(message.size());
+ for (int i=0; i<(int)this->size(); i++) {
+ (*this)[i] = message[i];
+ }
+ return *this;
+}
+
+
+MidiEvent& MidiEvent::operator=(const vector& bytes) {
+ clearVariables();
+ this->resize(bytes.size());
+ for (int i=0; i<(int)this->size(); i++) {
+ (*this)[i] = bytes[i];
+ }
+ return *this;
+}
+
+
+MidiEvent& MidiEvent::operator=(const vector& bytes) {
+ clearVariables();
+ setMessage(bytes);
+ return *this;
+}
+
+
+MidiEvent& MidiEvent::operator=(const vector& bytes) {
+ clearVariables();
+ setMessage(bytes);
+ return *this;
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::unlinkEvent -- Disassociate this event with another.
+// Also tell the other event to disassociate from this event.
+//
+
+void MidiEvent::unlinkEvent(void) {
+ if (m_eventlink == NULL) {
+ return;
+ }
+ MidiEvent* mev = m_eventlink;
+ m_eventlink = NULL;
+ mev->unlinkEvent();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::linkEvent -- Make a link between two messages.
+// Unlinking
+//
+
+void MidiEvent::linkEvent(MidiEvent* mev) {
+ if (mev->m_eventlink != NULL) {
+ // unlink other event if it is linked to something else;
+ mev->unlinkEvent();
+ }
+ // if this is already linked to something else, then unlink:
+ if (m_eventlink != NULL) {
+ m_eventlink->unlinkEvent();
+ }
+ unlinkEvent();
+
+ mev->m_eventlink = this;
+ m_eventlink = mev;
+}
+
+
+void MidiEvent::linkEvent(MidiEvent& mev) {
+ linkEvent(&mev);
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::getLinkedEvent -- Returns a linked event. Usually
+// this is the note-off message for a note-on message and vice-versa.
+// Returns null if there are no links.
+//
+
+MidiEvent* MidiEvent::getLinkedEvent(void) {
+ return m_eventlink;
+}
+
+
+const MidiEvent* MidiEvent::getLinkedEvent(void) const {
+ return m_eventlink;
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::isLinked -- Returns true if there is an event which is not
+// NULL. This function is similar to getLinkedEvent().
+//
+
+int MidiEvent::isLinked(void) const {
+ return m_eventlink == NULL ? 0 : 1;
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::getTickDuration -- For linked events (note-ons and note-offs),
+// return the absolute tick time difference between the two events.
+// The tick values are presumed to be in absolute tick mode rather than
+// delta tick mode. Returns 0 if not linked.
+//
+
+int MidiEvent::getTickDuration(void) const {
+ const MidiEvent* mev = getLinkedEvent();
+ if (mev == NULL) {
+ return 0;
+ }
+ int tick2 = mev->tick;
+ if (tick2 > tick) {
+ return tick2 - tick;
+ } else {
+ return tick - tick2;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiEvent::getDurationInSeconds -- For linked events (note-ons and
+// note-offs), return the duration of the note in seconds. The
+// seconds analysis must be done first; otherwise the duration will be
+// reported as zero.
+//
+
+double MidiEvent::getDurationInSeconds(void) const {
+ const MidiEvent* mev = getLinkedEvent();
+ if (mev == NULL) {
+ return 0;
+ }
+ double seconds2 = mev->seconds;
+ if (seconds2 > seconds) {
+ return seconds2 - seconds;
+ } else {
+ return seconds - seconds2;
+ }
+}
+
+
+} // end namespace smf
+
+
+
diff --git a/midifile/src/MidiEventList.cpp b/midifile/src/MidiEventList.cpp
new file mode 100644
index 0000000..52749c5
--- /dev/null
+++ b/midifile/src/MidiEventList.cpp
@@ -0,0 +1,624 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 21:55:38 PST 2015
+// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
+// Filename: midifile/src/MidiEventList.cpp
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class which stores a MidiEvents for a MidiFile track.
+//
+
+
+#include "MidiEventList.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace smf {
+
+//////////////////////////////
+//
+// MidiEventList::MidiEventList -- Constructor.
+//
+
+MidiEventList::MidiEventList(void) {
+ reserve(1000);
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::MidiEventList(MidiEventList&) -- Copy constructor.
+//
+
+MidiEventList::MidiEventList(const MidiEventList& other) {
+ list.reserve(other.list.size());
+ auto it = other.list.begin();
+ std::generate_n(std::back_inserter(list), other.list.size(), [&]() -> MidiEvent* {
+ return new MidiEvent(**it++);
+ });
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::MidiEventList(MidiEventList&&) -- Move constructor.
+//
+
+MidiEventList::MidiEventList(MidiEventList&& other) {
+ list = std::move(other.list);
+ other.list.clear();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::~MidiEventList -- Deconstructor. Deallocate all stored
+// data.
+//
+
+MidiEventList::~MidiEventList() {
+ clear();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::operator[] --
+//
+
+MidiEvent& MidiEventList::operator[](int index) {
+ return *list[index];
+}
+
+
+const MidiEvent& MidiEventList::operator[](int index) const {
+ return *list[index];
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::back -- Return the last element in the list.
+//
+
+MidiEvent& MidiEventList::back(void) {
+ return *list.back();
+}
+
+
+const MidiEvent& MidiEventList::back(void) const {
+ return *list.back();
+}
+
+//
+// MidiEventList::last -- Alias for MidiEventList::back().
+//
+
+MidiEvent& MidiEventList::last(void) {
+ return back();
+}
+
+
+const MidiEvent& MidiEventList::last(void) const {
+ return back();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::getEvent -- The same thing as operator[], for
+// internal use when operator[] would look more messy.
+//
+
+MidiEvent& MidiEventList::getEvent(int index) {
+ return *list[index];
+}
+
+
+const MidiEvent& MidiEventList::getEvent(int index) const {
+ return *list[index];
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::clear -- De-allocate any MidiEvents present in the list
+// and set the size of the list to 0.
+//
+
+void MidiEventList::clear(void) {
+ for (int i=0; i<(int)list.size(); i++) {
+ if (list[i] != NULL) {
+ delete list[i];
+ list[i] = NULL;
+ }
+ }
+ list.resize(0);
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::data -- Return the low-level array of MidiMessage
+// pointers. This is useful for applying your own sorting
+// function to the list.
+//
+
+MidiEvent** MidiEventList::data(void) {
+ return list.data();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::reserve -- Pre-allocate space in the list for storing
+// elements.
+//
+
+void MidiEventList::reserve(int rsize) {
+ if (rsize > (int)list.size()) {
+ list.reserve(rsize);
+ }
+}
+
+
+//////////////////////////////
+//
+// MidiEventList::getSize -- Return the number of MidiEvents stored
+// in the list.
+//
+
+int MidiEventList::getSize(void) const {
+ return (int)list.size();
+}
+
+//
+// MidiEventList::size -- Alias for MidiEventList::getSize().
+//
+
+int MidiEventList::size(void) const {
+ return getSize();
+}
+
+//
+// MidiEventList::getEventCount -- Alias for MidiEventList::getSize().
+//
+
+int MidiEventList::getEventCount(void) const {
+ return getSize();
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::append -- add a MidiEvent at the end of the list. Returns
+// the index of the appended event.
+//
+
+int MidiEventList::append(MidiEvent& event) {
+ MidiEvent* ptr = new MidiEvent(event);
+ list.push_back(ptr);
+ return (int)list.size()-1;
+}
+
+//
+// MidiEventList::push -- Alias for MidiEventList::append().
+//
+
+int MidiEventList::push(MidiEvent& event) {
+ return append(event);
+}
+
+//
+// MidiEventList::push_back -- Alias for MidiEventList::append().
+//
+
+int MidiEventList::push_back(MidiEvent& event) {
+ return append(event);
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::removeEmpties -- Remove any MIDI message which contain no
+// bytes. This function first deallocates any empty MIDI events, and then
+// removes them from the list of events.
+//
+
+void MidiEventList::removeEmpties(void) {
+ int count = 0;
+ for (int i=0; i<(int)list.size(); i++) {
+ if (list[i]->empty()) {
+ delete list[i];
+ list[i] = NULL;
+ count++;
+ }
+ }
+ if (count == 0) {
+ return;
+ }
+ std::vector newlist;
+ newlist.reserve(list.size() - count);
+ for (int i=0; i<(int)list.size(); i++) {
+ if (list[i]) {
+ newlist.push_back(list[i]);
+ }
+ }
+ list.swap(newlist);
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::linkNotePairs -- Match note-ones and note-offs together
+// There are two models that can be done if two notes are overlapping
+// on the same pitch: the first note-off affects the last note-on,
+// or the first note-off affects the first note-on. Currently the
+// first note-off affects the last note-on, but both methods could
+// be implemented with user selectability. The current state of the
+// track is assumed to be in time-sorted order. Returns the number
+// of linked notes (note-on/note-off pairs).
+//
+
+int MidiEventList::linkEventPairs(void) {
+ return linkNotePairs();
+}
+
+
+int MidiEventList::linkNotePairs(void) {
+
+ // Note-on states:
+ // dimension 1: MIDI channel (0-15)
+ // dimension 2: MIDI key (0-127) (but 0 not used for note-ons)
+ // dimension 3: List of active note-ons or note-offs.
+ std::vector>> noteons;
+ noteons.resize(16);
+ for (int i=0; i<(int)noteons.size(); i++) {
+ noteons[i].resize(128);
+ }
+
+ // Controller linking: The following General MIDI controller numbers are
+ // also monitored for linking within the track (but not between tracks).
+ // hex dec name range
+ // 40 64 Hold pedal (Sustain) on/off 0..63=off 64..127=on
+ // 41 65 Portamento on/off 0..63=off 64..127=on
+ // 42 66 Sustenuto Pedal on/off 0..63=off 64..127=on
+ // 43 67 Soft Pedal on/off 0..63=off 64..127=on
+ // 44 68 Legato Pedal on/off 0..63=off 64..127=on
+ // 45 69 Hold Pedal 2 on/off 0..63=off 64..127=on
+ // 50 80 General Purpose Button 0..63=off 64..127=on
+ // 51 81 General Purpose Button 0..63=off 64..127=on
+ // 52 82 General Purpose Button 0..63=off 64..127=on
+ // 53 83 General Purpose Button 0..63=off 64..127=on
+ // 54 84 Undefined on/off 0..63=off 64..127=on
+ // 55 85 Undefined on/off 0..63=off 64..127=on
+ // 56 86 Undefined on/off 0..63=off 64..127=on
+ // 57 87 Undefined on/off 0..63=off 64..127=on
+ // 58 88 Undefined on/off 0..63=off 64..127=on
+ // 59 89 Undefined on/off 0..63=off 64..127=on
+ // 5A 90 Undefined on/off 0..63=off 64..127=on
+ // 7A 122 Local Keyboard On/Off 0..63=off 64..127=on
+
+ // first keep track of whether the controller is an on/off switch:
+ std::vector> contmap;
+ contmap.resize(128);
+ std::pair zero(0, 0);
+ std::fill(contmap.begin(), contmap.end(), zero);
+ contmap[64].first = 1; contmap[64].second = 0;
+ contmap[65].first = 1; contmap[65].second = 1;
+ contmap[66].first = 1; contmap[66].second = 2;
+ contmap[67].first = 1; contmap[67].second = 3;
+ contmap[68].first = 1; contmap[68].second = 4;
+ contmap[69].first = 1; contmap[69].second = 5;
+ contmap[80].first = 1; contmap[80].second = 6;
+ contmap[81].first = 1; contmap[81].second = 7;
+ contmap[82].first = 1; contmap[82].second = 8;
+ contmap[83].first = 1; contmap[83].second = 9;
+ contmap[84].first = 1; contmap[84].second = 10;
+ contmap[85].first = 1; contmap[85].second = 11;
+ contmap[86].first = 1; contmap[86].second = 12;
+ contmap[87].first = 1; contmap[87].second = 13;
+ contmap[88].first = 1; contmap[88].second = 14;
+ contmap[89].first = 1; contmap[89].second = 15;
+ contmap[90].first = 1; contmap[90].second = 16;
+ contmap[122].first = 1; contmap[122].second = 17;
+
+ // dimensions:
+ // 1: mapped controller (0 to 17)
+ // 2: channel (0 to 15)
+ std::vector> contevents;
+ contevents.resize(18);
+ std::vector> oldstates;
+ oldstates.resize(18);
+ for (int i=0; i<18; i++) {
+ contevents[i].resize(16);
+ std::fill(contevents[i].begin(), contevents[i].end(), nullptr);
+ oldstates[i].resize(16);
+ std::fill(oldstates[i].begin(), oldstates[i].end(), -1);
+ }
+
+ // Now iterate through the MidiEventList keeping track of note and
+ // select controller states and linking notes/controllers as needed.
+ int channel;
+ int key;
+ int contnum;
+ int contval;
+ int conti;
+ int contstate;
+ int counter = 0;
+ MidiEvent* mev;
+ MidiEvent* noteon;
+ for (int i=0; iunlinkEvent();
+ if (mev->isNoteOn()) {
+ // store the note-on to pair later with a note-off message.
+ key = mev->getKeyNumber();
+ channel = mev->getChannel();
+ noteons[channel][key].push_back(mev);
+ } else if (mev->isNoteOff()) {
+ key = mev->getKeyNumber();
+ channel = mev->getChannel();
+ if (noteons[channel][key].size() > 0) {
+ noteon = noteons[channel][key].back();
+ noteons[channel][key].pop_back();
+ noteon->linkEvent(mev);
+ counter++;
+ }
+ } else if (mev->isController()) {
+ contnum = mev->getP1();
+ if (contmap[contnum].first) {
+ conti = contmap[contnum].second;
+ channel = mev->getChannel();
+ contval = mev->getP2();
+ contstate = contval < 64 ? 0 : 1;
+ if ((oldstates[conti][channel] == -1) && contstate) {
+ // a newly initialized onstate was detected, so store for
+ // later linking to an off state.
+ contevents[conti][channel] = mev;
+ oldstates[conti][channel] = contstate;
+ } else if (oldstates[conti][channel] == contstate) {
+ // the controller state is redundant and will be ignored.
+ } else if ((oldstates[conti][channel] == 0) && contstate) {
+ // controller is currently off, so store on-state for next link
+ contevents[conti][channel] = mev;
+ oldstates[conti][channel] = contstate;
+ } else if ((oldstates[conti][channel] == 1) && (contstate == 0)) {
+ // controller has just been turned off, so link to
+ // stored on-message.
+ contevents[conti][channel]->linkEvent(mev);
+ oldstates[conti][channel] = contstate;
+ // not necessary, but maybe use for something later:
+ contevents[conti][channel] = mev;
+ }
+ }
+ }
+ }
+ return counter;
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::clearLinks -- remove all note-on/note-off links.
+//
+
+void MidiEventList::clearLinks(void) {
+ for (int i=0; i<(int)getSize(); i++) {
+ getEvent(i).unlinkEvent();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiEventList::clearSequence -- Remove any seqence serial numbers from
+// MidiEvents in the list. This will cause the default ordering by
+// sortTracks() to be used, in which case the ordering of MidiEvents
+// occurring at the same tick may switch their ordering.
+//
+
+void MidiEventList::clearSequence(void) {
+ for (int i=0; i bevent.tick) {
+ // aevent occurs after bevent
+ return +1;
+ } else if (aevent.tick < bevent.tick) {
+ // aevent occurs before bevent
+ return -1;
+ } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) {
+ // aevent sequencing state occurs after bevent
+ // see MidiEventList::markSequence()
+ return +1;
+ } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) {
+ // aevent sequencing state occurs before bevent
+ // see MidiEventList::markSequence()
+ return -1;
+ } else if (aevent.getP0() == 0xff && aevent.getP1() == 0x2f) {
+ // end-of-track meta-message should always be last (but won't really
+ // matter since the writing function ignores all end-of-track messages
+ // and writes its own.
+ return +1;
+ } else if (bevent.getP0() == 0xff && bevent.getP1() == 0x2f) {
+ // end-of-track meta-message should always be last (but won't really
+ // matter since the writing function ignores all end-of-track messages
+ // and writes its own.
+ return -1;
+ } else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) {
+ // other meta-messages are placed before real MIDI messages
+ return -1;
+ } else if (aevent.getP0() != 0xff && bevent.getP0() == 0xff) {
+ // other meta-messages are placed before real MIDI messages
+ return +1;
+ } else if (((aevent.getP0() & 0xf0) == 0x90) && (aevent.getP2() != 0)) {
+ // note-ons come after all other types of MIDI messages
+ return +1;
+ } else if (((bevent.getP0() & 0xf0) == 0x90) && (bevent.getP2() != 0)) {
+ // note-ons come after all other types of MIDI messages
+ return -1;
+ } else if (((aevent.getP0() & 0xf0) == 0x90) || ((aevent.getP0() & 0xf0) == 0x80)) {
+ // note-offs come after all other MIDI messages (except note-ons)
+ return +1;
+ } else if (((bevent.getP0() & 0xf0) == 0x90) || ((bevent.getP0() & 0xf0) == 0x80)) {
+ // note-offs come after all other MIDI messages (except note-ons)
+ return -1;
+ } else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) {
+ // both events are continuous controllers. Sort them by controller number
+ if (aevent.getP1() > bevent.getP1()) {
+ return +1;
+ } if (aevent.getP1() < bevent.getP1()) {
+ return -1;
+ } else {
+ // same controller number, so sort by data value
+ if (aevent.getP2() > bevent.getP2()) {
+ return +1;
+ } if (aevent.getP2() < bevent.getP2()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ } else {
+ return 0;
+ }
+}
+
+
+} // end namespace smf
+
+
+
diff --git a/midifile/src/MidiFile.cpp b/midifile/src/MidiFile.cpp
new file mode 100644
index 0000000..685198b
--- /dev/null
+++ b/midifile/src/MidiFile.cpp
@@ -0,0 +1,3395 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Fri Nov 26 14:12:01 PST 1999
+// Last Modified: Thu Jun 24 18:35:30 PDT 2021 Added base64 encoding read/write
+// Filename: midifile/src/MidiFile.cpp
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: A class which can read/write Standard MIDI files.
+// MIDI data is stored by track in an array. This
+// class is used for example in the MidiPerform class.
+//
+
+#include "MidiFile.h"
+#include "Binasc.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+namespace smf {
+
+
+const std::string MidiFile::encodeLookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+const std::vector MidiFile::decodeLookup {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
+ -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+};
+
+
+
+//////////////////////////////
+//
+// MidiFile::MidiFile -- Constuctor.
+//
+
+MidiFile::MidiFile(void) {
+ m_events.resize(1);
+ for (int i=0; i<(int)m_events.size(); i++) {
+ m_events[i] = new MidiEventList;
+ }
+}
+
+
+MidiFile::MidiFile(const std::string& filename) {
+ m_events.resize(1);
+ for (int i=0; i<(int)m_events.size(); i++) {
+ m_events[i] = new MidiEventList;
+ }
+ read(filename);
+}
+
+
+MidiFile::MidiFile(std::istream& input) {
+ m_events.resize(1);
+ for (int i=0; i<(int)m_events.size(); i++) {
+ m_events[i] = new MidiEventList;
+ }
+ read(input);
+}
+
+
+
+MidiFile::MidiFile(const MidiFile& other) {
+ *this = other;
+}
+
+
+
+MidiFile::MidiFile(MidiFile&& other) {
+ *this = std::move(other);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::~MidiFile -- Deconstructor.
+//
+
+MidiFile::~MidiFile() {
+ m_readFileName.clear();
+ clear();
+ if (m_events[0] != NULL) {
+ delete m_events[0];
+ m_events[0] = NULL;
+ }
+ m_events.resize(0);
+ m_rwstatus = false;
+ m_timemap.clear();
+ m_timemapvalid = 0;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::operator= -- Copying another
+//
+
+MidiFile& MidiFile::operator=(const MidiFile& other) {
+ if (this == &other) {
+ return *this;
+ }
+ m_events.reserve(other.m_events.size());
+ auto it = other.m_events.begin();
+ std::generate_n(std::back_inserter(m_events), other.m_events.size(),
+ [&]()->MidiEventList* {
+ return new MidiEventList(**it++);
+ }
+ );
+ m_ticksPerQuarterNote = other.m_ticksPerQuarterNote;
+ m_theTrackState = other.m_theTrackState;
+ m_theTimeState = other.m_theTimeState;
+ m_readFileName = other.m_readFileName;
+ m_timemapvalid = other.m_timemapvalid;
+ m_timemap = other.m_timemap;
+ m_rwstatus = other.m_rwstatus;
+ if (other.m_linkedEventsQ) {
+ linkEventPairs();
+ }
+ return *this;
+}
+
+
+MidiFile& MidiFile::operator=(MidiFile&& other) {
+ m_events = std::move(other.m_events);
+ m_linkedEventsQ = other.m_linkedEventsQ;
+ other.m_linkedEventsQ = false;
+ other.m_events.clear();
+ other.m_events.emplace_back(new MidiEventList);
+ m_ticksPerQuarterNote = other.m_ticksPerQuarterNote;
+ m_theTrackState = other.m_theTrackState;
+ m_theTimeState = other.m_theTimeState;
+ m_readFileName = other.m_readFileName;
+ m_timemapvalid = other.m_timemapvalid;
+ m_timemap = other.m_timemap;
+ m_rwstatus = other.m_rwstatus;
+ return *this;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// reading/writing functions --
+//
+
+
+//////////////////////////////
+//
+// MidiFile::read -- Parse a Standard MIDI File or ASCII-encoded Standard MIDI
+// File and store its contents in the object.
+//
+
+bool MidiFile::read(const std::string& filename) {
+ m_timemapvalid = 0;
+ setFilename(filename);
+ m_rwstatus = true;
+
+ std::fstream input;
+ input.open(filename.c_str(), std::ios::binary | std::ios::in);
+
+ if (!input.is_open()) {
+ m_rwstatus = false;
+ return m_rwstatus;
+ }
+
+ m_rwstatus = read(input);
+ return m_rwstatus;
+}
+
+//
+// istream version of read().
+//
+
+bool MidiFile::read(std::istream& input) {
+ m_rwstatus = true;
+ if (input.peek() != 'M') {
+ // If the first byte in the input stream is not 'M', then presume that
+ // the MIDI file is in the binasc format which is an ASCII representation
+ // of the MIDI file. Convert the binasc content into binary content and
+ // then continue reading with this function.
+ std::stringstream binarydata;
+ Binasc binasc;
+ binasc.writeToBinary(binarydata, input);
+ binarydata.seekg(0, std::ios_base::beg);
+ if (binarydata.peek() != 'M') {
+ std::cerr << "Bad MIDI data input" << std::endl;
+ m_rwstatus = false;
+ return m_rwstatus;
+ } else {
+ m_rwstatus = readSmf(binarydata);
+ return m_rwstatus;
+ }
+ } else {
+ m_rwstatus = readSmf(input);
+ return m_rwstatus;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::readBase64 -- First decode base64 string and then parse as either a
+// Standard MIDI File or binasc-encoded Standard MIDI File.
+//
+
+bool MidiFile::readBase64(const std::string& base64data) {
+ std::stringstream stream;
+ stream << MidiFile::base64Decode(base64data);
+ return MidiFile::read(stream);
+}
+
+bool MidiFile::readBase64(std::istream& instream) {
+ std::string base64data((std::istreambuf_iterator(instream)),
+ std::istreambuf_iterator());
+ std::stringstream stream;
+ stream << MidiFile::base64Decode(base64data);
+ return MidiFile::read(stream);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::readSmf -- Parse a Standard MIDI File and store its contents
+// in the object.
+//
+
+bool MidiFile::readSmf(const std::string& filename) {
+ m_timemapvalid = 0;
+ setFilename(filename);
+ m_rwstatus = true;
+
+ std::fstream input;
+ input.open(filename.c_str(), std::ios::binary | std::ios::in);
+
+ if (!input.is_open()) {
+ m_rwstatus = false;
+ return m_rwstatus;
+ }
+
+ m_rwstatus = readSmf(input);
+ return m_rwstatus;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::readSmf -- Parse a Standard MIDI File and store its contents in the object.
+//
+
+bool MidiFile::readSmf(std::istream& input) {
+ m_rwstatus = true;
+
+ std::string filename = getFilename();
+
+ int character;
+ // uchar buffer[123456] = {0};
+ ulong longdata;
+ ushort shortdata;
+
+ // Read the MIDI header (4 bytes of ID, 4 byte data size,
+ // anticipated 6 bytes of data.
+
+ character = input.get();
+ if (character == EOF) {
+ std::cerr << "In file " << filename << ": unexpected end of file." << std::endl;
+ std::cerr << "Expecting 'M' at first byte, but found nothing." << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else if (character != 'M') {
+ std::cerr << "File " << filename << " is not a MIDI file" << std::endl;
+ std::cerr << "Expecting 'M' at first byte but got '"
+ << (char)character << "'" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ character = input.get();
+ if (character == EOF) {
+ std::cerr << "In file " << filename << ": unexpected end of file." << std::endl;
+ std::cerr << "Expecting 'T' at second byte, but found nothing." << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else if (character != 'T') {
+ std::cerr << "File " << filename << " is not a MIDI file" << std::endl;
+ std::cerr << "Expecting 'T' at second byte but got '"
+ << (char)character << "'" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ character = input.get();
+ if (character == EOF) {
+ std::cerr << "In file " << filename << ": unexpected end of file." << std::endl;
+ std::cerr << "Expecting 'h' at third byte, but found nothing." << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else if (character != 'h') {
+ std::cerr << "File " << filename << " is not a MIDI file" << std::endl;
+ std::cerr << "Expecting 'h' at third byte but got '"
+ << (char)character << "'" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ character = input.get();
+ if (character == EOF) {
+ std::cerr << "In file " << filename << ": unexpected end of file." << std::endl;
+ std::cerr << "Expecting 'd' at fourth byte, but found nothing." << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else if (character != 'd') {
+ std::cerr << "File " << filename << " is not a MIDI file" << std::endl;
+ std::cerr << "Expecting 'd' at fourth byte but got '"
+ << (char)character << "'" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ // read header size (allow larger header size?)
+ longdata = readLittleEndian4Bytes(input);
+ if (longdata != 6) {
+ std::cerr << "File " << filename
+ << " is not a MIDI 1.0 Standard MIDI file." << std::endl;
+ std::cerr << "The header size is " << longdata << " bytes." << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ // Header parameter #1: format type
+ int type;
+ shortdata = readLittleEndian2Bytes(input);
+ switch (shortdata) {
+ case 0:
+ type = 0;
+ break;
+ case 1:
+ type = 1;
+ break;
+ case 2:
+ // Type-2 MIDI files should probably be allowed as well,
+ // but I have never seen one in the wild to test with.
+ default:
+ std::cerr << "Error: cannot handle a type-" << shortdata
+ << " MIDI file" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+
+ // Header parameter #2: track count
+ int tracks;
+ shortdata = readLittleEndian2Bytes(input);
+ if (type == 0 && shortdata != 1) {
+ std::cerr << "Error: Type 0 MIDI file can only contain one track" << std::endl;
+ std::cerr << "Instead track count is: " << shortdata << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else {
+ tracks = shortdata;
+ }
+ clear();
+ if (m_events[0] != NULL) {
+ delete m_events[0];
+ }
+ m_events.resize(tracks);
+ for (int z=0; zreserve(10000); // Initialize with 10,000 event storage.
+ m_events[z]->clear();
+ }
+
+ // Header parameter #3: Ticks per quarter note
+ shortdata = readLittleEndian2Bytes(input);
+ if (shortdata >= 0x8000) {
+ int framespersecond = 255 - ((shortdata >> 8) & 0x00ff) + 1;
+ int subframes = shortdata & 0x00ff;
+ switch (framespersecond) {
+ case 25: framespersecond = 25; break;
+ case 24: framespersecond = 24; break;
+ case 29: framespersecond = 29; break; // really 29.97 for color television
+ case 30: framespersecond = 30; break;
+ default:
+ std::cerr << "Warning: unknown FPS: " << framespersecond << std::endl;
+ std::cerr << "Using non-standard FPS: " << framespersecond << std::endl;
+ }
+ m_ticksPerQuarterNote = framespersecond * subframes;
+
+ // std::cerr << "SMPTE ticks: " << m_ticksPerQuarterNote << " ticks/sec" << std::endl;
+ // std::cerr << "SMPTE frames per second: " << framespersecond << std::endl;
+ // std::cerr << "SMPTE subframes per frame: " << subframes << std::endl;
+ } else {
+ m_ticksPerQuarterNote = shortdata;
+ }
+
+
+ //////////////////////////////////////////////////
+ //
+ // now read individual tracks:
+ //
+
+ uchar runningCommand;
+ MidiEvent event;
+ std::vector bytes;
+ int xstatus;
+
+ for (int i=0; ireserve((int)longdata/2);
+ m_events[i]->clear();
+
+ // Read MIDI events in the track, which are pairs of VLV values
+ // and then the bytes for the MIDI message. Running status messags
+ // will be filled in with their implicit command byte.
+ // The timestamps are converted from delta ticks to absolute ticks,
+ // with the absticks variable accumulating the VLV tick values.
+ int absticks = 0;
+ while (!input.eof()) {
+ longdata = readVLValue(input);
+ absticks += longdata;
+ xstatus = extractMidiData(input, bytes, runningCommand);
+ if (xstatus == 0) {
+ m_rwstatus = false; return m_rwstatus;
+ }
+ event.setMessage(bytes);
+ event.tick = absticks;
+ event.track = i;
+
+ if (bytes[0] == 0xff && bytes[1] == 0x2f) {
+ // end-of-track message
+ // comment out the following line if you don't want to see the
+ // end of track message (which is always required, and will added
+ // automatically when a MIDI is written, so it is not necessary.
+ m_events[i]->push_back(event);
+ break;
+ }
+ m_events[i]->push_back(event);
+ }
+ }
+
+ m_theTimeState = TIME_STATE_ABSOLUTE;
+
+ // The original order of the MIDI events is marked with an enumeration which
+ // allows for reconstruction of the order when merging/splitting tracks to/from
+ // a type-0 configuration.
+ markSequence();
+
+ return m_rwstatus;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::write -- write a standard MIDI file to a file or an output
+// stream.
+//
+
+bool MidiFile::write(const std::string& filename) {
+ std::fstream output(filename.c_str(), std::ios::binary | std::ios::out);
+
+ if (!output.is_open()) {
+ std::cerr << "Error: could not write: " << filename << std::endl;
+ return false;
+ }
+ m_rwstatus = write(output);
+ output.close();
+ return m_rwstatus;
+}
+
+//
+// ostream version of MidiFile::write().
+//
+
+bool MidiFile::write(std::ostream& out) {
+ int oldTimeState = getTickState();
+ if (oldTimeState == TIME_STATE_ABSOLUTE) {
+ makeDeltaTicks();
+ }
+
+ // write the header of the Standard MIDI File
+ char ch;
+ // 1. The characters "MThd"
+ ch = 'M'; out << ch;
+ ch = 'T'; out << ch;
+ ch = 'h'; out << ch;
+ ch = 'd'; out << ch;
+
+ // 2. write the size of the header (always a "6" stored in unsigned long
+ // (4 bytes).
+ ulong longdata = 6;
+ writeBigEndianULong(out, longdata);
+
+ // 3. MIDI file format, type 0, 1, or 2
+ ushort shortdata;
+ shortdata = static_cast(getNumTracks() == 1 ? 0 : 1);
+ writeBigEndianUShort(out,shortdata);
+
+ // 4. write out the number of tracks.
+ shortdata = static_cast(getNumTracks());
+ writeBigEndianUShort(out, shortdata);
+
+ // 5. write out the number of ticks per quarternote. (avoiding SMTPE for now)
+ shortdata = static_cast(getTicksPerQuarterNote());
+ writeBigEndianUShort(out, shortdata);
+
+ // now write each track.
+ std::vector trackdata;
+ uchar endoftrack[4] = {0, 0xff, 0x2f, 0x00};
+ int i, j, k;
+ int size;
+ for (i=0; isize(); j++) {
+ if ((*m_events[i])[j].empty()) {
+ // Don't write empty m_events (probably a delete message).
+ continue;
+ }
+ if ((*m_events[i])[j].isEndOfTrack()) {
+ // Suppress end-of-track meta messages (one will be added
+ // automatically after all track data has been written).
+ continue;
+ }
+ writeVLValue((*m_events[i])[j].tick, trackdata);
+ if (((*m_events[i])[j].getCommandByte() == 0xf0) ||
+ ((*m_events[i])[j].getCommandByte() == 0xf7)) {
+ // 0xf0 == Complete sysex message (0xf0 is part of the raw MIDI).
+ // 0xf7 == Raw byte message (0xf7 not part of the raw MIDI).
+ // Print the first byte of the message (0xf0 or 0xf7), then
+ // print a VLV length for the rest of the bytes in the message.
+ // In other words, when creating a 0xf0 or 0xf7 MIDI message,
+ // do not insert the VLV byte length yourself, as this code will
+ // do it for you automatically.
+ trackdata.push_back((*m_events[i])[j][0]); // 0xf0 or 0xf7;
+ writeVLValue(((int)(*m_events[i])[j].size())-1, trackdata);
+ for (k=1; k<(int)(*m_events[i])[j].size(); k++) {
+ trackdata.push_back((*m_events[i])[j][k]);
+ }
+ } else {
+ // non-sysex type of message, so just output the
+ // bytes of the message:
+ for (k=0; k<(int)(*m_events[i])[j].size(); k++) {
+ trackdata.push_back((*m_events[i])[j][k]);
+ }
+ }
+ }
+ size = (int)trackdata.size();
+ if ((size < 3) || !((trackdata[size-3] == 0xff)
+ && (trackdata[size-2] == 0x2f))) {
+ trackdata.push_back(endoftrack[0]);
+ trackdata.push_back(endoftrack[1]);
+ trackdata.push_back(endoftrack[2]);
+ trackdata.push_back(endoftrack[3]);
+ }
+
+ // now ready to write to MIDI file.
+
+ // first write the track ID marker "MTrk":
+ ch = 'M'; out << ch;
+ ch = 'T'; out << ch;
+ ch = 'r'; out << ch;
+ ch = 'k'; out << ch;
+
+ // A. write the size of the MIDI data to follow:
+ longdata = (int)trackdata.size();
+ writeBigEndianULong(out, longdata);
+
+ // B. write the actual data
+ out.write((char*)trackdata.data(), trackdata.size());
+ }
+
+ if (oldTimeState == TIME_STATE_ABSOLUTE) {
+ makeAbsoluteTicks();
+ }
+
+ return true;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBase64 -- Write Standard MIDI file with base64 encoding.
+// The width parameter can be used to add line breaks. Zero or negative
+// width will prevent linebreaks from being added to the data.
+// Default value: width = 0
+//
+
+bool MidiFile::writeBase64(const std::string& filename, int width) {
+ std::fstream output(filename.c_str(), std::ios::binary | std::ios::out);
+
+ if (!output.is_open()) {
+ std::cerr << "Error: could not write: " << filename << std::endl;
+ return false;
+ }
+ m_rwstatus = writeBase64(output, width);
+ output.close();
+ return m_rwstatus;
+}
+
+
+bool MidiFile::writeBase64(std::ostream& out, int width) {
+ std::stringstream raw;
+ bool status = MidiFile::write(raw);
+ if (!status) {
+ return status;
+ }
+ std::string encoded = MidiFile::base64Encode(raw.str());
+ if (width <= 0) {
+ out << encoded;
+ return status;
+ }
+ int length = (int)encoded.size();
+ for (int i=0; i= 0 ? width : 25;
+ for (int i=0; iremoveEmpties();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::markSequence -- Assign a sequence serial number to
+// every MidiEvent in every track in the MIDI file. This is
+// useful if you want to preseve the order of MIDI messages in
+// a track when they occur at the same tick time. Particularly
+// for use with joinTracks() or sortTracks(). markSequence will
+// be done automatically when a MIDI file is read, in case the
+// ordering of m_events occuring at the same time is important.
+// Use clearSequence() to use the default sorting behavior of
+// sortTracks().
+//
+
+void MidiFile::markSequence(void) {
+ int sequence = 1;
+ for (int i=0; i= 0) && (track < getTrackCount())) {
+ operator[](track).markSequence(sequence);
+ } else {
+ std::cerr << "Warning: track " << track << " does not exist." << std::endl;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::clearSequence -- Remove any seqence serial numbers from
+// MidiEvents in the MidiFile. This will cause the default ordering by
+// sortTracks() to be used, in which case the ordering of MidiEvents
+// occurring at the same tick may switch their ordering.
+//
+
+void MidiFile::clearSequence(void) {
+ for (int i=0; i= 0) && (track < getTrackCount())) {
+ operator[](track).clearSequence();
+ } else {
+ std::cerr << "Warning: track " << track << " does not exist." << std::endl;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::joinTracks -- Interleave the data from all tracks,
+// but keeping the identity of the tracks unique so that
+// the function splitTracks can be called to split the
+// tracks into separate units again. The style of the
+// MidiFile when read from a file is with tracks split.
+// The original track index is stored in the MidiEvent::track
+// variable.
+//
+
+void MidiFile::joinTracks(void) {
+ if (getTrackState() == TRACK_STATE_JOINED) {
+ return;
+ }
+ if (getNumTracks() == 1) {
+ m_theTrackState = TRACK_STATE_JOINED;
+ return;
+ }
+
+ MidiEventList* joinedTrack;
+ joinedTrack = new MidiEventList;
+
+ int messagesum = 0;
+ int length = getNumTracks();
+ int i, j;
+ for (i=0; ireserve((int)(messagesum + 32 + messagesum * 0.1));
+
+ int oldTimeState = getTickState();
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeAbsoluteTicks();
+ }
+ for (i=0; isize(); j++) {
+ joinedTrack->push_back_no_copy(&(*m_events[i])[j]);
+ }
+ }
+
+ clear_no_deallocate();
+
+ delete m_events[0];
+ m_events.resize(0);
+ m_events.push_back(joinedTrack);
+ sortTracks();
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeDeltaTicks();
+ }
+
+ m_theTrackState = TRACK_STATE_JOINED;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::splitTracks -- Take the joined tracks and split them
+// back into their separate track identities.
+//
+
+void MidiFile::splitTracks(void) {
+ if (getTrackState() == TRACK_STATE_SPLIT) {
+ return;
+ }
+ int oldTimeState = getTickState();
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeAbsoluteTicks();
+ }
+
+ int maxTrack = 0;
+ int i;
+ int length = m_events[0]->size();
+ for (i=0; i maxTrack) {
+ maxTrack = (*m_events[0])[i].track;
+ }
+ }
+ int trackCount = maxTrack + 1;
+
+ if (trackCount <= 1) {
+ return;
+ }
+
+ MidiEventList* olddata = m_events[0];
+ m_events[0] = NULL;
+ m_events.resize(trackCount);
+ for (i=0; ipush_back_no_copy(&(*olddata)[i]);
+ }
+
+ olddata->detach();
+ delete olddata;
+
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeDeltaTicks();
+ }
+
+ m_theTrackState = TRACK_STATE_SPLIT;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::splitTracksByChannel -- Take the joined tracks and split them
+// back into their separate track identities.
+//
+
+void MidiFile::splitTracksByChannel(void) {
+ joinTracks();
+ if (getTrackState() == TRACK_STATE_SPLIT) {
+ return;
+ }
+
+ int oldTimeState = getTickState();
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeAbsoluteTicks();
+ }
+
+ int maxTrack = 0;
+ int i;
+ MidiEventList& eventlist = *m_events[0];
+ MidiEventList* olddata = &eventlist;
+ int length = eventlist.size();
+ for (i=0; i 0) {
+ trackValue = (eventlist[i][0] & 0x0f) + 1;
+ }
+ m_events[trackValue]->push_back_no_copy(&eventlist[i]);
+ }
+
+ olddata->detach();
+ delete olddata;
+
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeDeltaTicks();
+ }
+
+ m_theTrackState = TRACK_STATE_SPLIT;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getTrackState -- returns what type of track method
+// is being used: either TRACK_STATE_JOINED or TRACK_STATE_SPLIT.
+//
+
+int MidiFile::getTrackState(void) const {
+ return m_theTrackState;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::hasJoinedTracks -- Returns true if the MidiFile tracks
+// are in a joined state.
+//
+
+int MidiFile::hasJoinedTracks(void) const {
+ return m_theTrackState == TRACK_STATE_JOINED;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::hasSplitTracks -- Returns true if the MidiFile tracks
+// are in a split state.
+//
+
+int MidiFile::hasSplitTracks(void) const {
+ return m_theTrackState == TRACK_STATE_SPLIT;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getSplitTrack -- Return the track index when the MidiFile
+// is in the split state. This function returns the original track
+// when the MidiFile is in the joined state. The MidiEvent::track
+// variable is used to store the original track index when the
+// MidiFile is converted to the joined-track state.
+//
+
+int MidiFile::getSplitTrack(int track, int index) const {
+ if (hasSplitTracks()) {
+ return track;
+ } else {
+ return getEvent(track, index).track;
+ }
+}
+
+//
+// When the parameter is void, assume track 0:
+//
+
+int MidiFile::getSplitTrack(int index) const {
+ if (hasSplitTracks()) {
+ return 0;
+ } else {
+ return getEvent(0, index).track;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// tick-related functions --
+//
+
+//////////////////////////////
+//
+// MidiFile::makeDeltaTicks -- convert the time data to
+// delta time, which means that the time field
+// in the MidiEvent struct represents the time
+// since the last event was played. When a MIDI file
+// is read from a file, this is the default setting.
+//
+
+void MidiFile::makeDeltaTicks(void) {
+ if (getTickState() == TIME_STATE_DELTA) {
+ return;
+ }
+ int i, j;
+ int temp;
+ int length = getNumTracks();
+ int *timedata = new int[length];
+ for (i=0; isize() > 0) {
+ timedata[i] = (*m_events[i])[0].tick;
+ } else {
+ continue;
+ }
+ for (j=1; j<(int)m_events[i]->size(); j++) {
+ temp = (*m_events[i])[j].tick;
+ int deltatick = temp - timedata[i];
+ if (deltatick < 0) {
+ std::cerr << "Error: negative delta tick value: " << deltatick << std::endl
+ << "Timestamps must be sorted first"
+ << " (use MidiFile::sortTracks() before writing)." << std::endl;
+ }
+ (*m_events[i])[j].tick = deltatick;
+ timedata[i] = temp;
+ }
+ }
+ m_theTimeState = TIME_STATE_DELTA;
+ delete [] timedata;
+}
+
+//
+// MidiFile::deltaTicks -- Alias for MidiFile::makeDeltaTicks().
+//
+
+void MidiFile::deltaTicks(void) {
+ makeDeltaTicks();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::makeAbsoluteTicks -- convert the time data to
+// absolute time, which means that the time field
+// in the MidiEvent struct represents the exact tick
+// time to play the event rather than the time since
+// the last event to wait untill playing the current
+// event.
+//
+
+void MidiFile::makeAbsoluteTicks(void) {
+ if (getTickState() == TIME_STATE_ABSOLUTE) {
+ return;
+ }
+ int i, j;
+ int length = getNumTracks();
+ int* timedata = new int[length];
+ for (i=0; isize() > 0) {
+ timedata[i] = (*m_events[i])[0].tick;
+ } else {
+ continue;
+ }
+ for (j=1; j<(int)m_events[i]->size(); j++) {
+ timedata[i] += (*m_events[i])[j].tick;
+ (*m_events[i])[j].tick = timedata[i];
+ }
+ }
+ m_theTimeState = TIME_STATE_ABSOLUTE;
+ delete [] timedata;
+}
+
+//
+// MidiFile::absoluteTicks -- Alias for MidiFile::makeAbsoluteTicks().
+//
+
+void MidiFile::absoluteTicks(void) {
+ makeAbsoluteTicks();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getTickState -- returns what type of time method is
+// being used: either TIME_STATE_ABSOLUTE or TIME_STATE_DELTA.
+//
+
+int MidiFile::getTickState(void) const {
+ return m_theTimeState;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::isDeltaTicks -- Returns true if MidiEvent .tick
+// variables are in delta time mode.
+//
+
+bool MidiFile::isDeltaTicks(void) const {
+ return m_theTimeState == TIME_STATE_DELTA ? true : false;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::isAbsoluteTicks -- Returns true if MidiEvent .tick
+// variables are in absolute time mode.
+//
+
+bool MidiFile::isAbsoluteTicks(void) const {
+ return m_theTimeState == TIME_STATE_ABSOLUTE ? true : false;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getFileDurationInTicks -- Returns the largest
+// tick value in any track. The tracks must be sorted
+// before calling this function, since this function
+// assumes that the last MidiEvent in the track has the
+// highest tick timestamp. The file state can be in delta
+// ticks since this function will temporarily go to absolute
+// tick mode for the calculation of the max tick.
+//
+
+int MidiFile::getFileDurationInTicks(void) {
+ bool revertToDelta = false;
+ if (isDeltaTicks()) {
+ makeAbsoluteTicks();
+ revertToDelta = true;
+ }
+ const MidiFile& mf = *this;
+ int output = 0;
+ for (int i=0; i output) {
+ output = mf[i].back().tick;
+ }
+ }
+ if (revertToDelta) {
+ deltaTicks();
+ }
+ return output;
+}
+
+
+
+///////////////////////////////
+//
+// MidiFile::getFileDurationInQuarters -- Returns the Duration of the MidiFile
+// in units of quarter notes. If the MidiFile is in delta tick mode,
+// then temporarily got into absolute tick mode to do the calculations.
+// Note that this is expensive, so you should normally call this function
+// while in aboslute tick (default) mode.
+//
+
+double MidiFile::getFileDurationInQuarters(void) {
+ return (double)getFileDurationInTicks() / (double)getTicksPerQuarterNote();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getFileDurationInSeconds -- returns the duration of the
+// logest track in the file. The tracks must be sorted before
+// calling this function, since this function assumes that the
+// last MidiEvent in the track has the highest timestamp.
+// The file state can be in delta ticks since this function
+// will temporarily go to absolute tick mode for the calculation
+// of the max time.
+
+double MidiFile::getFileDurationInSeconds(void) {
+ if (m_timemapvalid == 0) {
+ buildTimeMap();
+ if (m_timemapvalid == 0) {
+ return -1.0; // something went wrong
+ }
+ }
+ bool revertToDelta = false;
+ if (isDeltaTicks()) {
+ makeAbsoluteTicks();
+ revertToDelta = true;
+ }
+ const MidiFile& mf = *this;
+ double output = 0.0;
+ for (int i=0; i output) {
+ output = mf[i].back().seconds;
+ }
+ }
+ if (revertToDelta) {
+ deltaTicks();
+ }
+ return output;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// physical-time analysis functions --
+//
+
+//////////////////////////////
+//
+// MidiFile::doTimeAnalysis -- Identify the real-time position of
+// all events by monitoring the tempo in relations to the tick
+// times in the file.
+//
+
+void MidiFile::doTimeAnalysis(void) {
+ buildTimeMap();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getTimeInSeconds -- return the time in seconds for
+// the current message.
+//
+
+double MidiFile::getTimeInSeconds(int aTrack, int anIndex) {
+ return getTimeInSeconds(getEvent(aTrack, anIndex).tick);
+}
+
+
+double MidiFile::getTimeInSeconds(int tickvalue) {
+ if (m_timemapvalid == 0) {
+ buildTimeMap();
+ if (m_timemapvalid == 0) {
+ return -1.0; // something went wrong
+ }
+ }
+
+ _TickTime key;
+ key.tick = tickvalue;
+ key.seconds = -1;
+
+ void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(),
+ sizeof(_TickTime), ticksearch);
+
+ if (ptr == NULL) {
+ // The specific tick value was not found, so do a linear
+ // search for the two tick values which occur before and
+ // after the tick value, and do a linear interpolation of
+ // the time in seconds values to figure out the final
+ // time in seconds.
+ // Since the code is not yet written, kill the program at this point:
+ return linearSecondInterpolationAtTick(tickvalue);
+ } else {
+ return ((_TickTime*)ptr)->seconds;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getAbsoluteTickTime -- return the tick value represented
+// by the input time in seconds. If there is not tick entry at
+// the given time in seconds, then interpolate between two values.
+//
+
+double MidiFile::getAbsoluteTickTime(double starttime) {
+ if (m_timemapvalid == 0) {
+ buildTimeMap();
+ if (m_timemapvalid == 0) {
+ return -1.0; // something went wrong
+ }
+ }
+
+ _TickTime key;
+ key.tick = -1;
+ key.seconds = starttime;
+
+ void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(),
+ sizeof(_TickTime), secondsearch);
+
+ if (ptr == NULL) {
+ // The specific seconds value was not found, so do a linear
+ // search for the two time values which occur before and
+ // after the given time value, and do a linear interpolation of
+ // the time in tick values to figure out the final time in ticks.
+ return linearTickInterpolationAtSecond(starttime);
+ } else {
+ return ((_TickTime*)ptr)->tick;
+ }
+
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// note-analysis functions --
+//
+
+//////////////////////////////
+//
+// MidiFile::linkNotePairs -- Link note-ons to note-offs separately
+// for each track. Returns the total number of note message pairs
+// that were linked.
+//
+
+int MidiFile::linkNotePairs(void) {
+ int i;
+ int sum = 0;
+ for (i=0; ilinkNotePairs();
+ }
+ m_linkedEventsQ = true;
+ return sum;
+}
+
+//
+// MidiFile::linkEventPairs -- Alias for MidiFile::linkNotePairs().
+//
+
+int MidiFile::linkEventPairs(void) {
+ return linkNotePairs();
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// filename functions --
+//
+
+//////////////////////////////
+//
+// MidiFile::setFilename -- sets the filename of the MIDI file.
+// Currently removed any directory path.
+//
+
+void MidiFile::setFilename(const std::string& aname) {
+ auto loc = aname.rfind('/');
+ if (loc != std::string::npos) {
+ m_readFileName = aname.substr(loc+1);
+ } else {
+ m_readFileName = aname;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getFilename -- returns the name of the file read into the
+// structure (if the data was read from a file).
+//
+
+const char* MidiFile::getFilename(void) const {
+ return m_readFileName.c_str();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addEvent --
+//
+
+MidiEvent* MidiFile::addEvent(int aTrack, int aTick,
+ std::vector& midiData) {
+ m_timemapvalid = 0;
+ MidiEvent* me = new MidiEvent;
+ me->tick = aTick;
+ me->track = aTrack;
+ me->setMessage(midiData);
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addEvent -- Some bug here when joinedTracks(), but track==1...
+//
+
+MidiEvent* MidiFile::addEvent(MidiEvent& mfevent) {
+ if (getTrackState() == TRACK_STATE_JOINED) {
+ m_events[0]->push_back(mfevent);
+ return &m_events[0]->back();
+ } else {
+ m_events.at(mfevent.track)->push_back(mfevent);
+ return &m_events.at(mfevent.track)->back();
+ }
+}
+
+//
+// Variant where the track is an input parameter:
+//
+
+MidiEvent* MidiFile::addEvent(int aTrack, MidiEvent& mfevent) {
+ if (getTrackState() == TRACK_STATE_JOINED) {
+ m_events[0]->push_back(mfevent);
+ m_events[0]->back().track = aTrack;
+ return &m_events[0]->back();
+ } else {
+ m_events.at(aTrack)->push_back(mfevent);
+ m_events.at(aTrack)->back().track = aTrack;
+ return &m_events.at(aTrack)->back();
+ }
+}
+
+
+
+///////////////////////////////
+//
+// MidiFile::addMetaEvent --
+//
+
+MidiEvent* MidiFile::addMetaEvent(int aTrack, int aTick, int aType,
+ std::vector& metaData) {
+ m_timemapvalid = 0;
+ int i;
+ int length = (int)metaData.size();
+ std::vector fulldata;
+ uchar size[23] = {0};
+ int lengthsize = makeVLV(size, length);
+
+ fulldata.resize(2+lengthsize+length);
+ fulldata[0] = 0xff;
+ fulldata[1] = aType & 0x7F;
+ for (i=0; i buffer;
+ buffer.resize(length);
+ int i;
+ for (i=0; imakeText(text);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addCopyright -- Add a copyright notice meta-message (#2).
+//
+
+MidiEvent* MidiFile::addCopyright(int aTrack, int aTick, const std::string& text) {
+ MidiEvent* me = new MidiEvent;
+ me->makeCopyright(text);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addTrackName -- Add an track name meta-message (#3).
+//
+
+MidiEvent* MidiFile::addTrackName(int aTrack, int aTick, const std::string& name) {
+ MidiEvent* me = new MidiEvent;
+ me->makeTrackName(name);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addInstrumentName -- Add an instrument name meta-message (#4).
+//
+
+MidiEvent* MidiFile::addInstrumentName(int aTrack, int aTick,
+ const std::string& name) {
+ MidiEvent* me = new MidiEvent;
+ me->makeInstrumentName(name);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addLyric -- Add a lyric meta-message (meta #5).
+//
+
+MidiEvent* MidiFile::addLyric(int aTrack, int aTick, const std::string& text) {
+ MidiEvent* me = new MidiEvent;
+ me->makeLyric(text);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addMarker -- Add a marker meta-message (meta #6).
+//
+
+MidiEvent* MidiFile::addMarker(int aTrack, int aTick, const std::string& text) {
+ MidiEvent* me = new MidiEvent;
+ me->makeMarker(text);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addCue -- Add a cue-point meta-message (meta #7).
+//
+
+MidiEvent* MidiFile::addCue(int aTrack, int aTick, const std::string& text) {
+ MidiEvent* me = new MidiEvent;
+ me->makeCue(text);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addTempo -- Add a tempo meta message (meta #0x51).
+//
+
+MidiEvent* MidiFile::addTempo(int aTrack, int aTick, double aTempo) {
+ MidiEvent* me = new MidiEvent;
+ me->makeTempo(aTempo);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addTimeSignature -- Add a time signature meta message
+// (meta #0x58). The "bottom" parameter must be a power of two;
+// otherwise, it will be set to the next highest power of two.
+//
+// Default values:
+// clocksPerClick == 24 (quarter note)
+// num32ndsPerQuarter == 8 (8 32nds per quarter note)
+//
+// Time signature of 4/4 would be:
+// top = 4
+// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2).
+// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter)
+// num32ndsPerQuarter = 8
+//
+// Time signature of 6/8 would be:
+// top = 6
+// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2).
+// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter)
+// num32ndsPerQuarter = 8
+//
+
+MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom,
+ int clocksPerClick, int num32ndsPerQuarter) {
+ MidiEvent* me = new MidiEvent;
+ me->makeTimeSignature(top, bottom, clocksPerClick, num32ndsPerQuarter);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addCompoundTimeSignature -- Add a time signature meta message
+// (meta #0x58), where the clocksPerClick parameter is set to three
+// eighth notes for compount meters such as 6/8 which represents
+// two beats per measure.
+//
+// Default values:
+// clocksPerClick == 36 (quarter note)
+// num32ndsPerQuarter == 8 (8 32nds per quarter note)
+//
+
+MidiEvent* MidiFile::addCompoundTimeSignature(int aTrack, int aTick, int top,
+ int bottom, int clocksPerClick, int num32ndsPerQuarter) {
+ return addTimeSignature(aTrack, aTick, top, bottom, clocksPerClick,
+ num32ndsPerQuarter);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::makeVLV -- This function is used to create
+// size byte(s) for meta-messages. If the size of the data
+// in the meta-message is greater than 127, then the size
+// should (?) be specified as a VLV.
+//
+
+int MidiFile::makeVLV(uchar *buffer, int number) {
+
+ unsigned long value = (unsigned long)number;
+
+ if (value >= (1 << 28)) {
+ std::cerr << "Error: Meta-message size too large to handle" << std::endl;
+ buffer[0] = 0;
+ buffer[1] = 0;
+ buffer[2] = 0;
+ buffer[3] = 0;
+ return 1;
+ }
+
+ buffer[0] = (value >> 21) & 0x7f;
+ buffer[1] = (value >> 14) & 0x7f;
+ buffer[2] = (value >> 7) & 0x7f;
+ buffer[3] = (value >> 0) & 0x7f;
+
+ int i;
+ int flag = 0;
+ int length = -1;
+ for (i=0; i<3; i++) {
+ if (buffer[i] != 0) {
+ flag = 1;
+ }
+ if (flag) {
+ buffer[i] |= 0x80;
+ }
+ if (length == -1 && buffer[i] >= 0x80) {
+ length = 4-i;
+ }
+ }
+
+ if (length == -1) {
+ length = 1;
+ }
+
+ if (length < 4) {
+ for (i=0; imakeNoteOn(aChannel, key, vel);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addNoteOff -- Add a note-off message (using 0x80 messages).
+//
+
+MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key,
+ int vel) {
+ MidiEvent* me = new MidiEvent;
+ me->makeNoteOff(aChannel, key, vel);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addNoteOff -- Add a note-off message (using 0x90 messages with
+// zero attack velocity).
+//
+
+MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key) {
+ MidiEvent* me = new MidiEvent;
+ me->makeNoteOff(aChannel, key);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addController -- Add a controller message in the given
+// track at the given tick time in the given channel.
+//
+
+MidiEvent* MidiFile::addController(int aTrack, int aTick, int aChannel,
+ int num, int value) {
+ MidiEvent* me = new MidiEvent;
+ me->makeController(aChannel, num, value);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addPatchChange -- Add a patch-change message in the given
+// track at the given tick time in the given channel.
+//
+
+MidiEvent* MidiFile::addPatchChange(int aTrack, int aTick, int aChannel,
+ int patchnum) {
+ MidiEvent* me = new MidiEvent;
+ me->makePatchChange(aChannel, patchnum);
+ me->tick = aTick;
+ m_events[aTrack]->push_back_no_copy(me);
+ return me;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addTimbre -- Add a patch-change message in the given
+// track at the given tick time in the given channel. Alias for
+// MidiFile::addPatchChange().
+//
+
+MidiEvent* MidiFile::addTimbre(int aTrack, int aTick, int aChannel, int patchnum) {
+ return addPatchChange(aTrack, aTick, aChannel, patchnum);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addPitchBend -- convert number in the range from -1 to +1
+// into two 7-bit numbers (smallest piece first)
+//
+// -1.0 maps to 0 (0x0000)
+// 0.0 maps to 8192 (0x2000 --> 0x40 0x00)
+// +1.0 maps to 16383 (0x3FFF --> 0x7F 0x7F)
+//
+
+MidiEvent* MidiFile::addPitchBend(int aTrack, int aTick, int aChannel, double amount) {
+ m_timemapvalid = 0;
+ amount += 1.0;
+ int value = int(amount * 8192 + 0.5);
+
+ // prevent any wrap-around in case of round-off errors
+ if (value > 0x3fff) {
+ value = 0x3fff;
+ }
+ if (value < 0) {
+ value = 0;
+ }
+
+ int lsbint = 0x7f & value;
+ int msbint = 0x7f & (value >> 7);
+
+ std::vector mididata;
+ mididata.resize(3);
+ if (aChannel < 0) {
+ aChannel = 0;
+ } else if (aChannel > 15) {
+ aChannel = 15;
+ }
+ mididata[0] = uchar(0xe0 | aChannel);
+ mididata[1] = uchar(lsbint);
+ mididata[2] = uchar(msbint);
+
+ return addEvent(aTrack, aTick, mididata);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// RPN convenience functions:
+//
+
+//////////////////////////////
+//
+// MidiFile::setPitchBendRange -- Set the range for the min/max pitch bend
+// alteration of a note. Default is 2.0 (meaning +/- 2 semitones from given pitch).
+// Fractional values are cents, so 2.5 means a range of two semitones plus 50 cents,
+// which is two semitones plus a quarter tone.
+//
+
+void MidiFile::setPitchBendRange(int aTrack, int aTick, int aChannel, double range) {
+ if (range < 0.0) {
+ range = -range;
+ }
+ if (range > 24.0) {
+ std::cerr << "Warning: pitch bend range is too large: " << range << std::endl;
+ std::cerr << "Setting to 24." << std::endl;
+ range = 24.0;
+ }
+ int irange = int(range);
+ int cents = int((range - irange) * 100.0 + 0.5);
+
+ // Select pitch bend RPN:
+ addController(aTrack, aTick, aChannel, 101, 0); // RPN selector (byte 1)
+ addController(aTrack, aTick, aChannel, 100, 0); // RPN selector (byte 2)
+
+ // Set the semitone range (will be +/-range above/below a note):
+ addController(aTrack, aTick, aChannel, 6, irange); // coarse: number of semitones
+ addController(aTrack, aTick, aChannel, 38, cents); // fine: cents (1/100ths of semitone)
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Controller message adding convenience functions:
+//
+
+//////////////////////////////
+//
+// MidiFile::addSustain -- Add a continuous controller message for the sustain pedal.
+//
+
+MidiEvent* MidiFile::addSustain(int aTrack, int aTick, int aChannel, int value) {
+ return addController(aTrack, aTick, aChannel, 64, value);
+}
+
+//
+// MidiFile::addSustainPedal -- Alias for MidiFile::addSustain().
+//
+
+MidiEvent* MidiFile::addSustainPedal(int aTrack, int aTick, int aChannel, int value) {
+ return addSustain(aTrack, aTick, aChannel, value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addSustainOn -- Add a continuous controller message for the sustain pedal on.
+//
+
+MidiEvent* MidiFile::addSustainOn(int aTrack, int aTick, int aChannel) {
+ return addSustain(aTrack, aTick, aChannel, 127);
+}
+
+//
+// MidiFile::addSustainPedalOn -- Alias for MidiFile::addSustainOn().
+//
+
+MidiEvent* MidiFile::addSustainPedalOn(int aTrack, int aTick, int aChannel) {
+ return addSustainOn(aTrack, aTick, aChannel);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addSustainOff -- Add a continuous controller message for the sustain pedal off.
+//
+
+MidiEvent* MidiFile::addSustainOff(int aTrack, int aTick, int aChannel) {
+ return addSustain(aTrack, aTick, aChannel, 0);
+}
+
+//
+// MidiFile::addSustainPedalOff -- Alias for MidiFile::addSustainOff().
+//
+
+MidiEvent* MidiFile::addSustainPedalOff(int aTrack, int aTick, int aChannel) {
+ return addSustainOff(aTrack, aTick, aChannel);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::addTrack -- adds a blank track at end of the
+// track list. Returns the track number of the added
+// track.
+//
+
+int MidiFile::addTrack(void) {
+ int length = getNumTracks();
+ m_events.resize(length+1);
+ m_events[length] = new MidiEventList;
+ m_events[length]->reserve(10000);
+ m_events[length]->clear();
+ return length;
+}
+
+int MidiFile::addTrack(int count) {
+ int length = getNumTracks();
+ m_events.resize(length+count);
+ int i;
+ for (i=0; ireserve(10000);
+ m_events[length + i]->clear();
+ }
+ return length + count - 1;
+}
+
+//
+// MidiFile::addTracks -- Alias for MidiFile::addTrack().
+//
+
+int MidiFile::addTracks(int count) {
+ return addTrack(count);
+}
+
+
+
+
+//////////////////////////////
+//
+// MidiFile::allocateEvents --
+//
+
+void MidiFile::allocateEvents(int track, int aSize) {
+ int oldsize = m_events[track]->size();
+ if (oldsize < aSize) {
+ m_events[track]->reserve(aSize);
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::deleteTrack -- remove a track from the MidiFile.
+// Tracks are numbered starting at track 0.
+//
+
+void MidiFile::deleteTrack(int aTrack) {
+ int length = getNumTracks();
+ if (aTrack < 0 || aTrack >= length) {
+ return;
+ }
+ if (length == 1) {
+ return;
+ }
+ delete m_events[aTrack];
+ for (int i=aTrack; isize();
+}
+
+
+int MidiFile::getNumEvents(int aTrack) const {
+ return m_events[aTrack]->size();
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::mergeTracks -- combine the data from two
+// tracks into one. Placing the data in the first
+// track location listed, and Moving the other tracks
+// in the file around to fill in the spot where Track2
+// used to be. The results of this function call cannot
+// be reversed.
+//
+
+void MidiFile::mergeTracks(int aTrack1, int aTrack2) {
+ MidiEventList* mergedTrack;
+ mergedTrack = new MidiEventList;
+ int oldTimeState = getTickState();
+ if (oldTimeState == TIME_STATE_DELTA) {
+ makeAbsoluteTicks();
+ }
+ int length = getNumTracks();
+ for (int i=0; i<(int)m_events[aTrack1]->size(); i++) {
+ mergedTrack->push_back((*m_events[aTrack1])[i]);
+ }
+ for (int j=0; j<(int)m_events[aTrack2]->size(); j++) {
+ (*m_events[aTrack2])[j].track = aTrack1;
+ mergedTrack->push_back((*m_events[aTrack2])[j]);
+ }
+
+ mergedTrack->sort();
+
+ delete m_events[aTrack1];
+
+ m_events[aTrack1] = mergedTrack;
+
+ for (int i=aTrack2; isize(); j++) {
+ (*m_events[i])[j].track = i;
+ }
+ }
+
+ m_events[length-1] = NULL;
+ m_events.resize(length-1);
+
+ if (oldTimeState == TIME_STATE_DELTA) {
+ deltaTicks();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::setTicksPerQuarterNote --
+//
+
+void MidiFile::setTicksPerQuarterNote(int ticks) {
+ m_ticksPerQuarterNote = ticks;
+}
+
+//
+// Alias for setTicksPerQuarterNote:
+//
+
+void MidiFile::setTPQ(int ticks) {
+ setTicksPerQuarterNote(ticks);
+}
+
+
+//////////////////////////////
+//
+// MidiFile::setMillisecondTicks -- set the ticks per quarter note
+// value to milliseconds. The format for this specification is
+// highest 8-bits: SMPTE Frame rate (as a negative 2's compliment value).
+// lowest 8-bits: divisions per frame (as a positive number).
+// for millisecond resolution, the SMPTE value is -25, and the
+// frame rate is 40 frame per division. In hexadecimal, these
+// values are: -25 = 1110,0111 = 0xE7 and 40 = 0010,1000 = 0x28
+// So setting the ticks per quarter note value to 0xE728 will cause
+// delta times in the MIDI file to represent milliseconds. Calling
+// this function will not change any exiting timestamps, it will
+// only change the meaning of the timestamps.
+//
+
+void MidiFile::setMillisecondTicks(void) {
+ m_ticksPerQuarterNote = 0xE728;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::sortTrack -- Sort the specified track in tick order.
+// If the MidiEvent::seq variables have been filled in with
+// a sequence value, this will preserve the order of the
+// events that occur at the same tick time before the sort
+// was done.
+//
+
+void MidiFile::sortTrack(int track) {
+ if ((track >= 0) && (track < getTrackCount())) {
+ m_events.at(track)->sort();
+ } else {
+ std::cerr << "Warning: track " << track << " does not exist." << std::endl;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::sortTracks -- sort all tracks in the MidiFile.
+//
+
+void MidiFile::sortTracks(void) {
+ if (m_theTimeState == TIME_STATE_ABSOLUTE) {
+ for (int i=0; isort();
+ }
+ } else {
+ std::cerr << "Warning: Sorting only allowed in absolute tick mode.";
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::getTrackCountAsType1 -- Return the number of tracks in the
+// MIDI file. Returns the size of the events if not in joined state.
+// If in joined state, reads track 0 to find the maximum track
+// value from the original unjoined tracks.
+//
+
+int MidiFile::getTrackCountAsType1(void) {
+ if (getTrackState() == TRACK_STATE_JOINED) {
+ int output = 0;
+ int i;
+ for (i=0; i<(int)m_events[0]->size(); i++) {
+ if (getEvent(0,i).track > output) {
+ output = getEvent(0,i).track;
+ }
+ }
+ return output+1; // I think the track values are 0 offset...
+ } else {
+ return (int)m_events.size();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::clearLinks --
+//
+
+void MidiFile::clearLinks(void) {
+ for (int i=0; iclearLinks();
+ }
+ m_linkedEventsQ = false;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// private functions
+//
+
+//////////////////////////////
+//
+// MidiFile::linearTickInterpolationAtSecond -- return the tick value at the
+// given input time.
+//
+
+double MidiFile::linearTickInterpolationAtSecond(double seconds) {
+ if (m_timemapvalid == 0) {
+ buildTimeMap();
+ if (m_timemapvalid == 0) {
+ return -1.0; // something went wrong
+ }
+ }
+
+ int i;
+ double lasttime = m_timemap[m_timemap.size()-1].seconds;
+ // give an error value of -1 if time is out of range of data.
+ if (seconds < 0.0) {
+ return -1.0;
+ }
+ if (seconds > m_timemap[m_timemap.size()-1].seconds) {
+ return -1.0;
+ }
+
+ // Guess which side of the list is closest to target:
+ // Could do a more efficient algorithm since time values are sorted,
+ // but good enough for now...
+ int startindex = -1;
+ if (seconds < lasttime / 2) {
+ for (i=0; i<(int)m_timemap.size(); i++) {
+ if (m_timemap[i].seconds > seconds) {
+ startindex = i-1;
+ break;
+ } else if (m_timemap[i].seconds == seconds) {
+ startindex = i;
+ break;
+ }
+ }
+ } else {
+ for (i=(int)m_timemap.size()-1; i>0; i--) {
+ if (m_timemap[i].seconds < seconds) {
+ startindex = i+1;
+ break;
+ } else if (m_timemap[i].seconds == seconds) {
+ startindex = i;
+ break;
+ }
+ }
+ }
+
+ if (startindex < 0) {
+ return -1.0;
+ }
+ if (startindex >= (int)m_timemap.size()-1) {
+ return -1.0;
+ }
+
+ double x1 = m_timemap[startindex].seconds;
+ double x2 = m_timemap[startindex+1].seconds;
+ double y1 = m_timemap[startindex].tick;
+ double y2 = m_timemap[startindex+1].tick;
+ double xi = seconds;
+
+ return (xi-x1) * ((y2-y1)/(x2-x1)) + y1;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::linearSecondInterpolationAtTick -- return the time in seconds
+// value at the given input tick time. (Ticks input could be made double).
+//
+
+double MidiFile::linearSecondInterpolationAtTick(int ticktime) {
+ if (m_timemapvalid == 0) {
+ buildTimeMap();
+ if (m_timemapvalid == 0) {
+ return -1.0; // something went wrong
+ }
+ }
+
+ int i;
+ double lasttick = m_timemap[m_timemap.size()-1].tick;
+ // give an error value of -1 if time is out of range of data.
+ if (ticktime < 0.0) {
+ return -1;
+ }
+ if (ticktime > m_timemap.back().tick) {
+ return -1; // don't try to extrapolate
+ }
+
+ // Guess which side of the list is closest to target:
+ // Could do a more efficient algorithm since time values are sorted,
+ // but good enough for now...
+ int startindex = -1;
+ if (ticktime < lasttick / 2) {
+ for (i=0; i<(int)m_timemap.size(); i++) {
+ if (m_timemap[i].tick > ticktime) {
+ startindex = i-1;
+ break;
+ } else if (m_timemap[i].tick == ticktime) {
+ startindex = i;
+ break;
+ }
+ }
+ } else {
+ for (i=(int)m_timemap.size()-1; i>0; i--) {
+ if (m_timemap[i].tick < ticktime) {
+ startindex = i;
+ break;
+ } else if (m_timemap[i].tick == ticktime) {
+ startindex = i;
+ break;
+ }
+ }
+ }
+
+ if (startindex < 0) {
+ return -1;
+ }
+ if (startindex >= (int)m_timemap.size()-1) {
+ return -1;
+ }
+
+ if (m_timemap[startindex].tick == ticktime) {
+ return m_timemap[startindex].seconds;
+ }
+
+ double x1 = m_timemap[startindex].tick;
+ double x2 = m_timemap[startindex+1].tick;
+ double y1 = m_timemap[startindex].seconds;
+ double y2 = m_timemap[startindex+1].seconds;
+ double xi = ticktime;
+
+ return (xi-x1) * ((y2-y1)/(x2-x1)) + y1;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::buildTimeMap -- build an index of the absolute tick values
+// found in a MIDI file, and their corresponding time values in
+// seconds, taking into consideration tempo change messages. If no
+// tempo messages are given (or untill they are given, then the
+// tempo is set to 120 beats per minute). If SMPTE time code is
+// used, then ticks are actually time values. So don't build
+// a time map for SMPTE ticks, and just calculate the time in
+// seconds from the tick value (1000 ticks per second SMPTE
+// is the only mode tested (25 frames per second and 40 subframes
+// per frame).
+//
+
+void MidiFile::buildTimeMap(void) {
+
+ // convert the MIDI file to absolute time representation
+ // in single track mode (and undo if the MIDI file was not
+ // in that state when this function was called.
+ //
+ int trackstate = getTrackState();
+ int timestate = getTickState();
+
+ makeAbsoluteTicks();
+ joinTracks();
+
+ int allocsize = getNumEvents(0);
+ m_timemap.reserve(allocsize+10);
+ m_timemap.clear();
+
+ _TickTime value;
+
+ int lasttick = 0;
+ int tickinit = 0;
+
+ int i;
+ int tpq = getTicksPerQuarterNote();
+ double defaultTempo = 120.0;
+ double secondsPerTick = 60.0 / (defaultTempo * tpq);
+
+ double lastsec = 0.0;
+ double cursec = 0.0;
+
+ for (i=0; i lasttick) || !tickinit) {
+ tickinit = 1;
+
+ // calculate the current time in seconds:
+ cursec = lastsec + (curtick - lasttick) * secondsPerTick;
+ getEvent(0, i).seconds = cursec;
+
+ // store the new tick to second mapping
+ value.tick = curtick;
+ value.seconds = cursec;
+ m_timemap.push_back(value);
+ lasttick = curtick;
+ lastsec = cursec;
+ }
+
+ // update the tempo if needed:
+ if (getEvent(0,i).isTempo()) {
+ secondsPerTick = getEvent(0,i).getTempoSPT(getTicksPerQuarterNote());
+ }
+ }
+
+ // reset the states of the tracks or time values if necessary here:
+ if (timestate == TIME_STATE_DELTA) {
+ deltaTicks();
+ }
+ if (trackstate == TRACK_STATE_SPLIT) {
+ splitTracks();
+ }
+
+ m_timemapvalid = 1;
+
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::extractMidiData -- Extract MIDI data from input
+// stream. Return value is 0 if failure; otherwise, returns 1.
+//
+
+int MidiFile::extractMidiData(std::istream& input, std::vector& array,
+ uchar& runningCommand) {
+
+ int character;
+ uchar byte;
+ array.clear();
+ int runningQ;
+
+ character = input.get();
+ if (character == EOF) {
+ std::cerr << "Error: unexpected end of file." << std::endl;
+ return 0;
+ } else {
+ byte = (uchar)character;
+ }
+
+ if (byte < 0x80) {
+ runningQ = 1;
+ if (runningCommand == 0) {
+ std::cerr << "Error: running command with no previous command" << std::endl;
+ return 0;
+ }
+ if (runningCommand >= 0xf0) {
+ std::cerr << "Error: running status not permitted with meta and sysex"
+ << " event." << std::endl;
+ std::cerr << "Byte is 0x" << std::hex << (int)byte << std::dec << std::endl;
+ return 0;
+ }
+ } else {
+ runningCommand = byte;
+ runningQ = 0;
+ }
+
+ array.push_back(runningCommand);
+ if (runningQ) {
+ array.push_back(byte);
+ }
+
+ switch (runningCommand & 0xf0) {
+ case 0x80: // note off (2 more bytes)
+ case 0x90: // note on (2 more bytes)
+ case 0xA0: // aftertouch (2 more bytes)
+ case 0xB0: // cont. controller (2 more bytes)
+ case 0xE0: // pitch wheel (2 more bytes)
+ byte = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ if (byte > 0x7f) {
+ std::cerr << "MIDI data byte too large: " << (int)byte << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+ array.push_back(byte);
+ if (!runningQ) {
+ byte = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ if (byte > 0x7f) {
+ std::cerr << "MIDI data byte too large: " << (int)byte << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+ array.push_back(byte);
+ }
+ break;
+ case 0xC0: // patch change (1 more byte)
+ case 0xD0: // channel pressure (1 more byte)
+ if (!runningQ) {
+ byte = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ if (byte > 0x7f) {
+ std::cerr << "MIDI data byte too large: " << (int)byte << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ }
+ array.push_back(byte);
+ }
+ break;
+ case 0xF0:
+ switch (runningCommand) {
+ case 0xff: // meta event
+ {
+ if (!runningQ) {
+ byte = readByte(input); // meta type
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte);
+ }
+ ulong length = 0;
+ uchar byte1 = 0;
+ uchar byte2 = 0;
+ uchar byte3 = 0;
+ uchar byte4 = 0;
+ byte1 = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte1);
+ if (byte1 >= 0x80) {
+ byte2 = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte2);
+ if (byte2 > 0x80) {
+ byte3 = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte3);
+ if (byte3 >= 0x80) {
+ byte4 = readByte(input);
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte4);
+ if (byte4 >= 0x80) {
+ std::cerr << "Error: cannot handle large VLVs" << std::endl;
+ m_rwstatus = false; return m_rwstatus;
+ } else {
+ length = unpackVLV(byte1, byte2, byte3, byte4);
+ if (!m_rwstatus) { return m_rwstatus; }
+ }
+ } else {
+ length = unpackVLV(byte1, byte2, byte3);
+ if (!m_rwstatus) { return m_rwstatus; }
+ }
+ } else {
+ length = unpackVLV(byte1, byte2);
+ if (!m_rwstatus) { return m_rwstatus; }
+ }
+ } else {
+ length = byte1;
+ }
+ for (int j=0; j<(int)length; j++) {
+ byte = readByte(input); // meta type
+ if (!status()) { return m_rwstatus; }
+ array.push_back(byte);
+ }
+ }
+ break;
+
+ // The 0xf0 and 0xf7 meta commands deal with system-exclusive
+ // messages. 0xf0 is used to either start a message or to store
+ // a complete message. The 0xf0 is part of the outgoing MIDI
+ // bytes. The 0xf7 message is used to send arbitrary bytes,
+ // typically the middle or ends of system exclusive messages. The
+ // 0xf7 byte at the start of the message is not part of the
+ // outgoing raw MIDI bytes, but is kept in the MidiFile message
+ // to indicate a raw MIDI byte message (typically a partial
+ // system exclusive message).
+ case 0xf7: // Raw bytes. 0xf7 is not part of the raw
+ // bytes, but are included to indicate
+ // that this is a raw byte message.
+ case 0xf0: // System Exclusive message
+ { // (complete, or start of message).
+ int length = (int)readVLValue(input);
+ for (int i=0; i 0x7f)) {
+ count++;
+ }
+ count++;
+ if (count >= 6) {
+ std::cerr << "VLV number is too large" << std::endl;
+ m_rwstatus = false;
+ return 0;
+ }
+
+ ulong output = 0;
+ for (int i=0; i& outdata) {
+ uchar bytes[4] = {0};
+
+ if ((unsigned long)aValue >= (1 << 28)) {
+ std::cerr << "Error: number too large to convert to VLV" << std::endl;
+ aValue = 0x0FFFffff;
+ }
+
+ bytes[0] = (uchar)(((ulong)aValue >> 21) & 0x7f); // most significant 7 bits
+ bytes[1] = (uchar)(((ulong)aValue >> 14) & 0x7f);
+ bytes[2] = (uchar)(((ulong)aValue >> 7) & 0x7f);
+ bytes[3] = (uchar)(((ulong)aValue) & 0x7f); // least significant 7 bits
+
+ int start = 0;
+ while ((start<4) && (bytes[start] == 0)) start++;
+
+ for (int i=start; i<3; i++) {
+ bytes[i] = bytes[i] | 0x80;
+ outdata.push_back(bytes[i]);
+ }
+ outdata.push_back(bytes[3]);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::clear_no_deallocate -- Similar to clear() but does not
+// delete the Events in the lists. This is primarily used internally
+// to the MidiFile class, so don't use unless you really know what you
+// are doing (otherwise you will end up with memory leaks or
+// segmentation faults).
+//
+
+void MidiFile::clear_no_deallocate(void) {
+ for (int i=0; idetach();
+ delete m_events[i];
+ m_events[i] = NULL;
+ }
+ m_events.resize(1);
+ m_events[0] = new MidiEventList;
+ m_timemapvalid=0;
+ m_timemap.clear();
+ // m_events.resize(0); // causes a memory leak [20150205 Jorden Thatcher]
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::ticksearch -- for finding a tick entry in the time map.
+//
+
+int MidiFile::ticksearch(const void* A, const void* B) {
+ _TickTime& a = *((_TickTime*)A);
+ _TickTime& b = *((_TickTime*)B);
+
+ if (a.tick < b.tick) {
+ return -1;
+ } else if (a.tick > b.tick) {
+ return 1;
+ }
+ return 0;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::secondsearch -- for finding a second entry in the time map.
+//
+
+int MidiFile::secondsearch(const void* A, const void* B) {
+ _TickTime& a = *((_TickTime*)A);
+ _TickTime& b = *((_TickTime*)B);
+
+ if (a.seconds < b.seconds) {
+ return -1;
+ } else if (a.seconds > b.seconds) {
+ return 1;
+ }
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Static functions:
+//
+
+
+//////////////////////////////
+//
+// MidiFile::readLittleEndian4Bytes -- Read four bytes which are in
+// little-endian order (smallest byte is first). Then flip
+// the order of the bytes to create the return value.
+//
+
+ulong MidiFile::readLittleEndian4Bytes(std::istream& input) {
+ uchar buffer[4] = {0};
+ input.read((char*)buffer, 4);
+ if (input.eof()) {
+ std::cerr << "Error: unexpected end of file." << std::endl;
+ return 0;
+ }
+ return buffer[3] | (buffer[2] << 8) | (buffer[1] << 16) | (buffer[0] << 24);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::readLittleEndian2Bytes -- Read two bytes which are in
+// little-endian order (smallest byte is first). Then flip
+// the order of the bytes to create the return value.
+//
+
+ushort MidiFile::readLittleEndian2Bytes(std::istream& input) {
+ uchar buffer[2] = {0};
+ input.read((char*)buffer, 2);
+ if (input.eof()) {
+ std::cerr << "Error: unexpected end of file." << std::endl;
+ return 0;
+ }
+ return buffer[1] | (buffer[0] << 8);
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::readByte -- Read one byte from input stream. Set
+// fail status error if there was a problem (calling function
+// has to check this status for an error after reading).
+//
+
+uchar MidiFile::readByte(std::istream& input) {
+ uchar buffer[1] = {0};
+ input.read((char*)buffer, 1);
+ if (input.eof()) {
+ std::cerr << "Error: unexpected end of file." << std::endl;
+ m_rwstatus = false;
+ return 0;
+ }
+ return buffer[0];
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianUShort --
+//
+
+std::ostream& MidiFile::writeLittleEndianUShort(std::ostream& out, ushort value) {
+ union { char bytes[2]; ushort us; } data;
+ data.us = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianUShort --
+//
+
+std::ostream& MidiFile::writeBigEndianUShort(std::ostream& out, ushort value) {
+ union { char bytes[2]; ushort us; } data;
+ data.us = value;
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianShort --
+//
+
+std::ostream& MidiFile::writeLittleEndianShort(std::ostream& out, short value) {
+ union { char bytes[2]; short s; } data;
+ data.s = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianShort --
+//
+
+std::ostream& MidiFile::writeBigEndianShort(std::ostream& out, short value) {
+ union { char bytes[2]; short s; } data;
+ data.s = value;
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianULong --
+//
+
+std::ostream& MidiFile::writeLittleEndianULong(std::ostream& out, ulong value) {
+ union { char bytes[4]; ulong ul; } data;
+ data.ul = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianULong --
+//
+
+std::ostream& MidiFile::writeBigEndianULong(std::ostream& out, ulong value) {
+ union { char bytes[4]; long ul; } data;
+ data.ul = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianLong --
+//
+
+std::ostream& MidiFile::writeLittleEndianLong(std::ostream& out, long value) {
+ union { char bytes[4]; long l; } data;
+ data.l = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianLong --
+//
+
+std::ostream& MidiFile::writeBigEndianLong(std::ostream& out, long value) {
+ union { char bytes[4]; long l; } data;
+ data.l = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianFloat --
+//
+
+std::ostream& MidiFile::writeBigEndianFloat(std::ostream& out, float value) {
+ union { char bytes[4]; float f; } data;
+ data.f = value;
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianFloat --
+//
+
+std::ostream& MidiFile::writeLittleEndianFloat(std::ostream& out, float value) {
+ union { char bytes[4]; float f; } data;
+ data.f = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeBigEndianDouble --
+//
+
+std::ostream& MidiFile::writeBigEndianDouble(std::ostream& out, double value) {
+ union { char bytes[8]; double d; } data;
+ data.d = value;
+ out << data.bytes[7];
+ out << data.bytes[6];
+ out << data.bytes[5];
+ out << data.bytes[4];
+ out << data.bytes[3];
+ out << data.bytes[2];
+ out << data.bytes[1];
+ out << data.bytes[0];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::writeLittleEndianDouble --
+//
+
+std::ostream& MidiFile::writeLittleEndianDouble(std::ostream& out, double value) {
+ union { char bytes[8]; double d; } data;
+ data.d = value;
+ out << data.bytes[0];
+ out << data.bytes[1];
+ out << data.bytes[2];
+ out << data.bytes[3];
+ out << data.bytes[4];
+ out << data.bytes[5];
+ out << data.bytes[6];
+ out << data.bytes[7];
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::base64Encode -- Encode a string as base64.
+//
+
+std::string MidiFile::base64Encode(const std::string& input) {
+ std::string output;
+ output.reserve(((input.size()/3) + (input.size() % 3 > 0)) * 4);
+ int vala = 0;
+ int valb = -6;
+ for (unsigned char c : input) {
+ vala = (vala << 8) + c;
+ valb += 8;
+ while (valb >=0) {
+ output.push_back(MidiFile::encodeLookup[(vala >> valb) & 0x3F]);
+ valb -= 6;
+ }
+ }
+ if (valb > -6) {
+ output.push_back(MidiFile::encodeLookup[((vala << 8) >> (valb + 8)) & 0x3F]);
+ }
+ while (output.size() % 4) {
+ output.push_back(MidiFile::encodeLookup.back());
+ }
+ return output;
+}
+
+
+
+//////////////////////////////
+//
+// MidiFile::base64Decode -- Decode a base64 string.
+//
+
+std::string MidiFile::base64Decode(const std::string& input) {
+ // vector decodeLookup(256,-1);
+ // for (int i=0; i<64; i++) decodeLookup[encodeLookup[i]] = i;
+
+ std::string output;
+ int vala = 0;
+ int valb = -8;
+ for (unsigned char c : input) {
+ if (c == '=') {
+ break;
+ } else if (MidiFile::decodeLookup[c] == -1) {
+ // Ignore whitespace, for example.
+ continue;
+ }
+ vala = (vala << 6) + MidiFile::decodeLookup[c];
+ valb += 6;
+ if (valb >= 0) {
+ output.push_back(char((vala >> valb) & 0xFF));
+ valb -= 8;
+ }
+ }
+ return output;
+}
+
+
+
+} // end namespace smf
+
+///////////////////////////////////////////////////////////////////////////
+//
+// external functions
+//
+
+//////////////////////////////
+//
+// operator<< -- for printing an ASCII version of the MIDI file
+//
+
+std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile) {
+ aMidiFile.writeBinascWithComments(out);
+ return out;
+}
+
+
+
diff --git a/midifile/src/MidiMessage.cpp b/midifile/src/MidiMessage.cpp
new file mode 100644
index 0000000..4edf9af
--- /dev/null
+++ b/midifile/src/MidiMessage.cpp
@@ -0,0 +1,1934 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sat Feb 14 20:49:21 PST 2015
+// Last Modified: Sun Apr 15 11:11:05 PDT 2018 Added event removal system.
+// Filename: midifile/src/MidiMessage.cpp
+// Website: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: Storage for bytes of a MIDI message for Standard
+// MIDI Files.
+//
+
+#include "MidiMessage.h"
+
+#include
+#include
+#include
+
+
+namespace smf {
+
+//////////////////////////////
+//
+// MidiMessage::MidiMessage -- Constructor.
+//
+
+MidiMessage::MidiMessage(void) : vector() {
+ // do nothing
+}
+
+
+MidiMessage::MidiMessage(int command) : vector(1, (uchar)command) {
+ // do nothing
+}
+
+
+MidiMessage::MidiMessage(int command, int p1) : vector(2) {
+ (*this)[0] = (uchar)command;
+ (*this)[1] = (uchar)p1;
+}
+
+
+MidiMessage::MidiMessage(int command, int p1, int p2) : vector(3) {
+ (*this)[0] = (uchar)command;
+ (*this)[1] = (uchar)p1;
+ (*this)[2] = (uchar)p2;
+}
+
+
+MidiMessage::MidiMessage(const MidiMessage& message) : vector() {
+ (*this) = message;
+}
+
+
+MidiMessage::MidiMessage(const std::vector& message) : vector() {
+ setMessage(message);
+}
+
+
+MidiMessage::MidiMessage(const std::vector& message) : vector() {
+ setMessage(message);
+}
+
+
+MidiMessage::MidiMessage(const std::vector& message) : vector() {
+ setMessage(message);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::~MidiMessage -- Deconstructor.
+//
+
+MidiMessage::~MidiMessage() {
+ resize(0);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::operator= --
+//
+
+MidiMessage& MidiMessage::operator=(const MidiMessage& message) {
+ if (this == &message) {
+ return *this;
+ }
+ std::vector::operator=(static_cast &>(message));
+ return *this;
+}
+
+
+MidiMessage& MidiMessage::operator=(const std::vector& bytes) {
+ if (this == &bytes) {
+ return *this;
+ }
+ setMessage(bytes);
+ return *this;
+}
+
+
+MidiMessage& MidiMessage::operator=(const std::vector& bytes) {
+ setMessage(bytes);
+ return *this;
+}
+
+
+MidiMessage& MidiMessage::operator=(const std::vector& bytes) {
+ setMessage(bytes);
+ return *this;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setSize -- Change the size of the message byte list.
+// If the size is increased, then the new bytes are not initialized
+// to any specific values.
+//
+
+void MidiMessage::setSize(int asize) {
+ this->resize(asize);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getSize -- Return the size of the MIDI message bytes.
+//
+
+int MidiMessage::getSize(void) const {
+ return (int)this->size();
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setSizeToCommand -- Set the number of parameters if the
+// command byte is set in the range from 0x80 to 0xef. Any newly
+// added parameter bytes will be set to 0. Commands in the range
+// of 0xF) should not use this function, and they will ignore
+// modification by this command.
+//
+
+int MidiMessage::setSizeToCommand(void) {
+ int osize = (int)this->size();
+ if (osize < 1) {
+ return 0;
+ }
+ int command = getCommandNibble();
+ if (command < 0) {
+ return 0;
+ }
+ int bytecount = 1;
+ switch (command) {
+ case 0x80: bytecount = 2; break; // Note Off
+ case 0x90: bytecount = 2; break; // Note On
+ case 0xA0: bytecount = 2; break; // Aftertouch
+ case 0xB0: bytecount = 2; break; // Continuous Controller
+ case 0xC0: bytecount = 1; break; // Patch Change
+ case 0xD0: bytecount = 1; break; // Channel Pressure
+ case 0xE0: bytecount = 2; break; // Pitch Bend
+ case 0xF0:
+ default:
+ return (int)size();
+ }
+ if (bytecount + 1 < osize) {
+ resize(bytecount+1);
+ for (int i=osize; i= 64) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isSustainOff -- Returns true if a sustain-pedal-off control message.
+// Sustain-off is a value in the range from 0-63 for controller 64.
+//
+
+bool MidiMessage::isSustainOff(void) const {
+ if (!isSustain()) {
+ return false;
+ }
+ if (getP2() < 64) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isSoft -- Returns true if the MidiMessages is a soft pedal
+// control event. Controller 67 is the sustain pedal for general MIDI.
+//
+
+bool MidiMessage::isSoft(void) const {
+ if (!isController()) {
+ return false;
+ }
+ if (getP1() == 67) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isSoftOn -- Returns true if a sustain-pedal-on control message.
+// Soft-on is a value in the range from 64-127 for controller 67.
+//
+
+bool MidiMessage::isSoftOn(void) const {
+ if (!isSoft()) {
+ return false;
+ }
+ if (getP2() >= 64) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isSoftOff -- Returns true if a sustain-pedal-off control message.
+// Soft-off is a value in the range from 0-63 for controller 67.
+//
+
+bool MidiMessage::isSoftOff(void) const {
+ if (!isSoft()) {
+ return false;
+ }
+ if (getP2() < 64) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isTimbre -- Returns true of a patch change message
+// (command nibble 0xc0).
+//
+
+bool MidiMessage::isTimbre(void) const {
+ if (((*this)[0] & 0xf0) != 0xc0) {
+ return false;
+ } else if (size() != 2) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+bool MidiMessage::isPatchChange(void) const {
+ return isTimbre();
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isPressure -- Returns true of a channel pressure message
+// (command nibble 0xd0).
+//
+
+bool MidiMessage::isPressure(void) const {
+ if (((*this)[0] & 0xf0) != 0xd0) {
+ return false;
+ } else if (size() != 2) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isPitchbend -- Returns true of a pitch-bend message
+// (command nibble 0xe0).
+//
+
+bool MidiMessage::isPitchbend(void) const {
+ if (((*this)[0] & 0xf0) != 0xe0) {
+ return false;
+ } else if (size() != 3) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isEmpty -- Returns true if size of data array is zero.
+//
+
+bool MidiMessage::isEmpty(void) const {
+ return empty();
+}
+
+
+
+///////////////////////////////
+//
+// MidiMessage::getMetaType -- returns the meta-message type for the
+// MidiMessage. If the message is not a meta message, then returns
+// -1.
+//
+
+int MidiMessage::getMetaType(void) const {
+ if (!isMetaMessage()) {
+ return -1;
+ } else {
+ return (int)(*this)[1];
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isText -- Returns true if message is a meta
+// message describing some text (meta message type 0x01).
+//
+
+bool MidiMessage::isText(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x01) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isCopyright -- Returns true if message is a meta
+// message describing a copyright notice (meta message type 0x02).
+// Copyright messages should be at absolute tick position 0
+// (and be the first event in the track chunk as well), but this
+// function does not check for those requirements.
+//
+
+bool MidiMessage::isCopyright(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x02) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isTrackName -- Returns true if message is a meta
+// message describing a track name (meta message type 0x03).
+//
+
+bool MidiMessage::isTrackName(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x03) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isInstrumentName -- Returns true if message is a
+// meta message describing an instrument name (for the track)
+// (meta message type 0x04).
+//
+
+bool MidiMessage::isInstrumentName(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x04) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isLyricText -- Returns true if message is a meta message
+// describing some lyric text (for karakoke MIDI files)
+// (meta message type 0x05).
+//
+
+bool MidiMessage::isLyricText(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x05) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isMarkerText -- Returns true if message is a meta message
+// describing a marker text (meta message type 0x06).
+//
+
+bool MidiMessage::isMarkerText(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x06) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isTempo -- Returns true if message is a meta message
+// describing tempo (meta message type 0x51).
+//
+
+bool MidiMessage::isTempo(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x51) {
+ return false;
+ } else if (size() != 6) {
+ // Meta tempo message can only be 6 bytes long.
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isTimeSignature -- Returns true if message is
+// a meta message describing a time signature (meta message
+// type 0x58).
+//
+
+bool MidiMessage::isTimeSignature(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x58) {
+ return false;
+ } else if (size() != 7) {
+ // Meta time signature message can only be 7 bytes long:
+ // FF 58 <32nds>
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::isKeySignature -- Returns true if message is
+// a meta message describing a key signature (meta message
+// type 0x59).
+//
+
+bool MidiMessage::isKeySignature(void) const {
+ if (!isMetaMessage()) {
+ return false;
+ } else if ((*this)[1] != 0x59) {
+ return false;
+ } else if (size() != 5) {
+ // Meta key signature message can only be 5 bytes long:
+ // FF 59
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+//////////////////////////////
+//
+// MidiMessage::isEndOfTrack -- Returns true if message is a meta message
+// for end-of-track (meta message type 0x2f).
+//
+
+bool MidiMessage::isEndOfTrack(void) const {
+ return getMetaType() == 0x2f ? 1 : 0;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getP0 -- Return index 1 byte, or -1 if it doesn't exist.
+//
+
+int MidiMessage::getP0(void) const {
+ return size() < 1 ? -1 : (*this)[0];
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getP1 -- Return index 1 byte, or -1 if it doesn't exist.
+//
+
+int MidiMessage::getP1(void) const {
+ return size() < 2 ? -1 : (*this)[1];
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getP2 -- Return index 2 byte, or -1 if it doesn't exist.
+//
+
+int MidiMessage::getP2(void) const {
+ return size() < 3 ? -1 : (*this)[2];
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getP3 -- Return index 3 byte, or -1 if it doesn't exist.
+//
+
+int MidiMessage::getP3(void) const {
+ return size() < 4 ? -1 : (*this)[3];
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getKeyNumber -- Return the key number (such as 60 for
+// middle C). If the message does not have a note parameter, then
+// return -1; if the key is invalid (above 127 in value), then
+// limit to the range 0 to 127.
+//
+
+int MidiMessage::getKeyNumber(void) const {
+ if (isNote() || isAftertouch()) {
+ int output = getP1();
+ if (output < 0) {
+ return output;
+ } else {
+ return 0xff & output;
+ }
+ } else {
+ return -1;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getVelocity -- Return the key veolocity. If the message
+// is not a note-on or a note-off, then return -1. If the value is
+// out of the range 0-127, then chop off the high-bits.
+//
+
+int MidiMessage::getVelocity(void) const {
+ if (isNote()) {
+ int output = getP2();
+ if (output < 0) {
+ return output;
+ } else {
+ return 0xff & output;
+ }
+ } else {
+ return -1;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getControllerNumber -- Return the controller number (such as 1
+// for modulation wheel). If the message does not have a controller number
+// parameter, then return -1. If the controller number is invalid (above 127
+// in value), then limit the range to to 0-127.
+//
+
+int MidiMessage::getControllerNumber(void) const {
+ if (isController()) {
+ int output = getP1();
+ if (output < 0) {
+ // -1 means no P1, although isController() is false in such a case.
+ return output;
+ } else {
+ return 0x7f & output;
+ }
+ } else {
+ return -1;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getControllerValue -- Return the controller value. If the
+// message is not a control change message, then return -1. If the value is
+// out of the range 0-127, then chop off the high-bits.
+//
+
+int MidiMessage::getControllerValue(void) const {
+ if (isController()) {
+ int output = getP2();
+ if (output < 0) {
+ // -1 means no P2, although isController() is false in such a case.
+ return output;
+ } else {
+ return 0x7f & output;
+ }
+ } else {
+ return -1;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setP0 -- Set the command byte.
+// If the MidiMessage is too short, add extra spaces to
+// allow for P0. The value should be in the range from
+// 128 to 255, but this function will not babysit you.
+//
+
+void MidiMessage::setP0(int value) {
+ if (getSize() < 1) {
+ resize(1);
+ }
+ (*this)[0] = static_cast(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setP1 -- Set the first parameter value.
+// If the MidiMessage is too short, add extra spaces to
+// allow for P1. The command byte will be undefined if
+// it was added. The value should be in the range from
+// 0 to 127, but this function will not babysit you.
+//
+
+void MidiMessage::setP1(int value) {
+ if (getSize() < 2) {
+ resize(2);
+ }
+ (*this)[1] = static_cast(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setP2 -- Set the second paramter value.
+// If the MidiMessage is too short, add extra spaces
+// to allow for P2. The command byte and/or the P1 value
+// will be undefined if extra space needs to be added and
+// those slots are created. The value should be in the range
+// from 0 to 127, but this function will not babysit you.
+//
+
+void MidiMessage::setP2(int value) {
+ if (getSize() < 3) {
+ resize(3);
+ }
+ (*this)[2] = static_cast(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setP3 -- Set the third paramter value.
+// If the MidiMessage is too short, add extra spaces
+// to allow for P3. The command byte and/or the P1/P2 values
+// will be undefined if extra space needs to be added and
+// those slots are created. The value should be in the range
+// from 0 to 127, but this function will not babysit you.
+//
+
+void MidiMessage::setP3(int value) {
+ if (getSize() < 4) {
+ resize(4);
+ }
+ (*this)[3] = static_cast(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setKeyNumber -- Set the note on/off key number (or
+// aftertouch key). Ignore if not note or aftertouch message.
+// Limits the input value to the range from 0 to 127.
+//
+
+void MidiMessage::setKeyNumber(int value) {
+ if (isNote() || isAftertouch()) {
+ setP1(value & 0xff);
+ } else {
+ // don't do anything since this is not a note-related message.
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setVelocity -- Set the note on/off velocity; ignore
+// if note a note message. Limits the input value to the range
+// from 0 to 127.
+//
+
+void MidiMessage::setVelocity(int value) {
+ if (isNote()) {
+ setP2(value & 0xff);
+ } else {
+ // don't do anything since this is not a note-related message.
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getCommandNibble -- Returns the top 4 bits of the (*this)[0]
+// entry, or -1 if there is not (*this)[0].
+//
+
+int MidiMessage::getCommandNibble(void) const {
+ if (size() < 1) {
+ return -1;
+ } else {
+ return (*this)[0] & 0xf0;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getCommandByte -- Return the command byte or -1 if not
+// allocated.
+//
+
+int MidiMessage::getCommandByte(void) const {
+ if (size() < 1) {
+ return -1;
+ } else {
+ return (*this)[0];
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getChannelNibble -- Returns the bottom 4 bites of the
+// (*this)[0] entry, or -1 if there is not (*this)[0]. Should be refined
+// to return -1 if the top nibble is 0xf0, since those commands are
+// not channel specific.
+//
+
+int MidiMessage::getChannelNibble(void) const {
+ if (size() < 1) {
+ return -1;
+ } else {
+ return (*this)[0] & 0x0f;
+ }
+}
+
+
+int MidiMessage::getChannel(void) const {
+ return getChannelNibble();
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setCommandByte --
+//
+
+void MidiMessage::setCommandByte(int value) {
+ if (size() < 1) {
+ resize(1);
+ } else {
+ (*this)[0] = (uchar)(value & 0xff);
+ }
+}
+
+void MidiMessage::setCommand(int value) {
+ setCommandByte(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setCommand -- Set the command byte and parameter bytes
+// for a MidiMessage. The size of the message will be adjusted to
+// the number of input parameters.
+//
+
+void MidiMessage::setCommand(int value, int p1) {
+ this->resize(2);
+ (*this)[0] = (uchar)value;
+ (*this)[1] = (uchar)p1;
+}
+
+
+void MidiMessage::setCommand(int value, int p1, int p2) {
+ this->resize(3);
+ (*this)[0] = (uchar)value;
+ (*this)[1] = (uchar)p1;
+ (*this)[2] = (uchar)p2;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setCommandNibble --
+//
+
+void MidiMessage::setCommandNibble(int value) {
+ if (this->size() < 1) {
+ this->resize(1);
+ }
+ if (value <= 0x0f) {
+ (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)((value << 4) & 0xf0));
+ } else {
+ (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)(value & 0xf0));
+ }
+}
+
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setChannelNibble --
+//
+
+void MidiMessage::setChannelNibble(int value) {
+ if (this->size() < 1) {
+ this->resize(1);
+ }
+ (*this)[0] = ((*this)[0] & 0xf0) | ((uchar)(value & 0x0f));
+}
+
+
+void MidiMessage::setChannel(int value) {
+ setChannelNibble(value);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setParameters -- Set the second and optionally the
+// third MIDI byte of a MIDI message. The command byte will not
+// be altered, and will be set to 0 if it currently does not exist.
+//
+
+void MidiMessage::setParameters(int p1) {
+ int oldsize = (int)size();
+ resize(2);
+ (*this)[1] = (uchar)p1;
+ if (oldsize < 1) {
+ (*this)[0] = 0;
+ }
+}
+
+
+void MidiMessage::setParameters(int p1, int p2) {
+ int oldsize = (int)size();
+ resize(3);
+ (*this)[1] = (uchar)p1;
+ (*this)[2] = (uchar)p2;
+ if (oldsize < 1) {
+ (*this)[0] = 0;
+ }
+}
+
+
+//////////////////////////////
+//
+// MidiMessage::setMessage -- Set the contents of MIDI bytes to the
+// input list of bytes.
+//
+
+void MidiMessage::setMessage(const std::vector& message) {
+ this->resize(message.size());
+ for (int i=0; i<(int)this->size(); i++) {
+ (*this)[i] = message[i];
+ }
+}
+
+
+void MidiMessage::setMessage(const std::vector& message) {
+ resize(message.size());
+ for (int i=0; i<(int)size(); i++) {
+ (*this)[i] = (uchar)message[i];
+ }
+}
+
+
+void MidiMessage::setMessage(const std::vector& message) {
+ resize(message.size());
+ for (int i=0; i<(int)size(); i++) {
+ (*this)[i] = (uchar)message[i];
+ }
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setSpelling -- Encode a MidiPlus accidental state for a note.
+// For example, if a note's key number is 60, the enharmonic pitch name
+// could be any of these possibilities:
+// C, B-sharp, D-double-flat
+// MIDI note 60 is ambiguous as to which of these names are intended,
+// so MIDIPlus allows these mappings to be preserved for later recovery.
+// See Chapter 5 (pp. 99-104) of Beyond MIDI (1997).
+//
+// The first parameter is the diatonic pitch number (or pitch class
+// if the octave is set to 0):
+// octave * 7 + 0 = C pitches
+// octave * 7 + 1 = D pitches
+// octave * 7 + 2 = E pitches
+// octave * 7 + 3 = F pitches
+// octave * 7 + 4 = G pitches
+// octave * 7 + 5 = A pitches
+// octave * 7 + 6 = B pitches
+//
+// The second parameter is the semitone alteration (accidental).
+// 0 = natural state, 1 = sharp, 2 = double sharp, -1 = flat,
+// -2 = double flat.
+//
+// Only note-on messages can be processed (other messages will be
+// silently ignored).
+//
+
+void MidiMessage::setSpelling(int base7, int accidental) {
+ if (!isNoteOn()) {
+ return;
+ }
+ // The bottom two bits of the attack velocity are used for the
+ // spelling, so need to make sure the velocity will not accidentally
+ // be set to zero (and make the note-on a note-off).
+ if (getVelocity() < 4) {
+ setVelocity(4);
+ }
+ int dpc = base7 % 7;
+ uchar spelling = 0;
+
+ // Table 5.1, page 101 in Beyond MIDI (1997)
+ // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf
+ switch (dpc) {
+
+ case 0:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Cbb
+ case -1: spelling = 1; break; // Cb
+ case 0: spelling = 2; break; // C
+ case +1: spelling = 2; break; // C#
+ case +2: spelling = 3; break; // C##
+ }
+ break;
+
+ case 1:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Dbb
+ case -1: spelling = 1; break; // Db
+ case 0: spelling = 2; break; // D
+ case +1: spelling = 3; break; // D#
+ case +2: spelling = 3; break; // D##
+ }
+ break;
+
+ case 2:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Ebb
+ case -1: spelling = 2; break; // Eb
+ case 0: spelling = 2; break; // E
+ case +1: spelling = 3; break; // E#
+ case +2: spelling = 3; break; // E##
+ }
+ break;
+
+ case 3:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Fbb
+ case -1: spelling = 1; break; // Fb
+ case 0: spelling = 2; break; // F
+ case +1: spelling = 2; break; // F#
+ case +2: spelling = 3; break; // F##
+ case +3: spelling = 3; break; // F###
+ }
+ break;
+
+ case 4:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Gbb
+ case -1: spelling = 1; break; // Gb
+ case 0: spelling = 2; break; // G
+ case +1: spelling = 2; break; // G#
+ case +2: spelling = 3; break; // G##
+ }
+ break;
+
+ case 5:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Abb
+ case -1: spelling = 1; break; // Ab
+ case 0: spelling = 2; break; // A
+ case +1: spelling = 3; break; // A#
+ case +2: spelling = 3; break; // A##
+ }
+ break;
+
+ case 6:
+ switch (accidental) {
+ case -2: spelling = 1; break; // Bbb
+ case -1: spelling = 2; break; // Bb
+ case 0: spelling = 2; break; // B
+ case +1: spelling = 3; break; // B#
+ case +2: spelling = 3; break; // B##
+ }
+ break;
+
+ }
+
+ uchar vel = static_cast(getVelocity());
+ // suppress any previous content in the first two bits:
+ vel = vel & 0xFC;
+ // insert the spelling code:
+ vel = vel | spelling;
+ setVelocity(vel);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getSpelling -- Return the diatonic pitch class and accidental
+// for a note-on's key number. The MIDI file must be encoded with MIDIPlus
+// pitch spelling codes for this function to return valid data; otherwise,
+// it will return a neutral fixed spelling for each MIDI key.
+//
+// The first parameter will be filled in with the base-7 diatonic pitch:
+// pc + octave * 7
+// where pc is the numbers 0 through 6 representing the pitch classes
+// C through B, the octave is MIDI octave (not the scientific pitch
+// octave which is one less than the MIDI ocatave, such as C4 = middle C).
+// The second number is the accidental for the base-7 pitch.
+//
+
+void MidiMessage::getSpelling(int& base7, int& accidental) {
+ if (!isNoteOn()) {
+ return;
+ }
+ base7 = -123456;
+ accidental = 123456;
+ int base12 = getKeyNumber();
+ int octave = base12 / 12;
+ int base12pc = base12 - octave * 12;
+ int base7pc = 0;
+ int spelling = 0x03 & getVelocity();
+
+ // Table 5.1, page 101 in Beyond MIDI (1997)
+ // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf
+ switch (base12pc) {
+
+ case 0:
+ switch (spelling) {
+ case 1: base7pc = 1; accidental = -2; break; // Dbb
+ case 0: case 2: base7pc = 0; accidental = 0; break; // C
+ case 3: base7pc = 6; accidental = +1; octave--; break; // B#
+ }
+ break;
+
+ case 1:
+ switch (spelling) {
+ case 1: base7pc = 1; accidental = -1; break; // Db
+ case 0: case 2: base7pc = 0; accidental = +1; break; // C#
+ case 3: base7pc = 6; accidental = +2; octave--; break; // B##
+ }
+ break;
+
+ case 2:
+ switch (spelling) {
+ case 1: base7pc = 2; accidental = -2; break; // Ebb
+ case 0: case 2: base7pc = 1; accidental = 0; break; // D
+ case 3: base7pc = 0; accidental = +2; break; // C##
+ }
+ break;
+
+ case 3:
+ switch (spelling) {
+ case 1: base7pc = 3; accidental = -2; break; // Fbb
+ case 0: case 2: base7pc = 2; accidental = -1; break; // Eb
+ case 3: base7pc = 1; accidental = +1; break; // D#
+ }
+ break;
+
+ case 4:
+ switch (spelling) {
+ case 1: base7pc = 3; accidental = -1; break; // Fb
+ case 0: case 2: base7pc = 2; accidental = 0; break; // E
+ case 3: base7pc = 1; accidental = +2; break; // D##
+ }
+ break;
+
+ case 5:
+ switch (spelling) {
+ case 1: base7pc = 4; accidental = -2; break; // Gbb
+ case 0: case 2: base7pc = 3; accidental = 0; break; // F
+ case 3: base7pc = 2; accidental = +1; break; // E#
+ }
+ break;
+
+ case 6:
+ switch (spelling) {
+ case 1: base7pc = 4; accidental = -1; break; // Gb
+ case 0: case 2: base7pc = 3; accidental = +1; break; // F#
+ case 3: base7pc = 2; accidental = +2; break; // E##
+ }
+ break;
+
+ case 7:
+ switch (spelling) {
+ case 1: base7pc = 5; accidental = -2; break; // Abb
+ case 0: case 2: base7pc = 4; accidental = 0; break; // G
+ case 3: base7pc = 3; accidental = +2; break; // F##
+ }
+ break;
+
+ case 8:
+ switch (spelling) {
+ case 1: base7pc = 5; accidental = -1; break; // Ab
+ case 0: case 2: base7pc = 4; accidental = +1; break; // G#
+ case 3: base7pc = 3; accidental = +3; break; // F###
+ }
+ break;
+
+ case 9:
+ switch (spelling) {
+ case 1: base7pc = 6; accidental = -2; break; // Bbb
+ case 0: case 2: base7pc = 5; accidental = 0; break; // A
+ case 3: base7pc = 4; accidental = +2; break; // G##
+ }
+ break;
+
+ case 10:
+ switch (spelling) {
+ case 1: base7pc = 0; accidental = -2; octave++; break; // Cbb
+ case 0: case 2: base7pc = 6; accidental = -1; break; // Bb
+ case 3: base7pc = 5; accidental = +1; break; // A#
+ }
+ break;
+
+ case 11:
+ switch (spelling) {
+ case 1: base7pc = 0; accidental = -1; octave++; break; // Cb
+ case 0: case 2: base7pc = 6; accidental = 0; break; // B
+ case 3: base7pc = 5; accidental = +2; break; // A##
+ }
+ break;
+
+ }
+
+ base7 = base7pc + 7 * octave;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::getMetaContent -- Returns the bytes of the meta
+// message after the length (which is a variable-length-value).
+//
+
+std::string MidiMessage::getMetaContent(void) {
+ std::string output;
+ if (!isMetaMessage()) {
+ return output;
+ }
+ int start = 3;
+ if (operator[](2) > 0x7f) {
+ start++;
+ if (operator[](3) > 0x7f) {
+ start++;
+ if (operator[](4) > 0x7f) {
+ start++;
+ if (operator[](5) > 0x7f) {
+ start++;
+ // maximum of 5 bytes in VLV, so last must be < 0x80
+ }
+ }
+ }
+ }
+ output.reserve(this->size());
+ for (int i=start; i<(int)this->size(); i++) {
+ output.push_back(operator[](i));
+ }
+ return output;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setMetaContent - Set the content of a meta-message. This
+// function handles the size of the message starting at byte 3 in the
+// message, and it does not alter the meta message type. The message
+// must be a meta-message before calling this function and be assigne
+// a meta-message type.
+//
+
+void MidiMessage::setMetaContent(const std::string& content) {
+ if (this->size() < 2) {
+ // invalid message, so ignore request
+ return;
+ }
+ if (operator[](0) != 0xFF) {
+ // not a meta message, so ignore request
+ return;
+ }
+ this->resize(2);
+
+ // add the size of the meta message data (VLV)
+ int dsize = (int)content.size();
+ if (dsize < 128) {
+ push_back((uchar)dsize);
+ } else {
+ // calculate VLV bytes and insert into message
+ uchar byte1 = dsize & 0x7f;
+ uchar byte2 = (dsize >> 7) & 0x7f;
+ uchar byte3 = (dsize >> 14) & 0x7f;
+ uchar byte4 = (dsize >> 21) & 0x7f;
+ uchar byte5 = (dsize >> 28) & 0x7f;
+ if (byte5) {
+ byte4 |= 0x80;
+ }
+ if (byte4) {
+ byte4 |= 0x80;
+ byte3 |= 0x80;
+ }
+ if (byte3) {
+ byte3 |= 0x80;
+ byte2 |= 0x80;
+ }
+ if (byte2) {
+ byte2 |= 0x80;
+ }
+ if (byte5) { push_back(byte5); }
+ if (byte4) { push_back(byte4); }
+ if (byte3) { push_back(byte3); }
+ if (byte2) { push_back(byte2); }
+ push_back(byte1);
+ }
+ std::copy(content.begin(), content.end(), std::back_inserter(*this));
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setMetaTempo -- Input tempo is in quarter notes per minute
+// (meta message #0x51).
+//
+
+void MidiMessage::setMetaTempo(double tempo) {
+ int microseconds = (int)(60.0 / tempo * 1000000.0 + 0.5);
+ setTempoMicroseconds(microseconds);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setTempo -- Alias for MidiMessage::setMetaTempo().
+//
+
+void MidiMessage::setTempo(double tempo) {
+ setMetaTempo(tempo);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::setTempoMicroseconds -- Set the tempo in terms
+// of microseconds per quarter note.
+//
+
+void MidiMessage::setTempoMicroseconds(int microseconds) {
+ resize(6);
+ (*this)[0] = 0xff;
+ (*this)[1] = 0x51;
+ (*this)[2] = 3;
+ (*this)[3] = (microseconds >> 16) & 0xff;
+ (*this)[4] = (microseconds >> 8) & 0xff;
+ (*this)[5] = (microseconds >> 0) & 0xff;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeTimeSignature -- create a time signature meta message
+// (meta #0x58). The "bottom" parameter should be a power of two;
+// otherwise, it will be forced to be the next highest power of two,
+// as MIDI time signatures must have a power of two in the denominator.
+//
+// Default values:
+// clocksPerClick == 24 (quarter note)
+// num32ndsPerQuarter == 8 (8 32nds per quarter note)
+//
+// Time signature of 4/4 would be:
+// top = 4
+// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2).
+// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter)
+// num32ndsPerQuarter = 8
+//
+// Time signature of 6/8 would be:
+// top = 6
+// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2).
+// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter)
+// num32ndsPerQuarter = 8
+//
+
+void MidiMessage::makeTimeSignature(int top, int bottom, int clocksPerClick,
+ int num32ndsPerQuarter) {
+ int base2 = 0;
+ while (bottom >>= 1) base2++;
+ resize(7);
+ (*this)[0] = 0xff;
+ (*this)[1] = 0x58;
+ (*this)[2] = 4;
+ (*this)[3] = 0xff & top;
+ (*this)[4] = 0xff & base2;
+ (*this)[5] = 0xff & clocksPerClick;
+ (*this)[6] = 0xff & num32ndsPerQuarter;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// make functions to create various MIDI message --
+//
+
+
+//////////////////////////////
+//
+// MidiMessage::makeNoteOn -- create a note-on message.
+//
+// default value: channel = 0
+//
+// Note: The channel parameter used to be last, but makes more sense to
+// have it first...
+//
+
+void MidiMessage::makeNoteOn(int channel, int key, int velocity) {
+ resize(3);
+ (*this)[0] = 0x90 | (0x0f & channel);
+ (*this)[1] = key & 0x7f;
+ (*this)[2] = velocity & 0x7f;
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeNoteOff -- create a note-off message. If no
+// parameters are given, the current contents is presumed to be a
+// note-on message, which will be converted into a note-off message.
+//
+// default value: channel = 0
+//
+// Note: The channel parameter used to be last, but makes more sense to
+// have it first...
+//
+
+
+void MidiMessage::makeNoteOff(int channel, int key, int velocity) {
+ resize(3);
+ (*this)[0] = 0x80 | (0x0f & channel);
+ (*this)[1] = key & 0x7f;
+ (*this)[2] = velocity & 0x7f;
+}
+
+
+void MidiMessage::makeNoteOff(int channel, int key) {
+ resize(3);
+ (*this)[0] = 0x90 | (0x0f & channel);
+ (*this)[1] = key & 0x7f;
+ (*this)[2] = 0x00;
+}
+
+//
+// MidiMessage::makeNoteOff(void) -- create a 0x90 note message with
+// The key and velocity set to 0.
+//
+
+void MidiMessage::makeNoteOff(void) {
+ if (!isNoteOn()) {
+ resize(3);
+ (*this)[0] = 0x90;
+ (*this)[1] = 0;
+ (*this)[2] = 0;
+ } else {
+ (*this)[2] = 0;
+ }
+}
+
+
+
+/////////////////////////////
+//
+// MidiMessage::makePatchChange -- Create a patch-change message.
+//
+
+void MidiMessage::makePatchChange(int channel, int patchnum) {
+ resize(0);
+ push_back(0xc0 | (0x0f & channel));
+ push_back(0x7f & patchnum);
+}
+
+//
+// MidiMessage::makeTimbre -- alias for MidiMessage::makePatchChange().
+//
+
+void MidiMessage::makeTimbre(int channel, int patchnum) {
+ makePatchChange(channel, patchnum);
+}
+
+
+/////////////////////////////
+//
+// MidiMessage::makeController -- Create a controller message.
+//
+
+void MidiMessage::makeController(int channel, int num, int value) {
+ resize(0);
+ push_back(0xb0 | (0x0f & channel));
+ push_back(0x7f & num);
+ push_back(0x7f & value);
+}
+
+
+
+/////////////////////////////
+//
+// MidiMessage::makeSustain -- Create a sustain pedal message.
+// Value in 0-63 range is a sustain off. Value in the
+// 64-127 value is a sustain on.
+//
+
+void MidiMessage::makeSustain(int channel, int value) {
+ makeController(channel, 64, value);
+}
+
+//
+// MidiMessage::makeSustain -- Alias for MidiMessage::makeSustain().
+//
+
+void MidiMessage::makeSustainPedal(int channel, int value) {
+ makeSustain(channel, value);
+}
+
+
+
+/////////////////////////////
+//
+// MidiMessage::makeSustainOn -- Create sustain-on controller message.
+//
+
+void MidiMessage::makeSustainOn(int channel) {
+ makeController(channel, 64, 127);
+}
+
+//
+// MidiMessage::makeSustainPedalOn -- Alias for MidiMessage::makeSustainOn().
+//
+
+void MidiMessage::makeSustainPedalOn(int channel) {
+ makeSustainOn(channel);
+}
+
+
+
+/////////////////////////////
+//
+// MidiMessage::makeSustainOff -- Create a sustain-off controller message.
+//
+
+void MidiMessage::makeSustainOff(int channel) {
+ makeController(channel, 64, 0);
+}
+
+//
+// MidiMessage::makeSustainPedalOff -- Alias for MidiMessage::makeSustainOff().
+//
+
+void MidiMessage::makeSustainPedalOff(int channel) {
+ makeSustainOff(channel);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeMetaMessage -- Create a Meta event with the
+// given text string as the parameter. The length of the string should
+// is a VLV. If the length is larger than 127 byte, then the length
+// will contain more than one byte.
+//
+
+void MidiMessage::makeMetaMessage(int mnum, const std::string& data) {
+ resize(0);
+ push_back(0xff);
+ push_back(mnum & 0x7f); // max meta-message number is 0x7f.
+ setMetaContent(data);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeText -- Create a metaevent text message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeText(const std::string& text) {
+ makeMetaMessage(0x01, text);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeCopyright -- Create a metaevent copyright message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeCopyright(const std::string& text) {
+ makeMetaMessage(0x02, text);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeTrackName -- Create a metaevent track name message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeTrackName(const std::string& name) {
+ makeMetaMessage(0x03, name);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeTrackName -- Create a metaevent instrument name message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeInstrumentName(const std::string& name) {
+ makeMetaMessage(0x04, name);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeLyric -- Create a metaevent lyrics/text message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeLyric(const std::string& text) {
+ makeMetaMessage(0x05, text);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeMarker -- Create a metaevent marker message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeMarker(const std::string& text) {
+ makeMetaMessage(0x06, text);
+}
+
+
+
+//////////////////////////////
+//
+// MidiMessage::makeCue -- Create a metaevent cue-point message.
+// This is not a real MIDI message, but rather a pretend message for use
+// within Standard MIDI Files.
+//
+
+void MidiMessage::makeCue(const std::string& text) {
+ makeMetaMessage(0x07, text);
+}
+
+
+} // end namespace smf
+
+
+
diff --git a/midifile/src/Options.cpp b/midifile/src/Options.cpp
new file mode 100644
index 0000000..2988b1d
--- /dev/null
+++ b/midifile/src/Options.cpp
@@ -0,0 +1,1219 @@
+//
+// Programmer: Craig Stuart Sapp
+// Creation Date: Sun Apr 5 13:07:18 PDT 1998
+// Last Modified: Mon Jan 18 18:25:23 PST 2021 Some cleanup
+// Filename: midifile/src/Options.cpp
+// Web Address: http://midifile.sapp.org
+// Syntax: C++11
+// vim: ts=3 noexpandtab
+//
+// Description: Interface to command-line options.
+//
+
+#include "Options.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+namespace smf {
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Option_register class function definitions.
+//
+
+
+//////////////////////////////
+//
+// Option_register::Option_register -- Constructor.
+//
+
+Option_register::Option_register(void) {
+ setType(OPTION_TYPE_string);
+ m_modifiedQ = false;
+}
+
+
+Option_register::Option_register(const std::string& aDefinition, char aType,
+ const std::string& aDefaultOption) {
+ m_modifiedQ = false;
+ setType(aType);
+ setDefinition(aDefinition);
+ setDefault(aDefaultOption);
+}
+
+Option_register::Option_register(const std::string& aDefinition, char aType,
+ const std::string& aDefaultOption, const std::string& aModifiedOption) {
+ setType(aType);
+ setDefinition(aDefinition);
+ setDefault(aDefaultOption);
+ setModified(aModifiedOption);
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::~Option_register -- Destructor.
+//
+
+Option_register::~Option_register() {
+ // do nothing
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::clearModified -- Clear any changes in the option value.
+//
+
+void Option_register::clearModified(void) {
+ m_modifiedOption.clear();
+ m_modifiedQ = false;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getDefinition -- Returns the initial definition.
+// string used to define this entry.
+//
+
+const std::string& Option_register::getDefinition(void) {
+ return m_definition;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getDescription -- Return the textual description
+// of the entry.
+//
+
+const std::string& Option_register::getDescription(void) {
+ return m_description;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getDefault -- Return the default value string.
+//
+
+const std::string& Option_register::getDefault(void) {
+ return m_defaultOption;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getModified -- Return the modified option string.
+//
+
+const std::string& Option_register::getModified(void) {
+ return m_modifiedOption;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::isModified -- Return true if option has been
+// set on the command-line.
+//
+
+bool Option_register::isModified(void) {
+ return m_modifiedQ;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getType -- Return the data type of the option.
+//
+
+char Option_register::getType(void) {
+ return m_type;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::getOption -- return the modified option
+// or the default option if no modified option.
+//
+
+const std::string& Option_register::getOption(void) {
+ if (isModified()) {
+ return getModified();
+ } else {
+ return getDefault();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::reset -- deallocate space for all
+// strings in object. (but default string is set to "")
+//
+
+void Option_register::reset(void) {
+ m_type = OPTION_TYPE_string;
+ m_definition.clear();
+ m_defaultOption.clear();
+ m_modifiedOption.clear();
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::setDefault -- Set the default value.
+//
+
+void Option_register::setDefault(const std::string& aString) {
+ m_defaultOption = aString;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::setDefinition -- Set the option definition.
+//
+
+void Option_register::setDefinition(const std::string& aString) {
+ m_definition = aString;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::setDescription -- Set the textual description.
+//
+
+void Option_register::setDescription(const std::string& aString) {
+ m_description = aString;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::setModified -- Set the modified value.
+//
+
+void Option_register::setModified(const std::string& aString) {
+ m_modifiedOption = aString;
+ m_modifiedQ = true;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::setType -- Set the option type.
+//
+
+void Option_register::setType(char aType) {
+ m_type = aType;
+}
+
+
+
+//////////////////////////////
+//
+// Option_register::print -- Print the state of the option register entry.
+// Useful for debugging.
+//
+
+std::ostream& Option_register::print(std::ostream& out) {
+ out << "definition:\t" << m_definition << std::endl;
+ out << "description:\t" << m_description << std::endl;
+ out << "defaultOption:\t" << m_defaultOption << std::endl;
+ out << "modifiedOption:\t" << m_modifiedOption << std::endl;
+ out << "modifiedQ:\t\t" << m_modifiedQ << std::endl;
+ out << "type:\t\t" << m_type << std::endl;
+ return out;
+};
+
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Options class function definitions.
+//
+
+//////////////////////////////
+//
+// Options::Options -- Constructor.
+//
+
+Options::Options(void) {
+ m_oargc = -1;
+ m_suppressQ = 0;
+ m_processedQ = 0;
+ m_optionsArgument = 0;
+ m_options_error_check = 1;
+ m_optionFlag = '-';
+
+ m_extraArgv.reserve(100);
+ m_extraArgv_strings.reserve(100);
+}
+
+
+Options::Options(int argc, char** argv) {
+ m_oargc = -1;
+ m_suppressQ = 0;
+ m_processedQ = 0;
+ m_optionsArgument = 0;
+ m_options_error_check = 1;
+ m_optionFlag = '-';
+
+ m_extraArgv.reserve(100);
+ m_extraArgv_strings.reserve(100);
+
+ setOptions(argc, argv);
+}
+
+
+
+//////////////////////////////
+//
+// Options::~Options -- Destructor.
+//
+
+Options::~Options() {
+ reset();
+}
+
+
+
+//////////////////////////////
+//
+// Options::argc -- returns the argument count as input from main().
+//
+
+int Options::argc(void) const {
+ return m_oargc;
+}
+
+
+
+//////////////////////////////
+//
+// Options::argv -- returns the arguments strings as input from main().
+//
+
+const std::vector& Options::argv(void) const {
+ return m_oargv;
+}
+
+
+
+//////////////////////////////
+//
+// Options::define -- store an option definition in the register. Option
+// definitions have this sructure:
+// option-name|alias-name1|alias-name2=option-type:option-default
+// option-name :: name of the option (one or more character, not including
+// spaces or equal signs.
+// alias-name :: equivalent name(s) of the option.
+// option-type :: single charater indicating the option data type.
+// option-default :: default value for option if no given on the command-line.
+//
+
+int Options::define(const std::string& aDefinition) {
+ Option_register* definitionEntry = NULL;
+
+ // Error if definition string doesn't contain an equals sign
+ auto location = aDefinition.find("=");
+ if (location == std::string::npos) {
+ std::cerr << "Error: no \"=\" in option definition: " << aDefinition << std::endl;
+ exit(1);
+ }
+
+ std::string aliases = aDefinition.substr(0, location);
+ std::string rest = aDefinition.substr(location+1);
+ std::string otype = rest;
+ std::string ovalue = "";
+
+ location = rest.find(":");
+ if (location != std::string::npos) {
+ otype = rest.substr(0, location);
+ ovalue = rest.substr(location+1);
+ }
+
+ // Remove anyspaces in the option type field
+ otype.erase(remove_if(otype.begin(), otype.end(), ::isspace), otype.end());
+
+ // Option types are only a single charater (b, i, d, c or s)
+ if (otype.size() != 1) {
+ std::cerr << "Error: option type is invalid: " << otype
+ << " in option definition: " << aDefinition << std::endl;
+ exit(1);
+ }
+
+ // Check to make sure that the type is known
+ if (otype[0] != OPTION_TYPE_string &&
+ otype[0] != OPTION_TYPE_int &&
+ otype[0] != OPTION_TYPE_float &&
+ otype[0] != OPTION_TYPE_double &&
+ otype[0] != OPTION_TYPE_boolean &&
+ otype[0] != OPTION_TYPE_char ) {
+ std::cerr << "Error: unknown option type \'" << otype[0]
+ << "\' in defintion: " << aDefinition << std::endl;
+ exit(1);
+ }
+
+ // Set up space for a option entry in the register
+ definitionEntry = new Option_register(aDefinition, otype[0], ovalue);
+
+ int definitionIndex = (int)m_optionRegister.size();
+
+ // Store option aliases
+ std::string optionName;
+ unsigned int i;
+ aliases += '|';
+ for (i=0; i 0) {
+ m_optionList[optionName] = definitionIndex;
+ }
+ optionName.clear();
+ } else {
+ optionName += aliases[i];
+ }
+ }
+
+ // Store definition in register and return its indexed location.
+ // This location will be used to link option aliases to the main
+ // command name.
+ m_optionRegister.push_back(definitionEntry);
+ return definitionIndex;
+}
+
+
+int Options::define(const std::string& aDefinition,
+ const std::string& aDescription) {
+ int index = define(aDefinition);
+ m_optionRegister[index]->setDescription(aDescription);
+ return index;
+}
+
+
+
+//////////////////////////////
+//
+// Options::isDefined -- Return true if option is present in register.
+//
+
+bool Options::isDefined(const std::string& name) {
+ return m_optionList.find(name) == m_optionList.end() ? false : true;
+}
+
+
+
+//////////////////////////////
+//
+// Options::getArg -- returns the specified argument.
+// argurment 0 is the command name.
+//
+
+const std::string& Options::getArg(int index) {
+ if (index < 0 || index >= (int)m_argument.size()) {
+ std::cerr << "Error: m_argument " << index << " does not exist." << std::endl;
+ exit(1);
+ }
+ return m_argument[index];
+}
+
+// Alias:
+
+const std::string& Options::getArgument(int index) {
+ return getArg(index);
+}
+
+
+
+//////////////////////////////
+//
+// Options::getArgCount -- number of arguments on command line.
+// does not count the options or the command name.
+//
+
+int Options::getArgCount(void) {
+ return ((int)m_argument.size()) - 1;
+}
+
+// Alias:
+
+int Options::getArgumentCount(void) {
+ return getArgCount();
+}
+
+
+
+//////////////////////////////
+//
+// Options::getArgList -- return a string vector of the arguments
+// after the options have been parsed out of it.
+//
+
+const std::vector& Options::getArgList(void) {
+ return m_argument;
+}
+
+// Alias:
+
+const std::vector& Options::getArgumentList(void) {
+ return getArgList();
+}
+
+
+
+//////////////////////////////
+//
+// Options::getBoolean -- returns true if the option was
+// used on the command line.
+//
+
+bool Options::getBoolean(const std::string& optionName) {
+ int index = getRegIndex(optionName);
+ if (index < 0) {
+ return 0;
+ }
+ return m_optionRegister[index]->isModified();
+}
+
+
+
+//////////////////////////////
+//
+// Options::getCommand -- returns argv[0] (the first string
+// in the original argv list.
+//
+
+std::string Options::getCommand(void) {
+ if (m_argument.size() == 0) {
+ return "";
+ } else {
+ return m_argument[0];
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::getCommandLine -- returns a string which contains the
+// command-line call to the program. Deal with spaces in arguments...
+//
+
+const std::string& Options::getCommandLine(void) {
+ if (m_commandString.size()) {
+ return m_commandString;
+ }
+
+ m_commandString = m_oargv[0];
+
+ int i;
+ for (i=1; isecond]->getDefinition();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::getDouble -- returns the double float associated
+// with the given option. Returns 0 if there is no
+// number associated with the option.
+//
+
+double Options::getDouble(const std::string& optionName) {
+ return strtod(getString(optionName).c_str(), (char**)NULL);
+}
+
+
+
+//////////////////////////////
+//
+// Options::getChar -- Return the first character in the option string;
+// If the length is zero, then return '\0'.
+//
+
+char Options::getChar(const std::string& optionName) {
+ return getString(optionName).c_str()[0];
+}
+
+
+
+//////////////////////////////
+//
+// Options::getFloat -- Return the floating point number
+// associated with the given option.
+//
+
+float Options::getFloat(const std::string& optionName) {
+ return (float)getDouble(optionName);
+}
+
+
+
+//////////////////////////////
+//
+// Options::getInt -- Return the integer argument. Can handle
+// hexadecimal, decimal, and octal written in standard
+// C syntax.
+//
+
+int Options::getInt(const std::string& optionName) {
+ return (int)strtol(getString(optionName).c_str(), (char**)NULL, 0);
+}
+
+int Options::getInteger(const std::string& optionName) {
+ return getInt(optionName);
+}
+
+
+
+//////////////////////////////
+//
+// Options::getString -- Return the option argument string.
+//
+
+std::string Options::getString(const std::string& optionName) {
+ int index = getRegIndex(optionName);
+ if (index < 0) {
+ return "UNKNOWN OPTION";
+ } else {
+ return m_optionRegister[index]->getOption();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::optionsArg -- Return true if --options is present
+// on the command line, otherwise returns false.
+//
+
+int Options::optionsArg(void) {
+ return m_optionsArgument;
+}
+
+
+
+//////////////////////////////
+//
+// Options::print -- Print a list of the defined options.
+//
+
+std::ostream& Options::print(std::ostream& out) {
+ for (unsigned int i=0; igetDefinition() << "\t"
+ << m_optionRegister[i]->getDescription() << std::endl;
+ }
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Options::reset -- Clear all defined options.
+//
+
+void Options::reset(void) {
+ unsigned int i;
+ for (i=0; isetModified(aString);
+}
+
+
+
+
+//////////////////////////////
+//
+// Options::setOptions -- Store the input list of options.
+//
+
+void Options::setOptions(int argc, char** argv) {
+ m_processedQ = 0;
+
+ m_extraArgv.resize(argc);
+ m_extraArgv_strings.resize(argc);
+ int oldsize = 0;
+
+ int i;
+ for (i=0; i& argv) {
+ m_processedQ = 0;
+
+ int oldsize = (int)m_extraArgv.size();
+ m_extraArgv.resize(oldsize + argv.size());
+ m_extraArgv_strings.resize(oldsize + argv.size());
+
+ unsigned int i;
+ for (i=0; i tokens;
+ std::vector tempargv;
+ std::string tempvalue;
+
+ tokens.reserve(100);
+ tempargv.reserve(100);
+ tempvalue.reserve(1000);
+
+ char ch = '\0';
+
+ int length = (int)strang.size();
+ for (int i=0; i0) && (strang[i-1] != '\\')) {
+ doublequote = !doublequote;
+ if (doublequote == 0) {
+ // finished a doublequoted section of data, so store
+ // even if it is the empty string
+ ch = '\0';
+ tempvalue += (ch);
+ tokens.push_back(tempvalue);
+ tempvalue.clear();
+ continue;
+ } else {
+ // don't store the leading ":
+ continue;
+ }
+ }
+ } else if (!doublequote && (strang[i] == '\'')) {
+ if ((i>0) && (strang[i-1] != '\\')) {
+ singlequote = !singlequote;
+ if (singlequote == 0) {
+ // finished a singlequote section of data, so store
+ // even if it is the empty string
+ ch = '\0';
+ tempvalue += ch;
+ tokens.push_back(tempvalue);
+ tempvalue.clear();
+ continue;
+ } else {
+ // don't store the leading ":
+ continue;
+ }
+ }
+ }
+
+
+ if ((!doublequote && !singlequote) && std::isspace(strang[i])) {
+ if (tempvalue.size() > 0) {
+ tempvalue += ch;
+ tokens.push_back(tempvalue);
+ tempvalue.clear();
+ }
+ } else {
+ ch = strang[i];
+ tempvalue += ch;
+ }
+ }
+ if (tempvalue.size() > 0) {
+ tokens.push_back(tempvalue);
+ tempvalue.clear();
+ }
+
+ // now that the input string has been chopped up into pieces,
+ // assemble the argv structure
+
+ tempargv.reserve(tokens.size());
+ for (int i=0; i<(int)tempargv.size(); i++) {
+ tempargv[i] = tokens[i];
+ }
+
+ // now store the argv and argc data in opts:
+
+ // now store the parsed command-line simulated tokens
+ // for actual storage:
+ appendOptions(tempargv);
+}
+
+
+
+//////////////////////////////
+//
+// Options:getType -- Return the type of the option.
+//
+
+char Options::getType(const std::string& optionName) {
+ int index = getRegIndex(optionName);
+ if (index < 0) {
+ return -1;
+ } else {
+ return m_optionRegister[getRegIndex(optionName)]->getType();
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::process -- Same as xverify.
+// default values: error_check = 1, suppress = 0;
+//
+
+void Options::process(int argc, char** argv, int error_check, int suppress) {
+ setOptions(argc, argv);
+ xverify(error_check, suppress);
+}
+
+
+void Options::process(int error_check, int suppress) {
+ xverify(error_check, suppress);
+}
+
+
+
+//////////////////////////////
+//
+// Options::xverify --
+// default value: error_check = 1, suppress = 0;
+//
+
+void Options::xverify(int error_check, int suppress) {
+ m_options_error_check = error_check;
+ int gargp = 1;
+ int optionend = 0;
+
+ if (suppress) {
+ m_suppressQ = 1;
+ } else {
+ m_suppressQ = 0;
+ }
+
+ // if calling xverify again, must remove previous argument list.
+ if (m_argument.size() != 0) {
+ m_argument.clear();
+ }
+
+ m_argument.push_back(m_oargv[0]);
+ int oldgargp;
+ int position = 0;
+ int running = 0;
+ while (gargp < m_oargc && optionend == 0) {
+ if (optionQ(m_oargv[gargp], gargp)) {
+ oldgargp = gargp;
+ gargp = storeOption(gargp, position, running);
+ if (gargp != oldgargp) {
+ running = 0;
+ position = 0;
+ }
+ } else {
+ if (m_oargv[gargp].size() == 2 && m_oargv[gargp][0] == getFlag() &&
+ m_oargv[gargp][2] == getFlag() ) {
+ optionend = 1;
+ gargp++;
+ break;
+ } else { // this is an argument
+ m_argument.push_back(m_oargv[gargp]);
+ gargp++;
+ }
+ }
+ }
+
+ while (gargp < m_oargc) {
+ m_argument.push_back(m_oargv[gargp]);
+ gargp++;
+ }
+
+}
+
+
+void Options::xverify(int argc, char** argv, int error_check, int suppress) {
+ setOptions(argc, argv);
+ xverify(error_check, suppress);
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// private functions
+//
+
+
+//////////////////////////////
+//
+// Options::getRegIndex -- returns the index of the option associated
+// with this name.
+//
+
+int Options::getRegIndex(const std::string& optionName) {
+ if (m_suppressQ && (optionName == "options")) {
+ return -1;
+ }
+
+ if (optionName == "options") {
+ print(std::cout);
+ exit(0);
+ }
+
+
+ auto it = m_optionList.find(optionName);
+ if (it == m_optionList.end()) {
+ if (m_options_error_check) {
+ std::cerr << "Error: unknown option \"" << optionName << "\"." << std::endl;
+ print(std::cout);
+ exit(1);
+ } else {
+ return -1;
+ }
+ } else {
+ return it->second;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::optionQ -- returns true if the string is an option
+// "--" is not an option, also '-' is not an option.
+// aString is assumed to not be NULL.
+//
+
+int Options::optionQ(const std::string& aString, int& argp) {
+ if (aString[0] == getFlag()) {
+ if (aString[1] == '\0') {
+ argp++;
+ return 0;
+ } else if (aString[1] == getFlag()) {
+ if (aString[2] == '\0') {
+ argp++;
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ return 1;
+ }
+ } else {
+ return 0;
+ }
+}
+
+
+
+//////////////////////////////
+//
+// Options::storeOption --
+//
+
+#define OPTION_FORM_short 0
+#define OPTION_FORM_long 1
+#define OPTION_FORM_continue 2
+
+int Options::storeOption(int gargp, int& position, int& running) {
+ int optionForm;
+ char tempname[4096];
+ char optionType = OPTION_TYPE_unknown;
+
+ if (running) {
+ optionForm = OPTION_FORM_continue;
+ } else if (m_oargv[gargp][1] == getFlag()) {
+ optionForm = OPTION_FORM_long;
+ } else {
+ optionForm = OPTION_FORM_short;
+ }
+
+ switch (optionForm) {
+ case OPTION_FORM_continue:
+ position++;
+ tempname[0] = m_oargv[gargp][position];
+ tempname[1] = '\0';
+ optionType = getType(tempname);
+ if (optionType != OPTION_TYPE_boolean) {
+ running = 0;
+ position++;
+ }
+ break;
+ case OPTION_FORM_short:
+ position = 1;
+ tempname[0] = m_oargv[gargp][position];
+ tempname[1] = '\0';
+ optionType = getType(tempname);
+ if (optionType != OPTION_TYPE_boolean) {
+ position++;
+ }
+ break;
+ case OPTION_FORM_long:
+ position = 2;
+ while (m_oargv[gargp][position] != '=' &&
+ m_oargv[gargp][position] != '\0') {
+ tempname[position-2] = m_oargv[gargp][position];
+ position++;
+ }
+ tempname[position-2] = '\0';
+ optionType = getType(tempname);
+ if (optionType == OPTION_TYPE_unknown) { // suppressed --options option
+ m_optionsArgument = 1;
+ break;
+ }
+ if (m_oargv[gargp][position] == '=') {
+ if (optionType == OPTION_TYPE_boolean) {
+ std::cerr << "Error: boolean variable cannot have any options: "
+ << tempname << std::endl;
+ exit(1);
+ }
+ position++;
+ }
+ break;
+ }
+
+ if (optionType == OPTION_TYPE_unknown) { // suppressed --options option
+ m_optionsArgument = 1;
+ gargp++;
+ position = 0;
+ return gargp;
+ }
+
+ if (m_oargv[gargp][position] == '\0' &&
+ optionType != OPTION_TYPE_boolean) {
+ gargp++;
+ position = 0;
+ }
+
+ if ((optionForm != OPTION_FORM_long) && (optionType == OPTION_TYPE_boolean) &&
+ (m_oargv[gargp][position+1] != '\0')) {
+ running = 1;
+ } else if ((optionType == OPTION_TYPE_boolean) &&
+ (m_oargv[gargp][position+1] == '\0')) {
+ running = 0;
+ }
+
+ if (gargp >= m_oargc) {
+ std::cerr << "Error: last option requires a parameter" << std::endl;
+ exit(1);
+ }
+ setModified(tempname, &m_oargv[gargp][position]);
+
+ if (!running) {
+ gargp++;
+ }
+ return gargp;
+}
+
+
+
+//////////////////////////////
+//
+// Options::printOptionList --
+//
+
+std::ostream& Options::printOptionList(std::ostream& out) {
+ for (auto it = m_optionList.begin(); it != m_optionList.end(); it++) {
+ out << it->first << "\t" << it->second << std::endl;
+ }
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Options::printOptionBooleanState --
+//
+
+std::ostream& Options::printOptionListBooleanState(std::ostream& out) {
+ for (auto it = m_optionList.begin(); it != m_optionList.end(); it++) {
+ out << it->first << "\t"
+ << m_optionRegister[it->second]->isModified() << std::endl;
+ }
+ return out;
+}
+
+
+
+//////////////////////////////
+//
+// Options::printRegister --
+//
+
+std::ostream& Options::printRegister(std::ostream& out) {
+ for (auto it = m_optionRegister.begin(); it != m_optionRegister.end(); it++) {
+ (*it)->print(out);
+ }
+ return out;
+}
+
+
+} // end namespace smf
+
+
+
diff --git a/resources/MIDI_LOGO.svg b/resources/MIDI_LOGO.svg
new file mode 100644
index 0000000..6f954eb
--- /dev/null
+++ b/resources/MIDI_LOGO.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+Created by potrace 1.16, written by Peter Selinger 2001-2019
+
+
+
+
+
+
+
+
diff --git a/resources/MIDI_TRAY.svg b/resources/MIDI_TRAY.svg
new file mode 100644
index 0000000..6640ec5
--- /dev/null
+++ b/resources/MIDI_TRAY.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+Created by potrace 1.16, written by Peter Selinger 2001-2019
+
+
+
diff --git a/resources/keys.png b/resources/keys.png
new file mode 100644
index 0000000..9b445d1
Binary files /dev/null and b/resources/keys.png differ
diff --git a/resources/resources.qrc b/resources/resources.qrc
new file mode 100644
index 0000000..292aa66
--- /dev/null
+++ b/resources/resources.qrc
@@ -0,0 +1,7 @@
+
+
+ MIDI_LOGO.svg
+ MIDI_TRAY.svg
+ keys.png
+
+
diff --git a/resources/shot.png b/resources/shot.png
new file mode 100644
index 0000000..6fce2c5
Binary files /dev/null and b/resources/shot.png differ
diff --git a/resources/xmidix.desktop b/resources/xmidix.desktop
new file mode 100644
index 0000000..a30990f
--- /dev/null
+++ b/resources/xmidix.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=XMIDIX
+GenericName=HARDWARE MIDI PLAYER
+Comment=XMIDIX HARDWARE MIDI PLAYER
+Type=Application
+Terminal=false
+Exec=xmidix
+Icon=xmidix
+MimeType=audio/midi;audio/x-midi
+Categories=AudioVideo;Audio;Player
diff --git a/test/test.mid b/test/test.mid
new file mode 100644
index 0000000..eca4bd6
Binary files /dev/null and b/test/test.mid differ
diff --git a/test/test2.mid b/test/test2.mid
new file mode 100644
index 0000000..5525232
Binary files /dev/null and b/test/test2.mid differ
diff --git a/test/test3.mid b/test/test3.mid
new file mode 100644
index 0000000..bafbe93
Binary files /dev/null and b/test/test3.mid differ
diff --git a/xmidix.cc b/xmidix.cc
new file mode 100644
index 0000000..14fc774
--- /dev/null
+++ b/xmidix.cc
@@ -0,0 +1,43 @@
+#include "xmidix_player.h"
+
+#include
+#include
+#include
+#include
+#include
+
+typedef struct {
+ QList files;
+} args_t;
+
+args_t parse_args(int argc, char **argv) {
+ QList files;
+
+ for (int i = 1; i < argc; i++) {
+ auto arg = argv[i];
+ if (QFileInfo::exists(arg)) {
+ auto url = QUrl::fromLocalFile(arg);
+ files.append(url);
+ } else {
+ spdlog::warn("file does not exist: {}", arg);
+ }
+ }
+
+ return {files};
+}
+
+int main(int argc, char **argv) {
+ spdlog::set_level(spdlog::level::debug);
+ QApplication a(argc, argv);
+ QCoreApplication::setOrganizationName("xeyes.org");
+ QCoreApplication::setOrganizationDomain("xeyes.org");
+ QCoreApplication::setApplicationName("XMIDIX");
+
+ auto args = parse_args(argc, argv);
+
+ xmidix_player w{nullptr, args.files};
+ w.show();
+
+ bool result = a.exec();
+ return result;
+}
diff --git a/xmidix.ui b/xmidix.ui
new file mode 100644
index 0000000..c7eb254
--- /dev/null
+++ b/xmidix.ui
@@ -0,0 +1,272 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 526
+ 252
+
+
+
+
+ MS Mincho
+ 12
+
+
+
+ PointingHandCursor
+
+
+ XMIDIX
+
+
+
+ ../../.designer/backup ../../.designer/backup
+
+
+
+ -
+
+
+
+ MS Mincho
+ 16
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ QAbstractItemView::InternalMove
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ false
+
+
+ false
+
+
+ Qt::Horizontal
+
+
+ false
+
+
+ false
+
+
+ QSlider::NoTicks
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 10
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ . .
+
+
+
+ -
+
+
+
+
+
+
+ :/icons/MIDI_LOGO.svg :/icons/MIDI_LOGO.svg
+
+
+
+ -
+
+
+
+
+
+
+ :/icons/keys.png :/icons/keys.png
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 30
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+ xmidix_vu_array
+ QFrame
+
+ 1
+
+
+
+
+
+
+
diff --git a/xmidix_config.cc b/xmidix_config.cc
new file mode 100644
index 0000000..2378759
--- /dev/null
+++ b/xmidix_config.cc
@@ -0,0 +1,78 @@
+#include "xmidix_config.h"
+
+xmidix_config::xmidix_config(QWidget *parent)
+ : QDialog(parent), ui{} {
+ ui.setupUi(this);
+ output_device = ui.output_device;
+
+ connect(ui.midi_refresh_button, &QPushButton::clicked,
+ [this] { emit clients_update(); });
+ connect(ui.buttonBox, &QDialogButtonBox::accepted,
+ this, &xmidix_config::accepted_handler);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected,
+ this, &xmidix_config::rejected_handler);
+}
+
+xmidix_client_info xmidix_config::get_selected_client() {
+ return clients[output_index];
+}
+
+void xmidix_config::reload_settings() {
+// ui.systray_check->setChecked(settings.)
+}
+
+void xmidix_config::set_selected_client(int client_id, int port_id) {
+ for (int i = 0; i < clients.size(); i++) {
+ auto client = clients[i];
+ if (client.client_id == client_id && client.port_id == port_id) {
+ output_device->setCurrentIndex(i);
+ return;
+ }
+ }
+}
+
+void xmidix_config::clients_updated(const std::vector &c) {
+ clients = c;
+ output_index = output_device->currentIndex();
+ output_device->clear();
+
+ for (const auto &client : clients) {
+ auto txt = fmt::format("{}:{}\t{} - {}",
+ client.client_id, client.port_id,
+ client.client_name, client.port_name);
+ output_device->addItem(QString::fromStdString(txt));
+ }
+
+ output_device->setCurrentIndex(output_index);
+}
+
+void xmidix_config::accepted_handler() {
+ bool updated = false;
+ if (output_device->currentIndex() != output_index) {
+ output_index = output_device->currentIndex();
+ updated = true;
+ }
+
+ if (ui.systray_check->isChecked() != systray) {
+ updated = true;
+ systray = ui.systray_check->isChecked();
+ }
+
+ if (ui.channel_status_check->isChecked() != chanbar) {
+ updated = true;
+ chanbar = ui.channel_status_check->isChecked();
+ }
+
+ if (updated) {
+ emit config_updated();
+ }
+}
+
+void xmidix_config::rejected_handler() {
+ output_device->setCurrentIndex(output_index);
+}
+
+void xmidix_config::showEvent(QShowEvent *event) {
+ reload_settings();
+ QDialog::showEvent(event);
+}
diff --git a/xmidix_config.h b/xmidix_config.h
new file mode 100644
index 0000000..9de93b0
--- /dev/null
+++ b/xmidix_config.h
@@ -0,0 +1,55 @@
+#ifndef XMIDIX__XMIDIX_CONFIG_H_
+#define XMIDIX__XMIDIX_CONFIG_H_
+
+#include "ui_xmidix_config.h"
+#include "xmidix_seq.h"
+
+#include
+#include
+#include
+#include
+#include
+
+class xmidix_config : public QDialog {
+ Q_OBJECT
+ public:
+ explicit xmidix_config(QWidget *parent = nullptr);
+ xmidix_client_info get_selected_client();
+ void set_selected_client(int client_id, int port_id);
+
+ void set_systray(bool val) {
+ systray = val;
+ ui.systray_check->setChecked(val);
+ }
+
+ void set_chanbar(bool val) {
+ chanbar = val;
+ ui.channel_status_check->setChecked(val);
+ }
+
+ Ui::config_window ui;
+
+ signals:
+ void config_updated();
+ void clients_update();
+
+ public slots:
+ void clients_updated(const std::vector& c);
+ void accepted_handler();
+ void rejected_handler();
+
+ protected:
+ void showEvent(QShowEvent *event) override;
+
+ private:
+ void reload_settings();
+
+ std::vector clients;
+
+ QComboBox *output_device;
+ int output_index = 0;
+ bool systray{true};
+ bool chanbar{true};
+};
+
+#endif //XMIDIX__XMIDIX_CONFIG_H_
diff --git a/xmidix_config.ui b/xmidix_config.ui
new file mode 100644
index 0000000..5689482
--- /dev/null
+++ b/xmidix_config.ui
@@ -0,0 +1,206 @@
+
+
+ config_window
+
+
+
+ 0
+ 0
+ 407
+ 118
+
+
+
+
+ 0
+ 0
+
+
+
+
+ MS Mincho
+ 12
+
+
+
+ PointingHandCursor
+
+
+ XMIDI CONFIG
+
+
+
+ . .
+
+
+ -
+
+
+ 8
+
+ -
+
+
+
+ 0
+ 26
+
+
+
+ OUTPUT DEVICE:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 205
+ 26
+
+
+
+
+ 16777215
+ 26
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 26
+
+
+
+
+
+
+
+ . .
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ SHOW CHANNEL STATUS BAR
+
+
+ true
+
+
+
+ -
+
+
+ SHOW SYSTEM TRAY
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ config_window
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ config_window
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/xmidix_file.cc b/xmidix_file.cc
new file mode 100644
index 0000000..480d9f4
--- /dev/null
+++ b/xmidix_file.cc
@@ -0,0 +1,94 @@
+#include "xmidix_file.h"
+
+#include
+#include
+#include
+
+#define PARSER_BUF_SIZE 16
+
+std::string decode_string(MidiEvent *event) {
+ auto txt = dynamic_cast(event)->getMetaContent();
+ auto codec = QTextCodec::codecForName("Shift-JIS");
+ auto decoded = codec->toUnicode(txt.c_str());
+
+ return decoded.toStdString();
+}
+
+void xmidix_file::handle_meta_event(MidiEvent *event, snd_seq_event_t *seq_event) {
+ if (event->isTempo()) {
+ snd_seq_ev_set_queue_tempo(seq_event, 0, event->getTempoMicro());
+ return;
+ }
+
+ if (event->isText()) {
+ texts.emplace_back(decode_string(event));
+ return;
+ }
+
+ if (event->isMarkerText()) {
+ markers.emplace_back(xmidix_marker_t{
+ event->tick,
+ decode_string(event)
+ });
+ return;
+ }
+
+ if (event->isTrackName()) {
+ track_names[event->track] = decode_string(event);
+ return;
+ }
+}
+
+xmidix_file::xmidix_file(const std::string &filename) {
+ MidiFile midifile;
+ if (!midifile.read(filename)) {
+ spdlog::warn("could not open MIDI file...");
+ return;
+ }
+
+ midifile.joinTracks();
+ ppqn = midifile.getTicksPerQuarterNote();
+
+ snd_midi_event_t *parser;
+ snd_midi_event_new(PARSER_BUF_SIZE, &parser);
+
+ auto midi_events = midifile[0]; // only support single track atm
+ for (int i = 0; i < midi_events.getEventCount(); i++) {
+ auto event = midi_events[i];
+
+ snd_seq_event_t seq_event;
+ snd_seq_ev_clear(&seq_event);
+
+ snd_midi_event_reset_encode(parser);
+ auto result = snd_midi_event_encode(parser, event.data(), static_cast(event.size()), &seq_event);
+ if (result <= 0) {
+ spdlog::warn("error encoding midi event at tick = {}", event.tick);
+ continue;
+ }
+
+ if (event.isMeta()) {
+ handle_meta_event(&event, &seq_event);
+ }
+
+ seq_event.time.tick = event.tick;
+ events.emplace_back(seq_event);
+ }
+
+ for (const auto& t : texts) {
+ spdlog::info("text = [{}]", t);
+ }
+
+ for (const auto& m : markers) {
+ spdlog::info("marker = {}: [{}]", m.tick, m.name);
+ }
+
+ for (const auto& [k, v] : track_names) {
+ spdlog::info("track = {}: [{}]", k, v);
+ }
+
+ snd_midi_event_free(parser);
+}
+
+xmidix_file::~xmidix_file() {
+ spdlog::debug("destructing xmidix_file");
+}
diff --git a/xmidix_file.h b/xmidix_file.h
new file mode 100644
index 0000000..2f7782b
--- /dev/null
+++ b/xmidix_file.h
@@ -0,0 +1,35 @@
+#ifndef ARP__XMIDI_FILE_H_
+#define ARP__XMIDI_FILE_H_
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+using namespace smf;
+
+typedef struct {
+ int tick;
+ std::string name;
+} xmidix_marker_t;
+
+class xmidix_file {
+ public:
+ explicit xmidix_file(const std::string &filename);
+ ~xmidix_file();
+
+ std::vector events;
+ std::vector markers;
+ std::map track_names;
+ std::vector texts;
+ int ppqn;
+
+ private:
+ void handle_meta_event(MidiEvent *event, snd_seq_event_t *seq_event);
+};
+
+#endif //ARP__XMIDI_FILE_H_
diff --git a/xmidix_piano.cc b/xmidix_piano.cc
new file mode 100644
index 0000000..3bcd3f1
--- /dev/null
+++ b/xmidix_piano.cc
@@ -0,0 +1,129 @@
+#include "xmidix_piano.h"
+
+#include
+#include
+#include
+#include
+
+#define TRIGGER_COLOR Qt::red
+#define WHITE_COLOR Qt::white
+#define BLACK_COLOR Qt::black
+#define OUTLINE_COLOR Qt::black
+
+bool is_white_key(int idx) {
+ switch (idx % 12) {
+ case 1:
+ case 3:
+ case 6:
+ case 8:
+ case 10:
+ return false;
+ default:
+ return true;
+ }
+}
+
+xmidix_piano::xmidix_piano(QWidget *parent, std::shared_ptr grad)
+ : QWidget{parent}, gradient{grad} {
+ setMinimumHeight(30);
+ setMinimumWidth(128 * 6);
+// setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+}
+
+void xmidix_piano::paintEvent(QPaintEvent *event) {
+ QPainter painter{this};
+ painter.setPen(OUTLINE_COLOR);
+
+ double white_width = (width() - 1) / 75.0;
+ double black_width = white_width * 0.60;
+ double black_adjust = black_width / 2.0;
+ double x = 0;
+
+ std::map black_offsets;
+
+ // draw white keys
+ for (int i = 0; i < 128; i++) {
+ auto velocity = key_state[i];
+ auto color = velocity == 0 ? WHITE_COLOR : gradient->pixelColor(velocity, 0);
+
+ if (is_white_key(i)) {
+ QRectF r(x, 0, white_width, height() - 1);
+// painter.fillRect(r, on ? TRIGGER_COLOR : WHITE_COLOR);
+ painter.fillRect(r, color);
+ painter.drawRect(r);
+ x += white_width;
+ } else {
+ // save x position for next loop
+ black_offsets[i] = x - black_adjust;
+ }
+ }
+
+ // draw black keys using offsets
+ for (const auto&[i, offset] : black_offsets) {
+ auto velocity = key_state[i];
+ auto color = velocity == 0 ? BLACK_COLOR : gradient->pixelColor(velocity, 0);
+
+// QRectF r(offset, 0, black_width, height() / 2.0);
+ QRectF r(offset, 0, black_width, height() * 0.6);
+// painter.fillRect(r, on ? TRIGGER_COLOR : BLACK_COLOR);
+ painter.fillRect(r, color);
+ painter.drawRect(r);
+ }
+}
+
+xmidix_piano_array::xmidix_piano_array(QWidget *parent)
+ : QDialog{parent} {
+ setAttribute(Qt::WA_X11NetWmWindowTypeUtility);
+ setWindowTitle("XMIDIXPIANOX");
+// setFixedHeight(518);
+// setFixedWidth(600);
+// setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+
+ setLayout(new QVBoxLayout(this));
+
+ QLinearGradient grad(QPoint(0, 0), QPoint(127, 0));
+ grad.setColorAt(1, Qt::red);
+ grad.setColorAt(0.75, Qt::yellow);
+ grad.setColorAt(0, Qt::green);
+
+ auto img = std::make_shared(128, 1, QImage::Format_RGB32);
+ QPainter painter{img.get()};
+ painter.fillRect(img->rect(), QBrush(grad));
+ painter.end();
+
+ for (int i = 0; i < 16; i++) {
+ auto piano = std::make_shared(this, img);
+ layout()->addWidget(piano.get());
+ pianos.emplace_back(piano);
+ }
+
+ updateGeometry();
+}
+
+void xmidix_piano_array::midi_event(const snd_seq_event_t &event) {
+ auto type = event.type;
+ if (type == SND_SEQ_EVENT_NOTEOFF || type == SND_SEQ_EVENT_NOTEON || type == SND_SEQ_EVENT_KEYPRESS) {
+ auto data = event.data.note;
+ auto chan = data.channel;
+
+ if (chan < 0 || chan >= 16) {
+ spdlog::warn("got invalid channel = {}", chan);
+ return;
+ }
+
+ uint8_t velocity = type != SND_SEQ_EVENT_NOTEOFF ? data.velocity : 0;
+ pianos[data.channel]->set_key(data.note, velocity);
+ }
+}
+
+void xmidix_piano_array::hideEvent(QHideEvent *event) {
+ QWidget::hideEvent(event);
+ position = pos();
+}
+
+void xmidix_piano_array::showEvent(QShowEvent *event) {
+ QDialog::showEvent(event);
+ move(position);
+}
diff --git a/xmidix_piano.h b/xmidix_piano.h
new file mode 100644
index 0000000..dbda165
--- /dev/null
+++ b/xmidix_piano.h
@@ -0,0 +1,54 @@
+#ifndef XMIDIX__XMIDIX_PIANO_H_
+#define XMIDIX__XMIDIX_PIANO_H_
+
+#include
+#include
+#include
+#include
+
+class xmidix_piano : public QWidget {
+ public:
+ explicit xmidix_piano(QWidget *parent = nullptr, std::shared_ptr grad = nullptr);
+
+ void clear() {
+ for (int i = 0; i < 128; i++)
+ key_state[i] = false;
+
+ repaint();
+ }
+
+ void set_key(int key, uint8_t velocity) {
+ key_state[key] = velocity;
+ repaint();
+ }
+
+ protected:
+ void paintEvent(QPaintEvent *event) override;
+
+ private:
+ uint8_t key_state[128] = {0};
+ std::shared_ptr gradient;
+};
+
+class xmidix_piano_array : public QDialog {
+ Q_OBJECT
+ public:
+ explicit xmidix_piano_array(QWidget *parent = nullptr);
+ void clear() {
+ for (const auto &p : pianos)
+ p->clear();
+ }
+
+ public slots:
+ void midi_event(const snd_seq_event_t &event);
+
+ protected:
+ void showEvent(QShowEvent *event) override;
+ void hideEvent(QHideEvent *event) override;
+
+ private:
+ std::vector> pianos;
+ QPoint position;
+};
+
+#endif //XMIDIX__XMIDIX_PIANO_H_
diff --git a/xmidix_player.cc b/xmidix_player.cc
new file mode 100644
index 0000000..1ceb6cf
--- /dev/null
+++ b/xmidix_player.cc
@@ -0,0 +1,297 @@
+#include "xmidix_player.h"
+
+#include
+#include
+#include
+#include
+#include
+
+xmidix_player::xmidix_player(QWidget *parent, QList files)
+ : QMainWindow(parent),
+ ui{},
+ seq{},
+ config_window{this},
+ model{this},
+ progress_timer{this},
+ tray_icon{QIcon(":/icons/MIDI_LOGO.svg"), this},
+ settings{this},
+ status{play_status::STOPPED},
+ last_tick{0} {
+
+ setAttribute(Qt::WA_X11NetWmWindowTypeUtility);
+ setWindowTitle(WINDOW_TITLE);
+ tray_icon.show();
+
+ model.replace(files);
+
+ ui.setupUi(this);
+ ui.listView->setModel(&model);
+ ui.listView->installEventFilter(this);
+
+ setup_actions();
+ setup_connections();
+ config_client_update();
+
+ load_settings();
+}
+
+void xmidix_player::load_settings() {
+ auto client = settings.value(SETTINGS_CLIENT);
+ auto port = settings.value(SETTINGS_PORT);
+
+ auto client_id = client.isNull() ? 14 : client.toInt();
+ auto port_id = port.isNull() ? 0 : port.toInt();
+
+ seq.connect(client_id, port_id);
+ config_window.set_selected_client(client_id, port_id);
+
+ auto tray = settings.value(SETTINGS_SYSTRAY);
+ auto tray_visible = tray.isNull() || tray.toBool();
+ tray_icon.setVisible(tray_visible);
+ config_window.set_systray(tray_visible);
+
+ auto chanbar = settings.value(SETTINGS_CHANBAR);
+ auto chanbar_visible = chanbar.isNull() || chanbar.toBool();
+ ui.vus->setVisible(chanbar_visible);
+ config_window.set_chanbar(chanbar_visible);
+}
+
+void xmidix_player::setup_action(QAction *a, const QKeySequence &s) {
+ a->setShortcut(s);
+ addAction(a);
+}
+
+// to satisfy c++20 pedantry
+inline constexpr int operator|(Qt::Modifier m, Qt::Key k) { return (int) m | (int) k; }
+void xmidix_player::setup_actions() {
+ setup_action(&play_action, Qt::Key_Space);
+ setup_action(&load_action, Qt::CTRL | Qt::Key_O);
+ setup_action(&stop_action, Qt::CTRL | Qt::Key_Space);
+ setup_action(&conf_action, Qt::CTRL | Qt::Key_P);
+ setup_action(&back_action, Qt::CTRL | Qt::Key_Left);
+ setup_action(&forw_action, Qt::CTRL | Qt::Key_Right);
+ setup_action(&shuf_action, {});
+}
+
+void xmidix_player::setup_connections() {
+ connect(&seq, &xmidix_seq::song_complete,
+ this, &xmidix_player::song_complete);
+ connect(&seq, &xmidix_seq::song_looping,
+ [] { spdlog::info("song looping."); });
+
+ connect(&progress_timer, &QTimer::timeout,
+ this, &xmidix_player::tick_update);
+
+ connect(this, &xmidix_player::status_update,
+ this, &xmidix_player::status_updated);
+ connect(this, &xmidix_player::clients_updated,
+ &config_window, &xmidix_config::clients_updated);
+ connect(this, &xmidix_player::song_change,
+ this, &xmidix_player::play);
+
+ connect(&config_window, &xmidix_config::clients_update,
+ this, &xmidix_player::config_client_update);
+ connect(&config_window, &xmidix_config::config_updated,
+ this, &xmidix_player::config_updated);
+
+ connect(ui.listView, &QListView::activated,
+ this, &xmidix_player::playlist_activated);
+ connect(ui.slider, &QSlider::sliderReleased,
+ this, &xmidix_player::slider_seek);
+
+ connect(ui.play_button, &QPushButton::clicked,
+ &play_action, &QAction::trigger);
+ connect(ui.load_button, &QPushButton::clicked,
+ &load_action, &QAction::trigger);
+ connect(ui.stop_button, &QPushButton::clicked,
+ &stop_action, &QAction::trigger);
+ connect(ui.config_button, &QPushButton::clicked,
+ &conf_action, &QAction::trigger);
+ connect(ui.backward_button, &QPushButton::clicked,
+ &back_action, &QAction::trigger);
+ connect(ui.forward_button, &QPushButton::clicked,
+ &forw_action, &QAction::trigger);
+ connect(ui.shuffle_button, &QPushButton::clicked,
+ &shuf_action, &QAction::trigger);
+
+ connect(&play_action, &QAction::triggered,
+ this, &xmidix_player::play_button_click);
+ connect(&load_action, &QAction::triggered,
+ this, &xmidix_player::load_button_click);
+ connect(&stop_action, &QAction::triggered,
+ this, &xmidix_player::stop_button_click);
+ connect(&conf_action, &QAction::triggered,
+ this, &xmidix_player::config_button_click);
+ connect(&back_action, &QAction::triggered,
+ [&] { emit song_change(model.previous_item()); });
+ connect(&forw_action, &QAction::triggered,
+ [&] { emit song_change(model.next_item()); });
+ connect(&shuf_action, &QAction::triggered,
+ [&] { model.shuffle(); });
+
+ connect(&tray_icon, &QSystemTrayIcon::activated,
+ [&] { this->setVisible(!this->isVisible()); });
+ connect(ui.piano_button, &QPushButton::clicked,
+ [&] { pianos.setVisible(!pianos.isVisible()); });
+
+ connect(&seq, &xmidix_seq::midi_event,
+ ui.vus, &xmidix_vu_array::midi_event);
+ connect(&seq, &xmidix_seq::midi_event,
+ &pianos, &xmidix_piano_array::midi_event);
+}
+
+xmidix_player::~xmidix_player() {
+
+}
+
+void xmidix_player::play_button_click() {
+ switch (status) {
+ case play_status::PLAYING:
+ emit status_update(play_status::PAUSED);
+ seq.stop();
+ break;
+ case play_status::PAUSED:
+ emit status_update(play_status::PLAYING);
+ seq.unpause();
+ break;
+ case play_status::STOPPED:
+ emit song_change(model.current_item());
+ break;
+ }
+}
+
+void xmidix_player::stop_button_click() {
+ emit status_update(play_status::STOPPED);
+ seq.stop();
+}
+
+void xmidix_player::load_button_click() {
+ auto cwd = settings.value(SETTINGS_CWD).toString();
+ auto files = QFileDialog::getOpenFileUrls(
+ this, "OPEN MIDI FILES",
+ QUrl::fromLocalFile(cwd),
+ "MIDI FILES (*.mid *.midi)");
+
+ if (!files.empty()) {
+ auto f = QFileInfo(files[0].toLocalFile());
+ settings.setValue(SETTINGS_CWD, f.dir().canonicalPath());
+
+ model.replace(files);
+ }
+}
+
+void xmidix_player::playlist_activated(const QModelIndex &index) {
+ model.set_index(index.row());
+ emit song_change(model.current_item());
+}
+
+void xmidix_player::play(const QUrl &url) {
+ auto path = url.path().toStdString();
+
+ xmidix_file midi(path);
+ if (midi.events.empty()) {
+ spdlog::warn("bad midi file = {}", path);
+ return;
+ }
+
+ auto events = midi.events;
+ auto last_event = events[events.size() - 1];
+ last_tick = last_event.time.tick;
+ spdlog::info("last tick of song = {}", last_tick);
+
+ spdlog::info("playing = {}", url.path().toStdString());
+ emit status_update(play_status::PLAYING);
+ seq.load(midi);
+ seq.play();
+
+ setWindowTitle(
+ fmt::format("{} - {}", WINDOW_TITLE, url.fileName().toStdString()).c_str());
+
+ auto idx = model.current_index();
+ ui.listView->clearSelection();
+ ui.listView->setCurrentIndex(idx);
+ ui.listView->scrollTo(idx);
+}
+
+void xmidix_player::config_button_click() {
+ config_window.show();
+}
+
+void xmidix_player::config_updated() {
+ auto client = config_window.get_selected_client();
+ settings.setValue(SETTINGS_CLIENT, client.client_id);
+ settings.setValue(SETTINGS_PORT, client.port_id);
+ seq.connect(client.client_id, client.port_id);
+
+ auto tray = config_window.ui.systray_check->isChecked();
+ settings.setValue(SETTINGS_SYSTRAY, tray);
+ tray_icon.setVisible(tray);
+
+ auto chanbar = config_window.ui.channel_status_check->isChecked();
+ settings.setValue(SETTINGS_CHANBAR, chanbar);
+ ui.vus->setVisible(chanbar);
+}
+
+void xmidix_player::config_client_update() {
+ emit clients_updated(seq.enumerate_clients());
+}
+
+void xmidix_player::status_updated(play_status s) {
+ ui.vus->clear();
+ pianos.clear();
+
+ status = s;
+ switch (status) {
+ case play_status::STOPPED:
+ setWindowTitle(WINDOW_TITLE);
+ ui.play_button->setIcon(QIcon::fromTheme("media-playback-start"));
+ ui.slider->setValue(0);
+ ui.slider->setEnabled(false);
+ progress_timer.stop();
+ break;
+ case play_status::PLAYING:
+ ui.play_button->setIcon(QIcon::fromTheme("media-playback-pause"));
+ ui.slider->setEnabled(true);
+ progress_timer.start(std::chrono::seconds{1});
+ break;
+ case play_status::PAUSED:
+ ui.play_button->setIcon(QIcon::fromTheme("media-playback-start"));
+ progress_timer.stop();
+ break;
+ }
+}
+
+void xmidix_player::song_complete() {
+ if (status == play_status::PLAYING) {
+ spdlog::debug("song complete.");
+ emit song_change(model.next_item());
+ ui.vus->clear();
+ }
+}
+
+void xmidix_player::tick_update() {
+ if (!ui.slider->isSliderDown()) {
+ auto tick = seq.get_tick();
+ auto percent = (static_cast(tick) / last_tick) * 100;
+ ui.slider->setValue(static_cast