From 48f7fd685099fcb40505c4b8a7eb4bd696549060 Mon Sep 17 00:00:00 2001 From: Charlie Ray Date: Tue, 28 Apr 2026 12:40:19 -0500 Subject: [PATCH] Add Audio Stems panel with alt-audio-tracks integration and HTDemucs persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a collapsible stems panel below the main waveform with per-stem mini-waveforms that mirror SequenceFile::alt_tracks 1:1. The panel works with stems from two sources: - Imported stems: any audio files prepared outside xLights (e.g. from a DAW, an online stem splitter, or hand-rendered tracks). Pulled in via the panel's Import action or the Sequence Settings alt-tracks tab. - AI-generated stems: produced in-app by HTDemucs via the right-click waveform "Stem — Drums/Bass/Vocals/Other" submenu. The result is written to /audio-stems// and registered as alt tracks, so re-running on the same sequence is an instant cache hit instead of another inference pass. Each row supports rename, recolor, drag-reorder, and a DAW-style "S" (solo) button that switches main playback to that stem at the current play position. Right-click on a row offers "Create Timing Track from Transients..." which generates a timing track from stem onsets via aubio's spectral-flux peak picker. Stems imported via the panel and alt tracks added through Sequence Settings stay in sync — both show in the panel and are visible to VU Meter and Value Curve bindings. --- README.txt | 5 + dependencies/aubio/COPYING | 674 +++++++ dependencies/aubio/src/aubio.h | 233 +++ dependencies/aubio/src/aubio_priv.h | 413 ++++ dependencies/aubio/src/config.h | 23 + dependencies/aubio/src/cvec.c | 169 ++ dependencies/aubio/src/cvec.h | 247 +++ dependencies/aubio/src/fmat.c | 186 ++ dependencies/aubio/src/fmat.h | 172 ++ dependencies/aubio/src/fvec.c | 149 ++ dependencies/aubio/src/fvec.h | 178 ++ dependencies/aubio/src/lvec.c | 80 + dependencies/aubio/src/lvec.h | 118 ++ dependencies/aubio/src/mathutils.c | 681 +++++++ dependencies/aubio/src/mathutils.h | 338 ++++ dependencies/aubio/src/musicutils.c | 85 + dependencies/aubio/src/musicutils.h | 270 +++ dependencies/aubio/src/onset/onset.c | 355 ++++ dependencies/aubio/src/onset/onset.h | 347 ++++ dependencies/aubio/src/onset/peakpicker.c | 199 ++ dependencies/aubio/src/onset/peakpicker.h | 57 + dependencies/aubio/src/spectral/awhitening.c | 121 ++ dependencies/aubio/src/spectral/awhitening.h | 125 ++ dependencies/aubio/src/spectral/fft.c | 572 ++++++ dependencies/aubio/src/spectral/fft.h | 144 ++ dependencies/aubio/src/spectral/ooura_fft8g.c | 1672 +++++++++++++++++ dependencies/aubio/src/spectral/phasevoc.c | 224 +++ dependencies/aubio/src/spectral/phasevoc.h | 113 ++ dependencies/aubio/src/spectral/specdesc.c | 429 +++++ dependencies/aubio/src/spectral/specdesc.h | 204 ++ dependencies/aubio/src/spectral/statistics.c | 204 ++ dependencies/aubio/src/temporal/biquad.c | 54 + dependencies/aubio/src/temporal/biquad.h | 75 + dependencies/aubio/src/temporal/filter.c | 163 ++ dependencies/aubio/src/temporal/filter.h | 176 ++ dependencies/aubio/src/types.h | 70 + dependencies/aubio/src/utils/hist.c | 151 ++ dependencies/aubio/src/utils/hist.h | 63 + dependencies/aubio/src/utils/log.c | 92 + dependencies/aubio/src/utils/log.h | 99 + dependencies/aubio/src/utils/scale.c | 79 + dependencies/aubio/src/utils/scale.h | 80 + dependencies/aubio/src/vecutils.c | 36 + dependencies/aubio/src/vecutils.h | 116 ++ macOS | 2 +- src-core/render/SequenceFile.cpp | 14 + src-core/render/SequenceFile.h | 1 + src-core/utils/WavWriter.cpp | 101 + src-core/utils/WavWriter.h | 28 + src-ui-wx/StemOnsetDialog.cpp | 610 ++++++ src-ui-wx/StemOnsetDialog.h | 106 ++ src-ui-wx/import_export/SeqFileUtilities.cpp | 2 + src-ui-wx/sequencer/MainSequencer.cpp | 55 +- src-ui-wx/sequencer/MainSequencer.h | 5 + src-ui-wx/sequencer/RowHeading.cpp | 5 + src-ui-wx/sequencer/StemWaveform.cpp | 553 ++++++ src-ui-wx/sequencer/StemWaveform.h | 122 ++ src-ui-wx/sequencer/StemsPanel.cpp | 1360 ++++++++++++++ src-ui-wx/sequencer/StemsPanel.h | 234 +++ src-ui-wx/sequencer/Waveform.cpp | 297 ++- src-ui-wx/sequencer/Waveform.h | 7 + src-ui-wx/sequencer/tabSequencer.cpp | 23 + src-ui-wx/wxsmith/xLightsframe.wxs | 5 + src-ui-wx/xLightsMain.cpp | 9 + src-ui-wx/xLightsMain.h | 3 + xLights/Xlights.vcxproj | 42 +- xLights/Xlights.vcxproj.filters | 87 + xLights/xLights.cbp | 30 + 68 files changed, 13693 insertions(+), 49 deletions(-) create mode 100644 dependencies/aubio/COPYING create mode 100644 dependencies/aubio/src/aubio.h create mode 100644 dependencies/aubio/src/aubio_priv.h create mode 100644 dependencies/aubio/src/config.h create mode 100644 dependencies/aubio/src/cvec.c create mode 100644 dependencies/aubio/src/cvec.h create mode 100644 dependencies/aubio/src/fmat.c create mode 100644 dependencies/aubio/src/fmat.h create mode 100644 dependencies/aubio/src/fvec.c create mode 100644 dependencies/aubio/src/fvec.h create mode 100644 dependencies/aubio/src/lvec.c create mode 100644 dependencies/aubio/src/lvec.h create mode 100644 dependencies/aubio/src/mathutils.c create mode 100644 dependencies/aubio/src/mathutils.h create mode 100644 dependencies/aubio/src/musicutils.c create mode 100644 dependencies/aubio/src/musicutils.h create mode 100644 dependencies/aubio/src/onset/onset.c create mode 100644 dependencies/aubio/src/onset/onset.h create mode 100644 dependencies/aubio/src/onset/peakpicker.c create mode 100644 dependencies/aubio/src/onset/peakpicker.h create mode 100644 dependencies/aubio/src/spectral/awhitening.c create mode 100644 dependencies/aubio/src/spectral/awhitening.h create mode 100644 dependencies/aubio/src/spectral/fft.c create mode 100644 dependencies/aubio/src/spectral/fft.h create mode 100644 dependencies/aubio/src/spectral/ooura_fft8g.c create mode 100644 dependencies/aubio/src/spectral/phasevoc.c create mode 100644 dependencies/aubio/src/spectral/phasevoc.h create mode 100644 dependencies/aubio/src/spectral/specdesc.c create mode 100644 dependencies/aubio/src/spectral/specdesc.h create mode 100644 dependencies/aubio/src/spectral/statistics.c create mode 100644 dependencies/aubio/src/temporal/biquad.c create mode 100644 dependencies/aubio/src/temporal/biquad.h create mode 100644 dependencies/aubio/src/temporal/filter.c create mode 100644 dependencies/aubio/src/temporal/filter.h create mode 100644 dependencies/aubio/src/types.h create mode 100644 dependencies/aubio/src/utils/hist.c create mode 100644 dependencies/aubio/src/utils/hist.h create mode 100644 dependencies/aubio/src/utils/log.c create mode 100644 dependencies/aubio/src/utils/log.h create mode 100644 dependencies/aubio/src/utils/scale.c create mode 100644 dependencies/aubio/src/utils/scale.h create mode 100644 dependencies/aubio/src/vecutils.c create mode 100644 dependencies/aubio/src/vecutils.h create mode 100644 src-core/utils/WavWriter.cpp create mode 100644 src-core/utils/WavWriter.h create mode 100644 src-ui-wx/StemOnsetDialog.cpp create mode 100644 src-ui-wx/StemOnsetDialog.h create mode 100644 src-ui-wx/sequencer/StemWaveform.cpp create mode 100644 src-ui-wx/sequencer/StemWaveform.h create mode 100644 src-ui-wx/sequencer/StemsPanel.cpp create mode 100644 src-ui-wx/sequencer/StemsPanel.h diff --git a/README.txt b/README.txt index a3edce917b..112bf6a151 100644 --- a/README.txt +++ b/README.txt @@ -21,6 +21,11 @@ XLIGHTS/NUTCRACKER RELEASE NOTES -change (dkulp) Render engine cleanup: removed the main-thread effect render queue and all the wx CallAfter / drain plumbing that supported it. No effect now needs main-thread dispatch. + -enh (charlie) Audio Stems panel: collapsible row of per-stem mini-waveforms below the main + waveform with import/rename/recolor/reorder, S (solo) button per row, aubio-based + onset → timing track generation, and unified storage with upstream alternate audio + tracks (stems are visible to VU Meter and Value Curve bindings, alt tracks added via + Sequence Settings show in the panel, HTDemucs stems persist to disk as alt tracks). 2026.07 April 28, 2026 -enh (MrPierreB) Add node animation playback to SubModels dialog. diff --git a/dependencies/aubio/COPYING b/dependencies/aubio/COPYING new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/dependencies/aubio/COPYING @@ -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/dependencies/aubio/src/aubio.h b/dependencies/aubio/src/aubio.h new file mode 100644 index 0000000000..3c9216d09b --- /dev/null +++ b/dependencies/aubio/src/aubio.h @@ -0,0 +1,233 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \mainpage + + \section introduction Introduction + + aubio is a library to extract annotations from audio signals: it provides a + set of functions that take an input audio signal, and output pitch estimates, + attack times (onset), beat location estimates, and other annotation tasks. + + \section basics Basics + + All object structures in aubio share the same function prefixes and suffixes: + + - \p new_aubio_foo creates the object \p foo + - \p aubio_foo_do executes the object \p foo + - \p del_aubio_foo destroys the object \p foo + + All memory allocation and deallocation take place in the \p new_ and \p del_ + functions. Optionally, more than one \p _do methods are available. + Additional parameters can be adjusted and observed using: + + - \p aubio_foo_get_param, getter function, gets the value of a parameter + - \p aubio_foo_set_param, setter function, changes the value of a parameter + + Unless specified in its documentation, no memory operations take place in the + getter functions. However, memory resizing can take place in setter + functions. + + \subsection vectors Vectors + + Two basic structures are being used in aubio: ::fvec_t and ::cvec_t. The + ::fvec_t structures are used to store vectors of floating pointer number. + ::cvec_t are used to store complex number, as two vectors of norm and phase + elements. + + Additionally, the ::lvec_t structure can be used to store floating point + numbers in double precision. They are mostly used to store filter + coefficients, to avoid instability. + + \subsection objects Available objects + + Here is a list of some of the most common objects for aubio: + + \code + + // fast Fourier transform (FFT) + aubio_fft_t *fft = new_aubio_fft (winsize); + // phase vocoder + aubio_pvoc_t *pv = new_aubio_pvoc (winsize, stepsize); + // onset detection + aubio_onset_t *onset = new_aubio_onset (method, winsize, stepsize, samplerate); + // pitch detection + aubio_pitch_t *pitch = new_aubio_pitch (method, winsize, stepsize, samplerate); + // beat tracking + aubio_tempo_t *tempo = new_aubio_tempo (method, winsize, stepsize, samplerate); + + \endcode + + See the list of typedefs for a complete list. + + \subsection example Example + + Here is a simple example that creates an A-Weighting filter and applies it to a + vector. + + \code + + // set window size, and sampling rate + uint_t winsize = 1024, sr = 44100; + // create a vector + fvec_t *this_buffer = new_fvec (winsize); + // create the a-weighting filter + aubio_filter_t *this_filter = new_aubio_filter_a_weighting (sr); + + while (running) { + // here some code to put some data in this_buffer + // ... + + // apply the filter, in place + aubio_filter_do (this_filter, this_buffer); + + // here some code to get some data from this_buffer + // ... + } + + // and free the structures + del_aubio_filter (this_filter); + del_fvec (this_buffer); + + \endcode + + Several examples of C programs are available in the \p examples/ and \p tests/src + directories of the source tree. + + Some examples: + - @ref spectral/test-fft.c + - @ref spectral/test-phasevoc.c + - @ref onset/test-onset.c + - @ref pitch/test-pitch.c + - @ref tempo/test-tempo.c + - @ref test-fvec.c + - @ref test-cvec.c + + \subsection unstable_api Unstable API + + Several more functions are available and used within aubio, but not + documented here, either because they are not considered useful to the user, + or because they may need to be changed in the future. However, they can still + be used by defining AUBIO_UNSTABLE to 1 before including the aubio header: + + \code + #define AUBIO_UNSTABLE 1 + #include + \endcode + + Future versions of aubio could break API compatibility with these functions + without warning. If you choose to use functions in AUBIO_UNSTABLE, you are on + your own. + + \section download Download + + Latest versions, further documentation, examples, wiki, and mailing lists can + be found at https://aubio.org . + + */ + +#ifndef AUBIO_H +#define AUBIO_H + +/** @file aubio.h Global aubio include file. + + You will want to include this file as: + + @code + #include + @endcode + + To access headers with unstable prototypes, use: + + @code + #define AUBIO_UNSTABLE 1 + #include + @endcode + + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* in this order */ +#include "types.h" +#include "fvec.h" +#include "cvec.h" +#include "lvec.h" +#include "fmat.h" +#include "musicutils.h" +#include "vecutils.h" +#include "temporal/resampler.h" +#include "temporal/filter.h" +#include "temporal/biquad.h" +#include "temporal/a_weighting.h" +#include "temporal/c_weighting.h" +#include "spectral/fft.h" +#include "spectral/dct.h" +#include "spectral/phasevoc.h" +#include "spectral/filterbank.h" +#include "spectral/filterbank_mel.h" +#include "spectral/mfcc.h" +#include "spectral/specdesc.h" +#include "spectral/awhitening.h" +#include "spectral/tss.h" +#include "pitch/pitch.h" +#include "onset/onset.h" +#include "tempo/tempo.h" +#include "notes/notes.h" +#include "io/source.h" +#include "io/sink.h" +#include "synth/sampler.h" +#include "synth/wavetable.h" +#include "utils/parameter.h" +#include "utils/log.h" + +#if AUBIO_UNSTABLE +#include "mathutils.h" +#include "io/source_sndfile.h" +#include "io/source_apple_audio.h" +#include "io/source_avcodec.h" +#include "io/source_wavread.h" +#include "io/sink_sndfile.h" +#include "io/sink_apple_audio.h" +#include "io/sink_wavwrite.h" +#include "io/audio_unit.h" +#include "onset/peakpicker.h" +#include "pitch/pitchmcomb.h" +#include "pitch/pitchyin.h" +#include "pitch/pitchyinfft.h" +#include "pitch/pitchyinfast.h" +#include "pitch/pitchschmitt.h" +#include "pitch/pitchfcomb.h" +#include "pitch/pitchspecacf.h" +#include "tempo/beattracking.h" +#include "effects/pitchshift.h" +#include "effects/timestretch.h" +#include "utils/scale.h" +#include "utils/hist.h" +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/dependencies/aubio/src/aubio_priv.h b/dependencies/aubio/src/aubio_priv.h new file mode 100644 index 0000000000..390a125539 --- /dev/null +++ b/dependencies/aubio/src/aubio_priv.h @@ -0,0 +1,413 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** @file + * Private include file + * + * This file is for inclusion from _within_ the library only. + */ + +#ifndef AUBIO_PRIV_H +#define AUBIO_PRIV_H + +/********************* + * + * External includes + * + */ + +/* xLights: always include our config.h */ +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef HAVE_STDIO_H +#include +#endif + +/* must be included before fftw3.h */ +#ifdef HAVE_COMPLEX_H +#include +#endif + +#if defined(HAVE_FFTW3) || defined(HAVE_FFTW3F) +#include +#endif + +#ifdef HAVE_MATH_H +#include +#endif + +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif + +#ifdef HAVE_LIMITS_H +#include // for CHAR_BIT, in C99 standard +#endif + +#ifdef HAVE_STDARG_H +#include +#endif + +#if defined(HAVE_BLAS) // --enable-blas=true +// check which cblas header we found +#if defined(HAVE_ATLAS_CBLAS_H) +#define HAVE_ATLAS 1 +#include +#elif defined(HAVE_OPENBLAS_CBLAS_H) +#include +#elif defined(HAVE_CBLAS_H) +#include +#elif !defined(HAVE_ACCELERATE) +#error "HAVE_BLAS was defined, but no blas header was found" +#endif /* end of cblas includes */ +#endif + +#if defined(HAVE_ACCELERATE) +// include accelerate framework after blas +#define HAVE_ATLAS 1 +#define HAVE_BLAS 1 +#define ACCELERATE_NEW_LAPACK 1 +#define ACCELERATE_LAPACK_ILP6 1 +#include + +#ifndef HAVE_AUBIO_DOUBLE +#define aubio_vDSP_mmov vDSP_mmov +#define aubio_vDSP_vmul vDSP_vmul +#define aubio_vDSP_vsmul vDSP_vsmul +#define aubio_vDSP_vsadd vDSP_vsadd +#define aubio_vDSP_vfill vDSP_vfill +#define aubio_vDSP_meanv vDSP_meanv +#define aubio_vDSP_sve vDSP_sve +#define aubio_vDSP_maxv vDSP_maxv +#define aubio_vDSP_maxvi vDSP_maxvi +#define aubio_vDSP_minv vDSP_minv +#define aubio_vDSP_minvi vDSP_minvi +#define aubio_vDSP_dotpr vDSP_dotpr +#define aubio_vDSP_vclr vDSP_vclr +#else /* HAVE_AUBIO_DOUBLE */ +#define aubio_vDSP_mmov vDSP_mmovD +#define aubio_vDSP_vmul vDSP_vmulD +#define aubio_vDSP_vsmul vDSP_vsmulD +#define aubio_vDSP_vsadd vDSP_vsaddD +#define aubio_vDSP_vfill vDSP_vfillD +#define aubio_vDSP_meanv vDSP_meanvD +#define aubio_vDSP_sve vDSP_sveD +#define aubio_vDSP_maxv vDSP_maxvD +#define aubio_vDSP_maxvi vDSP_maxviD +#define aubio_vDSP_minv vDSP_minvD +#define aubio_vDSP_minvi vDSP_minviD +#define aubio_vDSP_dotpr vDSP_dotprD +#define aubio_vDSP_vclr vDSP_vclrD +#endif /* HAVE_AUBIO_DOUBLE */ +#endif /* HAVE_ACCELERATE */ + +#if defined(HAVE_BLAS) +#ifndef HAVE_AUBIO_DOUBLE +#ifdef HAVE_ATLAS +#define aubio_catlas_set catlas_sset +#endif /* HAVE_ATLAS */ +#define aubio_cblas_copy cblas_scopy +#define aubio_cblas_swap cblas_sswap +#define aubio_cblas_dot cblas_sdot +#else /* HAVE_AUBIO_DOUBLE */ +#ifdef HAVE_ATLAS +#define aubio_catlas_set catlas_dset +#endif /* HAVE_ATLAS */ +#define aubio_cblas_copy cblas_dcopy +#define aubio_cblas_swap cblas_dswap +#define aubio_cblas_dot cblas_ddot +#endif /* HAVE_AUBIO_DOUBLE */ +#endif /* HAVE_BLAS */ + +#if defined HAVE_INTEL_IPP +#include +#include +#include +#ifndef HAVE_AUBIO_DOUBLE +#define aubio_ippsSet ippsSet_32f +#define aubio_ippsZero ippsZero_32f +#define aubio_ippsCopy ippsCopy_32f +#define aubio_ippsMul ippsMul_32f +#define aubio_ippsMulC ippsMulC_32f +#define aubio_ippsAddC ippsAddC_32f +#define aubio_ippsLn ippsLn_32f_A21 +#define aubio_ippsMean(a,b,c) ippsMean_32f(a, b, c, ippAlgHintFast) +#define aubio_ippsSum(a,b,c) ippsSum_32f(a, b, c, ippAlgHintFast) +#define aubio_ippsMax ippsMax_32f +#define aubio_ippsMin ippsMin_32f +#else /* HAVE_AUBIO_DOUBLE */ +#define aubio_ippsSet ippsSet_64f +#define aubio_ippsZero ippsZero_64f +#define aubio_ippsCopy ippsCopy_64f +#define aubio_ippsMul ippsMul_64f +#define aubio_ippsMulC ippsMulC_64f +#define aubio_ippsAddC ippsAddC_64f +#define aubio_ippsLn ippsLn_64f_A26 +#define aubio_ippsMean ippsMean_64f +#define aubio_ippsSum ippsSum_64f +#define aubio_ippsMax ippsMax_64f +#define aubio_ippsMin ippsMin_64f +#endif /* HAVE_AUBIO_DOUBLE */ +#endif + +#if !defined(HAVE_MEMCPY_HACKS) && !defined(HAVE_ACCELERATE) && !defined(HAVE_ATLAS) && !defined(HAVE_INTEL_IPP) +#define HAVE_NOOPT 1 +#endif + +#include "types.h" + +#define AUBIO_UNSTABLE 1 + +#include "mathutils.h" + +/**** + * + * SYSTEM INTERFACE + * + */ + +/* Memory management */ +#define AUBIO_MALLOC(_n) malloc(_n) +#define AUBIO_REALLOC(_p,_n) realloc(_p,_n) +#define AUBIO_NEW(_t) (_t*)calloc(sizeof(_t), 1) +#define AUBIO_ARRAY(_t,_n) (_t*)calloc((_n)*sizeof(_t), 1) +#define AUBIO_MEMCPY(_dst,_src,_n) memcpy(_dst,_src,_n) +#define AUBIO_MEMSET(_dst,_src,_t) memset(_dst,_src,_t) +#define AUBIO_FREE(_p) free(_p) + + +/* file interface */ +#define AUBIO_FOPEN(_f,_m) fopen(_f,_m) +#define AUBIO_FCLOSE(_f) fclose(_f) +#define AUBIO_FREAD(_p,_s,_n,_f) fread(_p,_s,_n,_f) +#define AUBIO_FSEEK(_f,_n,_set) fseek(_f,_n,_set) + +/* strings */ +#define AUBIO_STRLEN(_s) strlen(_s) +#define AUBIO_STRCMP(_s,_t) strcmp(_s,_t) +#define AUBIO_STRNCMP(_s,_t,_n) strncmp(_s,_t,_n) +#define AUBIO_STRCPY(_dst,_src) strcpy(_dst,_src) +#define AUBIO_STRCHR(_s,_c) strchr(_s,_c) +#ifdef strdup +#define AUBIO_STRDUP(s) strdup(s) +#else +#define AUBIO_STRDUP(s) AUBIO_STRCPY(AUBIO_MALLOC(AUBIO_STRLEN(s) + 1), s) +#endif + + +/* Error reporting */ +typedef enum { + AUBIO_OK = 0, + AUBIO_FAIL = 1 +} aubio_status; + +/* Logging */ + +#include "utils/log.h" + +/** internal logging function, defined in utils/log.c */ +uint_t aubio_log(sint_t level, const char_t *fmt, ...); + +#ifdef HAVE_C99_VARARGS_MACROS +#define AUBIO_ERR(...) aubio_log(AUBIO_LOG_ERR, "AUBIO ERROR: " __VA_ARGS__) +#define AUBIO_INF(...) aubio_log(AUBIO_LOG_INF, "AUBIO INFO: " __VA_ARGS__) +#define AUBIO_MSG(...) aubio_log(AUBIO_LOG_MSG, __VA_ARGS__) +#define _AUBIO_DBG(...) aubio_log(AUBIO_LOG_DBG, __VA_ARGS__) +#define AUBIO_WRN(...) aubio_log(AUBIO_LOG_WRN, "AUBIO WARNING: " __VA_ARGS__) +#else +#define AUBIO_ERR(format, args...) aubio_log(AUBIO_LOG_ERR, "AUBIO ERROR: " format , ##args) +#define AUBIO_INF(format, args...) aubio_log(AUBIO_LOG_INF, "AUBIO INFO: " format , ##args) +#define AUBIO_MSG(format, args...) aubio_log(AUBIO_LOG_MSG, format , ##args) +#define _AUBIO_DBG(format, args...) aubio_log(AUBIO_LOG_DBG, format , ##args) +#define AUBIO_WRN(format, args...) aubio_log(AUBIO_LOG_WRN, "AUBIO WARNING: " format, ##args) +#endif + +#ifdef DEBUG +#define AUBIO_DBG _AUBIO_DBG +#else +// disable debug output +#ifdef HAVE_C99_VARARGS_MACROS +#define AUBIO_DBG(...) {} +#else +#define AUBIO_DBG(format, args...) {} +#endif +#endif + +#define AUBIO_ERROR AUBIO_ERR + +#define AUBIO_QUIT(_s) exit(_s) +#define AUBIO_SNPRINTF snprintf + +#define AUBIO_MAX_SAMPLERATE (192000*8) +#define AUBIO_MAX_CHANNELS 1024 + +/* pi and 2*pi */ +#ifndef M_PI +#define PI (3.14159265358979323846) +#else +#define PI (M_PI) +#endif +#define TWO_PI (PI*2.) + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +/* aliases to math.h functions */ +#if !HAVE_AUBIO_DOUBLE +#define EXP expf +#define COS cosf +#define SIN sinf +#define ABS fabsf +#define POW powf +#define SQRT sqrtf +#define LOG10 log10f +#define LOG logf +#define FLOOR floorf +#define CEIL ceilf +#define ATAN atanf +#define ATAN2 atan2f +#else +#define EXP exp +#define COS cos +#define SIN sin +#define ABS fabs +#define POW pow +#define SQRT sqrt +#define LOG10 log10 +#define LOG log +#define FLOOR floor +#define CEIL ceil +#define ATAN atan +#define ATAN2 atan2 +#endif +#define ROUND(x) FLOOR(x+.5) + +/* aliases to complex.h functions */ +#if HAVE_AUBIO_DOUBLE || !defined(HAVE_COMPLEX_H) || defined(WIN32) +/* mingw32 does not know about c*f functions */ +#define EXPC cexp +/** complex = CEXPC(complex) */ +#define CEXPC cexp +/** sample = ARGC(complex) */ +#define ARGC carg +/** sample = ABSC(complex) norm */ +#define ABSC cabs +/** sample = REAL(complex) */ +#define REAL creal +/** sample = IMAG(complex) */ +#define IMAG cimag +#else +/** sample = EXPC(complex) */ +#define EXPC cexpf +/** complex = CEXPC(complex) */ +#define CEXPC cexp +/** sample = ARGC(complex) */ +#define ARGC cargf +/** sample = ABSC(complex) norm */ +#define ABSC cabsf +/** sample = REAL(complex) */ +#define REAL crealf +/** sample = IMAG(complex) */ +#define IMAG cimagf +#endif + +/* avoid unresolved symbol with msvc 9 */ +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define isnan _isnan +#endif + +#if !defined(_WIN32) +#define AUBIO_STRERROR(errno,buf,len) strerror_r(errno, buf, len) +#else +#define AUBIO_STRERROR(errno,buf,len) strerror_s(buf, len, errno) +#endif + +#ifdef HAVE_C99_VARARGS_MACROS +#define AUBIO_STRERR(...) \ + char errorstr[256]; \ + AUBIO_STRERROR(errno, errorstr, sizeof(errorstr)); \ + AUBIO_ERR(__VA_ARGS__) +#else +#define AUBIO_STRERR(format, args...) \ + char errorstr[256]; \ + AUBIO_STRERROR(errno, errorstr, sizeof(errorstr)); \ + AUBIO_ERR(format, ##args) +#endif + +/* handy shortcuts */ +#define DB2LIN(g) (POW(10.0,(g)*0.05f)) +#define LIN2DB(v) (20.0*LOG10(v)) +#define SQR(_a) ((_a)*(_a)) + +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif /* MAX */ +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif /* MIN */ + +#define ELEM_SWAP(a,b) { register smpl_t t=(a);(a)=(b);(b)=t; } + +#define VERY_SMALL_NUMBER 2.e-42 //1.e-37 + +/** if ABS(f) < VERY_SMALL_NUMBER, returns 1, else 0 */ +#define IS_DENORMAL(f) ABS(f) < VERY_SMALL_NUMBER + +/** if ABS(f) < VERY_SMALL_NUMBER, returns 0., else f */ +#define KILL_DENORMAL(f) IS_DENORMAL(f) ? 0. : f + +/** if f > VERY_SMALL_NUMBER, returns f, else returns VERY_SMALL_NUMBER */ +#define CEIL_DENORMAL(f) f < VERY_SMALL_NUMBER ? VERY_SMALL_NUMBER : f + +#define SAFE_LOG10(f) LOG10(CEIL_DENORMAL(f)) +#define SAFE_LOG(f) LOG(CEIL_DENORMAL(f)) + +/** silence unused parameter warning by adding an attribute */ +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif + +/* are we using gcc -std=c99 ? */ +#if defined(__STRICT_ANSI__) +#define strnlen(a,b) MIN(strlen(a),b) +#if !HAVE_AUBIO_DOUBLE +#define floorf floor +#endif +#endif /* __STRICT_ANSI__ */ + +#if defined(DEBUG) +#include +#define AUBIO_ASSERT(x) assert(x) +#else +#define AUBIO_ASSERT(x) +#endif /* DEBUG */ + +#endif /* AUBIO_PRIV_H */ diff --git a/dependencies/aubio/src/config.h b/dependencies/aubio/src/config.h new file mode 100644 index 0000000000..76c3a1b4b2 --- /dev/null +++ b/dependencies/aubio/src/config.h @@ -0,0 +1,23 @@ +/* aubio configuration for xLights native macOS build */ +#ifndef AUBIO_CONFIG_H +#define AUBIO_CONFIG_H + +#define HAVE_STDLIB_H 1 +#define HAVE_STDIO_H 1 +#define HAVE_MATH_H 1 +#define HAVE_STRING_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_COMPLEX_H 1 +#define HAVE_C99_VARARGS_MACROS 1 + +/* Use Apple Accelerate framework for FFT (vDSP) on macOS */ +#ifdef __APPLE__ +#define HAVE_ACCELERATE 1 +#endif + +/* Single precision (float, not double) */ +/* #undef HAVE_AUBIO_DOUBLE */ + +#endif /* AUBIO_CONFIG_H */ diff --git a/dependencies/aubio/src/cvec.c b/dependencies/aubio/src/cvec.c new file mode 100644 index 0000000000..00c43bee97 --- /dev/null +++ b/dependencies/aubio/src/cvec.c @@ -0,0 +1,169 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "cvec.h" + +cvec_t * new_cvec(uint_t length) { + cvec_t * s; + if ((sint_t)length <= 0) { + return NULL; + } + s = AUBIO_NEW(cvec_t); + s->length = length/2 + 1; + s->norm = AUBIO_ARRAY(smpl_t,s->length); + s->phas = AUBIO_ARRAY(smpl_t,s->length); + return s; +} + +void del_cvec(cvec_t *s) { + AUBIO_FREE(s->norm); + AUBIO_FREE(s->phas); + AUBIO_FREE(s); +} + +void cvec_norm_set_sample (cvec_t *s, smpl_t data, uint_t position) { + s->norm[position] = data; +} + +void cvec_phas_set_sample (cvec_t *s, smpl_t data, uint_t position) { + s->phas[position] = data; +} + +smpl_t cvec_norm_get_sample (cvec_t *s, uint_t position) { + return s->norm[position]; +} + +smpl_t cvec_phas_get_sample (cvec_t *s, uint_t position) { + return s->phas[position]; +} + +smpl_t * cvec_norm_get_data (const cvec_t *s) { + return s->norm; +} + +smpl_t * cvec_phas_get_data (const cvec_t *s) { + return s->phas; +} + +/* helper functions */ + +void cvec_print(const cvec_t *s) { + uint_t j; + AUBIO_MSG("norm: "); + for (j=0; j< s->length; j++) { + AUBIO_MSG(AUBIO_SMPL_FMT " ", s->norm[j]); + } + AUBIO_MSG("\n"); + AUBIO_MSG("phas: "); + for (j=0; j< s->length; j++) { + AUBIO_MSG(AUBIO_SMPL_FMT " ", s->phas[j]); + } + AUBIO_MSG("\n"); +} + +void cvec_copy(const cvec_t *s, cvec_t *t) { + if (s->length != t->length) { + AUBIO_ERR("trying to copy %d elements to %d elements \n", + s->length, t->length); + return; + } +#if defined(HAVE_INTEL_IPP) + aubio_ippsCopy(s->phas, t->phas, (int)s->length); + aubio_ippsCopy(s->norm, t->norm, (int)s->length); +#elif defined(HAVE_MEMCPY_HACKS) + memcpy(t->norm, s->norm, t->length * sizeof(smpl_t)); + memcpy(t->phas, s->phas, t->length * sizeof(smpl_t)); +#else + uint_t j; + for (j=0; j< t->length; j++) { + t->norm[j] = s->norm[j]; + t->phas[j] = s->phas[j]; + } +#endif +} + +void cvec_norm_set_all(cvec_t *s, smpl_t val) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsSet(val, s->norm, (int)s->length); +#else + uint_t j; + for (j=0; j< s->length; j++) { + s->norm[j] = val; + } +#endif +} + +void cvec_norm_zeros(cvec_t *s) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsZero(s->norm, (int)s->length); +#elif defined(HAVE_MEMCPY_HACKS) + memset(s->norm, 0, s->length * sizeof(smpl_t)); +#else + cvec_norm_set_all (s, 0.); +#endif +} + +void cvec_norm_ones(cvec_t *s) { + cvec_norm_set_all (s, 1.); +} + +void cvec_phas_set_all (cvec_t *s, smpl_t val) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsSet(val, s->phas, (int)s->length); +#else + uint_t j; + for (j=0; j< s->length; j++) { + s->phas[j] = val; + } +#endif +} + +void cvec_phas_zeros(cvec_t *s) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsZero(s->phas, (int)s->length); +#elif defined(HAVE_MEMCPY_HACKS) + memset(s->phas, 0, s->length * sizeof(smpl_t)); +#else + cvec_phas_set_all (s, 0.); +#endif +} + +void cvec_phas_ones(cvec_t *s) { + cvec_phas_set_all (s, 1.); +} + +void cvec_zeros(cvec_t *s) { + cvec_norm_zeros(s); + cvec_phas_zeros(s); +} + +void cvec_logmag(cvec_t *s, smpl_t lambda) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsMulC(s->norm, lambda, s->norm, (int)s->length); + aubio_ippsAddC(s->norm, 1.0, s->norm, (int)s->length); + aubio_ippsLn(s->norm, s->norm, (int)s->length); +#else + uint_t j; + for (j=0; j< s->length; j++) { + s->norm[j] = LOG(lambda * s->norm[j] + 1); + } +#endif +} diff --git a/dependencies/aubio/src/cvec.h b/dependencies/aubio/src/cvec.h new file mode 100644 index 0000000000..7c826b680b --- /dev/null +++ b/dependencies/aubio/src/cvec.h @@ -0,0 +1,247 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_CVEC_H +#define AUBIO_CVEC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Vector of complex-valued data, stored in polar coordinates + + This file specifies the ::cvec_t buffer type, which is used throughout aubio + to store complex data. Complex values are stored in terms of ::cvec_t.phas + and norm, within 2 vectors of ::smpl_t of size (size/2+1) each. + + \example test-cvec.c + +*/ + +/** Vector of real-valued phase and spectrum data + + \code + + uint_t buffer_size = 1024; + + // create a complex vector of 512 values + cvec_t * input = new_cvec (buffer_size); + + // set some values of the vector + input->norm[23] = 2.; + input->phas[23] = M_PI; + // .. + + // compute the mean of the vector + mean = cvec_mean(input); + + // destroy the vector + del_cvec (input); + + \endcode + + */ +typedef struct { + uint_t length; /**< length of buffer = (requested length)/2 + 1 */ + smpl_t *norm; /**< norm array of size ::cvec_t.length */ + smpl_t *phas; /**< phase array of size ::cvec_t.length */ +} cvec_t; + +/** cvec_t buffer creation function + + This function creates a cvec_t structure holding two arrays of size + [length/2+1], corresponding to the norm and phase values of the + spectral frame. The length stored in the structure is the actual size of both + arrays, not the length of the complex and symmetrical vector, specified as + creation argument. + + \param length the length of the buffer to create + +*/ +cvec_t * new_cvec(uint_t length); + +/** cvec_t buffer deletion function + + \param s buffer to delete as returned by new_cvec() + +*/ +void del_cvec(cvec_t *s); + +/** write norm value in a complex buffer + + This is equivalent to: + \code + s->norm[position] = val; + \endcode + + \param s vector to write to + \param val norm value to write in s->norm[position] + \param position sample position to write to + +*/ +void cvec_norm_set_sample (cvec_t *s, smpl_t val, uint_t position); + +/** write phase value in a complex buffer + + This is equivalent to: + \code + s->phas[position] = val; + \endcode + + \param s vector to write to + \param val phase value to write in s->phas[position] + \param position sample position to write to + +*/ +void cvec_phas_set_sample (cvec_t *s, smpl_t val, uint_t position); + +/** read norm value from a complex buffer + + This is equivalent to: + \code + smpl_t foo = s->norm[position]; + \endcode + + \param s vector to read from + \param position sample position to read from + +*/ +smpl_t cvec_norm_get_sample (cvec_t *s, uint_t position); + +/** read phase value from a complex buffer + + This is equivalent to: + \code + smpl_t foo = s->phas[position]; + \endcode + + \param s vector to read from + \param position sample position to read from + \returns the value of the sample at position + +*/ +smpl_t cvec_phas_get_sample (cvec_t *s, uint_t position); + +/** read norm data from a complex buffer + + \code + smpl_t *data = s->norm; + \endcode + + \param s vector to read from + +*/ +smpl_t * cvec_norm_get_data (const cvec_t *s); + +/** read phase data from a complex buffer + + This is equivalent to: + \code + smpl_t *data = s->phas; + \endcode + + \param s vector to read from + +*/ +smpl_t * cvec_phas_get_data (const cvec_t *s); + +/** print out cvec data + + \param s vector to print out + +*/ +void cvec_print(const cvec_t *s); + +/** make a copy of a vector + + \param s source vector + \param t vector to copy to + +*/ +void cvec_copy(const cvec_t *s, cvec_t *t); + +/** set all norm elements to a given value + + \param s vector to modify + \param val value to set elements to + +*/ +void cvec_norm_set_all (cvec_t *s, smpl_t val); + +/** set all norm elements to zero + + \param s vector to modify + +*/ +void cvec_norm_zeros(cvec_t *s); + +/** set all norm elements to one + + \param s vector to modify + +*/ +void cvec_norm_ones(cvec_t *s); + +/** set all phase elements to a given value + + \param s vector to modify + \param val value to set elements to + +*/ +void cvec_phas_set_all (cvec_t *s, smpl_t val); + +/** set all phase elements to zero + + \param s vector to modify + +*/ +void cvec_phas_zeros(cvec_t *s); + +/** set all phase elements to one + + \param s vector to modify + +*/ +void cvec_phas_ones(cvec_t *s); + +/** set all norm and phas elements to zero + + \param s vector to modify + +*/ +void cvec_zeros(cvec_t *s); + +/** take logarithmic magnitude + + \param s input cvec to compress + \param lambda value to use for normalisation + + \f$ S_k = log( \lambda * S_k + 1 ) \f$ + +*/ +void cvec_logmag(cvec_t *s, smpl_t lambda); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_CVEC_H */ diff --git a/dependencies/aubio/src/fmat.c b/dependencies/aubio/src/fmat.c new file mode 100644 index 0000000000..8dde20ce6e --- /dev/null +++ b/dependencies/aubio/src/fmat.c @@ -0,0 +1,186 @@ +/* + Copyright (C) 2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fmat.h" + +fmat_t * new_fmat (uint_t height, uint_t length) { + fmat_t * s; + uint_t i,j; + if ((sint_t)length <= 0 || (sint_t)height <= 0 ) { + return NULL; + } + s = AUBIO_NEW(fmat_t); + s->height = height; + s->length = length; + s->data = AUBIO_ARRAY(smpl_t*,s->height); + for (i=0; i< s->height; i++) { + s->data[i] = AUBIO_ARRAY(smpl_t, s->length); + for (j=0; j< s->length; j++) { + s->data[i][j]=0.; + } + } + return s; +} + +void del_fmat (fmat_t *s) { + uint_t i; + for (i=0; iheight; i++) { + AUBIO_FREE(s->data[i]); + } + AUBIO_FREE(s->data); + AUBIO_FREE(s); +} + +void fmat_set_sample(fmat_t *s, smpl_t data, uint_t channel, uint_t position) { + s->data[channel][position] = data; +} + +smpl_t fmat_get_sample(const fmat_t *s, uint_t channel, uint_t position) { + return s->data[channel][position]; +} + +void fmat_get_channel(const fmat_t *s, uint_t channel, fvec_t *output) { + output->data = s->data[channel]; + output->length = s->length; + return; +} + +smpl_t * fmat_get_channel_data(const fmat_t *s, uint_t channel) { + return s->data[channel]; +} + +smpl_t ** fmat_get_data(const fmat_t *s) { + return s->data; +} + +/* helper functions */ + +void fmat_print(const fmat_t *s) { + uint_t i,j; + for (i=0; i< s->height; i++) { + for (j=0; j< s->length; j++) { + AUBIO_MSG(AUBIO_SMPL_FMT " ", s->data[i][j]); + } + AUBIO_MSG("\n"); + } +} + +void fmat_set(fmat_t *s, smpl_t val) { + uint_t i,j; + for (i=0; i< s->height; i++) { + for (j=0; j< s->length; j++) { + s->data[i][j] = val; + } + } +} + +void fmat_zeros(fmat_t *s) { +#ifdef HAVE_MEMCPY_HACKS + uint_t i; + for (i=0; i< s->height; i++) { + memset(s->data[i], 0, s->length * sizeof(smpl_t)); + } +#else /* HAVE_MEMCPY_HACKS */ + fmat_set(s, 0.); +#endif /* HAVE_MEMCPY_HACKS */ +} + +void fmat_ones(fmat_t *s) { + fmat_set(s, 1.); +} + +void fmat_rev(fmat_t *s) { + uint_t i,j; + for (i=0; i< s->height; i++) { + for (j=0; j< FLOOR((smpl_t)s->length/2); j++) { + ELEM_SWAP(s->data[i][j], s->data[i][s->length-1-j]); + } + } +} + +void fmat_weight(fmat_t *s, const fmat_t *weight) { + uint_t i,j; + uint_t length = MIN(s->length, weight->length); + for (i=0; i< s->height; i++) { + for (j=0; j< length; j++) { + s->data[i][j] *= weight->data[0][j]; + } + } +} + +void fmat_copy(const fmat_t *s, fmat_t *t) { + uint_t i; +#ifndef HAVE_MEMCPY_HACKS + uint_t j; +#endif /* HAVE_MEMCPY_HACKS */ + if (s->height != t->height) { + AUBIO_ERR("trying to copy %d rows to %d rows \n", + s->height, t->height); + return; + } + if (s->length != t->length) { + AUBIO_ERR("trying to copy %d columns to %d columns\n", + s->length, t->length); + return; + } +#ifdef HAVE_MEMCPY_HACKS + for (i=0; i< s->height; i++) { + memcpy(t->data[i], s->data[i], t->length * sizeof(smpl_t)); + } +#else /* HAVE_MEMCPY_HACKS */ + for (i=0; i< t->height; i++) { + for (j=0; j< t->length; j++) { + t->data[i][j] = s->data[i][j]; + } + } +#endif /* HAVE_MEMCPY_HACKS */ +} + +void fmat_vecmul(const fmat_t *s, const fvec_t *scale, fvec_t *output) { + uint_t k; +#if 0 + assert(s->height == output->length); + assert(s->length == scale->length); +#endif +#if !defined(HAVE_ACCELERATE) && !defined(HAVE_BLAS) + uint_t j; + fvec_zeros(output); + for (j = 0; j < s->length; j++) { + for (k = 0; k < s->height; k++) { + output->data[k] += scale->data[j] + * s->data[k][j]; + } + } +#elif defined(HAVE_ACCELERATE) +#if 0 + // seems slower and less precise (and dangerous?) + vDSP_mmul (s->data[0], 1, scale->data, 1, output->data, 1, s->height, 1, s->length); +#else + for (k = 0; k < s->height; k++) { + aubio_vDSP_dotpr( scale->data, 1, s->data[k], 1, &(output->data[k]), s->length); + } +#endif +#elif defined(HAVE_BLAS) + for (k = 0; k < s->height; k++) { + output->data[k] = aubio_cblas_dot( s->length, scale->data, 1, s->data[k], 1); + } +#endif +} diff --git a/dependencies/aubio/src/fmat.h b/dependencies/aubio/src/fmat.h new file mode 100644 index 0000000000..8e65d110e1 --- /dev/null +++ b/dependencies/aubio/src/fmat.h @@ -0,0 +1,172 @@ +/* + Copyright (C) 2009-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_FMAT_H +#define AUBIO_FMAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Matrix of real valued data + + This file specifies the fmat_t type, which is used in aubio to store arrays + of floating point values. + + \example test-fmat.c + +*/ + +/** Buffer for real data */ +typedef struct { + uint_t length; /**< length of matrix */ + uint_t height; /**< height of matrix */ + smpl_t **data; /**< data array of size [length] * [height] */ +} fmat_t; + +/** fmat_t buffer creation function + + \param length the length of the matrix to create + \param height the height of the matrix to create + +*/ +fmat_t * new_fmat(uint_t height, uint_t length); + +/** fmat_t buffer deletion function + + \param s buffer to delete as returned by new_fmat() + +*/ +void del_fmat(fmat_t *s); + +/** read sample value in a buffer + + \param s vector to read from + \param channel channel to read from + \param position sample position to read from + +*/ +smpl_t fmat_get_sample(const fmat_t *s, uint_t channel, uint_t position); + +/** write sample value in a buffer + + \param s vector to write to + \param data value to write in s->data[channel][position] + \param channel channel to write to + \param position sample position to write to + +*/ +void fmat_set_sample(fmat_t *s, smpl_t data, uint_t channel, uint_t position); + +/** read channel vector from a buffer + + \param s vector to read from + \param channel channel to read from + \param output ::fvec_t to output to + +*/ +void fmat_get_channel (const fmat_t *s, uint_t channel, fvec_t *output); + +/** get vector buffer from an fmat data + + \param s vector to read from + \param channel channel to read from + +*/ +smpl_t * fmat_get_channel_data (const fmat_t *s, uint_t channel); + +/** read data from a buffer + + \param s vector to read from + +*/ +smpl_t ** fmat_get_data(const fmat_t *s); + +/** print out fmat data + + \param s vector to print out + +*/ +void fmat_print(const fmat_t *s); + +/** set all elements to a given value + + \param s vector to modify + \param val value to set elements to + +*/ +void fmat_set(fmat_t *s, smpl_t val); + +/** set all elements to zero + + \param s vector to modify + +*/ +void fmat_zeros(fmat_t *s); + +/** set all elements to ones + + \param s vector to modify + +*/ +void fmat_ones(fmat_t *s); + +/** revert order of vector elements + + \param s vector to revert + +*/ +void fmat_rev(fmat_t *s); + +/** apply weight to vector + + If the weight vector is longer than s, only the first elements are used. If + the weight vector is shorter than s, the last elements of s are not weighted. + + \param s vector to weight + \param weight weighting coefficients + +*/ +void fmat_weight(fmat_t *s, const fmat_t *weight); + +/** make a copy of a matrix + + \param s source vector + \param t vector to copy to + +*/ +void fmat_copy(const fmat_t *s, fmat_t *t); + +/** compute the product of a matrix by a vector + + \param s matrix to compute product with + \param scale vector to compute product with + \param output vector to store restults in + +*/ +void fmat_vecmul(const fmat_t *s, const fvec_t *scale, fvec_t *output); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_FMAT_H */ diff --git a/dependencies/aubio/src/fvec.c b/dependencies/aubio/src/fvec.c new file mode 100644 index 0000000000..6b5681f5e4 --- /dev/null +++ b/dependencies/aubio/src/fvec.c @@ -0,0 +1,149 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" + +fvec_t * new_fvec(uint_t length) { + fvec_t * s; + if ((sint_t)length <= 0) { + return NULL; + } + s = AUBIO_NEW(fvec_t); + s->length = length; + s->data = AUBIO_ARRAY(smpl_t, s->length); + return s; +} + +void del_fvec(fvec_t *s) { + AUBIO_FREE(s->data); + AUBIO_FREE(s); +} + +void fvec_set_sample(fvec_t *s, smpl_t data, uint_t position) { + s->data[position] = data; +} + +smpl_t fvec_get_sample(const fvec_t *s, uint_t position) { + return s->data[position]; +} + +smpl_t * fvec_get_data(const fvec_t *s) { + return s->data; +} + +/* helper functions */ + +void fvec_print(const fvec_t *s) { + uint_t j; + for (j=0; j< s->length; j++) { + AUBIO_MSG(AUBIO_SMPL_FMT " ", s->data[j]); + } + AUBIO_MSG("\n"); +} + +void fvec_set_all (fvec_t *s, smpl_t val) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsSet(val, s->data, (int)s->length); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_vfill(&val, s->data, 1, s->length); +#elif defined(HAVE_ATLAS) + aubio_catlas_set(s->length, val, s->data, 1); +#else + uint_t j; + for ( j = 0; j< s->length; j++ ) + { + s->data[j] = val; + } +#endif +} + +void fvec_zeros(fvec_t *s) { +#if defined(HAVE_INTEL_IPP) + aubio_ippsZero(s->data, (int)s->length); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_vclr(s->data, 1, s->length); +#elif defined(HAVE_MEMCPY_HACKS) + memset(s->data, 0, s->length * sizeof(smpl_t)); +#else + fvec_set_all(s, 0.); +#endif +} + +void fvec_ones(fvec_t *s) { + fvec_set_all (s, 1.); +} + +void fvec_rev(fvec_t *s) { + uint_t j; + for (j=0; j< FLOOR((smpl_t)s->length/2); j++) { + ELEM_SWAP(s->data[j], s->data[s->length-1-j]); + } +} + +void fvec_weight(fvec_t *s, const fvec_t *weight) { + uint_t length = MIN(s->length, weight->length); +#if defined(HAVE_INTEL_IPP) + aubio_ippsMul(s->data, weight->data, s->data, (int)length); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_vmul( s->data, 1, weight->data, 1, s->data, 1, length ); +#else + uint_t j; + for (j = 0; j < length; j++) { + s->data[j] *= weight->data[j]; + } +#endif /* HAVE_ACCELERATE */ +} + +void fvec_weighted_copy(const fvec_t *in, const fvec_t *weight, fvec_t *out) { + uint_t length = MIN(in->length, MIN(out->length, weight->length)); +#if defined(HAVE_INTEL_IPP) + aubio_ippsMul(in->data, weight->data, out->data, (int)length); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_vmul(in->data, 1, weight->data, 1, out->data, 1, length); +#else + uint_t j; + for (j = 0; j < length; j++) { + out->data[j] = in->data[j] * weight->data[j]; + } +#endif +} + +void fvec_copy(const fvec_t *s, fvec_t *t) { + if (s->length != t->length) { + AUBIO_ERR("trying to copy %d elements to %d elements \n", + s->length, t->length); + return; + } +#if defined(HAVE_INTEL_IPP) + aubio_ippsCopy(s->data, t->data, (int)s->length); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_mmov(s->data, t->data, 1, s->length, 1, 1); +#elif defined(HAVE_BLAS) + aubio_cblas_copy(s->length, s->data, 1, t->data, 1); +#elif defined(HAVE_MEMCPY_HACKS) + memcpy(t->data, s->data, t->length * sizeof(smpl_t)); +#else + uint_t j; + for (j = 0; j < t->length; j++) { + t->data[j] = s->data[j]; + } +#endif +} diff --git a/dependencies/aubio/src/fvec.h b/dependencies/aubio/src/fvec.h new file mode 100644 index 0000000000..bd8c5a600c --- /dev/null +++ b/dependencies/aubio/src/fvec.h @@ -0,0 +1,178 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_FVEC_H +#define AUBIO_FVEC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Vector of real-valued data + + This file specifies the ::fvec_t buffer type, which is used throughout aubio + to store vector of real-valued ::smpl_t. + + \example test-fvec.c + +*/ + +/** Buffer for real data + + Vector of real-valued data + + ::fvec_t is is the structure used to store vector of real-valued data, ::smpl_t . + + \code + + uint_t buffer_size = 1024; + + // create a vector of 512 values + fvec_t * input = new_fvec (buffer_size); + + // set some values of the vector + input->data[23] = 2.; + // .. + + // compute the mean of the vector + mean = fvec_mean(a_vector); + + // destroy the vector + del_fvec(a_vector); + + \endcode + + See `examples/` and `tests/src` directories for more examples. + + */ +typedef struct { + uint_t length; /**< length of buffer */ + smpl_t *data; /**< data vector of length ::fvec_t.length */ +} fvec_t; + +/** fvec_t buffer creation function + + \param length the length of the buffer to create + +*/ +fvec_t * new_fvec(uint_t length); + +/** fvec_t buffer deletion function + + \param s buffer to delete as returned by new_fvec() + +*/ +void del_fvec(fvec_t *s); + +/** read sample value in a buffer + + \param s vector to read from + \param position sample position to read from + +*/ +smpl_t fvec_get_sample(const fvec_t *s, uint_t position); + +/** write sample value in a buffer + + \param s vector to write to + \param data value to write in s->data[position] + \param position sample position to write to + +*/ +void fvec_set_sample(fvec_t *s, smpl_t data, uint_t position); + +/** read data from a buffer + + \param s vector to read from + +*/ +smpl_t * fvec_get_data(const fvec_t *s); + +/** print out fvec data + + \param s vector to print out + +*/ +void fvec_print(const fvec_t *s); + +/** set all elements to a given value + + \param s vector to modify + \param val value to set elements to + +*/ +void fvec_set_all (fvec_t *s, smpl_t val); + +/** set all elements to zero + + \param s vector to modify + +*/ +void fvec_zeros(fvec_t *s); + +/** set all elements to ones + + \param s vector to modify + +*/ +void fvec_ones(fvec_t *s); + +/** revert order of vector elements + + \param s vector to revert + +*/ +void fvec_rev(fvec_t *s); + +/** apply weight to vector + + If the weight vector is longer than s, only the first elements are used. If + the weight vector is shorter than s, the last elements of s are not weighted. + + \param s vector to weight + \param weight weighting coefficients + +*/ +void fvec_weight(fvec_t *s, const fvec_t *weight); + +/** make a copy of a vector + + \param s source vector + \param t vector to copy to + +*/ +void fvec_copy(const fvec_t *s, fvec_t *t); + +/** make a copy of a vector, applying weights to each element + + \param in input vector + \param weight weights vector + \param out output vector + +*/ +void fvec_weighted_copy(const fvec_t *in, const fvec_t *weight, fvec_t *out); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_FVEC_H */ diff --git a/dependencies/aubio/src/lvec.c b/dependencies/aubio/src/lvec.c new file mode 100644 index 0000000000..dd63bf42de --- /dev/null +++ b/dependencies/aubio/src/lvec.c @@ -0,0 +1,80 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "lvec.h" + +lvec_t * new_lvec(uint_t length) { + lvec_t * s; + if ((sint_t)length <= 0) { + return NULL; + } + s = AUBIO_NEW(lvec_t); + s->length = length; + s->data = AUBIO_ARRAY(lsmp_t, s->length); + return s; +} + +void del_lvec(lvec_t *s) { + AUBIO_FREE(s->data); + AUBIO_FREE(s); +} + +void lvec_set_sample(lvec_t *s, lsmp_t data, uint_t position) { + s->data[position] = data; +} + +lsmp_t lvec_get_sample(lvec_t *s, uint_t position) { + return s->data[position]; +} + +lsmp_t * lvec_get_data(const lvec_t *s) { + return s->data; +} + +/* helper functions */ + +void lvec_print(const lvec_t *s) { + uint_t j; + for (j=0; j< s->length; j++) { + AUBIO_MSG(AUBIO_LSMP_FMT " ", s->data[j]); + } + AUBIO_MSG("\n"); +} + +void lvec_set_all (lvec_t *s, smpl_t val) { + uint_t j; + for (j=0; j< s->length; j++) { + s->data[j] = val; + } +} + +void lvec_zeros(lvec_t *s) { +#if HAVE_MEMCPY_HACKS + memset(s->data, 0, s->length * sizeof(lsmp_t)); +#else + lvec_set_all (s, 0.); +#endif +} + +void lvec_ones(lvec_t *s) { + lvec_set_all (s, 1.); +} + diff --git a/dependencies/aubio/src/lvec.h b/dependencies/aubio/src/lvec.h new file mode 100644 index 0000000000..402ba0ff84 --- /dev/null +++ b/dependencies/aubio/src/lvec.h @@ -0,0 +1,118 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_LVEC_H +#define AUBIO_LVEC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Vector of real-valued data in double precision + + This file specifies the ::lvec_t buffer type, which is used in some places in + aubio to store a vector of ::lsmp_t. + + Note: the lvec_t data type is required in some algorithms such as IIR filters + (see temporal/filter.h). + + \example test-lvec.c + +*/ + +/** Buffer for real data in double precision */ +typedef struct { + uint_t length; /**< length of buffer */ + lsmp_t *data; /**< data array of size [length] */ +} lvec_t; + +/** lvec_t buffer creation function + + \param length the length of the buffer to create + +*/ +lvec_t * new_lvec(uint_t length); +/** lvec_t buffer deletion function + + \param s buffer to delete as returned by new_lvec() + +*/ +void del_lvec(lvec_t *s); + +/** read sample value in a buffer + + \param s vector to read from + \param position sample position to read from + +*/ +lsmp_t lvec_get_sample(lvec_t *s, uint_t position); + +/** write sample value in a buffer + + \param s vector to write to + \param data value to write in s->data[position] + \param position sample position to write to + +*/ +void lvec_set_sample(lvec_t *s, lsmp_t data, uint_t position); + +/** read data from a buffer + + \param s vector to read from + +*/ +lsmp_t * lvec_get_data(const lvec_t *s); + +/** print out lvec data + + \param s vector to print out + +*/ +void lvec_print(const lvec_t *s); + +/** set all elements to a given value + + \param s vector to modify + \param val value to set elements to + +*/ +void lvec_set_all(lvec_t *s, smpl_t val); + +/** set all elements to zero + + \param s vector to modify + +*/ +void lvec_zeros(lvec_t *s); + +/** set all elements to ones + + \param s vector to modify + +*/ +void lvec_ones(lvec_t *s); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_LVEC_H */ diff --git a/dependencies/aubio/src/mathutils.c b/dependencies/aubio/src/mathutils.c new file mode 100644 index 0000000000..35755fe497 --- /dev/null +++ b/dependencies/aubio/src/mathutils.c @@ -0,0 +1,681 @@ +/* + Copyright (C) 2003-2014 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/* see in mathutils.h for doc */ + +#include "aubio_priv.h" +#include "fvec.h" +#include "mathutils.h" +#include "musicutils.h" + +/** Window types */ +typedef enum +{ + aubio_win_ones, + aubio_win_rectangle, + aubio_win_hamming, + aubio_win_hanning, + aubio_win_hanningz, + aubio_win_blackman, + aubio_win_blackman_harris, + aubio_win_gaussian, + aubio_win_welch, + aubio_win_parzen, + aubio_win_default = aubio_win_hanningz, +} aubio_window_type; + +fvec_t * +new_aubio_window (char_t * window_type, uint_t length) +{ + fvec_t * win = new_fvec (length); + uint_t err; + if (win == NULL) { + return NULL; + } + err = fvec_set_window (win, window_type); + if (err != 0) { + del_fvec(win); + return NULL; + } + return win; +} + +uint_t fvec_set_window (fvec_t *win, char_t *window_type) { + smpl_t * w = win->data; + uint_t i, size = win->length; + aubio_window_type wintype; + if (window_type == NULL) { + AUBIO_ERR ("window type can not be null.\n"); + return 1; + } else if (strcmp (window_type, "ones") == 0) + wintype = aubio_win_ones; + else if (strcmp (window_type, "rectangle") == 0) + wintype = aubio_win_rectangle; + else if (strcmp (window_type, "hamming") == 0) + wintype = aubio_win_hamming; + else if (strcmp (window_type, "hanning") == 0) + wintype = aubio_win_hanning; + else if (strcmp (window_type, "hanningz") == 0) + wintype = aubio_win_hanningz; + else if (strcmp (window_type, "blackman") == 0) + wintype = aubio_win_blackman; + else if (strcmp (window_type, "blackman_harris") == 0) + wintype = aubio_win_blackman_harris; + else if (strcmp (window_type, "gaussian") == 0) + wintype = aubio_win_gaussian; + else if (strcmp (window_type, "welch") == 0) + wintype = aubio_win_welch; + else if (strcmp (window_type, "parzen") == 0) + wintype = aubio_win_parzen; + else if (strcmp (window_type, "default") == 0) + wintype = aubio_win_default; + else { + AUBIO_ERR ("unknown window type `%s`.\n", window_type); + return 1; + } + switch(wintype) { + case aubio_win_ones: + fvec_ones(win); + break; + case aubio_win_rectangle: + fvec_set_all(win, .5); + break; + case aubio_win_hamming: + for (i=0;idata, (int)s->length, &tmp); + return tmp; +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_meanv(s->data, 1, &tmp, s->length); + return tmp; +#else + uint_t j; + for (j = 0; j < s->length; j++) { + tmp += s->data[j]; + } + return tmp / (smpl_t)(s->length); +#endif +} + +smpl_t +fvec_sum (fvec_t * s) +{ + smpl_t tmp = 0.0; +#if defined(HAVE_INTEL_IPP) + aubio_ippsSum(s->data, (int)s->length, &tmp); +#elif defined(HAVE_ACCELERATE) + aubio_vDSP_sve(s->data, 1, &tmp, s->length); +#else + uint_t j; + for (j = 0; j < s->length; j++) { + tmp += s->data[j]; + } +#endif + return tmp; +} + +smpl_t +fvec_max (fvec_t * s) +{ +#if defined(HAVE_INTEL_IPP) + smpl_t tmp = 0.; + aubio_ippsMax( s->data, (int)s->length, &tmp); +#elif defined(HAVE_ACCELERATE) + smpl_t tmp = 0.; + aubio_vDSP_maxv( s->data, 1, &tmp, s->length ); +#else + uint_t j; + smpl_t tmp = s->data[0]; + for (j = 1; j < s->length; j++) { + tmp = (tmp > s->data[j]) ? tmp : s->data[j]; + } +#endif + return tmp; +} + +smpl_t +fvec_min (fvec_t * s) +{ +#if defined(HAVE_INTEL_IPP) + smpl_t tmp = 0.; + aubio_ippsMin(s->data, (int)s->length, &tmp); +#elif defined(HAVE_ACCELERATE) + smpl_t tmp = 0.; + aubio_vDSP_minv(s->data, 1, &tmp, s->length); +#else + uint_t j; + smpl_t tmp = s->data[0]; + for (j = 1; j < s->length; j++) { + tmp = (tmp < s->data[j]) ? tmp : s->data[j]; + } +#endif + return tmp; +} + +uint_t +fvec_min_elem (fvec_t * s) +{ +#ifndef HAVE_ACCELERATE + uint_t j, pos = 0.; + smpl_t tmp = s->data[0]; + for (j = 0; j < s->length; j++) { + pos = (tmp < s->data[j]) ? pos : j; + tmp = (tmp < s->data[j]) ? tmp : s->data[j]; + } +#else + smpl_t tmp = 0.; + vDSP_Length pos = 0; + aubio_vDSP_minvi(s->data, 1, &tmp, &pos, s->length); +#endif + return (uint_t)pos; +} + +uint_t +fvec_max_elem (fvec_t * s) +{ +#ifndef HAVE_ACCELERATE + uint_t j, pos = 0; + smpl_t tmp = 0.0; + for (j = 0; j < s->length; j++) { + pos = (tmp > s->data[j]) ? pos : j; + tmp = (tmp > s->data[j]) ? tmp : s->data[j]; + } +#else + smpl_t tmp = 0.; + vDSP_Length pos = 0; + aubio_vDSP_maxvi(s->data, 1, &tmp, &pos, s->length); +#endif + return (uint_t)pos; +} + +void +fvec_shift (fvec_t * s) +{ + uint_t half = s->length / 2, start = half, j; + // if length is odd, middle element is moved to the end + if (2 * half < s->length) start ++; +#ifndef HAVE_BLAS + for (j = 0; j < half; j++) { + ELEM_SWAP (s->data[j], s->data[j + start]); + } +#else + aubio_cblas_swap(half, s->data, 1, s->data + start, 1); +#endif + if (start != half) { + for (j = 0; j < half; j++) { + ELEM_SWAP (s->data[j + start - 1], s->data[j + start]); + } + } +} + +void +fvec_ishift (fvec_t * s) +{ + uint_t half = s->length / 2, start = half, j; + // if length is odd, middle element is moved to the beginning + if (2 * half < s->length) start ++; +#ifndef HAVE_BLAS + for (j = 0; j < half; j++) { + ELEM_SWAP (s->data[j], s->data[j + start]); + } +#else + aubio_cblas_swap(half, s->data, 1, s->data + start, 1); +#endif + if (start != half) { + for (j = 0; j < half; j++) { + ELEM_SWAP (s->data[half], s->data[j]); + } + } +} + +void fvec_push(fvec_t *in, smpl_t new_elem) { + uint_t i; + for (i = 0; i < in->length - 1; i++) { + in->data[i] = in->data[i + 1]; + } + in->data[in->length - 1] = new_elem; +} + +void fvec_clamp(fvec_t *in, smpl_t absmax) { + uint_t i; + for (i = 0; i < in->length; i++) { + if (in->data[i] > 0 && in->data[i] > ABS(absmax)) { + in->data[i] = absmax; + } else if (in->data[i] < 0 && in->data[i] < -ABS(absmax)) { + in->data[i] = -absmax; + } + } +} + +smpl_t +aubio_level_lin (const fvec_t * f) +{ + smpl_t energy = 0.; +#ifndef HAVE_BLAS + uint_t j; + for (j = 0; j < f->length; j++) { + energy += SQR (f->data[j]); + } +#else + energy = aubio_cblas_dot(f->length, f->data, 1, f->data, 1); +#endif + return energy / f->length; +} + +smpl_t +fvec_local_hfc (fvec_t * v) +{ + smpl_t hfc = 0.; + uint_t j; + for (j = 0; j < v->length; j++) { + hfc += (j + 1) * v->data[j]; + } + return hfc; +} + +void +fvec_min_removal (fvec_t * v) +{ + smpl_t v_min = fvec_min (v); + fvec_add (v, - v_min ); +} + +smpl_t +fvec_alpha_norm (fvec_t * o, smpl_t alpha) +{ + uint_t j; + smpl_t tmp = 0.; + for (j = 0; j < o->length; j++) { + tmp += POW (ABS (o->data[j]), alpha); + } + return POW (tmp / o->length, 1. / alpha); +} + +void +fvec_alpha_normalise (fvec_t * o, smpl_t alpha) +{ + uint_t j; + smpl_t norm = fvec_alpha_norm (o, alpha); + for (j = 0; j < o->length; j++) { + o->data[j] /= norm; + } +} + +void +fvec_add (fvec_t * o, smpl_t val) +{ + uint_t j; + for (j = 0; j < o->length; j++) { + o->data[j] += val; + } +} + +void +fvec_mul (fvec_t *o, smpl_t val) +{ + uint_t j; + for (j = 0; j < o->length; j++) { + o->data[j] *= val; + } +} + +void fvec_adapt_thres(fvec_t * vec, fvec_t * tmp, + uint_t post, uint_t pre) { + uint_t length = vec->length, j; + for (j=0;jdata[j] -= fvec_moving_thres(vec, tmp, post, pre, j); + } +} + +smpl_t +fvec_moving_thres (fvec_t * vec, fvec_t * tmpvec, + uint_t post, uint_t pre, uint_t pos) +{ + uint_t k; + smpl_t *medar = (smpl_t *) tmpvec->data; + uint_t win_length = post + pre + 1; + uint_t length = vec->length; + /* post part of the buffer does not exist */ + if (pos < post + 1) { + for (k = 0; k < post + 1 - pos; k++) + medar[k] = 0.; /* 0-padding at the beginning */ + for (k = post + 1 - pos; k < win_length; k++) + medar[k] = vec->data[k + pos - post]; + /* the buffer is fully defined */ + } else if (pos + pre < length) { + for (k = 0; k < win_length; k++) + medar[k] = vec->data[k + pos - post]; + /* pre part of the buffer does not exist */ + } else { + for (k = 0; k < length - pos + post; k++) + medar[k] = vec->data[k + pos - post]; + for (k = length - pos + post; k < win_length; k++) + medar[k] = 0.; /* 0-padding at the end */ + } + return fvec_median (tmpvec); +} + +smpl_t fvec_median (fvec_t * input) { + uint_t n = input->length; + smpl_t * arr = (smpl_t *) input->data; + uint_t low, high ; + uint_t median; + uint_t middle, ll, hh; + + low = 0 ; high = n-1 ; median = (low + high) / 2; + for (;;) { + if (high <= low) /* One element only */ + return arr[median] ; + + if (high == low + 1) { /* Two elements only */ + if (arr[low] > arr[high]) + ELEM_SWAP(arr[low], arr[high]) ; + return arr[median] ; + } + + /* Find median of low, middle and high items; swap into position low */ + middle = (low + high) / 2; + if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]); + if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]); + if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]) ; + + /* Swap low item (now in position middle) into position (low+1) */ + ELEM_SWAP(arr[middle], arr[low+1]) ; + + /* Nibble from each end towards middle, swapping items when stuck */ + ll = low + 1; + hh = high; + for (;;) { + do ll++; while (arr[low] > arr[ll]) ; + do hh--; while (arr[hh] > arr[low]) ; + + if (hh < ll) + break; + + ELEM_SWAP(arr[ll], arr[hh]) ; + } + + /* Swap middle item (in position low) back into correct position */ + ELEM_SWAP(arr[low], arr[hh]) ; + + /* Re-set active partition */ + if (hh <= median) + low = ll; + if (hh >= median) + high = hh - 1; + } +} + +smpl_t fvec_quadratic_peak_pos (const fvec_t * x, uint_t pos) { + smpl_t s0, s1, s2; uint_t x0, x2; + smpl_t half = .5, two = 2.; + if (pos == 0 || pos == x->length - 1) return pos; + x0 = (pos < 1) ? pos : pos - 1; + x2 = (pos + 1 < x->length) ? pos + 1 : pos; + if (x0 == pos) return (x->data[pos] <= x->data[x2]) ? pos : x2; + if (x2 == pos) return (x->data[pos] <= x->data[x0]) ? pos : x0; + s0 = x->data[x0]; + s1 = x->data[pos]; + s2 = x->data[x2]; + return pos + half * (s0 - s2 ) / (s0 - two * s1 + s2); +} + +smpl_t fvec_quadratic_peak_mag (fvec_t *x, smpl_t pos) { + smpl_t x0, x1, x2; + uint_t index = (uint_t)(pos - .5) + 1; + if (pos >= x->length || pos < 0.) return 0.; + if ((smpl_t)index == pos) return x->data[index]; + x0 = x->data[index - 1]; + x1 = x->data[index]; + x2 = x->data[index + 1]; + return x1 - .25 * (x0 - x2) * (pos - index); +} + +uint_t fvec_peakpick(const fvec_t * onset, uint_t pos) { + uint_t tmp=0; + tmp = (onset->data[pos] > onset->data[pos-1] + && onset->data[pos] > onset->data[pos+1] + && onset->data[pos] > 0.); + return tmp; +} + +smpl_t +aubio_quadfrac (smpl_t s0, smpl_t s1, smpl_t s2, smpl_t pf) +{ + smpl_t tmp = + s0 + (pf / 2.) * (pf * (s0 - 2. * s1 + s2) - 3. * s0 + 4. * s1 - s2); + return tmp; +} + +smpl_t +aubio_freqtomidi (smpl_t freq) +{ + smpl_t midi; + if (freq < 2. || freq > 100000.) return 0.; // avoid nans and infs + /* log(freq/A-2)/log(2) */ + midi = freq / 6.875; + midi = LOG (midi) / 0.6931471805599453; + midi *= 12; + midi -= 3; + return midi; +} + +smpl_t +aubio_miditofreq (smpl_t midi) +{ + smpl_t freq; + if (midi > 140.) return 0.; // avoid infs + freq = (midi + 3.) / 12.; + freq = EXP (freq * 0.6931471805599453); + freq *= 6.875; + return freq; +} + +smpl_t +aubio_bintofreq (smpl_t bin, smpl_t samplerate, smpl_t fftsize) +{ + smpl_t freq = samplerate / fftsize; + return freq * MAX(bin, 0); +} + +smpl_t +aubio_bintomidi (smpl_t bin, smpl_t samplerate, smpl_t fftsize) +{ + smpl_t midi = aubio_bintofreq (bin, samplerate, fftsize); + return aubio_freqtomidi (midi); +} + +smpl_t +aubio_freqtobin (smpl_t freq, smpl_t samplerate, smpl_t fftsize) +{ + smpl_t bin = fftsize / samplerate; + return MAX(freq, 0) * bin; +} + +smpl_t +aubio_miditobin (smpl_t midi, smpl_t samplerate, smpl_t fftsize) +{ + smpl_t freq = aubio_miditofreq (midi); + return aubio_freqtobin (freq, samplerate, fftsize); +} + +uint_t +aubio_is_power_of_two (uint_t a) +{ + if ((a & (a - 1)) == 0) { + return 1; + } else { + return 0; + } +} + +uint_t +aubio_next_power_of_two (uint_t a) +{ + uint_t i = 1; + while (i < a) i <<= 1; + return i; +} + +uint_t +aubio_power_of_two_order (uint_t a) +{ + int order = 0; + int temp = aubio_next_power_of_two(a); + while (temp >>= 1) { + ++order; + } + return order; +} + +smpl_t +aubio_db_spl (const fvec_t * o) +{ + return 10. * LOG10 (aubio_level_lin (o)); +} + +uint_t +aubio_silence_detection (const fvec_t * o, smpl_t threshold) +{ + return (aubio_db_spl (o) < threshold); +} + +smpl_t +aubio_level_detection (const fvec_t * o, smpl_t threshold) +{ + smpl_t db_spl = aubio_db_spl (o); + if (db_spl < threshold) { + return 1.; + } else { + return db_spl; + } +} + +smpl_t +aubio_zero_crossing_rate (fvec_t * input) +{ + uint_t j; + uint_t zcr = 0; + for (j = 1; j < input->length; j++) { + // previous was strictly negative + if (input->data[j - 1] < 0.) { + // current is positive or null + if (input->data[j] >= 0.) { + zcr += 1; + } + // previous was positive or null + } else { + // current is strictly negative + if (input->data[j] < 0.) { + zcr += 1; + } + } + } + return zcr / (smpl_t) input->length; +} + +void +aubio_autocorr (const fvec_t * input, fvec_t * output) +{ + uint_t i, j, length = input->length; + smpl_t *data, *acf; + smpl_t tmp = 0; + data = input->data; + acf = output->data; + for (i = 0; i < length; i++) { + tmp = 0.; + for (j = i; j < length; j++) { + tmp += data[j - i] * data[j]; + } + acf[i] = tmp / (smpl_t) (length - i); + } +} + +void +aubio_cleanup (void) +{ +#ifdef HAVE_FFTW3F + fftwf_cleanup (); +#else +#ifdef HAVE_FFTW3 + fftw_cleanup (); +#endif +#endif +} diff --git a/dependencies/aubio/src/mathutils.h b/dependencies/aubio/src/mathutils.h new file mode 100644 index 0000000000..4336d7ec5f --- /dev/null +++ b/dependencies/aubio/src/mathutils.h @@ -0,0 +1,338 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Various math functions + + \example test-mathutils.c + \example test-mathutils-window.c + + */ + +#ifndef AUBIO_MATHUTILS_H +#define AUBIO_MATHUTILS_H + +#include "fvec.h" +#include "musicutils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** compute the mean of a vector + + \param s vector to compute mean from + \return the mean of `v` + +*/ +smpl_t fvec_mean (fvec_t * s); + +/** find the max of a vector + + \param s vector to get the max from + + \return the value of the minimum of v + +*/ +smpl_t fvec_max (fvec_t * s); + +/** find the min of a vector + + \param s vector to get the min from + + \return the value of the maximum of v + +*/ +smpl_t fvec_min (fvec_t * s); + +/** find the index of the min of a vector + + \param s vector to get the index from + + \return the index of the minimum element of v + +*/ +uint_t fvec_min_elem (fvec_t * s); + +/** find the index of the max of a vector + + \param s vector to get the index from + + \return the index of the maximum element of v + +*/ +uint_t fvec_max_elem (fvec_t * s); + +/** swap the left and right halves of a vector + + This function swaps the left part of the signal with the right part of the +signal. Therefore + + \f$ a[0], a[1], ..., a[\frac{N}{2}], a[\frac{N}{2}+1], ..., a[N-1], a[N] \f$ + + becomes + + \f$ a[\frac{N}{2}+1], ..., a[N-1], a[N], a[0], a[1], ..., a[\frac{N}{2}] \f$ + + This operation, known as 'fftshift' in the Matlab Signal Processing Toolbox, +can be used before computing the FFT to simplify the phase relationship of the +resulting spectrum. See Amalia de Götzen's paper referred to above. + +*/ +void fvec_shift (fvec_t * v); + +/** swap the left and right halves of a vector + + This function swaps the left part of the signal with the right part of the +signal. Therefore + + \f$ a[0], a[1], ..., a[\frac{N}{2}], a[\frac{N}{2}+1], ..., a[N-1], a[N] \f$ + + becomes + + \f$ a[\frac{N}{2}+1], ..., a[N-1], a[N], a[0], a[1], ..., a[\frac{N}{2}] \f$ + + This operation, known as 'ifftshift' in the Matlab Signal Processing Toolbox, +can be used after computing the inverse FFT to simplify the phase relationship +of the resulting spectrum. See Amalia de Götzen's paper referred to above. + +*/ +void fvec_ishift (fvec_t * v); + +/** push a new element to the end of a vector, erasing the first element and + * sliding all others + + \param in vector to push to + \param new_elem new_element to add at the end of the vector + + In numpy words, this is equivalent to: in = np.concatenate([in, [new_elem]])[1:] + +*/ +void fvec_push(fvec_t *in, smpl_t new_elem); + +/** compute the sum of all elements of a vector + + \param v vector to compute the sum of + + \return the sum of v + +*/ +smpl_t fvec_sum (fvec_t * v); + +/** compute the High Frequency Content of a vector + + The High Frequency Content is defined as \f$ \sum_0^{N-1} (k+1) v[k] \f$. + + \param v vector to get the energy from + + \return the HFC of v + +*/ +smpl_t fvec_local_hfc (fvec_t * v); + +/** computes the p-norm of a vector + + Computes the p-norm of a vector for \f$ p = \alpha \f$ + + \f$ L^p = ||x||_p = (|x_1|^p + |x_2|^p + ... + |x_n|^p ) ^ \frac{1}{p} \f$ + + If p = 1, the result is the Manhattan distance. + + If p = 2, the result is the Euclidean distance. + + As p tends towards large values, \f$ L^p \f$ tends towards the maximum of the +input vector. + + References: + + - \f$L^p\f$ space on + Wikipedia + + \param v vector to compute norm from + \param p order of the computed norm + + \return the p-norm of v + +*/ +smpl_t fvec_alpha_norm (fvec_t * v, smpl_t p); + +/** alpha normalisation + + This function divides all elements of a vector by the p-norm as computed by +fvec_alpha_norm(). + + \param v vector to compute norm from + \param p order of the computed norm + +*/ +void fvec_alpha_normalise (fvec_t * v, smpl_t p); + +/** add a constant to each elements of a vector + + \param v vector to add constant to + \param c constant to add to v + +*/ +void fvec_add (fvec_t * v, smpl_t c); + +/** multiply each elements of a vector by a scalar + + \param v vector to add constant to + \param s constant to scale v with + +*/ +void fvec_mul (fvec_t * v, smpl_t s); + +/** remove the minimum value of the vector to each elements + + \param v vector to remove minimum from + +*/ +void fvec_min_removal (fvec_t * v); + +/** compute moving median threshold of a vector + + This function computes the moving median threshold value of at the given +position of a vector, taking the median among post elements before and up to +pre elements after pos. + + \param v input vector + \param tmp temporary vector of length post+1+pre + \param post length of causal part to take before pos + \param pre length of anti-causal part to take after pos + \param pos index to compute threshold for + + \return moving median threshold value + +*/ +smpl_t fvec_moving_thres (fvec_t * v, fvec_t * tmp, uint_t post, uint_t pre, + uint_t pos); + +/** apply adaptive threshold to a vector + + For each points at position p of an input vector, this function remove the +moving median threshold computed at p. + + \param v input vector + \param tmp temporary vector of length post+1+pre + \param post length of causal part to take before pos + \param pre length of anti-causal part to take after pos + +*/ +void fvec_adapt_thres (fvec_t * v, fvec_t * tmp, uint_t post, uint_t pre); + +/** returns the median of a vector + + The QuickSelect routine is based on the algorithm described in "Numerical +recipes in C", Second Edition, Cambridge University Press, 1992, Section 8.5, +ISBN 0-521-43108-5 + + This implementation of the QuickSelect routine is based on Nicolas +Devillard's implementation, available at http://ndevilla.free.fr/median/median/ +and in the Public Domain. + + \param v vector to get median from + + \return the median of v + +*/ +smpl_t fvec_median (fvec_t * v); + +/** finds exact peak index by quadratic interpolation + + See [Quadratic Interpolation of Spectral + Peaks](https://ccrma.stanford.edu/~jos/sasp/Quadratic_Peak_Interpolation.html), + by Julius O. Smith III + + \f$ p_{frac} = \frac{1}{2} \frac {x[p-1] - x[p+1]} {x[p-1] - 2 x[p] + x[p+1]} \in [ -.5, .5] \f$ + + \param x vector to get the interpolated peak position from + \param p index of the peak in vector `x` + \return \f$ p + p_{frac} \f$ exact peak position of interpolated maximum or minimum + +*/ +smpl_t fvec_quadratic_peak_pos (const fvec_t * x, uint_t p); + +/** finds magnitude of peak by quadratic interpolation + + See [Quadratic Interpolation of Spectral + Peaks](https://ccrma.stanford.edu/~jos/sasp/Quadratic_Peak_Interpolation.html), + by Julius O. Smith III + + \param x vector to get the magnitude of the interpolated peak position from + \param p index of the peak in vector `x` + \return magnitude of interpolated peak + +*/ +smpl_t fvec_quadratic_peak_mag (fvec_t * x, smpl_t p); + +/** Quadratic interpolation using Lagrange polynomial. + + Inspired from ``Comparison of interpolation algorithms in real-time sound +processing'', Vladimir Arnost, + + \param s0,s1,s2 are 3 consecutive samples of a curve + \param pf is the floating point index [0;2] + + \return \f$ s0 + (pf/2.)*((pf-3.)*s0-2.*(pf-2.)*s1+(pf-1.)*s2); \f$ + +*/ +smpl_t aubio_quadfrac (smpl_t s0, smpl_t s1, smpl_t s2, smpl_t pf); + +/** return 1 if v[p] is a peak and positive, 0 otherwise + + This function returns 1 if a peak is found at index p in the vector v. The +peak is defined as follows: + + - v[p] is positive + - v[p-1] < v[p] + - v[p] > v[p+1] + + \param v input vector + \param p position of supposed for peak + + \return 1 if a peak is found, 0 otherwise + +*/ +uint_t fvec_peakpick (const fvec_t * v, uint_t p); + +/** return 1 if a is a power of 2, 0 otherwise */ +uint_t aubio_is_power_of_two(uint_t a); + +/** return the next power of power of 2 greater than a */ +uint_t aubio_next_power_of_two(uint_t a); + +/** return the log2 factor of the given power of 2 value a */ +uint_t aubio_power_of_two_order(uint_t a); + +/** compute normalised autocorrelation function + + \param input vector to compute autocorrelation from + \param output vector to store autocorrelation function to + +*/ +void aubio_autocorr (const fvec_t * input, fvec_t * output); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_MATHUTILS_H */ diff --git a/dependencies/aubio/src/musicutils.c b/dependencies/aubio/src/musicutils.c new file mode 100644 index 0000000000..14ef849e02 --- /dev/null +++ b/dependencies/aubio/src/musicutils.c @@ -0,0 +1,85 @@ +/* + Copyright (C) 2018 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "musicutils.h" + +smpl_t +aubio_hztomel (smpl_t freq) +{ + const smpl_t lin_space = 3./200.; + const smpl_t split_hz = 1000.; + const smpl_t split_mel = split_hz * lin_space; + const smpl_t log_space = 27./LOG(6400/1000.); + if (freq < 0) { + AUBIO_WRN("hztomel: input frequency should be >= 0\n"); + return 0; + } + if (freq < split_hz) + { + return freq * lin_space; + } else { + return split_mel + log_space * LOG (freq / split_hz); + } + +} + +smpl_t +aubio_meltohz (smpl_t mel) +{ + const smpl_t lin_space = 200./3.; + const smpl_t split_hz = 1000.; + const smpl_t split_mel = split_hz / lin_space; + const smpl_t logSpacing = POW(6400/1000., 1/27.); + if (mel < 0) { + AUBIO_WRN("meltohz: input mel should be >= 0\n"); + return 0; + } + if (mel < split_mel) { + return lin_space * mel; + } else { + return split_hz * POW(logSpacing, mel - split_mel); + } +} + +smpl_t +aubio_hztomel_htk (smpl_t freq) +{ + const smpl_t split_hz = 700.; + const smpl_t log_space = 1127.; + if (freq < 0) { + AUBIO_WRN("hztomel_htk: input frequency should be >= 0\n"); + return 0; + } + return log_space * LOG (1 + freq / split_hz); +} + +smpl_t +aubio_meltohz_htk (smpl_t mel) +{ + const smpl_t split_hz = 700.; + const smpl_t log_space = 1./1127.; + if (mel < 0) { + AUBIO_WRN("meltohz_htk: input frequency should be >= 0\n"); + return 0; + } + return split_hz * ( EXP ( mel * log_space) - 1.); +} + diff --git a/dependencies/aubio/src/musicutils.h b/dependencies/aubio/src/musicutils.h new file mode 100644 index 0000000000..af222e5b5a --- /dev/null +++ b/dependencies/aubio/src/musicutils.h @@ -0,0 +1,270 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** @file + * various functions useful in audio signal processing + */ + +#ifndef AUBIO_MUSICUTILS_H +#define AUBIO_MUSICUTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** create window + + \param window_type type of the window to create + \param size length of the window to create (see fvec_set_window()) + +*/ +fvec_t *new_aubio_window (char_t * window_type, uint_t size); + +/** set elements of a vector to window coefficients + + \param window exsting ::fvec_t to use + \param window_type type of the window to create + + List of available window types: "rectangle", "hamming", "hanning", + "hanningz", "blackman", "blackman_harris", "gaussian", "welch", "parzen", + "default". + + "default" is equivalent to "hanningz". + + References: + + - Window +function on Wikipedia + - Amalia de Götzen, Nicolas Bernardini, and Daniel Arfib. Traditional (?) +implementations of a phase vocoder: the tricks of the trade. In Proceedings of +the International Conference on Digital Audio Effects (DAFx-00), pages 37–44, +Uni- versity of Verona, Italy, 2000. + ( + pdf) + + */ +uint_t fvec_set_window (fvec_t * window, char_t * window_type); + +/** compute the principal argument + + This function maps the input phase to its corresponding value wrapped in the +range \f$ [-\pi, \pi] \f$. + + \param phase unwrapped phase to map to the unit circle + + \return equivalent phase wrapped to the unit circle + +*/ +smpl_t aubio_unwrap2pi (smpl_t phase); + +/** convert frequency bin to midi value */ +smpl_t aubio_bintomidi (smpl_t bin, smpl_t samplerate, smpl_t fftsize); + +/** convert midi value to frequency bin */ +smpl_t aubio_miditobin (smpl_t midi, smpl_t samplerate, smpl_t fftsize); + +/** convert frequency bin to frequency (Hz) */ +smpl_t aubio_bintofreq (smpl_t bin, smpl_t samplerate, smpl_t fftsize); + +/** convert frequency (Hz) to frequency bin */ +smpl_t aubio_freqtobin (smpl_t freq, smpl_t samplerate, smpl_t fftsize); + +/** convert frequency (Hz) to mel + + \param freq input frequency, in Hz + + \return output mel + + Converts a scalar from the frequency domain to the mel scale using Slaney + Auditory Toolbox's implementation: + + If \f$ f < 1000 \f$, \f$ m = 3 f / 200 \f$. + + If \f$ f >= 1000 \f$, \f$ m = 1000 + 27 \frac{{ln}(f) - ln(1000))} + {{ln}(6400) - ln(1000)} + \f$ + + See also + -------- + + aubio_meltohz(), aubio_hztomel_htk(). + +*/ +smpl_t aubio_hztomel (smpl_t freq); + +/** convert mel to frequency (Hz) + + \param mel input mel + + \return output frequency, in Hz + + Converts a scalar from the mel scale to the frequency domain using Slaney + Auditory Toolbox's implementation: + + If \f$ f < 1000 \f$, \f$ f = 200 m/3 \f$. + + If \f$ f \geq 1000 \f$, \f$ f = 1000 + \left(\frac{6400}{1000}\right) + ^{\frac{m - 1000}{27}} \f$ + + See also + -------- + + aubio_hztomel(), aubio_meltohz_htk(). + + References + ---------- + + Malcolm Slaney, *Auditory Toolbox Version 2, Technical Report #1998-010* + https://engineering.purdue.edu/~malcolm/interval/1998-010/ + +*/ +smpl_t aubio_meltohz (smpl_t mel); + +/** convert frequency (Hz) to mel + + \param freq input frequency, in Hz + + \return output mel + + Converts a scalar from the frequency domain to the mel scale, using the + equation defined by O'Shaughnessy, as implemented in the HTK speech + recognition toolkit: + + \f$ m = 1127 + ln(1 + \frac{f}{700}) \f$ + + See also + -------- + + aubio_meltohz_htk(), aubio_hztomel(). + + References + ---------- + + Douglas O'Shaughnessy (1987). *Speech communication: human and machine*. + Addison-Wesley. p. 150. ISBN 978-0-201-16520-3. + + HTK Speech Recognition Toolkit: http://htk.eng.cam.ac.uk/ + + */ +smpl_t aubio_hztomel_htk (smpl_t freq); + +/** convert mel to frequency (Hz) + + \param mel input mel + + \return output frequency, in Hz + + Converts a scalar from the mel scale to the frequency domain, using the + equation defined by O'Shaughnessy, as implemented in the HTK speech + recognition toolkit: + + \f$ f = 700 * {e}^\left(\frac{f}{1127} - 1\right) \f$ + + See also + -------- + + aubio_hztomel_htk(), aubio_meltohz(). + +*/ +smpl_t aubio_meltohz_htk (smpl_t mel); + +/** convert frequency (Hz) to midi value (0-128) */ +smpl_t aubio_freqtomidi (smpl_t freq); + +/** convert midi value (0-128) to frequency (Hz) */ +smpl_t aubio_miditofreq (smpl_t midi); + +/** clean up cached memory at the end of program + + This function should be used at the end of programs to purge all cached + memory. So far it is only useful to clean FFTW's cache. + +*/ +void aubio_cleanup (void); + +/** zero-crossing rate (ZCR) + + The zero-crossing rate is the number of times a signal changes sign, + divided by the length of this signal. + + \param v vector to compute ZCR from + + \return zero-crossing rate of v + +*/ +smpl_t aubio_zero_crossing_rate (fvec_t * v); + +/** compute sound level on a linear scale + + This gives the average of the square amplitudes. + + \param v vector to compute level from + + \return level of v + +*/ +smpl_t aubio_level_lin (const fvec_t * v); + +/** compute sound pressure level (SPL) in dB + + This quantity is often wrongly called 'loudness'. + + This gives ten times the log10 of the average of the square amplitudes. + + \param v vector to compute dB SPL from + + \return level of v in dB SPL + +*/ +smpl_t aubio_db_spl (const fvec_t * v); + +/** check if buffer level in dB SPL is under a given threshold + + \param v vector to get level from + \param threshold threshold in dB SPL + + \return 1 if level is under the given threshold, 0 otherwise + +*/ +uint_t aubio_silence_detection (const fvec_t * v, smpl_t threshold); + +/** get buffer level if level >= threshold, 1. otherwise + + \param v vector to get level from + \param threshold threshold in dB SPL + + \return level in dB SPL if level >= threshold, 1. otherwise + +*/ +smpl_t aubio_level_detection (const fvec_t * v, smpl_t threshold); + +/** clamp the values of a vector within the range [-abs(max), abs(max)] + + \param in vector to clamp + \param absmax maximum value over which input vector elements should be clamped + +*/ +void fvec_clamp(fvec_t *in, smpl_t absmax); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_MUSICUTILS_H */ diff --git a/dependencies/aubio/src/onset/onset.c b/dependencies/aubio/src/onset/onset.c new file mode 100644 index 0000000000..6123d51ae1 --- /dev/null +++ b/dependencies/aubio/src/onset/onset.c @@ -0,0 +1,355 @@ +/* + Copyright (C) 2006-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "cvec.h" +#include "spectral/specdesc.h" +#include "spectral/phasevoc.h" +#include "spectral/awhitening.h" +#include "onset/peakpicker.h" +#include "mathutils.h" +#include "onset/onset.h" + +void aubio_onset_default_parameters (aubio_onset_t *o, const char_t * method); + +/** structure to store object state */ +struct _aubio_onset_t { + aubio_pvoc_t * pv; /**< phase vocoder */ + aubio_specdesc_t * od; /**< spectral descriptor */ + aubio_peakpicker_t * pp; /**< peak picker */ + cvec_t * fftgrain; /**< phase vocoder output */ + fvec_t * desc; /**< spectral description */ + smpl_t silence; /**< silence threhsold */ + uint_t minioi; /**< minimum inter onset interval */ + uint_t delay; /**< constant delay, in samples, removed from detected onset times */ + uint_t samplerate; /**< sampling rate of the input signal */ + uint_t hop_size; /**< number of samples between two runs */ + + uint_t total_frames; /**< total number of frames processed since the beginning */ + uint_t last_onset; /**< last detected onset location, in frames */ + + uint_t apply_compression; + smpl_t lambda_compression; + uint_t apply_awhitening; /**< apply adaptive spectral whitening */ + aubio_spectral_whitening_t *spectral_whitening; +}; + +/* execute onset detection function on iput buffer */ +void aubio_onset_do (aubio_onset_t *o, const fvec_t * input, fvec_t * onset) +{ + smpl_t isonset = 0; + aubio_pvoc_do (o->pv,input, o->fftgrain); + /* + if (apply_filtering) { + } + */ + if (o->apply_awhitening) { + aubio_spectral_whitening_do(o->spectral_whitening, o->fftgrain); + } + if (o->apply_compression) { + cvec_logmag(o->fftgrain, o->lambda_compression); + } + aubio_specdesc_do (o->od, o->fftgrain, o->desc); + aubio_peakpicker_do(o->pp, o->desc, onset); + isonset = onset->data[0]; + if (isonset > 0.) { + if (aubio_silence_detection(input, o->silence)==1) { + //AUBIO_DBG ("silent onset, not marking as onset\n"); + isonset = 0; + } else { + // we have an onset + uint_t new_onset = o->total_frames + (uint_t)ROUND(isonset * o->hop_size); + // check if last onset time was more than minioi ago + if (o->last_onset + o->minioi < new_onset) { + // start of file: make sure (new_onset - delay) >= 0 + if (o->last_onset > 0 && o->delay > new_onset) { + isonset = 0; + } else { + //AUBIO_DBG ("accepted detection, marking as onset\n"); + o->last_onset = MAX(o->delay, new_onset); + } + } else { + //AUBIO_DBG ("doubled onset, not marking as onset\n"); + isonset = 0; + } + } + } else { + // we are at the beginning of the file + if (o->total_frames <= o->delay) { + // and we don't find silence + if (aubio_silence_detection(input, o->silence) == 0) { + uint_t new_onset = o->total_frames; + if (o->total_frames == 0 || o->last_onset + o->minioi < new_onset) { + isonset = o->delay / o->hop_size; + o->last_onset = o->total_frames + o->delay; + } + } + } + } + onset->data[0] = isonset; + o->total_frames += o->hop_size; + return; +} + +uint_t aubio_onset_get_last (const aubio_onset_t *o) +{ + return o->last_onset - o->delay; +} + +smpl_t aubio_onset_get_last_s (const aubio_onset_t *o) +{ + return aubio_onset_get_last (o) / (smpl_t) (o->samplerate); +} + +smpl_t aubio_onset_get_last_ms (const aubio_onset_t *o) +{ + return aubio_onset_get_last_s (o) * 1000.; +} + +uint_t aubio_onset_set_awhitening (aubio_onset_t *o, uint_t enable) +{ + o->apply_awhitening = enable == 1 ? 1 : 0; + return AUBIO_OK; +} + +smpl_t aubio_onset_get_awhitening (aubio_onset_t *o) +{ + return o->apply_awhitening; +} + +uint_t aubio_onset_set_compression (aubio_onset_t *o, smpl_t lambda) +{ + if (lambda < 0.) { + return AUBIO_FAIL; + } + o->lambda_compression = lambda; + o->apply_compression = (o->lambda_compression > 0.) ? 1 : 0; + return AUBIO_OK; +} + +smpl_t aubio_onset_get_compression (aubio_onset_t *o) +{ + return o->apply_compression ? o->lambda_compression : 0; +} + +uint_t aubio_onset_set_silence(aubio_onset_t * o, smpl_t silence) { + o->silence = silence; + return AUBIO_OK; +} + +smpl_t aubio_onset_get_silence(const aubio_onset_t * o) { + return o->silence; +} + +uint_t aubio_onset_set_threshold(aubio_onset_t * o, smpl_t threshold) { + aubio_peakpicker_set_threshold(o->pp, threshold); + return AUBIO_OK; +} + +smpl_t aubio_onset_get_threshold(const aubio_onset_t * o) { + return aubio_peakpicker_get_threshold(o->pp); +} + +uint_t aubio_onset_set_minioi(aubio_onset_t * o, uint_t minioi) { + o->minioi = minioi; + return AUBIO_OK; +} + +uint_t aubio_onset_get_minioi(const aubio_onset_t * o) { + return o->minioi; +} + +uint_t aubio_onset_set_minioi_s(aubio_onset_t * o, smpl_t minioi) { + return aubio_onset_set_minioi (o, (uint_t)ROUND(minioi * o->samplerate)); +} + +smpl_t aubio_onset_get_minioi_s(const aubio_onset_t * o) { + return aubio_onset_get_minioi (o) / (smpl_t) o->samplerate; +} + +uint_t aubio_onset_set_minioi_ms(aubio_onset_t * o, smpl_t minioi) { + return aubio_onset_set_minioi_s (o, minioi / 1000.); +} + +smpl_t aubio_onset_get_minioi_ms(const aubio_onset_t * o) { + return aubio_onset_get_minioi_s (o) * 1000.; +} + +uint_t aubio_onset_set_delay(aubio_onset_t * o, uint_t delay) { + o->delay = delay; + return AUBIO_OK; +} + +uint_t aubio_onset_get_delay(const aubio_onset_t * o) { + return o->delay; +} + +uint_t aubio_onset_set_delay_s(aubio_onset_t * o, smpl_t delay) { + return aubio_onset_set_delay (o, delay * o->samplerate); +} + +smpl_t aubio_onset_get_delay_s(const aubio_onset_t * o) { + return aubio_onset_get_delay (o) / (smpl_t) o->samplerate; +} + +uint_t aubio_onset_set_delay_ms(aubio_onset_t * o, smpl_t delay) { + return aubio_onset_set_delay_s (o, delay / 1000.); +} + +smpl_t aubio_onset_get_delay_ms(const aubio_onset_t * o) { + return aubio_onset_get_delay_s (o) * 1000.; +} + +smpl_t aubio_onset_get_descriptor(const aubio_onset_t * o) { + return o->desc->data[0]; +} + +smpl_t aubio_onset_get_thresholded_descriptor(const aubio_onset_t * o) { + fvec_t * thresholded = aubio_peakpicker_get_thresholded_input(o->pp); + return thresholded->data[0]; +} + +/* Allocate memory for an onset detection */ +aubio_onset_t * new_aubio_onset (const char_t * onset_mode, + uint_t buf_size, uint_t hop_size, uint_t samplerate) +{ + aubio_onset_t * o = AUBIO_NEW(aubio_onset_t); + + /* check parameters are valid */ + if ((sint_t)hop_size < 1) { + AUBIO_ERR("onset: got hop_size %d, but can not be < 1\n", hop_size); + goto beach; + } else if ((sint_t)buf_size < 2) { + AUBIO_ERR("onset: got buffer_size %d, but can not be < 2\n", buf_size); + goto beach; + } else if (buf_size < hop_size) { + AUBIO_ERR("onset: hop size (%d) is larger than win size (%d)\n", hop_size, buf_size); + goto beach; + } else if ((sint_t)samplerate < 1) { + AUBIO_ERR("onset: samplerate (%d) can not be < 1\n", samplerate); + goto beach; + } + + /* store creation parameters */ + o->samplerate = samplerate; + o->hop_size = hop_size; + + /* allocate memory */ + o->pv = new_aubio_pvoc(buf_size, o->hop_size); + o->pp = new_aubio_peakpicker(); + o->od = new_aubio_specdesc(onset_mode,buf_size); + o->fftgrain = new_cvec(buf_size); + o->desc = new_fvec(1); + o->spectral_whitening = new_aubio_spectral_whitening(buf_size, hop_size, samplerate); + + if (!o->pv || !o->pp || !o->od || !o->fftgrain + || !o->desc || !o->spectral_whitening) + goto beach; + + /* initialize internal variables */ + aubio_onset_set_default_parameters (o, onset_mode); + + aubio_onset_reset(o); + return o; + +beach: + del_aubio_onset(o); + return NULL; +} + +void aubio_onset_reset (aubio_onset_t *o) { + o->last_onset = 0; + o->total_frames = 0; +} + +uint_t aubio_onset_set_default_parameters (aubio_onset_t * o, const char_t * onset_mode) +{ + uint_t ret = AUBIO_OK; + /* set some default parameter */ + aubio_onset_set_threshold (o, 0.3); + aubio_onset_set_delay (o, 4.3 * o->hop_size); + aubio_onset_set_minioi_ms (o, 50.); + aubio_onset_set_silence (o, -70.); + // disable spectral whitening + aubio_onset_set_awhitening (o, 0); + // disable logarithmic magnitude + aubio_onset_set_compression (o, 0.); + + /* method specific optimisations */ + if (strcmp (onset_mode, "energy") == 0) { + } else if (strcmp (onset_mode, "hfc") == 0 || strcmp (onset_mode, "default") == 0) { + aubio_onset_set_threshold (o, 0.058); + aubio_onset_set_compression (o, 1.); + } else if (strcmp (onset_mode, "complexdomain") == 0 + || strcmp (onset_mode, "complex") == 0) { + aubio_onset_set_delay (o, 4.6 * o->hop_size); + aubio_onset_set_threshold (o, 0.15); + aubio_onset_set_awhitening(o, 1); + aubio_onset_set_compression (o, 1.); + } else if (strcmp (onset_mode, "phase") == 0) { + o->apply_compression = 0; + aubio_onset_set_awhitening (o, 0); + } else if (strcmp (onset_mode, "wphase") == 0) { + // use defaults for now + } else if (strcmp (onset_mode, "mkl") == 0) { + aubio_onset_set_threshold (o, 0.05); + aubio_onset_set_awhitening(o, 1); + aubio_onset_set_compression (o, 0.02); + } else if (strcmp (onset_mode, "kl") == 0) { + aubio_onset_set_threshold (o, 0.35); + aubio_onset_set_awhitening(o, 1); + aubio_onset_set_compression (o, 0.02); + } else if (strcmp (onset_mode, "specflux") == 0) { + aubio_onset_set_threshold (o, 0.18); + aubio_onset_set_awhitening(o, 1); + aubio_spectral_whitening_set_relax_time(o->spectral_whitening, 100); + aubio_spectral_whitening_set_floor(o->spectral_whitening, 1.); + aubio_onset_set_compression (o, 10.); + } else if (strcmp (onset_mode, "specdiff") == 0) { + } else if (strcmp (onset_mode, "old_default") == 0) { + // used to reproduce results obtained with the previous version + aubio_onset_set_threshold (o, 0.3); + aubio_onset_set_minioi_ms (o, 20.); + aubio_onset_set_compression (o, 0.); + } else { + AUBIO_WRN("onset: unknown spectral descriptor type %s, " + "using default parameters.\n", onset_mode); + ret = AUBIO_FAIL; + } + return ret; +} + +void del_aubio_onset (aubio_onset_t *o) +{ + if (o->spectral_whitening) + del_aubio_spectral_whitening(o->spectral_whitening); + if (o->od) + del_aubio_specdesc(o->od); + if (o->pp) + del_aubio_peakpicker(o->pp); + if (o->pv) + del_aubio_pvoc(o->pv); + if (o->desc) + del_fvec(o->desc); + if (o->fftgrain) + del_cvec(o->fftgrain); + AUBIO_FREE(o); +} diff --git a/dependencies/aubio/src/onset/onset.h b/dependencies/aubio/src/onset/onset.h new file mode 100644 index 0000000000..b115e6aa2e --- /dev/null +++ b/dependencies/aubio/src/onset/onset.h @@ -0,0 +1,347 @@ +/* + Copyright (C) 2006-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Onset detection object + + The following routines compute the onset detection function and detect peaks + in these functions. When onsets are found above a given silence threshold, + and after a minimum inter-onset interval, the output vector returned by + aubio_onset_do() is filled with `1`. Otherwise, the output vector remains + `0`. + + The peak-picking threshold, the silence threshold, and the minimum + inter-onset interval can be adjusted during the execution of the + aubio_onset_do routine using the corresponding functions. + + \example onset/test-onset.c + \example examples/aubioonset.c + \example examples/aubionotes.c + +*/ + + +#ifndef AUBIO_ONSET_H +#define AUBIO_ONSET_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** onset detection object */ +typedef struct _aubio_onset_t aubio_onset_t; + +/** create onset detection object + + \param method onset detection type as specified in specdesc.h + \param buf_size buffer size for phase vocoder + \param hop_size hop size for phase vocoder + \param samplerate sampling rate of the input signal + + \return newly created ::aubio_onset_t + +*/ +aubio_onset_t * new_aubio_onset (const char_t * method, + uint_t buf_size, uint_t hop_size, uint_t samplerate); + +/** execute onset detection + + \param o onset detection object as returned by new_aubio_onset() + \param input new audio vector of length hop_size + \param onset output vector of length 1, containing 0 if no onset was found, + and a value equal or greater than 1 otherwise + + When no onset was detected, the first element of the output vector `onset` + is set to 0. + + When an onset is found, the first element of the output vector `onset` is set + to `offset = 1 + a` where `a` is a number in the range`[0, 1]`. + + The final onset detection time, in samples, can be obtained with + aubio_onset_get_last(). It can also be derived from `offset` as + follows: + + \code + t = total_frames + offset * hop_size - delay + \endcode + + where `total_frames` is the total number of frames processed so far, and + `delay` is the current delay of the onset object, as returned by + aubio_onset_get_delay(). + +*/ +void aubio_onset_do (aubio_onset_t *o, const fvec_t * input, fvec_t * onset); + +/** get the time of the latest onset detected, in samples + + \param o onset detection object as returned by new_aubio_onset() + + \return onset detection timestamps (in samples) + +*/ +uint_t aubio_onset_get_last (const aubio_onset_t *o); + +/** get the time of the latest onset detected, in seconds + + \param o onset detection object as returned by new_aubio_onset() + + \return onset detection timestamps (in seconds) + +*/ +smpl_t aubio_onset_get_last_s (const aubio_onset_t *o); + +/** get the time of the latest onset detected, in milliseconds + + \param o onset detection object as returned by new_aubio_onset() + + \return onset detection timestamps (in milliseconds) + +*/ +smpl_t aubio_onset_get_last_ms (const aubio_onset_t *o); + +/** set onset detection adaptive whitening + + \param o onset detection object as returned by new_aubio_onset() + \param enable 1 to enable, 0 to disable + + \return 0 if successful, 1 otherwise + +*/ +uint_t aubio_onset_set_awhitening(aubio_onset_t * o, uint_t enable); + +/** get onset detection adaptive whitening + + \param o onset detection object as returned by new_aubio_onset() + + \return 1 if enabled, 0 otherwise + +*/ +smpl_t aubio_onset_get_awhitening(aubio_onset_t * o); + +/** set or disable log compression + + \param o onset detection object as returned by new_aubio_onset() + \param lambda logarithmic compression factor, 0 to disable + + \return 0 if successful, 1 otherwise + + */ +uint_t aubio_onset_set_compression(aubio_onset_t *o, smpl_t lambda); + +/** get onset detection log compression + + \param o onset detection object as returned by new_aubio_onset() + + \returns 0 if disabled, compression factor otherwise + + */ +smpl_t aubio_onset_get_compression(aubio_onset_t *o); + +/** set onset detection silence threshold + + \param o onset detection object as returned by new_aubio_onset() + \param silence new silence detection threshold + +*/ +uint_t aubio_onset_set_silence(aubio_onset_t * o, smpl_t silence); + +/** get onset detection silence threshold + + \param o onset detection object as returned by new_aubio_onset() + + \return current silence threshold + +*/ +smpl_t aubio_onset_get_silence(const aubio_onset_t * o); + +/** get onset detection function + + \param o onset detection object as returned by new_aubio_onset() + \return the current value of the descriptor + +*/ +smpl_t aubio_onset_get_descriptor (const aubio_onset_t *o); + +/** get thresholded onset detection function + + \param o onset detection object as returned by new_aubio_onset() + \return the value of the thresholded descriptor + +*/ +smpl_t aubio_onset_get_thresholded_descriptor (const aubio_onset_t *o); + +/** set onset detection peak picking threshold + + \param o onset detection object as returned by new_aubio_onset() + \param threshold new peak-picking threshold + +*/ +uint_t aubio_onset_set_threshold(aubio_onset_t * o, smpl_t threshold); + +/** set minimum inter onset interval in samples + + \param o onset detection object as returned by new_aubio_onset() + \param minioi minimum interval between two consecutive onsets (in + samples) + +*/ +uint_t aubio_onset_set_minioi(aubio_onset_t * o, uint_t minioi); + +/** set minimum inter onset interval in seconds + + \param o onset detection object as returned by new_aubio_onset() + \param minioi minimum interval between two consecutive onsets (in + seconds) + +*/ +uint_t aubio_onset_set_minioi_s(aubio_onset_t * o, smpl_t minioi); + +/** set minimum inter onset interval in milliseconds + + \param o onset detection object as returned by new_aubio_onset() + \param minioi minimum interval between two consecutive onsets (in + milliseconds) + +*/ +uint_t aubio_onset_set_minioi_ms(aubio_onset_t * o, smpl_t minioi); + +/** set delay in samples + + \param o onset detection object as returned by new_aubio_onset() + \param delay constant system delay to take back from detection time + (in samples) + +*/ +uint_t aubio_onset_set_delay(aubio_onset_t * o, uint_t delay); + +/** set delay in seconds + + \param o onset detection object as returned by new_aubio_onset() + \param delay constant system delay to take back from detection time + (in seconds) + +*/ +uint_t aubio_onset_set_delay_s(aubio_onset_t * o, smpl_t delay); + +/** set delay in milliseconds + + \param o onset detection object as returned by new_aubio_onset() + \param delay constant system delay to take back from detection time + (in milliseconds) + +*/ +uint_t aubio_onset_set_delay_ms(aubio_onset_t * o, smpl_t delay); + +/** get minimum inter onset interval in samples + + \param o onset detection object as returned by new_aubio_onset() + \return minimum interval between two consecutive onsets (in + samples) + +*/ +uint_t aubio_onset_get_minioi(const aubio_onset_t * o); + +/** get minimum inter onset interval in seconds + + \param o onset detection object as returned by new_aubio_onset() + \return minimum interval between two consecutive onsets (in + seconds) + +*/ +smpl_t aubio_onset_get_minioi_s(const aubio_onset_t * o); + +/** get minimum inter onset interval in milliseconds + + \param o onset detection object as returned by new_aubio_onset() + \return minimum interval between two consecutive onsets (in + milliseconds) + +*/ +smpl_t aubio_onset_get_minioi_ms(const aubio_onset_t * o); + +/** get delay in samples + + \param o onset detection object as returned by new_aubio_onset() + \return constant system delay to take back from detection time + (in samples) + +*/ +uint_t aubio_onset_get_delay(const aubio_onset_t * o); + +/** get delay in seconds + + \param o onset detection object as returned by new_aubio_onset() + \return constant system delay to take back from detection time + (in seconds) + +*/ +smpl_t aubio_onset_get_delay_s(const aubio_onset_t * o); + +/** get delay in milliseconds + + \param o onset detection object as returned by new_aubio_onset() + \return constant system delay to take back from detection time + (in milliseconds) + +*/ +smpl_t aubio_onset_get_delay_ms(const aubio_onset_t * o); + +/** get onset peak picking threshold + + \param o onset detection object as returned by new_aubio_onset() + \return current onset detection threshold + +*/ +smpl_t aubio_onset_get_threshold(const aubio_onset_t * o); + +/** set default parameters + + \param o onset detection object as returned by new_aubio_onset() + \param onset_mode detection mode to adjust + + This function is called at the end of new_aubio_onset(). + + */ +uint_t aubio_onset_set_default_parameters (aubio_onset_t * o, const char_t * onset_mode); + +/** reset onset detection + + \param o onset detection object as returned by new_aubio_onset() + + Reset current time and last onset to 0. + + This function is called at the end of new_aubio_onset(). + + */ +void aubio_onset_reset(aubio_onset_t * o); + +/** delete onset detection object + + \param o onset detection object to delete + +*/ +void del_aubio_onset(aubio_onset_t * o); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_ONSET_H */ diff --git a/dependencies/aubio/src/onset/peakpicker.c b/dependencies/aubio/src/onset/peakpicker.c new file mode 100644 index 0000000000..fa1ee38eaf --- /dev/null +++ b/dependencies/aubio/src/onset/peakpicker.c @@ -0,0 +1,199 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "mathutils.h" +#include "lvec.h" +#include "temporal/filter.h" +#include "temporal/biquad.h" +#include "onset/peakpicker.h" + +/** function pointer to thresholding function */ +typedef smpl_t (*aubio_thresholdfn_t)(fvec_t *input); +/** function pointer to peak-picking function */ +typedef uint_t (*aubio_pickerfn_t)(fvec_t *input, uint_t pos); + +/** set peak picker thresholding function */ +uint_t aubio_peakpicker_set_thresholdfn(aubio_peakpicker_t * p, aubio_thresholdfn_t thresholdfn); +/** get peak picker thresholding function */ +aubio_thresholdfn_t aubio_peakpicker_get_thresholdfn(aubio_peakpicker_t * p); + +/* peak picking parameters, default values in brackets + * + * [<----post----|--pre-->] + * .................|............. + * time-> ^now + */ +struct _aubio_peakpicker_t +{ + /** thresh: offset threshold [0.033 or 0.01] */ + smpl_t threshold; + /** win_post: median filter window length (causal part) [8] */ + uint_t win_post; + /** pre: median filter window (anti-causal part) [post-1] */ + uint_t win_pre; + /** threshfn: name or handle of fn for computing adaptive threshold [median] */ + aubio_thresholdfn_t thresholdfn; + /** picker: name or handle of fn for picking event times [peakpick] */ + aubio_pickerfn_t pickerfn; + + /** biquad lowpass filter */ + aubio_filter_t *biquad; + /** original onsets */ + fvec_t *onset_keep; + /** modified onsets */ + fvec_t *onset_proc; + /** peak picked window [3] */ + fvec_t *onset_peek; + /** thresholded function */ + fvec_t *thresholded; + /** scratch pad for biquad and median */ + fvec_t *scratch; + + /** \bug should be used to calculate filter coefficients */ + /* cutoff: low-pass filter cutoff [0.34, 1] */ + /* smpl_t cutoff; */ + + /* not used anymore */ + /* time precision [512/44100 winlength/samplerate, fs/buffer_size */ + /* smpl_t tau; */ + /* alpha: normalisation exponent [9] */ + /* smpl_t alpha; */ +}; + + +/** modified version for real time, moving mean adaptive threshold this method + * is slightly more permissive than the offline one, and yelds to an increase + * of false positives. best */ +void +aubio_peakpicker_do (aubio_peakpicker_t * p, fvec_t * onset, fvec_t * out) +{ + fvec_t *onset_keep = p->onset_keep; + fvec_t *onset_proc = p->onset_proc; + fvec_t *onset_peek = p->onset_peek; + fvec_t *thresholded = p->thresholded; + fvec_t *scratch = p->scratch; + smpl_t mean = 0., median = 0.; + uint_t j = 0; + + /* push new novelty to the end */ + fvec_push(onset_keep, onset->data[0]); + /* store a copy */ + fvec_copy(onset_keep, onset_proc); + + /* filter this copy */ + aubio_filter_do_filtfilt (p->biquad, onset_proc, scratch); + + /* calculate mean and median for onset_proc */ + mean = fvec_mean (onset_proc); + + /* copy to scratch and compute its median */ + fvec_copy(onset_proc, scratch); + median = p->thresholdfn (scratch); + + /* shift peek array */ + for (j = 0; j < 3 - 1; j++) + onset_peek->data[j] = onset_peek->data[j + 1]; + /* calculate new tresholded value */ + thresholded->data[0] = + onset_proc->data[p->win_post] - median - mean * p->threshold; + onset_peek->data[2] = thresholded->data[0]; + out->data[0] = (p->pickerfn) (onset_peek, 1); + if (out->data[0]) { + out->data[0] = fvec_quadratic_peak_pos (onset_peek, 1); + } +} + +/** this method returns the current value in the pick peaking buffer + * after smoothing + */ +fvec_t * +aubio_peakpicker_get_thresholded_input (aubio_peakpicker_t * p) +{ + return p->thresholded; +} + +uint_t +aubio_peakpicker_set_threshold (aubio_peakpicker_t * p, smpl_t threshold) +{ + p->threshold = threshold; + return AUBIO_OK; +} + +smpl_t +aubio_peakpicker_get_threshold (aubio_peakpicker_t * p) +{ + return p->threshold; +} + +uint_t +aubio_peakpicker_set_thresholdfn (aubio_peakpicker_t * p, + aubio_thresholdfn_t thresholdfn) +{ + p->thresholdfn = thresholdfn; + return AUBIO_OK; +} + +aubio_thresholdfn_t +aubio_peakpicker_get_thresholdfn (aubio_peakpicker_t * p) +{ + return (aubio_thresholdfn_t) (p->thresholdfn); +} + +aubio_peakpicker_t * +new_aubio_peakpicker (void) +{ + aubio_peakpicker_t *t = AUBIO_NEW (aubio_peakpicker_t); + t->threshold = 0.1; /* 0.0668; 0.33; 0.082; 0.033; */ + t->win_post = 5; + t->win_pre = 1; + + t->thresholdfn = (aubio_thresholdfn_t) (fvec_median); /* (fvec_mean); */ + t->pickerfn = (aubio_pickerfn_t) (fvec_peakpick); + + t->scratch = new_fvec (t->win_post + t->win_pre + 1); + t->onset_keep = new_fvec (t->win_post + t->win_pre + 1); + t->onset_proc = new_fvec (t->win_post + t->win_pre + 1); + t->onset_peek = new_fvec (3); + t->thresholded = new_fvec (1); + + /* cutoff: low-pass filter with cutoff reduced frequency at 0.34 + generated with octave butter function: [b,a] = butter(2, 0.34); + */ + t->biquad = new_aubio_filter_biquad (0.15998789, 0.31997577, 0.15998789, + // FIXME: broken since c9e20ca, revert for now + //-0.59488894, 0.23484048); + 0.23484048, 0); + + return t; +} + +void +del_aubio_peakpicker (aubio_peakpicker_t * p) +{ + del_aubio_filter (p->biquad); + del_fvec (p->onset_keep); + del_fvec (p->onset_proc); + del_fvec (p->onset_peek); + del_fvec (p->thresholded); + del_fvec (p->scratch); + AUBIO_FREE (p); +} diff --git a/dependencies/aubio/src/onset/peakpicker.h b/dependencies/aubio/src/onset/peakpicker.h new file mode 100644 index 0000000000..dafaf9154b --- /dev/null +++ b/dependencies/aubio/src/onset/peakpicker.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Peak picking utilities function + + \example onset/test-peakpicker.c + +*/ + +#ifndef AUBIO_PEAKPICK_H +#define AUBIO_PEAKPICK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** peak-picker structure */ +typedef struct _aubio_peakpicker_t aubio_peakpicker_t; + +/** peak-picker creation function */ +aubio_peakpicker_t * new_aubio_peakpicker(void); +/** real time peak picking function */ +void aubio_peakpicker_do(aubio_peakpicker_t * p, fvec_t * in, fvec_t * out); +/** destroy peak picker structure */ +void del_aubio_peakpicker(aubio_peakpicker_t * p); + +/** get current peak value */ +fvec_t *aubio_peakpicker_get_thresholded_input (aubio_peakpicker_t * p); +/** set peak picking threshold */ +uint_t aubio_peakpicker_set_threshold(aubio_peakpicker_t * p, smpl_t threshold); +/** get peak picking threshold */ +smpl_t aubio_peakpicker_get_threshold(aubio_peakpicker_t * p); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_PEAKPICK_H */ diff --git a/dependencies/aubio/src/spectral/awhitening.c b/dependencies/aubio/src/spectral/awhitening.c new file mode 100644 index 0000000000..1543544ca8 --- /dev/null +++ b/dependencies/aubio/src/spectral/awhitening.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2015 Paul Brossier + * + * This file is part of aubio. + * + * aubio 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. + * + * aubio 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 + * aubio. If not, see . + * + */ + +#include "aubio_priv.h" +#include "fvec.h" +#include "cvec.h" +#include "mathutils.h" +#include "spectral/awhitening.h" + +#define aubio_spectral_whitening_default_relax_time 250 // in seconds, between 22 and 446 +#define aubio_spectral_whitening_default_decay 0.001 // -60dB attenuation +#define aubio_spectral_whitening_default_floor 1.e-4 // from 1.e-6 to .2 + +/** structure to store object state */ +struct _aubio_spectral_whitening_t { + uint_t buf_size; + uint_t hop_size; + uint_t samplerate; + smpl_t relax_time; + smpl_t r_decay; + smpl_t floor; + fvec_t *peak_values; +}; + +void +aubio_spectral_whitening_do (aubio_spectral_whitening_t * o, cvec_t * fftgrain) +{ + uint_t i = 0; + uint_t length = MIN(fftgrain->length, o->peak_values->length); + for (i = 0; i < length; i++) { + smpl_t tmp = MAX(o->r_decay * o->peak_values->data[i], o->floor); + o->peak_values->data[i] = MAX(fftgrain->norm[i], tmp); + fftgrain->norm[i] /= o->peak_values->data[i]; + } +} + +aubio_spectral_whitening_t * +new_aubio_spectral_whitening (uint_t buf_size, uint_t hop_size, uint_t samplerate) +{ + aubio_spectral_whitening_t *o = AUBIO_NEW (aubio_spectral_whitening_t); + if ((sint_t)buf_size < 1) { + AUBIO_ERR("spectral_whitening: got buffer_size %d, but can not be < 1\n", buf_size); + goto beach; + } else if ((sint_t)hop_size < 1) { + AUBIO_ERR("spectral_whitening: got hop_size %d, but can not be < 1\n", hop_size); + goto beach; + } else if ((sint_t)samplerate < 1) { + AUBIO_ERR("spectral_whitening: got samplerate %d, but can not be < 1\n", samplerate); + goto beach; + } + o->peak_values = new_fvec (buf_size / 2 + 1); + o->buf_size = buf_size; + o->hop_size = hop_size; + o->samplerate = samplerate; + o->floor = aubio_spectral_whitening_default_floor; + aubio_spectral_whitening_set_relax_time (o, aubio_spectral_whitening_default_relax_time); + aubio_spectral_whitening_reset (o); + return o; + +beach: + AUBIO_FREE(o); + return NULL; +} + +uint_t +aubio_spectral_whitening_set_relax_time (aubio_spectral_whitening_t * o, smpl_t relax_time) +{ + o->relax_time = relax_time; + o->r_decay = POW (aubio_spectral_whitening_default_decay, + (o->hop_size / (float) o->samplerate) / o->relax_time); + return AUBIO_OK; +} + +smpl_t +aubio_spectral_whitening_get_relax_time (aubio_spectral_whitening_t * o) +{ + return o->relax_time; +} + +uint_t +aubio_spectral_whitening_set_floor (aubio_spectral_whitening_t *o, smpl_t floor) +{ + o->floor = floor; + return AUBIO_OK; +} + +smpl_t aubio_spectral_whitening_get_floor (aubio_spectral_whitening_t *o) +{ + return o->floor; +} + +void +aubio_spectral_whitening_reset (aubio_spectral_whitening_t * o) +{ + /* cover the case n == 0. */ + fvec_set_all (o->peak_values, o->floor); +} + +void +del_aubio_spectral_whitening (aubio_spectral_whitening_t * o) +{ + del_fvec (o->peak_values); + AUBIO_FREE (o); +} diff --git a/dependencies/aubio/src/spectral/awhitening.h b/dependencies/aubio/src/spectral/awhitening.h new file mode 100644 index 0000000000..64150e7f18 --- /dev/null +++ b/dependencies/aubio/src/spectral/awhitening.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Spectral adaptive whitening + + References: + + D. Stowell and M. D. Plumbley. Adaptive whitening for improved real-time + audio onset detection. In Proceedings of the International Computer Music + Conference (ICMC), 2007, Copenhagen, Denmark. + + http://www.eecs.qmul.ac.uk/~markp/2007/StowellPlumbley07-icmc.pdf + + S. Böck,, F. Krebs, and M. Schedl. Evaluating the Online Capabilities of + Onset Detection Methods. In Proceedings of the 13th International Society for + Music Information Retrieval Conference (ISMIR), 2012, Porto, Portugal. + + http://ismir2012.ismir.net/event/papers/049_ISMIR_2012.pdf + http://www.cp.jku.at/research/papers/Boeck_etal_ISMIR_2012.pdf + +*/ + + +#ifndef _AUBIO_SPECTRAL_WHITENING_H +#define _AUBIO_SPECTRAL_WHITENING_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** spectral whitening structure */ +typedef struct _aubio_spectral_whitening_t aubio_spectral_whitening_t; + +/** execute spectral adaptive whitening, in-place + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + \param fftgrain input signal spectrum as computed by aubio_pvoc_do() or aubio_fft_do() + +*/ +void aubio_spectral_whitening_do (aubio_spectral_whitening_t * o, + cvec_t * fftgrain); + +/** creation of a spectral whitening object + + \param buf_size window size of input grains + \param hop_size number of samples between two consecutive input grains + \param samplerate sampling rate of the input signal + +*/ +aubio_spectral_whitening_t *new_aubio_spectral_whitening (uint_t buf_size, + uint_t hop_size, + uint_t samplerate); + +/** reset spectral whitening object + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + + */ +void aubio_spectral_whitening_reset (aubio_spectral_whitening_t * o); + +/** set relaxation time for spectral whitening + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + \param relax_time relaxation time in seconds between 20 and 500, defaults 250 + + */ +uint_t aubio_spectral_whitening_set_relax_time (aubio_spectral_whitening_t * o, + smpl_t relax_time); + +/** get relaxation time of spectral whitening + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + \return relaxation time in seconds + +*/ +smpl_t aubio_spectral_whitening_get_relax_time (aubio_spectral_whitening_t * o); + +/** set floor for spectral whitening + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + \param floor value (typically between 1.e-6 and .2, defaults to 1.e-4) + + */ +uint_t aubio_spectral_whitening_set_floor (aubio_spectral_whitening_t * o, + smpl_t floor); + +/** get floor of spectral whitening + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + \return floor value + +*/ +smpl_t aubio_spectral_whitening_get_floor (aubio_spectral_whitening_t * o); + +/** deletion of a spectral whitening + + \param o spectral whitening object as returned by new_aubio_spectral_whitening() + +*/ +void del_aubio_spectral_whitening (aubio_spectral_whitening_t * o); + +#ifdef __cplusplus +} +#endif + +#endif /* _AUBIO_SPECTRAL_WHITENING_H */ diff --git a/dependencies/aubio/src/spectral/fft.c b/dependencies/aubio/src/spectral/fft.c new file mode 100644 index 0000000000..598fe22dfa --- /dev/null +++ b/dependencies/aubio/src/spectral/fft.c @@ -0,0 +1,572 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "cvec.h" +#include "mathutils.h" +#include "spectral/fft.h" + +#ifdef HAVE_FFTW3 // using FFTW3 +/* note that is not included here but only in aubio_priv.h, so that + * c++ projects can still use their own complex definition. */ +#include +#include + +#ifdef HAVE_COMPLEX_H +#ifdef HAVE_FFTW3F +/** fft data type with complex.h and fftw3f */ +#define FFTW_TYPE fftwf_complex +#else +/** fft data type with complex.h and fftw3 */ +#define FFTW_TYPE fftw_complex +#endif +#else +#ifdef HAVE_FFTW3F +/** fft data type without complex.h and with fftw3f */ +#define FFTW_TYPE float +#else +/** fft data type without complex.h and with fftw */ +#define FFTW_TYPE double +#endif +#endif + +/** fft data type */ +typedef FFTW_TYPE fft_data_t; + +#ifdef HAVE_FFTW3F +#define fftw_malloc fftwf_malloc +#define fftw_free fftwf_free +#define fftw_execute fftwf_execute +#define fftw_plan_dft_r2c_1d fftwf_plan_dft_r2c_1d +#define fftw_plan_dft_c2r_1d fftwf_plan_dft_c2r_1d +#define fftw_plan_r2r_1d fftwf_plan_r2r_1d +#define fftw_plan fftwf_plan +#define fftw_destroy_plan fftwf_destroy_plan +#endif + +#ifdef HAVE_FFTW3F +#if HAVE_AUBIO_DOUBLE +#error "Using aubio in double precision with fftw3 in single precision" +#endif /* HAVE_AUBIO_DOUBLE */ +#define real_t float +#elif defined (HAVE_FFTW3) /* HAVE_FFTW3F */ +#if !HAVE_AUBIO_DOUBLE +#error "Using aubio in single precision with fftw3 in double precision" +#endif /* HAVE_AUBIO_DOUBLE */ +#define real_t double +#endif /* HAVE_FFTW3F */ + +// a global mutex for FFTW thread safety +pthread_mutex_t aubio_fftw_mutex = PTHREAD_MUTEX_INITIALIZER; + +#elif defined HAVE_ACCELERATE // using ACCELERATE +// https://developer.apple.com/library/mac/#documentation/Accelerate/Reference/vDSPRef/Reference/reference.html +#include + +#if !HAVE_AUBIO_DOUBLE +#define aubio_vDSP_ctoz vDSP_ctoz +#define aubio_vDSP_fft_zrip vDSP_fft_zrip +#define aubio_vDSP_ztoc vDSP_ztoc +#define aubio_vDSP_zvmags vDSP_zvmags +#define aubio_vDSP_zvphas vDSP_zvphas +#define aubio_vDSP_vsadd vDSP_vsadd +#define aubio_vDSP_vsmul vDSP_vsmul +#define aubio_DSPComplex DSPComplex +#define aubio_DSPSplitComplex DSPSplitComplex +#define aubio_vDSP_DFT_Setup vDSP_DFT_Setup +#define aubio_vDSP_DFT_zrop_CreateSetup vDSP_DFT_zrop_CreateSetup +#define aubio_vDSP_DFT_Execute vDSP_DFT_Execute +#define aubio_vDSP_DFT_DestroySetup vDSP_DFT_DestroySetup +#define aubio_vvsqrt vvsqrtf +#else +#define aubio_vDSP_ctoz vDSP_ctozD +#define aubio_vDSP_fft_zrip vDSP_fft_zripD +#define aubio_vDSP_ztoc vDSP_ztocD +#define aubio_vDSP_zvmags vDSP_zvmagsD +#define aubio_vDSP_zvphas vDSP_zvphasD +#define aubio_vDSP_vsadd vDSP_vsaddD +#define aubio_vDSP_vsmul vDSP_vsmulD +#define aubio_DSPComplex DSPDoubleComplex +#define aubio_DSPSplitComplex DSPDoubleSplitComplex +#define aubio_vDSP_DFT_Setup vDSP_DFT_SetupD +#define aubio_vDSP_DFT_zrop_CreateSetup vDSP_DFT_zrop_CreateSetupD +#define aubio_vDSP_DFT_Execute vDSP_DFT_ExecuteD +#define aubio_vDSP_DFT_DestroySetup vDSP_DFT_DestroySetupD +#define aubio_vvsqrt vvsqrt +#endif /* HAVE_AUBIO_DOUBLE */ + +#elif defined HAVE_INTEL_IPP // using INTEL IPP + +#if !HAVE_AUBIO_DOUBLE +#define aubio_IppFloat Ipp32f +#define aubio_IppComplex Ipp32fc +#define aubio_FFTSpec FFTSpec_R_32f +#define aubio_ippsMalloc_complex ippsMalloc_32fc +#define aubio_ippsFFTInit_R ippsFFTInit_R_32f +#define aubio_ippsFFTGetSize_R ippsFFTGetSize_R_32f +#define aubio_ippsFFTInv_CCSToR ippsFFTInv_CCSToR_32f +#define aubio_ippsFFTFwd_RToCCS ippsFFTFwd_RToCCS_32f +#define aubio_ippsAtan2 ippsAtan2_32f_A21 +#else /* HAVE_AUBIO_DOUBLE */ +#define aubio_IppFloat Ipp64f +#define aubio_IppComplex Ipp64fc +#define aubio_FFTSpec FFTSpec_R_64f +#define aubio_ippsMalloc_complex ippsMalloc_64fc +#define aubio_ippsFFTInit_R ippsFFTInit_R_64f +#define aubio_ippsFFTGetSize_R ippsFFTGetSize_R_64f +#define aubio_ippsFFTInv_CCSToR ippsFFTInv_CCSToR_64f +#define aubio_ippsFFTFwd_RToCCS ippsFFTFwd_RToCCS_64f +#define aubio_ippsAtan2 ippsAtan2_64f_A50 +#endif + + +#else // using OOURA +// let's use ooura instead +extern void aubio_ooura_rdft(int, int, smpl_t *, int *, smpl_t *); + +#endif + +struct _aubio_fft_t { + uint_t winsize; + uint_t fft_size; + +#ifdef HAVE_FFTW3 // using FFTW3 + real_t *in, *out; + fftw_plan pfw, pbw; + fft_data_t * specdata; /* complex spectral data */ + +#elif defined HAVE_ACCELERATE // using ACCELERATE + aubio_vDSP_DFT_Setup fftSetupFwd; + aubio_vDSP_DFT_Setup fftSetupBwd; + aubio_DSPSplitComplex spec; + smpl_t *in, *out; + +#elif defined HAVE_INTEL_IPP // using Intel IPP + smpl_t *in, *out; + Ipp8u* memSpec; + Ipp8u* memInit; + Ipp8u* memBuffer; + struct aubio_FFTSpec* fftSpec; + aubio_IppComplex* complexOut; +#else // using OOURA + smpl_t *in, *out; + smpl_t *w; + int *ip; +#endif /* using OOURA */ + + fvec_t * compspec; +}; + +aubio_fft_t * new_aubio_fft (uint_t winsize) { + aubio_fft_t * s = AUBIO_NEW(aubio_fft_t); + if ((sint_t)winsize < 2) { + AUBIO_ERR("fft: got winsize %d, but can not be < 2\n", winsize); + goto beach; + } + +#ifdef HAVE_FFTW3 + uint_t i; + s->winsize = winsize; + /* allocate memory */ + s->in = AUBIO_ARRAY(real_t,winsize); + s->out = AUBIO_ARRAY(real_t,winsize); + s->compspec = new_fvec(winsize); + /* create plans */ + pthread_mutex_lock(&aubio_fftw_mutex); +#ifdef HAVE_COMPLEX_H + s->fft_size = winsize/2 + 1; + s->specdata = (fft_data_t*)fftw_malloc(sizeof(fft_data_t)*s->fft_size); + s->pfw = fftw_plan_dft_r2c_1d(winsize, s->in, s->specdata, FFTW_ESTIMATE); + s->pbw = fftw_plan_dft_c2r_1d(winsize, s->specdata, s->out, FFTW_ESTIMATE); +#else + s->fft_size = winsize; + s->specdata = (fft_data_t*)fftw_malloc(sizeof(fft_data_t)*s->fft_size); + s->pfw = fftw_plan_r2r_1d(winsize, s->in, s->specdata, FFTW_R2HC, FFTW_ESTIMATE); + s->pbw = fftw_plan_r2r_1d(winsize, s->specdata, s->out, FFTW_HC2R, FFTW_ESTIMATE); +#endif + pthread_mutex_unlock(&aubio_fftw_mutex); + for (i = 0; i < s->winsize; i++) { + s->in[i] = 0.; + s->out[i] = 0.; + } + for (i = 0; i < s->fft_size; i++) { + s->specdata[i] = 0.; + } + +#elif defined HAVE_ACCELERATE // using ACCELERATE + { + uint_t radix = winsize; + uint_t order = 0; + while ((radix / 2) * 2 == radix) { + radix /= 2; + order++; + } + if (order < 4 || (radix != 1 && radix != 3 && radix != 5 && radix != 15)) { + AUBIO_ERR("fft: vDSP/Accelerate supports FFT with sizes = " + "f * 2 ** n, where n > 4 and f in [1, 3, 5, 15], but requested %d. " + "Use the closest power of two, or try recompiling aubio with " + "--enable-fftw3.\n", winsize); + goto beach; + } + } + s->winsize = winsize; + s->fft_size = winsize; + s->compspec = new_fvec(winsize); + s->in = AUBIO_ARRAY(smpl_t, s->fft_size); + s->out = AUBIO_ARRAY(smpl_t, s->fft_size); + s->spec.realp = AUBIO_ARRAY(smpl_t, s->fft_size/2); + s->spec.imagp = AUBIO_ARRAY(smpl_t, s->fft_size/2); + s->fftSetupFwd = aubio_vDSP_DFT_zrop_CreateSetup(NULL, + s->fft_size, vDSP_DFT_FORWARD); + s->fftSetupBwd = aubio_vDSP_DFT_zrop_CreateSetup(s->fftSetupFwd, + s->fft_size, vDSP_DFT_INVERSE); + +#elif defined HAVE_INTEL_IPP // using Intel IPP + const IppHintAlgorithm qualityHint = ippAlgHintAccurate; // OR ippAlgHintFast; + const int flags = IPP_FFT_NODIV_BY_ANY; // we're scaling manually afterwards + int order = aubio_power_of_two_order(winsize); + int sizeSpec, sizeInit, sizeBuffer; + IppStatus status; + + if (winsize <= 4 || aubio_is_power_of_two(winsize) != 1) + { + AUBIO_ERR("intel IPP fft: can only create with sizes > 4 and power of two, requested %d," + " try recompiling aubio with --enable-fftw3\n", winsize); + goto beach; + } + + status = aubio_ippsFFTGetSize_R(order, flags, qualityHint, + &sizeSpec, &sizeInit, &sizeBuffer); + if (status != ippStsNoErr) { + AUBIO_ERR("fft: failed to initialize fft. IPP error: %d\n", status); + goto beach; + } + s->fft_size = s->winsize = winsize; + s->compspec = new_fvec(winsize); + s->in = AUBIO_ARRAY(smpl_t, s->winsize); + s->out = AUBIO_ARRAY(smpl_t, s->winsize); + s->memSpec = ippsMalloc_8u(sizeSpec); + s->memBuffer = ippsMalloc_8u(sizeBuffer); + if (sizeInit > 0 ) { + s->memInit = ippsMalloc_8u(sizeInit); + } + s->complexOut = aubio_ippsMalloc_complex(s->fft_size / 2 + 1); + status = aubio_ippsFFTInit_R( + &s->fftSpec, order, flags, qualityHint, s->memSpec, s->memInit); + if (status != ippStsNoErr) { + AUBIO_ERR("fft: failed to initialize. IPP error: %d\n", status); + goto beach; + } + +#else // using OOURA + if (aubio_is_power_of_two(winsize) != 1) { + AUBIO_ERR("fft: can only create with sizes power of two, requested %d," + " try recompiling aubio with --enable-fftw3\n", winsize); + goto beach; + } + s->winsize = winsize; + s->fft_size = winsize / 2 + 1; + s->compspec = new_fvec(winsize); + s->in = AUBIO_ARRAY(smpl_t, s->winsize); + s->out = AUBIO_ARRAY(smpl_t, s->winsize); + s->ip = AUBIO_ARRAY(int , s->fft_size); + s->w = AUBIO_ARRAY(smpl_t, s->fft_size); + s->ip[0] = 0; +#endif /* using OOURA */ + + return s; + +beach: + AUBIO_FREE(s); + return NULL; +} + +void del_aubio_fft(aubio_fft_t * s) { + /* destroy data */ +#ifdef HAVE_FFTW3 // using FFTW3 + pthread_mutex_lock(&aubio_fftw_mutex); + fftw_destroy_plan(s->pfw); + fftw_destroy_plan(s->pbw); + fftw_free(s->specdata); + pthread_mutex_unlock(&aubio_fftw_mutex); + +#elif defined HAVE_ACCELERATE // using ACCELERATE + AUBIO_FREE(s->spec.realp); + AUBIO_FREE(s->spec.imagp); + aubio_vDSP_DFT_DestroySetup(s->fftSetupBwd); + aubio_vDSP_DFT_DestroySetup(s->fftSetupFwd); + +#elif defined HAVE_INTEL_IPP // using Intel IPP + ippFree(s->memSpec); + ippFree(s->memInit); + ippFree(s->memBuffer); + ippFree(s->complexOut); + +#else // using OOURA + AUBIO_FREE(s->w); + AUBIO_FREE(s->ip); +#endif + + del_fvec(s->compspec); + AUBIO_FREE(s->in); + AUBIO_FREE(s->out); + AUBIO_FREE(s); +} + +void aubio_fft_do(aubio_fft_t * s, const fvec_t * input, cvec_t * spectrum) { + aubio_fft_do_complex(s, input, s->compspec); + aubio_fft_get_spectrum(s->compspec, spectrum); +} + +void aubio_fft_rdo(aubio_fft_t * s, const cvec_t * spectrum, fvec_t * output) { + aubio_fft_get_realimag(spectrum, s->compspec); + aubio_fft_rdo_complex(s, s->compspec, output); +} + +void aubio_fft_do_complex(aubio_fft_t * s, const fvec_t * input, fvec_t * compspec) { + uint_t i; +#ifndef HAVE_MEMCPY_HACKS + for (i=0; i < s->winsize; i++) { + s->in[i] = input->data[i]; + } +#else + memcpy(s->in, input->data, s->winsize * sizeof(smpl_t)); +#endif /* HAVE_MEMCPY_HACKS */ + +#ifdef HAVE_FFTW3 // using FFTW3 + fftw_execute(s->pfw); +#ifdef HAVE_COMPLEX_H + compspec->data[0] = REAL(s->specdata[0]); + for (i = 1; i < s->fft_size -1 ; i++) { + compspec->data[i] = REAL(s->specdata[i]); + compspec->data[compspec->length - i] = IMAG(s->specdata[i]); + } + compspec->data[s->fft_size-1] = REAL(s->specdata[s->fft_size-1]); +#else /* HAVE_COMPLEX_H */ + for (i = 0; i < s->fft_size; i++) { + compspec->data[i] = s->specdata[i]; + } +#endif /* HAVE_COMPLEX_H */ + +#elif defined HAVE_ACCELERATE // using ACCELERATE + // convert real data to even/odd format used in vDSP + aubio_vDSP_ctoz((aubio_DSPComplex*)s->in, 2, &s->spec, 1, s->fft_size/2); + // compute the FFT + aubio_vDSP_DFT_Execute(s->fftSetupFwd, s->spec.realp, s->spec.imagp, + s->spec.realp, s->spec.imagp); + // convert from vDSP complex split to [ r0, r1, ..., rN, iN-1, .., i2, i1] + compspec->data[0] = s->spec.realp[0]; + compspec->data[s->fft_size / 2] = s->spec.imagp[0]; + for (i = 1; i < s->fft_size / 2; i++) { + compspec->data[i] = s->spec.realp[i]; + compspec->data[s->fft_size - i] = s->spec.imagp[i]; + } + // apply scaling + smpl_t scale = 1./2.; + aubio_vDSP_vsmul(compspec->data, 1, &scale, compspec->data, 1, s->fft_size); + +#elif defined HAVE_INTEL_IPP // using Intel IPP + + // apply fft + aubio_ippsFFTFwd_RToCCS(s->in, (aubio_IppFloat*)s->complexOut, s->fftSpec, s->memBuffer); + // convert complex buffer to [ r0, r1, ..., rN, iN-1, .., i2, i1] + compspec->data[0] = s->complexOut[0].re; + compspec->data[s->fft_size / 2] = s->complexOut[s->fft_size / 2].re; + for (i = 1; i < s->fft_size / 2; i++) { + compspec->data[i] = s->complexOut[i].re; + compspec->data[s->fft_size - i] = s->complexOut[i].im; + } + +#else // using OOURA + aubio_ooura_rdft(s->winsize, 1, s->in, s->ip, s->w); + compspec->data[0] = s->in[0]; + compspec->data[s->winsize / 2] = s->in[1]; + for (i = 1; i < s->fft_size - 1; i++) { + compspec->data[i] = s->in[2 * i]; + compspec->data[s->winsize - i] = - s->in[2 * i + 1]; + } +#endif /* using OOURA */ +} + +void aubio_fft_rdo_complex(aubio_fft_t * s, const fvec_t * compspec, fvec_t * output) { + uint_t i; +#ifdef HAVE_FFTW3 + const smpl_t renorm = 1./(smpl_t)s->winsize; +#ifdef HAVE_COMPLEX_H + s->specdata[0] = compspec->data[0]; + for (i=1; i < s->fft_size - 1; i++) { + s->specdata[i] = compspec->data[i] + + I * compspec->data[compspec->length - i]; + } + s->specdata[s->fft_size - 1] = compspec->data[s->fft_size - 1]; +#else + for (i=0; i < s->fft_size; i++) { + s->specdata[i] = compspec->data[i]; + } +#endif + fftw_execute(s->pbw); + for (i = 0; i < output->length; i++) { + output->data[i] = s->out[i]*renorm; + } + +#elif defined HAVE_ACCELERATE // using ACCELERATE + // convert from real imag [ r0, r1, ..., rN, iN-1, .., i2, i1] + // to vDSP packed format [ r0, rN, r1, i1, ..., rN-1, iN-1 ] + s->out[0] = compspec->data[0]; + s->out[1] = compspec->data[s->winsize / 2]; + for (i = 1; i < s->fft_size / 2; i++) { + s->out[2 * i] = compspec->data[i]; + s->out[2 * i + 1] = compspec->data[s->winsize - i]; + } + // convert to split complex format used in vDSP + aubio_vDSP_ctoz((aubio_DSPComplex*)s->out, 2, &s->spec, 1, s->fft_size/2); + // compute the FFT + aubio_vDSP_DFT_Execute(s->fftSetupBwd, s->spec.realp, s->spec.imagp, + s->spec.realp, s->spec.imagp); + // convert result to real output + aubio_vDSP_ztoc(&s->spec, 1, (aubio_DSPComplex*)output->data, 2, s->fft_size/2); + // apply scaling + smpl_t scale = 1.0 / s->winsize; + aubio_vDSP_vsmul(output->data, 1, &scale, output->data, 1, s->fft_size); + +#elif defined HAVE_INTEL_IPP // using Intel IPP + + // convert from real imag [ r0, 0, ..., rN, iN-1, .., i2, i1, iN-1] to complex format + s->complexOut[0].re = compspec->data[0]; + s->complexOut[0].im = 0; + s->complexOut[s->fft_size / 2].re = compspec->data[s->fft_size / 2]; + s->complexOut[s->fft_size / 2].im = 0.0; + for (i = 1; i < s->fft_size / 2; i++) { + s->complexOut[i].re = compspec->data[i]; + s->complexOut[i].im = compspec->data[s->fft_size - i]; + } + // apply fft + aubio_ippsFFTInv_CCSToR((const aubio_IppFloat *)s->complexOut, output->data, s->fftSpec, s->memBuffer); + // apply scaling + aubio_ippsMulC(output->data, 1.0 / s->winsize, output->data, s->fft_size); + +#else // using OOURA + smpl_t scale = 2.0 / s->winsize; + s->out[0] = compspec->data[0]; + s->out[1] = compspec->data[s->winsize / 2]; + for (i = 1; i < s->fft_size - 1; i++) { + s->out[2 * i] = compspec->data[i]; + s->out[2 * i + 1] = - compspec->data[s->winsize - i]; + } + aubio_ooura_rdft(s->winsize, -1, s->out, s->ip, s->w); + for (i=0; i < s->winsize; i++) { + output->data[i] = s->out[i] * scale; + } +#endif +} + +void aubio_fft_get_spectrum(const fvec_t * compspec, cvec_t * spectrum) { + aubio_fft_get_phas(compspec, spectrum); + aubio_fft_get_norm(compspec, spectrum); +} + +void aubio_fft_get_realimag(const cvec_t * spectrum, fvec_t * compspec) { + aubio_fft_get_imag(spectrum, compspec); + aubio_fft_get_real(spectrum, compspec); +} + +void aubio_fft_get_phas(const fvec_t * compspec, cvec_t * spectrum) { + uint_t i; + if (compspec->data[0] < 0) { + spectrum->phas[0] = PI; + } else { + spectrum->phas[0] = 0.; + } +#if defined(HAVE_INTEL_IPP) + // convert from real imag [ r0, r1, ..., rN, iN-1, ..., i2, i1, i0] + // to [ r0, r1, ..., rN, i0, i1, i2, ..., iN-1] + for (i = 1; i < spectrum->length / 2; i++) { + ELEM_SWAP(compspec->data[compspec->length - i], + compspec->data[spectrum->length + i - 1]); + } + aubio_ippsAtan2(compspec->data + spectrum->length, + compspec->data + 1, spectrum->phas + 1, spectrum->length - 1); + // revert the imaginary part back again + for (i = 1; i < spectrum->length / 2; i++) { + ELEM_SWAP(compspec->data[spectrum->length + i - 1], + compspec->data[compspec->length - i]); + } +#else + for (i=1; i < spectrum->length - 1; i++) { + spectrum->phas[i] = ATAN2(compspec->data[compspec->length-i], + compspec->data[i]); + } +#endif +#ifdef HAVE_FFTW3 + // for even length only, make sure last element is 0 or PI + if (2 * (compspec->length / 2) == compspec->length) { +#endif + if (compspec->data[compspec->length/2] < 0) { + spectrum->phas[spectrum->length - 1] = PI; + } else { + spectrum->phas[spectrum->length - 1] = 0.; + } +#ifdef HAVE_FFTW3 + } else { + i = spectrum->length - 1; + spectrum->phas[i] = ATAN2(compspec->data[compspec->length-i], + compspec->data[i]); + } +#endif +} + +void aubio_fft_get_norm(const fvec_t * compspec, cvec_t * spectrum) { + uint_t i = 0; + spectrum->norm[0] = ABS(compspec->data[0]); + for (i=1; i < spectrum->length - 1; i++) { + spectrum->norm[i] = SQRT(SQR(compspec->data[i]) + + SQR(compspec->data[compspec->length - i]) ); + } +#ifdef HAVE_FFTW3 + // for even length, make sure last element is > 0 + if (2 * (compspec->length / 2) == compspec->length) { +#endif + spectrum->norm[spectrum->length-1] = + ABS(compspec->data[compspec->length/2]); +#ifdef HAVE_FFTW3 + } else { + i = spectrum->length - 1; + spectrum->norm[i] = SQRT(SQR(compspec->data[i]) + + SQR(compspec->data[compspec->length - i]) ); + } +#endif +} + +void aubio_fft_get_imag(const cvec_t * spectrum, fvec_t * compspec) { + uint_t i; + for (i = 1; i < ( compspec->length + 1 ) / 2 /*- 1 + 1*/; i++) { + compspec->data[compspec->length - i] = + spectrum->norm[i]*SIN(spectrum->phas[i]); + } +} + +void aubio_fft_get_real(const cvec_t * spectrum, fvec_t * compspec) { + uint_t i; + for (i = 0; i < compspec->length / 2 + 1; i++) { + compspec->data[i] = + spectrum->norm[i]*COS(spectrum->phas[i]); + } +} diff --git a/dependencies/aubio/src/spectral/fft.h b/dependencies/aubio/src/spectral/fft.h new file mode 100644 index 0000000000..21072c8b44 --- /dev/null +++ b/dependencies/aubio/src/spectral/fft.h @@ -0,0 +1,144 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Fast Fourier Transform + + Depending on how aubio was compiled, FFT are computed using one of: + - [Ooura](http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html) + - [FFTW3](http://www.fftw.org) + - [vDSP](https://developer.apple.com/library/mac/#documentation/Accelerate/Reference/vDSPRef/Reference/reference.html) + + \example spectral/test-fft.c + +*/ + +#ifndef AUBIO_FFT_H +#define AUBIO_FFT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** FFT object + + This object computes forward and backward FFTs. + +*/ +typedef struct _aubio_fft_t aubio_fft_t; + +/** create new FFT computation object + + \param size length of the FFT + +*/ +aubio_fft_t * new_aubio_fft (uint_t size); +/** delete FFT object + + \param s fft object as returned by new_aubio_fft + +*/ +void del_aubio_fft(aubio_fft_t * s); + +/** compute forward FFT + + \param s fft object as returned by new_aubio_fft + \param input input signal + \param spectrum output spectrum + +*/ +void aubio_fft_do (aubio_fft_t *s, const fvec_t * input, cvec_t * spectrum); +/** compute backward (inverse) FFT + + \param s fft object as returned by new_aubio_fft + \param spectrum input spectrum + \param output output signal + +*/ +void aubio_fft_rdo (aubio_fft_t *s, const cvec_t * spectrum, fvec_t * output); + +/** compute forward FFT + + \param s fft object as returned by new_aubio_fft + \param input real input signal + \param compspec complex output fft real/imag + +*/ +void aubio_fft_do_complex (aubio_fft_t *s, const fvec_t * input, fvec_t * compspec); +/** compute backward (inverse) FFT from real/imag + + \param s fft object as returned by new_aubio_fft + \param compspec real/imag input fft array + \param output real output array + +*/ +void aubio_fft_rdo_complex (aubio_fft_t *s, const fvec_t * compspec, fvec_t * output); + +/** convert real/imag spectrum to norm/phas spectrum + + \param compspec real/imag input fft array + \param spectrum cvec norm/phas output array + +*/ +void aubio_fft_get_spectrum(const fvec_t * compspec, cvec_t * spectrum); +/** convert real/imag spectrum to norm/phas spectrum + + \param compspec real/imag input fft array + \param spectrum cvec norm/phas output array + +*/ +void aubio_fft_get_realimag(const cvec_t * spectrum, fvec_t * compspec); + +/** compute phas spectrum from real/imag parts + + \param compspec real/imag input fft array + \param spectrum cvec norm/phas output array + +*/ +void aubio_fft_get_phas(const fvec_t * compspec, cvec_t * spectrum); +/** compute imaginary part from the norm/phas cvec + + \param spectrum norm/phas input array + \param compspec real/imag output fft array + +*/ +void aubio_fft_get_imag(const cvec_t * spectrum, fvec_t * compspec); + +/** compute norm component from real/imag parts + + \param compspec real/imag input fft array + \param spectrum cvec norm/phas output array + +*/ +void aubio_fft_get_norm(const fvec_t * compspec, cvec_t * spectrum); +/** compute real part from norm/phas components + + \param spectrum norm/phas input array + \param compspec real/imag output fft array + +*/ +void aubio_fft_get_real(const cvec_t * spectrum, fvec_t * compspec); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_FFT_H */ diff --git a/dependencies/aubio/src/spectral/ooura_fft8g.c b/dependencies/aubio/src/spectral/ooura_fft8g.c new file mode 100644 index 0000000000..394bea084c --- /dev/null +++ b/dependencies/aubio/src/spectral/ooura_fft8g.c @@ -0,0 +1,1672 @@ +// modifications made for aubio: +// - replace all 'double' with 'smpl_t' +// - include "aubio_priv.h" (for config.h and types.h) +// - add missing prototypes +// - use COS, SIN, and ATAN macros +// - add cast to (smpl_t) to avoid float conversion warnings +// - declare initialization as static +// - prefix public function with aubio_ooura_ + +#include "aubio_priv.h" + +void aubio_ooura_cdft(int n, int isgn, smpl_t *a, int *ip, smpl_t *w); +void aubio_ooura_rdft(int n, int isgn, smpl_t *a, int *ip, smpl_t *w); +void aubio_ooura_ddct(int n, int isgn, smpl_t *a, int *ip, smpl_t *w); +void aubio_ooura_ddst(int n, int isgn, smpl_t *a, int *ip, smpl_t *w); +void aubio_ooura_dfct(int n, smpl_t *a, smpl_t *t, int *ip, smpl_t *w); +void aubio_ooura_dfst(int n, smpl_t *a, smpl_t *t, int *ip, smpl_t *w); +static void makewt(int nw, int *ip, smpl_t *w); +static void makect(int nc, int *ip, smpl_t *c); +static void bitrv2(int n, int *ip, smpl_t *a); +static void bitrv2conj(int n, int *ip, smpl_t *a); +static void cftfsub(int n, smpl_t *a, smpl_t *w); +static void cftbsub(int n, smpl_t *a, smpl_t *w); +static void cft1st(int n, smpl_t *a, smpl_t *w); +static void cftmdl(int n, int l, smpl_t *a, smpl_t *w); +static void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); +static void rftbsub(int n, smpl_t *a, int nc, smpl_t *c); +static void dctsub(int n, smpl_t *a, int nc, smpl_t *c); +static void dstsub(int n, smpl_t *a, int nc, smpl_t *c); + +/* +Fast Fourier/Cosine/Sine Transform + dimension :one + data length :power of 2 + decimation :frequency + radix :8, 4, 2 + data :inplace + table :use +functions + cdft: Complex Discrete Fourier Transform + rdft: Real Discrete Fourier Transform + ddct: Discrete Cosine Transform + ddst: Discrete Sine Transform + dfct: Cosine Transform of RDFT (Real Symmetric DFT) + dfst: Sine Transform of RDFT (Real Anti-symmetric DFT) +function prototypes + void cdft(int, int, smpl_t *, int *, smpl_t *); + void rdft(int, int, smpl_t *, int *, smpl_t *); + void ddct(int, int, smpl_t *, int *, smpl_t *); + void ddst(int, int, smpl_t *, int *, smpl_t *); + void dfct(int, smpl_t *, smpl_t *, int *, smpl_t *); + void dfst(int, smpl_t *, smpl_t *, int *, smpl_t *); + + +-------- Complex DFT (Discrete Fourier Transform) -------- + [definition] + + X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k + X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k + ip[0] = 0; // first time only + cdft(2*n, 1, a, ip, w); + + ip[0] = 0; // first time only + cdft(2*n, -1, a, ip, w); + [parameters] + 2*n :data length (int) + n >= 1, n = power of 2 + a[0...2*n-1] :input/output data (smpl_t *) + input data + a[2*j] = Re(x[j]), + a[2*j+1] = Im(x[j]), 0<=j= 2+sqrt(n) + strictly, + length of ip >= + 2+(1<<(int)(log(n+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + cdft(2*n, -1, a, ip, w); + is + cdft(2*n, 1, a, ip, w); + for (j = 0; j <= 2 * n - 1; j++) { + a[j] *= 1.0 / n; + } + . + + +-------- Real DFT / Inverse of Real DFT -------- + [definition] + RDFT + R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 + I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0 IRDFT (excluding scale) + a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + + sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + + sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k + ip[0] = 0; // first time only + rdft(n, 1, a, ip, w); + + ip[0] = 0; // first time only + rdft(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (smpl_t *) + + output data + a[2*k] = R[k], 0<=k + input data + a[2*j] = R[j], 0<=j= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + rdft(n, 1, a, ip, w); + is + rdft(n, -1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DCT (Discrete Cosine Transform) / Inverse of DCT -------- + [definition] + IDCT (excluding scale) + C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k DCT + C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k + ip[0] = 0; // first time only + ddct(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddct(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (smpl_t *) + output data + a[k] = C[k], 0<=k= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddct(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddct(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DST (Discrete Sine Transform) / Inverse of DST -------- + [definition] + IDST (excluding scale) + S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k DST + S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0 + ip[0] = 0; // first time only + ddst(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddst(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (smpl_t *) + + input data + a[j] = A[j], 0 + output data + a[k] = S[k], 0= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddst(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddst(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Cosine Transform of RDFT (Real Symmetric DFT) -------- + [definition] + C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n + [usage] + ip[0] = 0; // first time only + dfct(n, a, t, ip, w); + [parameters] + n :data length - 1 (int) + n >= 2, n = power of 2 + a[0...n] :input/output data (smpl_t *) + output data + a[k] = C[k], 0<=k<=n + t[0...n/2] :work area (smpl_t *) + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + is + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + for (j = 0; j <= n; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Sine Transform of RDFT (Real Anti-symmetric DFT) -------- + [definition] + S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0= 2, n = power of 2 + a[0...n-1] :input/output data (smpl_t *) + output data + a[k] = S[k], 0= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (smpl_t *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + dfst(n, a, t, ip, w); + is + dfst(n, a, t, ip, w); + for (j = 1; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +Appendix : + The cos/sin table is recalculated when the larger table required. + w[] and ip[] are compatible with all routines. +*/ + + +void aubio_ooura_cdft(int n, int isgn, smpl_t *a, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void bitrv2(int n, int *ip, smpl_t *a); + void bitrv2conj(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void cftbsub(int n, smpl_t *a, smpl_t *w); + + if (n > (ip[0] << 2)) { + makewt(n >> 2, ip, w); + } + if (n > 4) { + if (isgn >= 0) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + } else { + bitrv2conj(n, ip + 2, a); + cftbsub(n, a, w); + } + } else if (n == 4) { + cftfsub(n, a, w); + } +} + + +void aubio_ooura_rdft(int n, int isgn, smpl_t *a, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void makect(int nc, int *ip, smpl_t *c); + void bitrv2(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void cftbsub(int n, smpl_t *a, smpl_t *w); + void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); + void rftbsub(int n, smpl_t *a, int nc, smpl_t *c); + int nw, nc; + smpl_t xi; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 2)) { + nc = n >> 2; + makect(nc, ip, w + nw); + } + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } else { + a[1] = (smpl_t)0.5 * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } +} + + +void aubio_ooura_ddct(int n, int isgn, smpl_t *a, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void makect(int nc, int *ip, smpl_t *c); + void bitrv2(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void cftbsub(int n, smpl_t *a, smpl_t *w); + void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); + void rftbsub(int n, smpl_t *a, int nc, smpl_t *c); + void dctsub(int n, smpl_t *a, int nc, smpl_t *c); + int j, nw, nc; + smpl_t xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = a[j] - a[j - 1]; + a[j] += a[j - 1]; + } + a[1] = a[0] - xr; + a[0] += xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } + dctsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = a[j] - a[j + 1]; + a[j] += a[j + 1]; + } + a[n - 1] = xr; + } +} + + +void aubio_ooura_ddst(int n, int isgn, smpl_t *a, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void makect(int nc, int *ip, smpl_t *c); + void bitrv2(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void cftbsub(int n, smpl_t *a, smpl_t *w); + void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); + void rftbsub(int n, smpl_t *a, int nc, smpl_t *c); + void dstsub(int n, smpl_t *a, int nc, smpl_t *c); + int j, nw, nc; + smpl_t xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = -a[j] - a[j - 1]; + a[j] -= a[j - 1]; + } + a[1] = a[0] + xr; + a[0] -= xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } + dstsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = -a[j] - a[j + 1]; + a[j] -= a[j + 1]; + } + a[n - 1] = -xr; + } +} + + +void aubio_ooura_dfct(int n, smpl_t *a, smpl_t *t, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void makect(int nc, int *ip, smpl_t *c); + void bitrv2(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); + void dctsub(int n, smpl_t *a, int nc, smpl_t *c); + int j, k, l, m, mh, nw, nc; + smpl_t xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + m = n >> 1; + yi = a[m]; + xi = a[0] + a[n]; + a[0] -= a[n]; + t[0] = xi - yi; + t[m] = xi + yi; + if (n > 2) { + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] - a[n - j]; + xi = a[j] + a[n - j]; + yr = a[k] - a[n - k]; + yi = a[k] + a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi - yi; + t[k] = xi + yi; + } + t[mh] = a[mh] + a[n - mh]; + a[mh] -= a[n - mh]; + dctsub(m, a, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, a); + cftfsub(m, a, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, w); + } + a[n - 1] = a[0] - a[1]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] + a[j + 1]; + a[2 * j - 1] = a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dctsub(m, t, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, t); + cftfsub(m, t, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, w); + } + a[n - l] = t[0] - t[1]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = t[j] - t[j + 1]; + a[k + l] = t[j] + t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 0; j < mh; j++) { + k = m - j; + t[j] = t[m + k] - t[m + j]; + t[k] = t[m + k] + t[m + j]; + } + t[mh] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + a[n] = t[2] - t[1]; + a[0] = t[2] + t[1]; + } else { + a[1] = a[0]; + a[2] = t[0]; + a[0] = t[1]; + } +} + + +void aubio_ooura_dfst(int n, smpl_t *a, smpl_t *t, int *ip, smpl_t *w) +{ + void makewt(int nw, int *ip, smpl_t *w); + void makect(int nc, int *ip, smpl_t *c); + void bitrv2(int n, int *ip, smpl_t *a); + void cftfsub(int n, smpl_t *a, smpl_t *w); + void rftfsub(int n, smpl_t *a, int nc, smpl_t *c); + void dstsub(int n, smpl_t *a, int nc, smpl_t *c); + int j, k, l, m, mh, nw, nc; + smpl_t xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + if (n > 2) { + m = n >> 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] + a[n - j]; + xi = a[j] - a[n - j]; + yr = a[k] + a[n - k]; + yi = a[k] - a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi + yi; + t[k] = xi - yi; + } + t[0] = a[mh] - a[n - mh]; + a[mh] += a[n - mh]; + a[0] = a[m]; + dstsub(m, a, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, a); + cftfsub(m, a, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, w); + } + a[n - 1] = a[1] - a[0]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] - a[j + 1]; + a[2 * j - 1] = -a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dstsub(m, t, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, t); + cftfsub(m, t, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, w); + } + a[n - l] = t[1] - t[0]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = -t[j] - t[j + 1]; + a[k + l] = t[j] - t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + t[j] = t[m + k] + t[m + j]; + t[k] = t[m + k] - t[m + j]; + } + t[0] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + } + a[0] = 0; +} + + +/* -------- initializing routines -------- */ + + +#include + +void makewt(int nw, int *ip, smpl_t *w) +{ + void bitrv2(int n, int *ip, smpl_t *a); + int j, nwh; + smpl_t delta, x, y; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) { + nwh = nw >> 1; + delta = ATAN(1.0) / nwh; + w[0] = 1; + w[1] = 0; + w[nwh] = COS(delta * nwh); + w[nwh + 1] = w[nwh]; + if (nwh > 2) { + for (j = 2; j < nwh; j += 2) { + x = COS(delta * j); + y = SIN(delta * j); + w[j] = x; + w[j + 1] = y; + w[nw - j] = y; + w[nw - j + 1] = x; + } + for (j = nwh - 2; j >= 2; j -= 2) { + x = w[2 * j]; + y = w[2 * j + 1]; + w[nwh + j] = x; + w[nwh + j + 1] = y; + } + bitrv2(nw, ip + 2, w); + } + } +} + + +void makect(int nc, int *ip, smpl_t *c) +{ + int j, nch; + smpl_t delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = ATAN(1.0) / nch; + c[0] = COS(delta * nch); + c[nch] = (smpl_t)0.5 * c[0]; + for (j = 1; j < nch; j++) { + c[j] = (smpl_t)0.5 * COS(delta * j); + c[nc - j] = (smpl_t)0.5 * SIN(delta * j); + } + } +} + + +/* -------- child routines -------- */ + + +void bitrv2(int n, int *ip, smpl_t *a) +{ + int j, j1, k, k1, l, m, m2; + smpl_t xr, xi, yr, yi; + + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + j1 = 2 * k + m2 + ip[k]; + k1 = j1 + m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } + } +} + + +void bitrv2conj(int n, int *ip, smpl_t *a) +{ + int j, j1, k, k1, l, m, m2; + smpl_t xr, xi, yr, yi; + + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + j1 = k1 + m2; + k1 = j1 + m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + k1 += m2; + a[k1 + 1] = -a[k1 + 1]; + } + } else { + a[1] = -a[1]; + a[m2 + 1] = -a[m2 + 1]; + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + a[k1 + m2 + 1] = -a[k1 + m2 + 1]; + } + } +} + + +void cftfsub(int n, smpl_t *a, smpl_t *w) +{ + void cft1st(int n, smpl_t *a, smpl_t *w); + void cftmdl(int n, int l, smpl_t *a, smpl_t *w); + int j, j1, j2, j3, l; + smpl_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n >= 16) { + cft1st(n, a, w); + l = 16; + while ((l << 3) <= n) { + cftmdl(n, l, a, w); + l <<= 3; + } + } + if ((l << 1) < n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + } else if ((l << 1) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = a[j + 1] - a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] += a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + + +void cftbsub(int n, smpl_t *a, smpl_t *w) +{ + void cft1st(int n, smpl_t *a, smpl_t *w); + void cftmdl(int n, int l, smpl_t *a, smpl_t *w); + int j, j1, j2, j3, j4, j5, j6, j7, l; + smpl_t wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + l = 2; + if (n > 16) { + cft1st(n, a, w); + l = 16; + while ((l << 3) < n) { + cftmdl(n, l, a, w); + l <<= 3; + } + } + if ((l << 2) < n) { + wn4r = w[2]; + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + j4 = j3 + l; + j5 = j4 + l; + j6 = j5 + l; + j7 = j6 + l; + x0r = a[j] + a[j1]; + x0i = -a[j + 1] - a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = -a[j + 1] + a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + y0r = x0r + x2r; + y0i = x0i - x2i; + y2r = x0r - x2r; + y2i = x0i + x2i; + y1r = x1r - x3i; + y1i = x1i - x3r; + y3r = x1r + x3i; + y3i = x1i + x3r; + x0r = a[j4] + a[j5]; + x0i = a[j4 + 1] + a[j5 + 1]; + x1r = a[j4] - a[j5]; + x1i = a[j4 + 1] - a[j5 + 1]; + x2r = a[j6] + a[j7]; + x2i = a[j6 + 1] + a[j7 + 1]; + x3r = a[j6] - a[j7]; + x3i = a[j6 + 1] - a[j7 + 1]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[j1] = y1r + y5r; + a[j1 + 1] = y1i - y5i; + a[j5] = y1r - y5r; + a[j5 + 1] = y1i + y5i; + a[j3] = y3r - y7i; + a[j3 + 1] = y3i - y7r; + a[j7] = y3r + y7i; + a[j7 + 1] = y3i + y7r; + a[j] = y0r + y4r; + a[j + 1] = y0i - y4i; + a[j4] = y0r - y4r; + a[j4 + 1] = y0i + y4i; + a[j2] = y2r - y6i; + a[j2 + 1] = y2i - y6r; + a[j6] = y2r + y6i; + a[j6 + 1] = y2i + y6r; + } + } else if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = -a[j + 1] - a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = -a[j + 1] + a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i + x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i - x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i + x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = -a[j + 1] + a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] = -a[j + 1] - a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + + +void cft1st(int n, smpl_t *a, smpl_t *w) +{ + int j, k1; + smpl_t wn4r, wtmp, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + wk4r, wk4i, wk5r, wk5i, wk6r, wk6i, wk7r, wk7i; + smpl_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = w[2]; + x0r = a[0] + a[2]; + x0i = a[1] + a[3]; + x1r = a[0] - a[2]; + x1i = a[1] - a[3]; + x2r = a[4] + a[6]; + x2i = a[5] + a[7]; + x3r = a[4] - a[6]; + x3i = a[5] - a[7]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[8] + a[10]; + x0i = a[9] + a[11]; + x1r = a[8] - a[10]; + x1i = a[9] - a[11]; + x2r = a[12] + a[14]; + x2i = a[13] + a[15]; + x3r = a[12] - a[14]; + x3i = a[13] - a[15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[2] = y1r + y5r; + a[3] = y1i + y5i; + a[10] = y1r - y5r; + a[11] = y1i - y5i; + a[6] = y3r - y7i; + a[7] = y3i + y7r; + a[14] = y3r + y7i; + a[15] = y3i - y7r; + a[0] = y0r + y4r; + a[1] = y0i + y4i; + a[8] = y0r - y4r; + a[9] = y0i - y4i; + a[4] = y2r - y6i; + a[5] = y2i + y6r; + a[12] = y2r + y6i; + a[13] = y2i - y6r; + if (n > 16) { + wk1r = w[4]; + wk1i = w[5]; + x0r = a[16] + a[18]; + x0i = a[17] + a[19]; + x1r = a[16] - a[18]; + x1i = a[17] - a[19]; + x2r = a[20] + a[22]; + x2i = a[21] + a[23]; + x3r = a[20] - a[22]; + x3i = a[21] - a[23]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[24] + a[26]; + x0i = a[25] + a[27]; + x1r = a[24] - a[26]; + x1i = a[25] - a[27]; + x2r = a[28] + a[30]; + x2i = a[29] + a[31]; + x3r = a[28] - a[30]; + x3i = a[29] - a[31]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x3r - x1i; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + y7r = wk1r * x2r + wk1i * x2i; + y7i = wk1r * x2i - wk1i * x2r; + x0r = wk1r * y1r - wk1i * y1i; + x0i = wk1r * y1i + wk1i * y1r; + a[18] = x0r + y5r; + a[19] = x0i + y5i; + a[26] = y5i - x0i; + a[27] = x0r - y5r; + x0r = wk1i * y3r - wk1r * y3i; + x0i = wk1i * y3i + wk1r * y3r; + a[22] = x0r - y7r; + a[23] = x0i + y7i; + a[30] = y7i - x0i; + a[31] = x0r + y7r; + a[16] = y0r + y4r; + a[17] = y0i + y4i; + a[24] = y4i - y0i; + a[25] = y0r - y4r; + x0r = y2r - y6i; + x0i = y2i + y6r; + a[20] = wn4r * (x0r - x0i); + a[21] = wn4r * (x0i + x0r); + x0r = y6r - y2i; + x0i = y2r + y6i; + a[28] = wn4r * (x0r - x0i); + a[29] = wn4r * (x0i + x0r); + k1 = 4; + for (j = 32; j < n; j += 16) { + k1 += 4; + wk1r = w[k1]; + wk1i = w[k1 + 1]; + wk2r = w[k1 + 2]; + wk2i = w[k1 + 3]; + wtmp = 2 * wk2i; + wk3r = wk1r - wtmp * wk1i; + wk3i = wtmp * wk1r - wk1i; + wk4r = 1 - wtmp * wk2i; + wk4i = wtmp * wk2r; + wtmp = 2 * wk4i; + wk5r = wk3r - wtmp * wk1i; + wk5i = wtmp * wk1r - wk3i; + wk6r = wk2r - wtmp * wk2i; + wk6i = wtmp * wk2r - wk2i; + wk7r = wk1r - wtmp * wk3i; + wk7i = wtmp * wk3r - wk1i; + x0r = a[j] + a[j + 2]; + x0i = a[j + 1] + a[j + 3]; + x1r = a[j] - a[j + 2]; + x1i = a[j + 1] - a[j + 3]; + x2r = a[j + 4] + a[j + 6]; + x2i = a[j + 5] + a[j + 7]; + x3r = a[j + 4] - a[j + 6]; + x3i = a[j + 5] - a[j + 7]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[j + 8] + a[j + 10]; + x0i = a[j + 9] + a[j + 11]; + x1r = a[j + 8] - a[j + 10]; + x1i = a[j + 9] - a[j + 11]; + x2r = a[j + 12] + a[j + 14]; + x2i = a[j + 13] + a[j + 15]; + x3r = a[j + 12] - a[j + 14]; + x3i = a[j + 13] - a[j + 15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + x0r = y1r + y5r; + x0i = y1i + y5i; + a[j + 2] = wk1r * x0r - wk1i * x0i; + a[j + 3] = wk1r * x0i + wk1i * x0r; + x0r = y1r - y5r; + x0i = y1i - y5i; + a[j + 10] = wk5r * x0r - wk5i * x0i; + a[j + 11] = wk5r * x0i + wk5i * x0r; + x0r = y3r - y7i; + x0i = y3i + y7r; + a[j + 6] = wk3r * x0r - wk3i * x0i; + a[j + 7] = wk3r * x0i + wk3i * x0r; + x0r = y3r + y7i; + x0i = y3i - y7r; + a[j + 14] = wk7r * x0r - wk7i * x0i; + a[j + 15] = wk7r * x0i + wk7i * x0r; + a[j] = y0r + y4r; + a[j + 1] = y0i + y4i; + x0r = y0r - y4r; + x0i = y0i - y4i; + a[j + 8] = wk4r * x0r - wk4i * x0i; + a[j + 9] = wk4r * x0i + wk4i * x0r; + x0r = y2r - y6i; + x0i = y2i + y6r; + a[j + 4] = wk2r * x0r - wk2i * x0i; + a[j + 5] = wk2r * x0i + wk2i * x0r; + x0r = y2r + y6i; + x0i = y2i - y6r; + a[j + 12] = wk6r * x0r - wk6i * x0i; + a[j + 13] = wk6r * x0i + wk6i * x0r; + } + } +} + + +void cftmdl(int n, int l, smpl_t *a, smpl_t *w) +{ + int j, j1, j2, j3, j4, j5, j6, j7, k, k1, m; + smpl_t wn4r, wtmp, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + wk4r, wk4i, wk5r, wk5i, wk6r, wk6i, wk7r, wk7i; + smpl_t x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + m = l << 3; + wn4r = w[2]; + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + j4 = j3 + l; + j5 = j4 + l; + j6 = j5 + l; + j7 = j6 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[j4] + a[j5]; + x0i = a[j4 + 1] + a[j5 + 1]; + x1r = a[j4] - a[j5]; + x1i = a[j4 + 1] - a[j5 + 1]; + x2r = a[j6] + a[j7]; + x2i = a[j6 + 1] + a[j7 + 1]; + x3r = a[j6] - a[j7]; + x3i = a[j6 + 1] - a[j7 + 1]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[j1] = y1r + y5r; + a[j1 + 1] = y1i + y5i; + a[j5] = y1r - y5r; + a[j5 + 1] = y1i - y5i; + a[j3] = y3r - y7i; + a[j3 + 1] = y3i + y7r; + a[j7] = y3r + y7i; + a[j7 + 1] = y3i - y7r; + a[j] = y0r + y4r; + a[j + 1] = y0i + y4i; + a[j4] = y0r - y4r; + a[j4 + 1] = y0i - y4i; + a[j2] = y2r - y6i; + a[j2 + 1] = y2i + y6r; + a[j6] = y2r + y6i; + a[j6 + 1] = y2i - y6r; + } + if (m < n) { + wk1r = w[4]; + wk1i = w[5]; + for (j = m; j < l + m; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + j4 = j3 + l; + j5 = j4 + l; + j6 = j5 + l; + j7 = j6 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[j4] + a[j5]; + x0i = a[j4 + 1] + a[j5 + 1]; + x1r = a[j4] - a[j5]; + x1i = a[j4 + 1] - a[j5 + 1]; + x2r = a[j6] + a[j7]; + x2i = a[j6 + 1] + a[j7 + 1]; + x3r = a[j6] - a[j7]; + x3i = a[j6 + 1] - a[j7 + 1]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x3r - x1i; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + y7r = wk1r * x2r + wk1i * x2i; + y7i = wk1r * x2i - wk1i * x2r; + x0r = wk1r * y1r - wk1i * y1i; + x0i = wk1r * y1i + wk1i * y1r; + a[j1] = x0r + y5r; + a[j1 + 1] = x0i + y5i; + a[j5] = y5i - x0i; + a[j5 + 1] = x0r - y5r; + x0r = wk1i * y3r - wk1r * y3i; + x0i = wk1i * y3i + wk1r * y3r; + a[j3] = x0r - y7r; + a[j3 + 1] = x0i + y7i; + a[j7] = y7i - x0i; + a[j7 + 1] = x0r + y7r; + a[j] = y0r + y4r; + a[j + 1] = y0i + y4i; + a[j4] = y4i - y0i; + a[j4 + 1] = y0r - y4r; + x0r = y2r - y6i; + x0i = y2i + y6r; + a[j2] = wn4r * (x0r - x0i); + a[j2 + 1] = wn4r * (x0i + x0r); + x0r = y6r - y2i; + x0i = y2r + y6i; + a[j6] = wn4r * (x0r - x0i); + a[j6 + 1] = wn4r * (x0i + x0r); + } + k1 = 4; + for (k = 2 * m; k < n; k += m) { + k1 += 4; + wk1r = w[k1]; + wk1i = w[k1 + 1]; + wk2r = w[k1 + 2]; + wk2i = w[k1 + 3]; + wtmp = 2 * wk2i; + wk3r = wk1r - wtmp * wk1i; + wk3i = wtmp * wk1r - wk1i; + wk4r = 1 - wtmp * wk2i; + wk4i = wtmp * wk2r; + wtmp = 2 * wk4i; + wk5r = wk3r - wtmp * wk1i; + wk5i = wtmp * wk1r - wk3i; + wk6r = wk2r - wtmp * wk2i; + wk6i = wtmp * wk2r - wk2i; + wk7r = wk1r - wtmp * wk3i; + wk7i = wtmp * wk3r - wk1i; + for (j = k; j < l + k; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + j4 = j3 + l; + j5 = j4 + l; + j6 = j5 + l; + j7 = j6 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[j4] + a[j5]; + x0i = a[j4 + 1] + a[j5 + 1]; + x1r = a[j4] - a[j5]; + x1i = a[j4 + 1] - a[j5 + 1]; + x2r = a[j6] + a[j7]; + x2i = a[j6 + 1] + a[j7 + 1]; + x3r = a[j6] - a[j7]; + x3i = a[j6 + 1] - a[j7 + 1]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + x0r = y1r + y5r; + x0i = y1i + y5i; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = y1r - y5r; + x0i = y1i - y5i; + a[j5] = wk5r * x0r - wk5i * x0i; + a[j5 + 1] = wk5r * x0i + wk5i * x0r; + x0r = y3r - y7i; + x0i = y3i + y7r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + x0r = y3r + y7i; + x0i = y3i - y7r; + a[j7] = wk7r * x0r - wk7i * x0i; + a[j7 + 1] = wk7r * x0i + wk7i * x0r; + a[j] = y0r + y4r; + a[j + 1] = y0i + y4i; + x0r = y0r - y4r; + x0i = y0i - y4i; + a[j4] = wk4r * x0r - wk4i * x0i; + a[j4 + 1] = wk4r * x0i + wk4i * x0r; + x0r = y2r - y6i; + x0i = y2i + y6r; + a[j2] = wk2r * x0r - wk2i * x0i; + a[j2 + 1] = wk2r * x0i + wk2i * x0r; + x0r = y2r + y6i; + x0i = y2i - y6r; + a[j6] = wk6r * x0r - wk6i * x0i; + a[j6 + 1] = wk6r * x0i + wk6i * x0r; + } + } + } +} + + +void rftfsub(int n, smpl_t *a, int nc, smpl_t *c) +{ + int j, k, kk, ks, m; + smpl_t wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = (smpl_t)0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + + +void rftbsub(int n, smpl_t *a, int nc, smpl_t *c) +{ + int j, k, kk, ks, m; + smpl_t wkr, wki, xr, xi, yr, yi; + + a[1] = -a[1]; + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = (smpl_t)0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] = yi - a[j + 1]; + a[k] += yr; + a[k + 1] = yi - a[k + 1]; + } + a[m + 1] = -a[m + 1]; +} + + +void dctsub(int n, smpl_t *a, int nc, smpl_t *c) +{ + int j, k, kk, ks, m; + smpl_t wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[j] - wkr * a[k]; + a[j] = wkr * a[j] + wki * a[k]; + a[k] = xr; + } + a[m] *= c[0]; +} + + +void dstsub(int n, smpl_t *a, int nc, smpl_t *c) +{ + int j, k, kk, ks, m; + smpl_t wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[k] - wkr * a[j]; + a[k] = wkr * a[k] + wki * a[j]; + a[j] = xr; + } + a[m] *= c[0]; +} + diff --git a/dependencies/aubio/src/spectral/phasevoc.c b/dependencies/aubio/src/spectral/phasevoc.c new file mode 100644 index 0000000000..05ebdb0635 --- /dev/null +++ b/dependencies/aubio/src/spectral/phasevoc.c @@ -0,0 +1,224 @@ +/* + Copyright (C) 2003-2014 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "cvec.h" +#include "mathutils.h" +#include "spectral/fft.h" +#include "spectral/phasevoc.h" + +/** phasevocoder internal object */ +struct _aubio_pvoc_t { + uint_t win_s; /** grain length */ + uint_t hop_s; /** overlap step */ + aubio_fft_t * fft; /** fft object */ + fvec_t * data; /** current input grain, [win_s] frames */ + fvec_t * dataold; /** memory of past grain, [win_s-hop_s] frames */ + fvec_t * synth; /** current output grain, [win_s] frames */ + fvec_t * synthold; /** memory of past grain, [win_s-hop_s] frames */ + fvec_t * w; /** grain window [win_s] */ + uint_t start; /** where to start additive synthesis */ + uint_t end; /** where to end it */ + smpl_t scale; /** scaling factor for synthesis */ + uint_t end_datasize; /** size of memory to end */ + uint_t hop_datasize; /** size of memory to hop_s */ +}; + + +/** returns data and dataold slided by hop_s */ +static void aubio_pvoc_swapbuffers(aubio_pvoc_t *pv, const fvec_t *new); + +/** do additive synthesis from 'old' and 'cur' */ +static void aubio_pvoc_addsynth(aubio_pvoc_t *pv, fvec_t * synthnew); + +void aubio_pvoc_do(aubio_pvoc_t *pv, const fvec_t * datanew, cvec_t *fftgrain) { + /* slide */ + aubio_pvoc_swapbuffers(pv, datanew); + /* windowing */ + fvec_weight(pv->data, pv->w); + /* shift */ + fvec_shift(pv->data); + /* calculate fft */ + aubio_fft_do (pv->fft,pv->data,fftgrain); +} + +void aubio_pvoc_rdo(aubio_pvoc_t *pv,cvec_t * fftgrain, fvec_t * synthnew) { + /* calculate rfft */ + aubio_fft_rdo(pv->fft,fftgrain,pv->synth); + /* unshift */ + fvec_ishift(pv->synth); + /* windowing */ + // if overlap = 50%, do not apply window (identity) + if (pv->hop_s * 2 < pv->win_s) { + fvec_weight(pv->synth, pv->w); + } + /* additive synthesis */ + aubio_pvoc_addsynth(pv, synthnew); +} + +aubio_pvoc_t * new_aubio_pvoc (uint_t win_s, uint_t hop_s) { + aubio_pvoc_t * pv = AUBIO_NEW(aubio_pvoc_t); + + /* if (win_s < 2*hop_s) { + AUBIO_WRN("Hop size bigger than half the window size!\n"); + } */ + + if ((sint_t)hop_s < 1) { + AUBIO_ERR("pvoc: got hop_size %d, but can not be < 1\n", hop_s); + goto beach; + } else if ((sint_t)win_s < 2) { + AUBIO_ERR("pvoc: got buffer_size %d, but can not be < 2\n", win_s); + goto beach; + } else if (win_s < hop_s) { + AUBIO_ERR("pvoc: hop size (%d) is larger than win size (%d)\n", hop_s, win_s); + goto beach; + } + + pv->fft = new_aubio_fft (win_s); + if (pv->fft == NULL) { + goto beach; + } + + /* remember old */ + pv->data = new_fvec (win_s); + pv->synth = new_fvec (win_s); + + /* new input output */ + if (win_s > hop_s) { + pv->dataold = new_fvec (win_s-hop_s); + pv->synthold = new_fvec (win_s-hop_s); + } else { + pv->dataold = new_fvec (1); + pv->synthold = new_fvec (1); + } + pv->w = new_aubio_window ("hanningz", win_s); + + pv->hop_s = hop_s; + pv->win_s = win_s; + + /* more than 50% overlap, overlap anyway */ + if (win_s < 2 * hop_s) pv->start = 0; + /* less than 50% overlap, reset latest grain trail */ + else pv->start = win_s - hop_s - hop_s; + + if (win_s > hop_s) pv->end = win_s - hop_s; + else pv->end = 0; + + pv->end_datasize = pv->end * sizeof(smpl_t); + pv->hop_datasize = pv->hop_s * sizeof(smpl_t); + + // for reconstruction with 75% overlap + if (win_s == hop_s * 4) { + pv->scale = 2./3.; + } else if (win_s == hop_s * 8) { + pv->scale = 1./3.; + } else if (win_s == hop_s * 2) { + pv->scale = 1.; + } else { + pv->scale = .5; + } + + return pv; + +beach: + AUBIO_FREE (pv); + return NULL; +} + +uint_t aubio_pvoc_set_window(aubio_pvoc_t *pv, const char_t *window) { + return fvec_set_window(pv->w, (char_t*)window); +} + +void del_aubio_pvoc(aubio_pvoc_t *pv) { + del_fvec(pv->data); + del_fvec(pv->synth); + del_fvec(pv->dataold); + del_fvec(pv->synthold); + del_fvec(pv->w); + del_aubio_fft(pv->fft); + AUBIO_FREE(pv); +} + +static void aubio_pvoc_swapbuffers(aubio_pvoc_t *pv, const fvec_t *new) +{ + /* some convenience pointers */ + smpl_t * data = pv->data->data; + smpl_t * dataold = pv->dataold->data; + smpl_t * datanew = new->data; +#ifndef HAVE_MEMCPY_HACKS + uint_t i; + for (i = 0; i < pv->end; i++) + data[i] = dataold[i]; + for (i = 0; i < pv->hop_s; i++) + data[pv->end + i] = datanew[i]; + for (i = 0; i < pv->end; i++) + dataold[i] = data[i + pv->hop_s]; +#else + memcpy(data, dataold, pv->end_datasize); + data += pv->end; + memcpy(data, datanew, pv->hop_datasize); + data -= pv->end; + data += pv->hop_s; + memcpy(dataold, data, pv->end_datasize); +#endif +} + +static void aubio_pvoc_addsynth(aubio_pvoc_t *pv, fvec_t *synth_new) +{ + uint_t i; + /* some convenience pointers */ + smpl_t * synth = pv->synth->data; + smpl_t * synthold = pv->synthold->data; + smpl_t * synthnew = synth_new->data; + + /* put new result in synthnew */ + for (i = 0; i < pv->hop_s; i++) + synthnew[i] = synth[i] * pv->scale; + + /* no overlap, nothing else to do */ + if (pv->end == 0) return; + + /* add new synth to old one */ + for (i = 0; i < pv->hop_s; i++) + synthnew[i] += synthold[i]; + + /* shift synthold */ + for (i = 0; i < pv->start; i++) + synthold[i] = synthold[i + pv->hop_s]; + + /* erase last frame in synthold */ + for (i = pv->start; i < pv->end; i++) + synthold[i] = 0.; + + /* additive synth */ + for (i = 0; i < pv->end; i++) + synthold[i] += synth[i + pv->hop_s] * pv->scale; +} + +uint_t aubio_pvoc_get_win(aubio_pvoc_t* pv) +{ + return pv->win_s; +} + +uint_t aubio_pvoc_get_hop(aubio_pvoc_t* pv) +{ + return pv->hop_s; +} diff --git a/dependencies/aubio/src/spectral/phasevoc.h b/dependencies/aubio/src/spectral/phasevoc.h new file mode 100644 index 0000000000..e3caf2de57 --- /dev/null +++ b/dependencies/aubio/src/spectral/phasevoc.h @@ -0,0 +1,113 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Phase vocoder object + + This object implements a phase vocoder. The spectral frames are computed + using a HanningZ window and a swapped version of the signal to simplify the + phase relationships across frames. The window sizes and overlap are specified + at creation time. + + \example spectral/test-phasevoc.c + +*/ + +#ifndef AUBIO_PHASEVOC_H +#define AUBIO_PHASEVOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** phasevocoder object */ +typedef struct _aubio_pvoc_t aubio_pvoc_t; + +/** create phase vocoder object + + \param win_s size of analysis buffer (and length the FFT transform) + \param hop_s step size between two consecutive analysis + +*/ +aubio_pvoc_t * new_aubio_pvoc (uint_t win_s, uint_t hop_s); +/** delete phase vocoder object + + \param pv phase vocoder object as returned by new_aubio_pvoc + +*/ +void del_aubio_pvoc(aubio_pvoc_t *pv); + +/** compute spectral frame + + This function accepts an input vector of size [hop_s]. The + analysis buffer is rotated and filled with the new data. After windowing of + this signal window, the Fourier transform is computed and returned in + fftgrain as two vectors, magnitude and phase. + + \param pv phase vocoder object as returned by new_aubio_pvoc + \param in new input signal (hop_s long) + \param fftgrain output spectral frame + +*/ +void aubio_pvoc_do(aubio_pvoc_t *pv, const fvec_t *in, cvec_t * fftgrain); +/** compute signal from spectral frame + + This function takes an input spectral frame fftgrain of size + [buf_s] and computes its inverse Fourier transform. Overlap-add + synthesis is then computed using the previously synthetised frames, and the + output stored in out. + + \param pv phase vocoder object as returned by new_aubio_pvoc + \param fftgrain input spectral frame + \param out output signal (hop_s long) + +*/ +void aubio_pvoc_rdo(aubio_pvoc_t *pv, cvec_t * fftgrain, fvec_t *out); + +/** get window size + + \param pv phase vocoder to get the window size from + +*/ +uint_t aubio_pvoc_get_win(aubio_pvoc_t* pv); + +/** get hop size + + \param pv phase vocoder to get the hop size from + +*/ +uint_t aubio_pvoc_get_hop(aubio_pvoc_t* pv); + +/** set window type + + \param pv phase vocoder to set the window type + \param window_type a string representing a window + + \return 0 if successful, non-zero otherwise + + */ +uint_t aubio_pvoc_set_window(aubio_pvoc_t *pv, const char_t *window_type); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_PHASEVOC_H */ diff --git a/dependencies/aubio/src/spectral/specdesc.c b/dependencies/aubio/src/spectral/specdesc.c new file mode 100644 index 0000000000..cc1665a3ce --- /dev/null +++ b/dependencies/aubio/src/spectral/specdesc.c @@ -0,0 +1,429 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "cvec.h" +#include "spectral/fft.h" +#include "spectral/specdesc.h" +#include "mathutils.h" +#include "utils/hist.h" + +void aubio_specdesc_energy(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_hfc(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_complex(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_phase(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_wphase(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_specdiff(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_kl(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_mkl(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); +void aubio_specdesc_specflux(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset); + +extern void aubio_specdesc_centroid (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_spread (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_skewness (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_kurtosis (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_slope (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_decrease (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +extern void aubio_specdesc_rolloff (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); + +/** onsetdetection types */ +typedef enum { + aubio_onset_energy, /**< energy based */ + aubio_onset_specdiff, /**< spectral diff */ + aubio_onset_hfc, /**< high frequency content */ + aubio_onset_complex, /**< complex domain */ + aubio_onset_phase, /**< phase fast */ + aubio_onset_wphase, /**< weighted phase */ + aubio_onset_kl, /**< Kullback Liebler */ + aubio_onset_mkl, /**< modified Kullback Liebler */ + aubio_onset_specflux, /**< spectral flux */ + aubio_specmethod_centroid, /**< spectral centroid */ + aubio_specmethod_spread, /**< spectral spread */ + aubio_specmethod_skewness, /**< spectral skewness */ + aubio_specmethod_kurtosis, /**< spectral kurtosis */ + aubio_specmethod_slope, /**< spectral kurtosis */ + aubio_specmethod_decrease, /**< spectral decrease */ + aubio_specmethod_rolloff, /**< spectral rolloff */ + aubio_onset_default = aubio_onset_hfc, /**< default mode, set to hfc */ +} aubio_specdesc_type; + +/** structure to store object state */ +struct _aubio_specdesc_t { + aubio_specdesc_type onset_type; /**< onset detection type */ + /** Pointer to aubio_specdesc_ function */ + void (*funcpointer)(aubio_specdesc_t *o, + const cvec_t * fftgrain, fvec_t * onset); + smpl_t threshold; /**< minimum norm threshold for phase and specdiff */ + fvec_t *oldmag; /**< previous norm vector */ + fvec_t *dev1 ; /**< current onset detection measure vector */ + fvec_t *theta1; /**< previous phase vector, one frame behind */ + fvec_t *theta2; /**< previous phase vector, two frames behind */ + aubio_hist_t * histog; /**< histogram */ +}; + + +/* Energy based onset detection function */ +void aubio_specdesc_energy (aubio_specdesc_t *o UNUSED, + const cvec_t * fftgrain, fvec_t * onset) { + uint_t j; + onset->data[0] = 0.; + for (j=0;jlength;j++) { + onset->data[0] += SQR(fftgrain->norm[j]); + } +} + +/* High Frequency Content onset detection function */ +void aubio_specdesc_hfc(aubio_specdesc_t *o UNUSED, + const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + onset->data[0] = 0.; + for (j=0;jlength;j++) { + onset->data[0] += (j+1)*fftgrain->norm[j]; + } +} + + +/* Complex Domain Method onset detection function */ +void aubio_specdesc_complex (aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset) { + uint_t j; + uint_t nbins = fftgrain->length; + onset->data[0] = 0.; + for (j=0;jdev1->data[j] = 2. * o->theta1->data[j] - o->theta2->data[j]; + // compute the euclidean distance in the complex domain + // sqrt ( r_1^2 + r_2^2 - 2 * r_1 * r_2 * \cos ( \phi_1 - \phi_2 ) ) + onset->data[0] += + SQRT (ABS (SQR (o->oldmag->data[j]) + SQR (fftgrain->norm[j]) + - 2 * o->oldmag->data[j] * fftgrain->norm[j] + * COS (o->dev1->data[j] - fftgrain->phas[j]))); + /* swap old phase data (need to remember 2 frames behind)*/ + o->theta2->data[j] = o->theta1->data[j]; + o->theta1->data[j] = fftgrain->phas[j]; + /* swap old magnitude data (1 frame is enough) */ + o->oldmag->data[j] = fftgrain->norm[j]; + } +} + + +/* Phase Based Method onset detection function */ +void aubio_specdesc_phase(aubio_specdesc_t *o, + const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + uint_t nbins = fftgrain->length; + onset->data[0] = 0.0; + o->dev1->data[0]=0.; + for ( j=0;jdev1->data[j] = + aubio_unwrap2pi( + fftgrain->phas[j] + -2.0*o->theta1->data[j] + +o->theta2->data[j]); + if ( o->threshold < fftgrain->norm[j] ) + o->dev1->data[j] = ABS(o->dev1->data[j]); + else + o->dev1->data[j] = 0.0; + /* keep a track of the past frames */ + o->theta2->data[j] = o->theta1->data[j]; + o->theta1->data[j] = fftgrain->phas[j]; + } + /* apply o->histogram */ + aubio_hist_dyn_notnull(o->histog,o->dev1); + /* weight it */ + aubio_hist_weight(o->histog); + /* its mean is the result */ + onset->data[0] = aubio_hist_mean(o->histog); + //onset->data[0] = fvec_mean(o->dev1); +} + +/* weighted phase */ +void +aubio_specdesc_wphase(aubio_specdesc_t *o, + const cvec_t *fftgrain, fvec_t *onset) { + uint_t i; + aubio_specdesc_phase(o, fftgrain, onset); + for (i = 0; i < fftgrain->length; i++) { + o->dev1->data[i] *= fftgrain->norm[i]; + } + /* apply o->histogram */ + aubio_hist_dyn_notnull(o->histog,o->dev1); + /* weight it */ + aubio_hist_weight(o->histog); + /* its mean is the result */ + onset->data[0] = aubio_hist_mean(o->histog); +} + +/* Spectral difference method onset detection function */ +void aubio_specdesc_specdiff(aubio_specdesc_t *o, + const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + uint_t nbins = fftgrain->length; + onset->data[0] = 0.0; + for (j=0;jdev1->data[j] = SQRT( + ABS(SQR( fftgrain->norm[j]) + - SQR(o->oldmag->data[j]))); + if (o->threshold < fftgrain->norm[j] ) + o->dev1->data[j] = ABS(o->dev1->data[j]); + else + o->dev1->data[j] = 0.0; + o->oldmag->data[j] = fftgrain->norm[j]; + } + + /* apply o->histogram (act somewhat as a low pass on the + * overall function)*/ + aubio_hist_dyn_notnull(o->histog,o->dev1); + /* weight it */ + aubio_hist_weight(o->histog); + /* its mean is the result */ + onset->data[0] = aubio_hist_mean(o->histog); +} + +/* Kullback Liebler onset detection function + * note we use ln(1+Xn/(Xn-1+0.0001)) to avoid + * negative (1.+) and infinite values (+1.e-10) */ +void aubio_specdesc_kl(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + onset->data[0] = 0.; + for (j=0;jlength;j++) { + onset->data[0] += fftgrain->norm[j] + *LOG(1.+fftgrain->norm[j]/(o->oldmag->data[j]+1.e-1)); + o->oldmag->data[j] = fftgrain->norm[j]; + } + if (isnan(onset->data[0])) onset->data[0] = 0.; +} + +/* Modified Kullback Liebler onset detection function + * note we use ln(1+Xn/(Xn-1+0.0001)) to avoid + * negative (1.+) and infinite values (+1.e-10) */ +void aubio_specdesc_mkl(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + onset->data[0] = 0.; + for (j=0;jlength;j++) { + onset->data[0] += LOG(1.+fftgrain->norm[j]/(o->oldmag->data[j]+1.e-1)); + o->oldmag->data[j] = fftgrain->norm[j]; + } + if (isnan(onset->data[0])) onset->data[0] = 0.; +} + +/* Spectral flux */ +void aubio_specdesc_specflux(aubio_specdesc_t *o, const cvec_t * fftgrain, fvec_t * onset){ + uint_t j; + onset->data[0] = 0.; + for (j=0;jlength;j++) { + if (fftgrain->norm[j] > o->oldmag->data[j]) + onset->data[0] += fftgrain->norm[j] - o->oldmag->data[j]; + o->oldmag->data[j] = fftgrain->norm[j]; + } +} + +/* Generic function pointing to the choosen one */ +void +aubio_specdesc_do (aubio_specdesc_t *o, const cvec_t * fftgrain, + fvec_t * onset) { + o->funcpointer(o,fftgrain,onset); +} + +/* Allocate memory for an onset detection + * depending on the choosen type, allocate memory as needed + */ +aubio_specdesc_t * +new_aubio_specdesc (const char_t * onset_mode, uint_t size){ + aubio_specdesc_t * o = AUBIO_NEW(aubio_specdesc_t); + uint_t rsize = size/2+1; + aubio_specdesc_type onset_type; + if (strcmp (onset_mode, "energy") == 0) + onset_type = aubio_onset_energy; + else if (strcmp (onset_mode, "specdiff") == 0) + onset_type = aubio_onset_specdiff; + else if (strcmp (onset_mode, "hfc") == 0) + onset_type = aubio_onset_hfc; + else if (strcmp (onset_mode, "complexdomain") == 0) + onset_type = aubio_onset_complex; + else if (strcmp (onset_mode, "complex") == 0) + onset_type = aubio_onset_complex; + else if (strcmp (onset_mode, "phase") == 0) + onset_type = aubio_onset_phase; + else if (strcmp (onset_mode, "wphase") == 0) + onset_type = aubio_onset_wphase; + else if (strcmp (onset_mode, "mkl") == 0) + onset_type = aubio_onset_mkl; + else if (strcmp (onset_mode, "kl") == 0) + onset_type = aubio_onset_kl; + else if (strcmp (onset_mode, "specflux") == 0) + onset_type = aubio_onset_specflux; + else if (strcmp (onset_mode, "centroid") == 0) + onset_type = aubio_specmethod_centroid; + else if (strcmp (onset_mode, "spread") == 0) + onset_type = aubio_specmethod_spread; + else if (strcmp (onset_mode, "skewness") == 0) + onset_type = aubio_specmethod_skewness; + else if (strcmp (onset_mode, "kurtosis") == 0) + onset_type = aubio_specmethod_kurtosis; + else if (strcmp (onset_mode, "slope") == 0) + onset_type = aubio_specmethod_slope; + else if (strcmp (onset_mode, "decrease") == 0) + onset_type = aubio_specmethod_decrease; + else if (strcmp (onset_mode, "rolloff") == 0) + onset_type = aubio_specmethod_rolloff; + else if (strcmp (onset_mode, "old_default") == 0) + onset_type = aubio_onset_default; + else if (strcmp (onset_mode, "default") == 0) + onset_type = aubio_onset_default; + else { + AUBIO_ERR("specdesc: unknown spectral descriptor type '%s'\n", + onset_mode); + AUBIO_FREE(o); + return NULL; + } + switch(onset_type) { + /* for both energy and hfc, only fftgrain->norm is required */ + case aubio_onset_energy: + break; + case aubio_onset_hfc: + break; + /* the other approaches will need some more memory spaces */ + case aubio_onset_complex: + o->oldmag = new_fvec(rsize); + o->dev1 = new_fvec(rsize); + o->theta1 = new_fvec(rsize); + o->theta2 = new_fvec(rsize); + break; + case aubio_onset_phase: + case aubio_onset_wphase: + o->dev1 = new_fvec(rsize); + o->theta1 = new_fvec(rsize); + o->theta2 = new_fvec(rsize); + o->histog = new_aubio_hist(0.0, PI, 10); + o->threshold = 0.1; + break; + case aubio_onset_specdiff: + o->oldmag = new_fvec(rsize); + o->dev1 = new_fvec(rsize); + o->histog = new_aubio_hist(0.0, PI, 10); + o->threshold = 0.1; + break; + case aubio_onset_kl: + case aubio_onset_mkl: + case aubio_onset_specflux: + o->oldmag = new_fvec(rsize); + break; + default: + break; + } + + switch(onset_type) { + case aubio_onset_energy: + o->funcpointer = aubio_specdesc_energy; + break; + case aubio_onset_hfc: + o->funcpointer = aubio_specdesc_hfc; + break; + case aubio_onset_complex: + o->funcpointer = aubio_specdesc_complex; + break; + case aubio_onset_phase: + o->funcpointer = aubio_specdesc_phase; + break; + case aubio_onset_wphase: + o->funcpointer = aubio_specdesc_wphase; + break; + case aubio_onset_specdiff: + o->funcpointer = aubio_specdesc_specdiff; + break; + case aubio_onset_kl: + o->funcpointer = aubio_specdesc_kl; + break; + case aubio_onset_mkl: + o->funcpointer = aubio_specdesc_mkl; + break; + case aubio_onset_specflux: + o->funcpointer = aubio_specdesc_specflux; + break; + case aubio_specmethod_centroid: + o->funcpointer = aubio_specdesc_centroid; + break; + case aubio_specmethod_spread: + o->funcpointer = aubio_specdesc_spread; + break; + case aubio_specmethod_skewness: + o->funcpointer = aubio_specdesc_skewness; + break; + case aubio_specmethod_kurtosis: + o->funcpointer = aubio_specdesc_kurtosis; + break; + case aubio_specmethod_slope: + o->funcpointer = aubio_specdesc_slope; + break; + case aubio_specmethod_decrease: + o->funcpointer = aubio_specdesc_decrease; + break; + case aubio_specmethod_rolloff: + o->funcpointer = aubio_specdesc_rolloff; + break; + default: + break; + } + o->onset_type = onset_type; + return o; +} + +void del_aubio_specdesc (aubio_specdesc_t *o){ + switch(o->onset_type) { + case aubio_onset_energy: + break; + case aubio_onset_hfc: + break; + case aubio_onset_complex: + del_fvec(o->oldmag); + del_fvec(o->dev1); + del_fvec(o->theta1); + del_fvec(o->theta2); + break; + case aubio_onset_phase: + case aubio_onset_wphase: + del_fvec(o->dev1); + del_fvec(o->theta1); + del_fvec(o->theta2); + del_aubio_hist(o->histog); + break; + case aubio_onset_specdiff: + del_fvec(o->oldmag); + del_fvec(o->dev1); + del_aubio_hist(o->histog); + break; + case aubio_onset_kl: + case aubio_onset_mkl: + case aubio_onset_specflux: + del_fvec(o->oldmag); + break; + default: + break; + } + AUBIO_FREE(o); +} diff --git a/dependencies/aubio/src/spectral/specdesc.h b/dependencies/aubio/src/spectral/specdesc.h new file mode 100644 index 0000000000..0f688c14b6 --- /dev/null +++ b/dependencies/aubio/src/spectral/specdesc.h @@ -0,0 +1,204 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Spectral description functions + + All of the following spectral description functions take as arguments the FFT + of a windowed signal (as created with aubio_pvoc). They output one smpl_t per + buffer (stored in a vector of size [1]). + + \section specdesc Spectral description functions + + A list of the spectral description methods currently available follows. + + \subsection onsetdesc Onset detection functions + + These functions are designed to raise at notes attacks in music signals. + + \b \p energy : Energy based onset detection function + + This function calculates the local energy of the input spectral frame. + + \b \p hfc : High Frequency Content onset detection function + + This method computes the High Frequency Content (HFC) of the input spectral + frame. The resulting function is efficient at detecting percussive onsets. + + Paul Masri. Computer modeling of Sound for Transformation and Synthesis of + Musical Signal. PhD dissertation, University of Bristol, UK, 1996. + + \b \p complex : Complex Domain Method onset detection function + + Christopher Duxbury, Mike E. Davies, and Mark B. Sandler. Complex domain + onset detection for musical signals. In Proceedings of the Digital Audio + Effects Conference, DAFx-03, pages 90-93, London, UK, 2003. + + \b \p phase : Phase Based Method onset detection function + + Juan-Pablo Bello, Mike P. Davies, and Mark B. Sandler. Phase-based note onset + detection for music signals. In Proceedings of the IEEE International + Conference on Acoustics Speech and Signal Processing, pages 441­444, + Hong-Kong, 2003. + + \b \p wphase : Weighted Phase Deviation onset detection function + + S. Dixon. Onset detection revisited. In Proceedings of the 9th International + Conference on Digital Audio Ef- fects (DAFx) , pages 133–137, 2006. + + http://www.eecs.qmul.ac.uk/~simond/pub/2006/dafx.pdf + + \b \p specdiff : Spectral difference method onset detection function + + Jonhatan Foote and Shingo Uchihashi. The beat spectrum: a new approach to + rhythm analysis. In IEEE International Conference on Multimedia and Expo + (ICME 2001), pages 881­884, Tokyo, Japan, August 2001. + + \b \p kl : Kullback-Liebler onset detection function + + Stephen Hainsworth and Malcom Macleod. Onset detection in music audio + signals. In Proceedings of the International Computer Music Conference + (ICMC), Singapore, 2003. + + \b \p mkl : Modified Kullback-Liebler onset detection function + + Paul Brossier, ``Automatic annotation of musical audio for interactive + systems'', Chapter 2, Temporal segmentation, PhD thesis, Centre for Digital + music, Queen Mary University of London, London, UK, 2006. + + \b \p specflux : Spectral Flux + + Simon Dixon, Onset Detection Revisited, in ``Proceedings of the 9th + International Conference on Digital Audio Effects'' (DAFx-06), Montreal, + Canada, 2006. + + \subsection shapedesc Spectral shape descriptors + + The following descriptors are described in: + + Geoffroy Peeters, A large set of audio features for sound description + (similarity and classification) in the CUIDADO project, CUIDADO I.S.T. + Project Report 2004 (pdf) + + \b \p centroid : Spectral centroid + + The spectral centroid represents the barycenter of the spectrum. + + \e Note: This function returns the result in bin. To get the spectral + centroid in Hz, aubio_bintofreq() should be used. + + \b \p spread : Spectral spread + + The spectral spread is the variance of the spectral distribution around its + centroid. + + See also Standard + deviation on Wikipedia. + + \b \p skewness : Spectral skewness + + Similarly, the skewness is computed from the third order moment of the + spectrum. A negative skewness indicates more energy on the lower part of the + spectrum. A positive skewness indicates more energy on the high frequency of + the spectrum. + + See also Skewness on + Wikipedia. + + \b \p kurtosis : Spectral kurtosis + + The kurtosis is a measure of the flatness of the spectrum, computed from the + fourth order moment. + + See also Kurtosis on + Wikipedia. + + \b \p slope : Spectral slope + + The spectral slope represents decreasing rate of the spectral amplitude, + computed using a linear regression. + + \b \p decrease : Spectral decrease + + The spectral decrease is another representation of the decreasing rate, + based on perceptual criteria. + + \b \p rolloff : Spectral roll-off + + This function returns the bin number below which 95% of the spectrum energy + is found. + + \example spectral/test-specdesc.c + +*/ + + +#ifndef AUBIO_SPECDESC_H +#define AUBIO_SPECDESC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** spectral description structure */ +typedef struct _aubio_specdesc_t aubio_specdesc_t; + +/** execute spectral description function on a spectral frame + + Generic function to compute spectral description. + + \param o spectral description object as returned by new_aubio_specdesc() + \param fftgrain input signal spectrum as computed by aubio_pvoc_do + \param desc output vector (one sample long, to send to the peak picking) + +*/ +void aubio_specdesc_do (aubio_specdesc_t * o, const cvec_t * fftgrain, + fvec_t * desc); + +/** creation of a spectral description object + + \param method spectral description method + \param buf_size length of the input spectrum frame + + The parameter \p method is a string that can be any of: + + - onset novelty functions: `complex`, `energy`, `hfc`, `kl`, `mkl`, + `phase`, `specdiff`, `specflux`, `wphase`, + + - spectral descriptors: `centroid`, `decrease`, `kurtosis`, `rolloff`, + `skewness`, `slope`, `spread`. + +*/ +aubio_specdesc_t *new_aubio_specdesc (const char_t * method, uint_t buf_size); + +/** deletion of a spectral descriptor + + \param o spectral descriptor object as returned by new_aubio_specdesc() + +*/ +void del_aubio_specdesc (aubio_specdesc_t * o); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_SPECDESC_H */ diff --git a/dependencies/aubio/src/spectral/statistics.c b/dependencies/aubio/src/spectral/statistics.c new file mode 100644 index 0000000000..d303364377 --- /dev/null +++ b/dependencies/aubio/src/spectral/statistics.c @@ -0,0 +1,204 @@ +/* + Copyright (C) 2007-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "cvec.h" +#include "spectral/specdesc.h" + +void aubio_specdesc_centroid (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_spread (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_skewness (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_kurtosis (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_slope (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_decrease (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); +void aubio_specdesc_rolloff (aubio_specdesc_t * o, const cvec_t * spec, + fvec_t * desc); + + +smpl_t cvec_sum (const cvec_t * s); +smpl_t cvec_mean (const cvec_t * s); +smpl_t cvec_centroid (const cvec_t * s); +smpl_t cvec_moment (const cvec_t * s, uint_t moment); + +smpl_t +cvec_sum (const cvec_t * s) +{ + uint_t j; + smpl_t tmp = 0.0; + for (j = 0; j < s->length; j++) { + tmp += s->norm[j]; + } + return tmp; +} + +smpl_t +cvec_mean (const cvec_t * s) +{ + return cvec_sum (s) / (smpl_t) (s->length); +} + +smpl_t +cvec_centroid (const cvec_t * spec) +{ + smpl_t sum = 0., sc = 0.; + uint_t j; + sum = cvec_sum (spec); + if (sum == 0.) { + return 0.; + } else { + for (j = 0; j < spec->length; j++) { + sc += (smpl_t) j *spec->norm[j]; + } + return sc / sum; + } +} + +smpl_t +cvec_moment (const cvec_t * spec, uint_t order) +{ + smpl_t sum = 0., centroid = 0., sc = 0.; + uint_t j; + sum = cvec_sum (spec); + if (sum == 0.) { + return 0.; + } else { + centroid = cvec_centroid (spec); + for (j = 0; j < spec->length; j++) { + sc += (smpl_t) POW(j - centroid, order) * spec->norm[j]; + } + return sc / sum; + } +} + +void +aubio_specdesc_centroid (aubio_specdesc_t * o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + desc->data[0] = cvec_centroid (spec); +} + +void +aubio_specdesc_spread (aubio_specdesc_t * o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + desc->data[0] = cvec_moment (spec, 2); +} + +void +aubio_specdesc_skewness (aubio_specdesc_t * o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + smpl_t spread; + spread = cvec_moment (spec, 2); + if (spread == 0) { + desc->data[0] = 0.; + } else { + desc->data[0] = cvec_moment (spec, 3); + desc->data[0] /= POW ( SQRT (spread), 3); + } +} + +void +aubio_specdesc_kurtosis (aubio_specdesc_t * o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + smpl_t spread; + spread = cvec_moment (spec, 2); + if (spread == 0) { + desc->data[0] = 0.; + } else { + desc->data[0] = cvec_moment (spec, 4); + desc->data[0] /= SQR (spread); + } +} + +void +aubio_specdesc_slope (aubio_specdesc_t * o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + uint_t j; + smpl_t norm = 0, sum = 0.; + // compute N * sum(j**2) - sum(j)**2 + for (j = 0; j < spec->length; j++) { + norm += j*j; + } + norm *= spec->length; + // sum_0^N(j) = length * (length + 1) / 2 + norm -= SQR( (spec->length) * (spec->length - 1.) / 2. ); + sum = cvec_sum (spec); + desc->data[0] = 0.; + if (sum == 0.) { + return; + } else { + for (j = 0; j < spec->length; j++) { + desc->data[0] += j * spec->norm[j]; + } + desc->data[0] *= spec->length; + desc->data[0] -= sum * spec->length * (spec->length - 1) / 2.; + desc->data[0] /= norm; + desc->data[0] /= sum; + } +} + +void +aubio_specdesc_decrease (aubio_specdesc_t *o UNUSED, const cvec_t * spec, + fvec_t * desc) +{ + uint_t j; smpl_t sum; + sum = cvec_sum (spec); + desc->data[0] = 0; + if (sum == 0.) { + return; + } else { + sum -= spec->norm[0]; + for (j = 1; j < spec->length; j++) { + desc->data[0] += (spec->norm[j] - spec->norm[0]) / j; + } + desc->data[0] /= sum; + } +} + +void +aubio_specdesc_rolloff (aubio_specdesc_t *o UNUSED, const cvec_t * spec, + fvec_t *desc) +{ + uint_t j; smpl_t cumsum, rollsum; + cumsum = 0.; rollsum = 0.; + for (j = 0; j < spec->length; j++) { + cumsum += SQR (spec->norm[j]); + } + if (cumsum == 0) { + desc->data[0] = 0.; + } else { + cumsum *= 0.95; + j = 0; + while (rollsum < cumsum) { + rollsum += SQR (spec->norm[j]); + j++; + } + desc->data[0] = MAX (1, j) - 1.0f; + } +} diff --git a/dependencies/aubio/src/temporal/biquad.c b/dependencies/aubio/src/temporal/biquad.c new file mode 100644 index 0000000000..426b64fb52 --- /dev/null +++ b/dependencies/aubio/src/temporal/biquad.c @@ -0,0 +1,54 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "lvec.h" +#include "temporal/filter.h" +#include "temporal/biquad.h" + +uint_t +aubio_filter_set_biquad (aubio_filter_t * f, lsmp_t b0, lsmp_t b1, lsmp_t b2, + lsmp_t a1, lsmp_t a2) +{ + uint_t order = aubio_filter_get_order (f); + lvec_t *bs = aubio_filter_get_feedforward (f); + lvec_t *as = aubio_filter_get_feedback (f); + + if (order != 3) { + AUBIO_ERROR ("order of biquad filter must be 3, not %d\n", order); + return AUBIO_FAIL; + } + bs->data[0] = b0; + bs->data[1] = b1; + bs->data[2] = b2; + as->data[0] = 1.; + as->data[1] = a1; + as->data[2] = a2; + return AUBIO_OK; +} + +aubio_filter_t * +new_aubio_filter_biquad (lsmp_t b0, lsmp_t b1, lsmp_t b2, lsmp_t a1, lsmp_t a2) +{ + aubio_filter_t *f = new_aubio_filter (3); + aubio_filter_set_biquad (f, b0, b1, b2, a1, a2); + return f; +} diff --git a/dependencies/aubio/src/temporal/biquad.h b/dependencies/aubio/src/temporal/biquad.h new file mode 100644 index 0000000000..1d19d94211 --- /dev/null +++ b/dependencies/aubio/src/temporal/biquad.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_FILTER_BIQUAD_H +#define AUBIO_FILTER_BIQUAD_H + +/** \file + + Second order Infinite Impulse Response filter + + This file implements a normalised biquad filter (second order IIR): + + \f$ y[n] = b_0 x[n] + b_1 x[n-1] + b_2 x[n-2] - a_1 y[n-1] - a_2 y[n-2] \f$ + + The filtfilt version runs the filter twice, forward and backward, to + compensate the phase shifting of the forward operation. + + See also Digital + biquad filter on wikipedia. + + \example temporal/test-biquad.c + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** set coefficients of a biquad filter + + \param f filter object as returned by new_aubio_filter() + \param b0 forward filter coefficient + \param b1 forward filter coefficient + \param b2 forward filter coefficient + \param a1 feedback filter coefficient + \param a2 feedback filter coefficient + +*/ +uint_t aubio_filter_set_biquad (aubio_filter_t * f, lsmp_t b0, lsmp_t b1, + lsmp_t b2, lsmp_t a1, lsmp_t a2); + +/** create biquad filter with `b0`, `b1`, `b2`, `a1`, `a2` coeffs + + \param b0 forward filter coefficient + \param b1 forward filter coefficient + \param b2 forward filter coefficient + \param a1 feedback filter coefficient + \param a2 feedback filter coefficient + +*/ +aubio_filter_t *new_aubio_filter_biquad (lsmp_t b0, lsmp_t b1, lsmp_t b2, + lsmp_t a1, lsmp_t a2); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_FILTER_BIQUAD_H */ diff --git a/dependencies/aubio/src/temporal/filter.c b/dependencies/aubio/src/temporal/filter.c new file mode 100644 index 0000000000..776d2e6f2c --- /dev/null +++ b/dependencies/aubio/src/temporal/filter.c @@ -0,0 +1,163 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + + +/* Requires lsmp_t to be long or double. float will NOT give reliable + * results */ + +#include "aubio_priv.h" +#include "fvec.h" +#include "lvec.h" +#include "mathutils.h" +#include "temporal/filter.h" + +struct _aubio_filter_t +{ + uint_t order; + uint_t samplerate; + lvec_t *a; + lvec_t *b; + lvec_t *y; + lvec_t *x; +}; + +void +aubio_filter_do_outplace (aubio_filter_t * f, const fvec_t * in, fvec_t * out) +{ + fvec_copy (in, out); + aubio_filter_do (f, out); +} + +void +aubio_filter_do (aubio_filter_t * f, fvec_t * in) +{ + uint_t j, l, order = f->order; + lsmp_t *x = f->x->data; + lsmp_t *y = f->y->data; + lsmp_t *a = f->a->data; + lsmp_t *b = f->b->data; + + for (j = 0; j < in->length; j++) { + /* new input */ + x[0] = KILL_DENORMAL (in->data[j]); + y[0] = b[0] * x[0]; + for (l = 1; l < order; l++) { + y[0] += b[l] * x[l]; + y[0] -= a[l] * y[l]; + } + /* new output */ + in->data[j] = y[0]; + /* store for next sample */ + for (l = order - 1; l > 0; l--) { + x[l] = x[l - 1]; + y[l] = y[l - 1]; + } + } +} + +/* The rough way: reset memory of filter between each run to avoid end effects. */ +void +aubio_filter_do_filtfilt (aubio_filter_t * f, fvec_t * in, fvec_t * tmp) +{ + uint_t j; + uint_t length = in->length; + /* apply filtering */ + aubio_filter_do (f, in); + aubio_filter_do_reset (f); + /* mirror */ + for (j = 0; j < length; j++) + tmp->data[length - j - 1] = in->data[j]; + /* apply filtering on mirrored */ + aubio_filter_do (f, tmp); + aubio_filter_do_reset (f); + /* invert back */ + for (j = 0; j < length; j++) + in->data[j] = tmp->data[length - j - 1]; +} + +lvec_t * +aubio_filter_get_feedback (const aubio_filter_t * f) +{ + return f->a; +} + +lvec_t * +aubio_filter_get_feedforward (const aubio_filter_t * f) +{ + return f->b; +} + +uint_t +aubio_filter_get_order (const aubio_filter_t * f) +{ + return f->order; +} + +uint_t +aubio_filter_get_samplerate (const aubio_filter_t * f) +{ + return f->samplerate; +} + +uint_t +aubio_filter_set_samplerate (aubio_filter_t * f, uint_t samplerate) +{ + f->samplerate = samplerate; + return AUBIO_OK; +} + +void +aubio_filter_do_reset (aubio_filter_t * f) +{ + lvec_zeros (f->x); + lvec_zeros (f->y); +} + +aubio_filter_t * +new_aubio_filter (uint_t order) +{ + aubio_filter_t *f = AUBIO_NEW (aubio_filter_t); + if ((sint_t)order < 1) { + AUBIO_FREE(f); + return NULL; + } + f->x = new_lvec (order); + f->y = new_lvec (order); + f->a = new_lvec (order); + f->b = new_lvec (order); + /* by default, samplerate is not set */ + f->samplerate = 0; + f->order = order; + /* set default to identity */ + f->a->data[0] = 1.; + f->b->data[0] = 1.; + return f; +} + +void +del_aubio_filter (aubio_filter_t * f) +{ + del_lvec (f->a); + del_lvec (f->b); + del_lvec (f->x); + del_lvec (f->y); + AUBIO_FREE (f); + return; +} diff --git a/dependencies/aubio/src/temporal/filter.h b/dependencies/aubio/src/temporal/filter.h new file mode 100644 index 0000000000..b8b678e88e --- /dev/null +++ b/dependencies/aubio/src/temporal/filter.h @@ -0,0 +1,176 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_FILTER_H +#define AUBIO_FILTER_H + +/** \file + + Digital filter + + This object stores a digital filter of order \f$n\f$. + It contains the following data: + - \f$ n*1 b_i \f$ feedforward coefficients + - \f$ n*1 a_i \f$ feedback coefficients + - \f$ n*c x_i \f$ input signal + - \f$ n*c y_i \f$ output signal + + For convenience, the samplerate of the input signal is also stored in the + object. + + Feedforward and feedback parameters can be modified using + aubio_filter_get_feedback() and aubio_filter_get_feedforward(). + + The function aubio_filter_do_outplace() computes the following output signal + \f$ y[n] \f$ from the input signal \f$ x[n] \f$: + + \f{eqnarray*}{ + y[n] = b_0 x[n] & + & b_1 x[n-1] + b_2 x[n-2] + ... + b_P x[n-P] \\ + & - & a_1 y[n-1] - a_2 y[n-2] - ... - a_P y[n-P] \\ + \f} + + The function aubio_filter_do() executes the same computation but modifies + directly the input signal (in-place). + + The function aubio_filter_do_filtfilt() version runs the filter twice, first + forward then backward, to compensate with the phase shifting of the forward + operation. + + Some convenience functions are provided: + - new_aubio_filter_a_weighting() and aubio_filter_set_a_weighting(), + - new_aubio_filter_c_weighting() and aubio_filter_set_c_weighting(). + - new_aubio_filter_biquad() and aubio_filter_set_biquad(). + + \example temporal/test-filter.c + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Digital filter + +*/ +typedef struct _aubio_filter_t aubio_filter_t; + +/** filter input vector (in-place) + + \param f filter object as returned by new_aubio_filter() + \param in input vector to filter + +*/ +void aubio_filter_do (aubio_filter_t * f, fvec_t * in); + +/** filter input vector (out-of-place) + + \param f filter object as returned by new_aubio_filter() + \param in input vector to filter + \param out output vector to store filtered input + +*/ +void aubio_filter_do_outplace (aubio_filter_t * f, const fvec_t * in, fvec_t * out); + +/** filter input vector forward and backward + + \param f ::aubio_filter_t object as returned by new_aubio_filter() + \param in ::fvec_t input vector to filter + \param tmp memory space to use for computation + +*/ +void aubio_filter_do_filtfilt (aubio_filter_t * f, fvec_t * in, fvec_t * tmp); + +/** returns a pointer to feedback coefficients \f$ a_i \f$ + + \param f filter object to get parameters from + + \return a pointer to the \f$ a_0 ... a_i ... a_P \f$ coefficients + +*/ +lvec_t *aubio_filter_get_feedback (const aubio_filter_t * f); + +/** returns a pointer to feedforward coefficients \f$ b_i \f$ + + \param f filter object to get coefficients from + + \return a pointer to the \f$ b_0 ... b_i ... b_P \f$ coefficients + +*/ +lvec_t *aubio_filter_get_feedforward (const aubio_filter_t * f); + +/** get order of the filter + + \param f filter to get order from + + \return the order of the filter + +*/ +uint_t aubio_filter_get_order (const aubio_filter_t * f); + +/** get sampling rate of the filter + + \param f filter to get sampling rate from + + \return the sampling rate of the filter, in Hz + +*/ +uint_t aubio_filter_get_samplerate (const aubio_filter_t * f); + +/** get sampling rate of the filter + + \param f filter to get sampling rate from + \param samplerate sample rate to set the filter to + + \return the sampling rate of the filter, in Hz + +*/ +uint_t aubio_filter_set_samplerate (aubio_filter_t * f, uint_t samplerate); + +/** reset filter memory + + \param f filter object as returned by new_aubio_filter() + +*/ +void aubio_filter_do_reset (aubio_filter_t * f); + +/** create new filter object + + This function creates a new ::aubio_filter_t object, given the order of the + filter. + + \param order order of the filter (number of coefficients) + + \return the newly created filter object + +*/ +aubio_filter_t *new_aubio_filter (uint_t order); + +/** delete a filter object + + \param f filter object to delete + +*/ +void del_aubio_filter (aubio_filter_t * f); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_FILTER_H */ diff --git a/dependencies/aubio/src/types.h b/dependencies/aubio/src/types.h new file mode 100644 index 0000000000..57df2e0dd2 --- /dev/null +++ b/dependencies/aubio/src/types.h @@ -0,0 +1,70 @@ +/* + Copyright (C) 2003-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_TYPES_H +#define AUBIO_TYPES_H + +/** \file + + Definition of data types used in aubio + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_AUBIO_DOUBLE +/** defined to 1 if aubio is compiled in double precision */ +#define HAVE_AUBIO_DOUBLE 0 +#endif + +/** short sample format (32 or 64 bits) */ +#if !HAVE_AUBIO_DOUBLE +typedef float smpl_t; +/** print format for sample in single precision */ +#define AUBIO_SMPL_FMT "%f" +#else +typedef double smpl_t; +/** print format for double in single precision */ +#define AUBIO_SMPL_FMT "%lf" +#endif +/** long sample format (64 bits or more) */ +#if !HAVE_AUBIO_DOUBLE +typedef double lsmp_t; +/** print format for sample in double precision */ +#define AUBIO_LSMP_FMT "%lf" +#else +typedef long double lsmp_t; +/** print format for double in double precision */ +#define AUBIO_LSMP_FMT "%Lf" +#endif +/** unsigned integer */ +typedef unsigned int uint_t; +/** signed integer */ +typedef int sint_t; +/** character */ +typedef char char_t; + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_TYPES_H */ diff --git a/dependencies/aubio/src/utils/hist.c b/dependencies/aubio/src/utils/hist.c new file mode 100644 index 0000000000..2dcc443212 --- /dev/null +++ b/dependencies/aubio/src/utils/hist.c @@ -0,0 +1,151 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "utils/scale.h" +#include "mathutils.h" //fvec_min fvec_max +#include "utils/hist.h" + +/******** + * Object Structure + */ + +struct _aubio_hist_t { + fvec_t * hist; + uint_t nelems; + fvec_t * cent; + aubio_scale_t *scaler; +}; + +/** + * Object creation/deletion calls + */ +aubio_hist_t * new_aubio_hist (smpl_t flow, smpl_t fhig, uint_t nelems){ + aubio_hist_t * s = AUBIO_NEW(aubio_hist_t); + smpl_t step = (fhig-flow)/(smpl_t)(nelems); + smpl_t accum = step; + uint_t i; + if ((sint_t)nelems <= 0) { + AUBIO_FREE(s); + return NULL; + } + s->nelems = nelems; + s->hist = new_fvec(nelems); + s->cent = new_fvec(nelems); + + /* use scale to map flow/fhig -> 0/nelems */ + s->scaler = new_aubio_scale(flow,fhig,0,nelems); + /* calculate centers now once */ + s->cent->data[0] = flow + 0.5 * step; + for (i=1; i < s->nelems; i++, accum+=step ) + s->cent->data[i] = s->cent->data[0] + accum; + + return s; +} + +void del_aubio_hist(aubio_hist_t *s) { + del_fvec(s->hist); + del_fvec(s->cent); + del_aubio_scale(s->scaler); + AUBIO_FREE(s); +} + +/*** + * do it + */ +void aubio_hist_do (aubio_hist_t *s, fvec_t *input) { + uint_t j; + sint_t tmp = 0; + aubio_scale_do(s->scaler, input); + /* reset data */ + fvec_zeros(s->hist); + /* run accum */ + for (j=0; j < input->length; j++) + { + tmp = (sint_t)FLOOR(input->data[j]); + if ((tmp >= 0) && (tmp < (sint_t)s->nelems)) { + s->hist->data[tmp] += 1; + } + } +} + +void aubio_hist_do_notnull (aubio_hist_t *s, fvec_t *input) { + uint_t j; + sint_t tmp = 0; + aubio_scale_do(s->scaler, input); + /* reset data */ + fvec_zeros(s->hist); + /* run accum */ + for (j=0; j < input->length; j++) { + if (input->data[j] != 0) { + tmp = (sint_t)FLOOR(input->data[j]); + if ((tmp >= 0) && (tmp < (sint_t)s->nelems)) + s->hist->data[tmp] += 1; + } + } +} + + +void aubio_hist_dyn_notnull (aubio_hist_t *s, fvec_t *input) { + uint_t i; + sint_t tmp = 0; + smpl_t ilow = fvec_min(input); + smpl_t ihig = fvec_max(input); + smpl_t step = (ihig-ilow)/(smpl_t)(s->nelems); + + /* readapt */ + aubio_scale_set_limits (s->scaler, ilow, ihig, 0, s->nelems); + + /* recalculate centers */ + s->cent->data[0] = ilow + 0.5f * step; + for (i=1; i < s->nelems; i++) + s->cent->data[i] = s->cent->data[0] + i * step; + + /* scale */ + aubio_scale_do(s->scaler, input); + + /* reset data */ + fvec_zeros(s->hist); + /* run accum */ + for (i=0; i < input->length; i++) { + if (input->data[i] != 0) { + tmp = (sint_t)FLOOR(input->data[i]); + if ((tmp >= 0) && (tmp < (sint_t)s->nelems)) + s->hist->data[tmp] += 1; + } + } +} + +void aubio_hist_weight (aubio_hist_t *s) { + uint_t j; + for (j=0; j < s->nelems; j++) { + s->hist->data[j] *= s->cent->data[j]; + } +} + +smpl_t aubio_hist_mean (const aubio_hist_t *s) { + uint_t j; + smpl_t tmp = 0.0; + for (j=0; j < s->nelems; j++) + tmp += s->hist->data[j]; + return tmp/(smpl_t)(s->nelems); +} + diff --git a/dependencies/aubio/src/utils/hist.h b/dependencies/aubio/src/utils/hist.h new file mode 100644 index 0000000000..d39091a496 --- /dev/null +++ b/dependencies/aubio/src/utils/hist.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** @file + * + * Histogram function + * + * Big hacks to implement an histogram + */ + +#ifndef AUBIO_HIST_H +#define AUBIO_HIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** histogram object */ +typedef struct _aubio_hist_t aubio_hist_t; + +/** histogram creation + + \param flow minimum input + \param fhig maximum input + \param nelems number of histogram columns + +*/ +aubio_hist_t * new_aubio_hist(smpl_t flow, smpl_t fhig, uint_t nelems); +/** histogram deletion */ +void del_aubio_hist(aubio_hist_t *s); +/** compute the histogram */ +void aubio_hist_do(aubio_hist_t *s, fvec_t * input); +/** compute the histogram ignoring null elements */ +void aubio_hist_do_notnull(aubio_hist_t *s, fvec_t * input); +/** compute the mean of the histogram */ +smpl_t aubio_hist_mean(const aubio_hist_t *s); +/** weight the histogram */ +void aubio_hist_weight(aubio_hist_t *s); +/** compute dynamic histogram for non-null elements */ +void aubio_hist_dyn_notnull (aubio_hist_t *s, fvec_t *input); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_HIST_H */ diff --git a/dependencies/aubio/src/utils/log.c b/dependencies/aubio/src/utils/log.c new file mode 100644 index 0000000000..967c2d6cc3 --- /dev/null +++ b/dependencies/aubio/src/utils/log.c @@ -0,0 +1,92 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "log.h" + +/** array of pointers to logging functions, one per level */ +static aubio_log_function_t aubio_log_function[AUBIO_LOG_LAST_LEVEL]; +/** array of pointers to closure passed to logging functions, one per level */ +static void* aubio_log_user_data[AUBIO_LOG_LAST_LEVEL]; +/** buffer for logging messages */ +static char aubio_log_buffer[512]; + +/** private function used by default by logging functions */ +void +aubio_default_log(sint_t level, const char_t *message, void * data UNUSED) +{ + FILE *out; + out = stdout; + if (level == AUBIO_LOG_ERR || level == AUBIO_LOG_DBG || level == AUBIO_LOG_WRN) { + out = stderr; + } + fprintf(out, "%s", message); + //fflush(out); +} + +uint_t +aubio_log(sint_t level, const char_t *fmt, ...) +{ + aubio_log_function_t fun = NULL; + + va_list args; + va_start(args, fmt); + vsnprintf(aubio_log_buffer, sizeof(aubio_log_buffer), fmt, args); + va_end(args); + + if ((level >= 0) && (level < AUBIO_LOG_LAST_LEVEL)) { + fun = aubio_log_function[level]; + if (fun != NULL) { + (*fun)(level, aubio_log_buffer, aubio_log_user_data[level]); + } else { + aubio_default_log(level, aubio_log_buffer, NULL); + } + } + return AUBIO_FAIL; +} + +void +aubio_log_reset(void) +{ + uint_t i = 0; + for (i = 0; i < AUBIO_LOG_LAST_LEVEL; i++) { + aubio_log_set_level_function(i, aubio_default_log, NULL); + } +} + +aubio_log_function_t +aubio_log_set_level_function(sint_t level, aubio_log_function_t fun, void * data) +{ + aubio_log_function_t old = NULL; + if ((level >= 0) && (level < AUBIO_LOG_LAST_LEVEL)) { + old = aubio_log_function[level]; + aubio_log_function[level] = fun; + aubio_log_user_data[level] = data; + } + return old; +} + +void +aubio_log_set_function(aubio_log_function_t fun, void * data) { + uint_t i = 0; + for (i = 0; i < AUBIO_LOG_LAST_LEVEL; i++) { + aubio_log_set_level_function(i, fun, data); + } +} diff --git a/dependencies/aubio/src/utils/log.h b/dependencies/aubio/src/utils/log.h new file mode 100644 index 0000000000..091e91d4f2 --- /dev/null +++ b/dependencies/aubio/src/utils/log.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#ifndef AUBIO_LOG_H +#define AUBIO_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Logging features + + This file specifies ::aubio_log_set_function and + ::aubio_log_set_level_function, which let you define one or several custom + logging functions to redirect warnings and errors from aubio to your + application. The custom function should have the prototype defined in + ::aubio_log_function_t. + + After a call to ::aubio_log_set_level_function, ::aubio_log_reset can be used + to reset each logging functions to the default ones. + + \example utils/test-log.c + +*/ + +/** list of logging levels */ +enum aubio_log_level { + AUBIO_LOG_ERR, /**< critical errors */ + AUBIO_LOG_INF, /**< infos */ + AUBIO_LOG_MSG, /**< general messages */ + AUBIO_LOG_DBG, /**< debug messages */ + AUBIO_LOG_WRN, /**< warnings */ + AUBIO_LOG_LAST_LEVEL, /**< number of valid levels */ +}; + +/** Logging function prototype, to be passed to ::aubio_log_set_function + + \param level log level + \param message text to log + \param data optional closure used by the callback + + See @ref utils/test-log.c for an example of logging function. + + */ +typedef void (*aubio_log_function_t)(sint_t level, const char_t *message, void + *data); + +/** Set logging function for all levels + + \param fun the function to be used to log, of type ::aubio_log_function_t + \param data optional closure to be passed to the function (can be NULL if + nothing to pass) + + */ +void aubio_log_set_function(aubio_log_function_t fun, void* data); + +/** Set logging function for a given level + + \param level the level for which to set the logging function + \param fun the function to be used to log, of type ::aubio_log_function_t + \param data optional closure to be passed to the function (can be NULL if + nothing to pass) + +*/ +aubio_log_function_t aubio_log_set_level_function(sint_t level, + aubio_log_function_t fun, void* data); + +/** Reset all logging functions to the default one + + After calling this function, the default logging function will be used to + print error, warning, normal, and debug messages to `stdout` or `stderr`. + + */ +void aubio_log_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_LOG_H */ diff --git a/dependencies/aubio/src/utils/scale.c b/dependencies/aubio/src/utils/scale.c new file mode 100644 index 0000000000..3bd116c8e8 --- /dev/null +++ b/dependencies/aubio/src/utils/scale.c @@ -0,0 +1,79 @@ +/* + Copyright (C) 2003-2009 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +#include "aubio_priv.h" +#include "fvec.h" +#include "utils/scale.h" + +struct _aubio_scale_t { + smpl_t ilow; + smpl_t ihig; + smpl_t olow; + smpl_t ohig; + + smpl_t scaler; + smpl_t irange; + + /* not implemented yet : type in/out data + bool inint; + bool outint; + */ +}; + +aubio_scale_t * new_aubio_scale (smpl_t ilow, smpl_t ihig, + smpl_t olow, smpl_t ohig) { + aubio_scale_t * s = AUBIO_NEW(aubio_scale_t); + aubio_scale_set_limits (s, ilow, ihig, olow, ohig); + return s; +} + +void del_aubio_scale(aubio_scale_t *s) { + AUBIO_FREE(s); +} + +uint_t aubio_scale_set_limits (aubio_scale_t *s, smpl_t ilow, smpl_t ihig, + smpl_t olow, smpl_t ohig) { + smpl_t inputrange = ihig - ilow; + smpl_t outputrange= ohig - olow; + s->ilow = ilow; + s->ihig = ihig; + s->olow = olow; + s->ohig = ohig; + if (inputrange == 0) { + s->scaler = 0.0; + } else { + s->scaler = outputrange/inputrange; + if (inputrange < 0) { + inputrange = inputrange * -1.0f; + } + } + return AUBIO_OK; +} + +void aubio_scale_do (aubio_scale_t *s, fvec_t *input) +{ + uint_t j; + for (j=0; j < input->length; j++){ + input->data[j] -= s->ilow; + input->data[j] *= s->scaler; + input->data[j] += s->olow; + } +} + diff --git a/dependencies/aubio/src/utils/scale.h b/dependencies/aubio/src/utils/scale.h new file mode 100644 index 0000000000..da311140e5 --- /dev/null +++ b/dependencies/aubio/src/utils/scale.h @@ -0,0 +1,80 @@ +/* + Copyright (C) 2003-2013 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Vector scaling function + + This object, inspired from the scale object in FTS, the jMax engine, scales + the values of a vector according to an affine function defined as follow: + + \f$ y = (x - ilow)*(ohig-olow)/(ihig-ilow) + olow \f$ + +*/ +#ifndef AUBIO_SCALE_H +#define AUBIO_SCALE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** scale object */ +typedef struct _aubio_scale_t aubio_scale_t; + +/** create a scale object + + \param flow lower value of output function + \param fhig higher value of output function + \param ilow lower value of input function + \param ihig higher value of output function + +*/ +aubio_scale_t * new_aubio_scale(smpl_t flow, smpl_t fhig, + smpl_t ilow, smpl_t ihig); +/** delete a scale object + + \param s scale object as returned by new_aubio_scale + +*/ +void del_aubio_scale(aubio_scale_t *s); +/** scale input vector + + \param s scale object as returned by new_aubio_scale + \param input vector to scale + +*/ +void aubio_scale_do(aubio_scale_t *s, fvec_t * input); +/** modify scale parameters after object creation + + \param s scale object as returned by new_aubio_scale + \param olow lower value of output function + \param ohig higher value of output function + \param ilow lower value of input function + \param ihig higher value of output function + +*/ +uint_t aubio_scale_set_limits (aubio_scale_t *s, smpl_t ilow, smpl_t ihig, + smpl_t olow, smpl_t ohig); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_SCALE_H */ diff --git a/dependencies/aubio/src/vecutils.c b/dependencies/aubio/src/vecutils.c new file mode 100644 index 0000000000..8907b5ecfc --- /dev/null +++ b/dependencies/aubio/src/vecutils.c @@ -0,0 +1,36 @@ +#include "aubio_priv.h" +#include "types.h" +#include "fvec.h" +#include "cvec.h" +#include "vecutils.h" + +#define AUBIO_OP(OPNAME, OP, TYPE, OBJ) \ +void TYPE ## _ ## OPNAME (TYPE ## _t *o) \ +{ \ + uint_t j; \ + for (j = 0; j < o->length; j++) { \ + o->OBJ[j] = OP (o->OBJ[j]); \ + } \ +} + +#define AUBIO_OP_C(OPNAME, OP) \ + AUBIO_OP(OPNAME, OP, fvec, data) + +AUBIO_OP_C(exp, EXP) +AUBIO_OP_C(cos, COS) +AUBIO_OP_C(sin, SIN) +AUBIO_OP_C(abs, ABS) +AUBIO_OP_C(sqrt, SQRT) +AUBIO_OP_C(log10, SAFE_LOG10) +AUBIO_OP_C(log, SAFE_LOG) +AUBIO_OP_C(floor, FLOOR) +AUBIO_OP_C(ceil, CEIL) +AUBIO_OP_C(round, ROUND) + +void fvec_pow (fvec_t *s, smpl_t power) +{ + uint_t j; + for (j = 0; j < s->length; j++) { + s->data[j] = POW(s->data[j], power); + } +} diff --git a/dependencies/aubio/src/vecutils.h b/dependencies/aubio/src/vecutils.h new file mode 100644 index 0000000000..f0ed96e513 --- /dev/null +++ b/dependencies/aubio/src/vecutils.h @@ -0,0 +1,116 @@ +/* + Copyright (C) 2009-2015 Paul Brossier + + This file is part of aubio. + + aubio 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. + + aubio 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 aubio. If not, see . + +*/ + +/** \file + + Utility functions for ::fvec_t + + */ + +#ifndef AUBIO_VECUTILS_H +#define AUBIO_VECUTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** compute \f$e^x\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_exp (fvec_t *s); + +/** compute \f$cos(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_cos (fvec_t *s); + +/** compute \f$sin(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_sin (fvec_t *s); + +/** compute the \f$abs(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_abs (fvec_t *s); + +/** compute the \f$sqrt(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_sqrt (fvec_t *s); + +/** compute the \f$log10(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_log10 (fvec_t *s); + +/** compute the \f$log(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_log (fvec_t *s); + +/** compute the \f$floor(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_floor (fvec_t *s); + +/** compute the \f$ceil(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_ceil (fvec_t *s); + +/** compute the \f$round(x)\f$ of each vector elements + + \param s vector to modify + +*/ +void fvec_round (fvec_t *s); + +/** raise each vector elements to the power pow + + \param s vector to modify + \param pow power to raise to + +*/ +void fvec_pow (fvec_t *s, smpl_t pow); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_VECUTILS_H */ diff --git a/macOS b/macOS index 10761e81f6..09c4b642dc 160000 --- a/macOS +++ b/macOS @@ -1 +1 @@ -Subproject commit 10761e81f637410ad199541980d5db9a316b290c +Subproject commit 09c4b642dc31d5f18882d546e997de4c5110d175 diff --git a/src-core/render/SequenceFile.cpp b/src-core/render/SequenceFile.cpp index 2828f0d0a3..29e81baffa 100644 --- a/src-core/render/SequenceFile.cpp +++ b/src-core/render/SequenceFile.cpp @@ -297,6 +297,20 @@ void SequenceFile::SetAltTrackShortname(int idx, const std::string& name) } } +void SequenceFile::MoveAltTrack(int from, int to) +{ + if (from < 0 || from >= (int)alt_tracks.size()) return; + if (to < 0 || to >= (int)alt_tracks.size()) return; + if (from == to) return; + AlternateAudioTrack t = std::move(alt_tracks[from]); + alt_tracks.erase(alt_tracks.begin() + from); + alt_tracks.insert(alt_tracks.begin() + to, std::move(t)); + ValueCurve::ClearAltAudio(); + for (int i = 0; i < (int)alt_tracks.size(); i++) { + ValueCurve::SetAltAudio(GetAltTrackDisplayName(i), alt_tracks[i].audio); + } +} + std::string SequenceFile::GetAltTrackDisplayName(int idx) const { if (idx < 0 || idx >= (int)alt_tracks.size()) return ""; diff --git a/src-core/render/SequenceFile.h b/src-core/render/SequenceFile.h index b09a64caae..9a41e78fdd 100644 --- a/src-core/render/SequenceFile.h +++ b/src-core/render/SequenceFile.h @@ -129,6 +129,7 @@ class SequenceFile void RemoveAltTrack(int idx); void SetAltTrackPath(const std::string& ShowDir, int idx, const std::string& path); void SetAltTrackShortname(int idx, const std::string& name); + void MoveAltTrack(int from, int to); std::string GetAltTrackDisplayName(int idx) const; AudioManager* GetAltTrackMedia(int idx) const { diff --git a/src-core/utils/WavWriter.cpp b/src-core/utils/WavWriter.cpp new file mode 100644 index 0000000000..604ac29816 --- /dev/null +++ b/src-core/utils/WavWriter.cpp @@ -0,0 +1,101 @@ +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include "WavWriter.h" + +#include +#include +#include + +namespace xlights::wav { + +namespace { + +inline void put_u32_le(uint8_t* p, uint32_t v) { + p[0] = uint8_t(v & 0xFF); + p[1] = uint8_t((v >> 8) & 0xFF); + p[2] = uint8_t((v >> 16) & 0xFF); + p[3] = uint8_t((v >> 24) & 0xFF); +} + +inline void put_u16_le(uint8_t* p, uint16_t v) { + p[0] = uint8_t(v & 0xFF); + p[1] = uint8_t((v >> 8) & 0xFF); +} + +} + +bool WriteStereoFloatWav(const std::string& path, + const std::vector& left, + const std::vector& right, + uint32_t sampleRate) +{ + if (left.size() != right.size()) return false; + + const uint16_t channels = 2; + const uint16_t bitsPerSample = 32; + const uint16_t formatTag = 3; // WAVE_FORMAT_IEEE_FLOAT + const uint16_t blockAlign = channels * (bitsPerSample / 8); + const uint32_t byteRate = sampleRate * blockAlign; + + // RIFF/WAVE chunk sizes are 32-bit. Reject buffers that would overflow + // — caller would need >27h of stereo audio at 44.1kHz to hit this. + if (left.size() > UINT32_MAX / blockAlign - 36 / blockAlign) return false; + + const uint32_t numFrames = uint32_t(left.size()); + const uint32_t dataBytes = numFrames * blockAlign; + + // RIFF header (12) + fmt chunk (8 + 16) + data chunk header (8) = 44. + uint8_t hdr[44]; + std::memcpy(hdr + 0, "RIFF", 4); + put_u32_le(hdr + 4, 36 + dataBytes); + std::memcpy(hdr + 8, "WAVE", 4); + std::memcpy(hdr + 12, "fmt ", 4); + put_u32_le(hdr + 16, 16); // PCM/float fmt subchunk size + put_u16_le(hdr + 20, formatTag); + put_u16_le(hdr + 22, channels); + put_u32_le(hdr + 24, sampleRate); + put_u32_le(hdr + 28, byteRate); + put_u16_le(hdr + 32, blockAlign); + put_u16_le(hdr + 34, bitsPerSample); + std::memcpy(hdr + 36, "data", 4); + put_u32_le(hdr + 40, dataBytes); + + FILE* f = std::fopen(path.c_str(), "wb"); + if (f == nullptr) return false; + + if (std::fwrite(hdr, 1, sizeof(hdr), f) != sizeof(hdr)) { + std::fclose(f); + return false; + } + + // Interleave L/R per frame and write in chunks to keep memory bounded. + constexpr size_t kFramesPerChunk = 4096; + float chunk[kFramesPerChunk * 2]; + size_t i = 0; + while (i < numFrames) { + size_t n = std::min(kFramesPerChunk, numFrames - i); + for (size_t j = 0; j < n; j++) { + chunk[j * 2 + 0] = left[i + j]; + chunk[j * 2 + 1] = right[i + j]; + } + size_t bytes = n * blockAlign; + if (std::fwrite(chunk, 1, bytes, f) != bytes) { + std::fclose(f); + return false; + } + i += n; + } + + std::fclose(f); + return true; +} + +} diff --git a/src-core/utils/WavWriter.h b/src-core/utils/WavWriter.h new file mode 100644 index 0000000000..08767efaec --- /dev/null +++ b/src-core/utils/WavWriter.h @@ -0,0 +1,28 @@ +#pragma once + +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include + +namespace xlights::wav { + +// Writes a 32-bit IEEE float, interleaved stereo, RIFF/WAVE file at `path`. +// Returns true on success. Fails (returns false) if `left` and `right` differ +// in length, the file can't be opened, or any write fails. Designed for the +// "save AI-separated stems" path where buffers are already float [-1, 1]. +bool WriteStereoFloatWav(const std::string& path, + const std::vector& left, + const std::vector& right, + uint32_t sampleRate); + +} diff --git a/src-ui-wx/StemOnsetDialog.cpp b/src-ui-wx/StemOnsetDialog.cpp new file mode 100644 index 0000000000..0c46cf21cf --- /dev/null +++ b/src-ui-wx/StemOnsetDialog.cpp @@ -0,0 +1,610 @@ +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include "StemOnsetDialog.h" +#include "sequencer/StemWaveform.h" +#include "sequencer/TimeLine.h" +#include "render/Element.h" +#include "render/EffectLayer.h" +#include "xLightsMain.h" +#include "xLightsApp.h" +#include "media/AudioManager.h" + +extern "C" { +#include "aubio/src/types.h" +#include "aubio/src/fvec.h" +#include "aubio/src/cvec.h" +#include "aubio/src/spectral/phasevoc.h" +#include "aubio/src/spectral/specdesc.h" +} + + +#include +#include +#include + +const FrequencyBandPreset StemOnsetDialog::PRESETS[] = { + {"Kick", 20.0f, 150.0f, 0.30f, 200, "energy"}, + {"Snare", 150.0f, 5000.0f, 0.25f, 80, "specflux"}, + {"Hi-Hat", 5000.0f, 16000.0f, 0.50f, 60, "specflux"}, + {"Toms", 80.0f, 800.0f, 0.35f, 150, "energy"}, + {"Full Range", 20.0f, 20000.0f, 0.30f, 50, "default"}, + {"Custom", 20.0f, 20000.0f, 0.30f, 50, "default"}, +}; +const int StemOnsetDialog::NUM_PRESETS = sizeof(PRESETS) / sizeof(PRESETS[0]); +const int StemOnsetDialog::CUSTOM_PRESET_INDEX = 5; + +const long StemOnsetDialog::ID_CHOICE_PRESET = wxNewId(); +const long StemOnsetDialog::ID_SLIDER_LOW_FREQ = wxNewId(); +const long StemOnsetDialog::ID_SLIDER_HIGH_FREQ = wxNewId(); +const long StemOnsetDialog::ID_SLIDER_THRESHOLD = wxNewId(); +const long StemOnsetDialog::ID_SLIDER_MIN_INTERVAL = wxNewId(); +const long StemOnsetDialog::ID_TEXT_TRACK_NAME = wxNewId(); +const long StemOnsetDialog::ID_BTN_CREATE = wxNewId(); +const long StemOnsetDialog::ID_BTN_CANCEL = wxNewId(); +const long StemOnsetDialog::ID_DEBOUNCE_TIMER = wxNewId(); + +BEGIN_EVENT_TABLE(StemOnsetDialog, wxDialog) + EVT_BUTTON(StemOnsetDialog::ID_BTN_CREATE, StemOnsetDialog::OnCreateTimingTrack) + EVT_BUTTON(StemOnsetDialog::ID_BTN_CANCEL, StemOnsetDialog::OnCancel) + EVT_TIMER(StemOnsetDialog::ID_DEBOUNCE_TIMER, StemOnsetDialog::OnDebounceTimer) + EVT_CLOSE(StemOnsetDialog::OnClose) +END_EVENT_TABLE() + +// Log-scale frequency conversion: slider 0-1000 maps to 20-20000 Hz +float StemOnsetDialog::SliderToFreq(int val) +{ + float t = (float)val / 1000.0f; + return expf(logf(20.0f) + t * (logf(20000.0f) - logf(20.0f))); +} + +int StemOnsetDialog::FreqToSlider(float hz) +{ + if (hz < 20.0f) hz = 20.0f; + if (hz > 20000.0f) hz = 20000.0f; + float t = (logf(hz) - logf(20.0f)) / (logf(20000.0f) - logf(20.0f)); + return (int)(t * 1000.0f); +} + +wxString StemOnsetDialog::FormatFrequency(float hz) +{ + if (hz >= 1000.0f) { + return wxString::Format("%.1f kHz", hz / 1000.0f); + } + return wxString::Format("%.0f Hz", hz); +} + +StemOnsetDialog::StemOnsetDialog(wxWindow* parent, StemWaveform* waveform, + xLightsFrame* frame, const std::string& stemName) + : wxDialog(parent, wxID_ANY, "Onset Detection - " + stemName, + wxDefaultPosition, wxSize(420, 440), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxSTAY_ON_TOP), + _waveform(waveform), + _xlFrame(frame), + _stemName(stemName), + _debounceTimer(this, ID_DEBOUNCE_TIMER) +{ + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + // Frequency band preset + wxBoxSizer* presetSizer = new wxBoxSizer(wxHORIZONTAL); + presetSizer->Add(new wxStaticText(this, wxID_ANY, "Frequency Band:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + _presetChoice = new wxChoice(this, ID_CHOICE_PRESET); + for (int i = 0; i < NUM_PRESETS; i++) { + if (i == CUSTOM_PRESET_INDEX) { + _presetChoice->Append("Custom"); + } else { + wxString label = wxString::Format("%s (%s - %s)", + PRESETS[i].name.c_str(), + FormatFrequency(PRESETS[i].lowHz), + FormatFrequency(PRESETS[i].highHz)); + _presetChoice->Append(label); + } + } + _presetChoice->SetSelection(0); + _presetChoice->Bind(wxEVT_CHOICE, &StemOnsetDialog::OnPresetChanged, this); + presetSizer->Add(_presetChoice, 1, wxEXPAND, 0); + mainSizer->Add(presetSizer, 0, wxEXPAND | wxALL, 8); + + // Low frequency slider (log scale) + wxBoxSizer* lowFreqSizer = new wxBoxSizer(wxHORIZONTAL); + lowFreqSizer->Add(new wxStaticText(this, wxID_ANY, "Low Freq:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + int initLowSlider = FreqToSlider(PRESETS[0].lowHz); + _lowFreqSlider = new wxSlider(this, ID_SLIDER_LOW_FREQ, initLowSlider, 0, 1000, + wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + _lowFreqSlider->Bind(wxEVT_SLIDER, &StemOnsetDialog::OnLowFreqChanged, this); + lowFreqSizer->Add(_lowFreqSlider, 1, wxEXPAND | wxRIGHT, 5); + _lowFreqLabel = new wxStaticText(this, wxID_ANY, FormatFrequency(PRESETS[0].lowHz)); + _lowFreqLabel->SetMinSize(wxSize(65, -1)); + lowFreqSizer->Add(_lowFreqLabel, 0, wxALIGN_CENTER_VERTICAL, 0); + mainSizer->Add(lowFreqSizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 8); + + // High frequency slider (log scale) + wxBoxSizer* highFreqSizer = new wxBoxSizer(wxHORIZONTAL); + highFreqSizer->Add(new wxStaticText(this, wxID_ANY, "High Freq:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + int initHighSlider = FreqToSlider(PRESETS[0].highHz); + _highFreqSlider = new wxSlider(this, ID_SLIDER_HIGH_FREQ, initHighSlider, 0, 1000, + wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + _highFreqSlider->Bind(wxEVT_SLIDER, &StemOnsetDialog::OnHighFreqChanged, this); + highFreqSizer->Add(_highFreqSlider, 1, wxEXPAND | wxRIGHT, 5); + _highFreqLabel = new wxStaticText(this, wxID_ANY, FormatFrequency(PRESETS[0].highHz)); + _highFreqLabel->SetMinSize(wxSize(65, -1)); + highFreqSizer->Add(_highFreqLabel, 0, wxALIGN_CENTER_VERTICAL, 0); + mainSizer->Add(highFreqSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 8); + + // Threshold slider + wxBoxSizer* threshSizer = new wxBoxSizer(wxHORIZONTAL); + threshSizer->Add(new wxStaticText(this, wxID_ANY, "Sensitivity:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + int initThresh = (int)(PRESETS[0].defaultThreshold * 100.0f); + _thresholdSlider = new wxSlider(this, ID_SLIDER_THRESHOLD, initThresh, 0, 100, + wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + _thresholdSlider->Bind(wxEVT_SLIDER, &StemOnsetDialog::OnThresholdChanged, this); + threshSizer->Add(_thresholdSlider, 1, wxEXPAND | wxRIGHT, 5); + _thresholdLabel = new wxStaticText(this, wxID_ANY, wxString::Format("%.2f", PRESETS[0].defaultThreshold)); + _thresholdLabel->SetMinSize(wxSize(40, -1)); + threshSizer->Add(_thresholdLabel, 0, wxALIGN_CENTER_VERTICAL, 0); + mainSizer->Add(threshSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 8); + + // Minimum interval slider + wxBoxSizer* intervalSizer = new wxBoxSizer(wxHORIZONTAL); + intervalSizer->Add(new wxStaticText(this, wxID_ANY, "Min Interval:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + int initInterval = PRESETS[0].defaultMinIntervalMS; + _minIntervalSlider = new wxSlider(this, ID_SLIDER_MIN_INTERVAL, initInterval, 10, 500, + wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + _minIntervalSlider->Bind(wxEVT_SLIDER, &StemOnsetDialog::OnMinIntervalChanged, this); + intervalSizer->Add(_minIntervalSlider, 1, wxEXPAND | wxRIGHT, 5); + _minIntervalLabel = new wxStaticText(this, wxID_ANY, wxString::Format("%d ms", initInterval)); + _minIntervalLabel->SetMinSize(wxSize(55, -1)); + intervalSizer->Add(_minIntervalLabel, 0, wxALIGN_CENTER_VERTICAL, 0); + mainSizer->Add(intervalSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 8); + + // Timing track name + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(new wxStaticText(this, wxID_ANY, "Track Name:"), 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + _trackNameCtrl = new wxTextCtrl(this, ID_TEXT_TRACK_NAME, + stemName + " - " + PRESETS[0].name); + nameSizer->Add(_trackNameCtrl, 1, wxEXPAND, 0); + mainSizer->Add(nameSizer, 0, wxEXPAND | wxALL, 8); + + // Status text + _statusText = new wxStaticText(this, wxID_ANY, "Detecting onsets..."); + _statusText->SetForegroundColour(wxColour(100, 100, 100)); + mainSizer->Add(_statusText, 0, wxEXPAND | wxLEFT | wxRIGHT, 8); + + mainSizer->AddStretchSpacer(1); + + // Buttons (no Detect button - auto-detects) + wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); + _createButton = new wxButton(this, ID_BTN_CREATE, "Create Timing Track"); + _createButton->Enable(false); + _cancelButton = new wxButton(this, ID_BTN_CANCEL, "Cancel"); + buttonSizer->Add(_createButton, 0, wxRIGHT, 5); + buttonSizer->AddStretchSpacer(1); + buttonSizer->Add(_cancelButton, 0, 0, 0); + mainSizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 8); + + SetSizer(mainSizer); + SetMinSize(wxSize(380, 380)); + + // Auto-detect on open + CallAfter([this]() { RunOnsetDetection(); }); +} + +StemOnsetDialog::~StemOnsetDialog() +{ + _debounceTimer.Stop(); + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + _waveform->Refresh(false); + } +} + +void StemOnsetDialog::ApplyBiquadBandpass(const float* input, float* output, long numSamples, + int sampleRate, float lowHz, float highHz) +{ + float centerFreq = std::sqrt(lowHz * highHz); + float bandwidth = highHz - lowHz; + if (bandwidth <= 0) bandwidth = 100.0f; + + float w0 = 2.0f * M_PI * centerFreq / (float)sampleRate; + float sinW0 = std::sin(w0); + float cosW0 = std::cos(w0); + float alpha = sinW0 * std::sinh(std::log(2.0f) / 2.0f * (bandwidth / centerFreq) * (w0 / sinW0)); + + float b0 = alpha; + float b1 = 0.0f; + float b2 = -alpha; + float a0 = 1.0f + alpha; + float a1 = -2.0f * cosW0; + float a2 = 1.0f - alpha; + + // Normalize + b0 /= a0; + b1 /= a0; + b2 /= a0; + a1 /= a0; + a2 /= a0; + + float x1 = 0.0f, x2 = 0.0f; + float y1 = 0.0f, y2 = 0.0f; + + for (long i = 0; i < numSamples; i++) { + float x0 = input[i]; + float y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + output[i] = y0; + x2 = x1; + x1 = x0; + y2 = y1; + y1 = y0; + } +} + +void StemOnsetDialog::RunOnsetDetection() +{ + AudioManager* media = _waveform->GetMedia(); + if (media == nullptr) { + _statusText->SetLabel("No audio data available."); + return; + } + + long trackSize = media->GetTrackSize(); + long sampleRate = media->GetRate(); + if (trackSize <= 0 || sampleRate <= 0) { + _statusText->SetLabel("Invalid audio data."); + return; + } + + SetCursor(wxCURSOR_WAIT); + _statusText->SetLabel("Analyzing audio..."); + wxYield(); + + // Determine frequency band from sliders (not presets, since Custom may be active) + float lowHz = SliderToFreq(_lowFreqSlider->GetValue()); + float highHz = SliderToFreq(_highFreqSlider->GetValue()); + if (lowHz > highHz) std::swap(lowHz, highHz); + + // Determine onset method from current preset + int presetIdx = _presetChoice->GetSelection(); + if (presetIdx < 0 || presetIdx >= NUM_PRESETS) presetIdx = 0; + std::string onsetMethod = PRESETS[presetIdx].onsetMethod; + + // Skip bandpass filter for full-range (20-20000 Hz is effectively no-op) + bool needFilter = (lowHz > 25.0f || highHz < 19000.0f); + + // Get pointer to raw audio samples + float* rawPtr = media->GetRawLeftDataPtr(0); + if (rawPtr == nullptr) { + _statusText->SetLabel("Failed to access audio data."); + SetCursor(wxCURSOR_ARROW); + return; + } + + std::vector audioData(rawPtr, rawPtr + trackSize); + + // Apply bandpass filter if a frequency band is selected + std::vector filteredData; + const float* analysisData = audioData.data(); + if (needFilter) { + filteredData.resize(trackSize); + ApplyBiquadBandpass(audioData.data(), filteredData.data(), trackSize, + (int)sampleRate, lowHz, highHz); + analysisData = filteredData.data(); + } + + // Choose window/hop sizes based on sample rate + uint_t winSize = (sampleRate > 48000) ? 2048 : 1024; + uint_t hopSize = winSize / 2; + + int numFrames = (int)((trackSize - winSize) / hopSize); + if (numFrames <= 0) { + _statusText->SetLabel("Audio too short for onset detection."); + SetCursor(wxCURSOR_ARROW); + return; + } + + // Create aubio objects + aubio_pvoc_t* pv = new_aubio_pvoc(winSize, hopSize); + aubio_specdesc_t* sd = new_aubio_specdesc(onsetMethod.c_str(), winSize); + fvec_t* inputVec = new_fvec(hopSize); + cvec_t* grain = new_cvec(winSize); + fvec_t* onsetOut = new_fvec(1); + + if (pv == nullptr || sd == nullptr) { + spdlog::error("Failed to create aubio phase vocoder or spectral descriptor"); + _statusText->SetLabel("Onset detection failed (aubio init error)."); + if (pv) del_aubio_pvoc(pv); + if (sd) del_aubio_specdesc(sd); + if (inputVec) del_fvec(inputVec); + if (grain) del_cvec(grain); + if (onsetOut) del_fvec(onsetOut); + SetCursor(wxCURSOR_ARROW); + return; + } + + // Process audio in hops through phase vocoder + spectral descriptor + _detectionFunction.clear(); + _detectionFunction.reserve(numFrames); + + for (int f = 0; f < numFrames; f++) { + long offset = (long)f * hopSize; + // Copy hop of audio into aubio input vector + for (uint_t s = 0; s < hopSize && (offset + s) < trackSize; s++) { + inputVec->data[s] = analysisData[offset + s]; + } + // Phase vocoder: time domain -> spectral domain + aubio_pvoc_do(pv, inputVec, grain); + // Spectral descriptor: compute onset detection function value + aubio_specdesc_do(sd, grain, onsetOut); + _detectionFunction.push_back(onsetOut->data[0]); + } + + // Clean up aubio objects + del_fvec(onsetOut); + del_cvec(grain); + del_fvec(inputVec); + del_aubio_specdesc(sd); + del_aubio_pvoc(pv); + + _detectionFunctionHopSec = (float)hopSize / (float)sampleRate; + _detected = true; + + spdlog::debug("Onset detection complete: {} frames, hop={:.3f}s, method={}", + numFrames, _detectionFunctionHopSec, onsetMethod); + + SetCursor(wxCURSOR_ARROW); + ApplyThreshold(); +} + +void StemOnsetDialog::ApplyThreshold() +{ + if (!_detected || _detectionFunction.empty()) return; + + float threshold = (float)_thresholdSlider->GetValue() / 100.0f; + int minIntervalMS = _minIntervalSlider->GetValue(); + float minIntervalSec = (float)minIntervalMS / 1000.0f; + + // Normalize the detection function + float maxVal = *std::max_element(_detectionFunction.begin(), _detectionFunction.end()); + if (maxVal <= 0.0f) { + _onsetTimesMS.clear(); + UpdatePreviewMarkers(); + UpdateStatusText(); + return; + } + + _onsetTimesMS.clear(); + + int minFrameGap = (int)(minIntervalSec / _detectionFunctionHopSec); + if (minFrameGap < 1) minFrameGap = 1; + + float absThreshold = threshold * maxVal; + int lastOnsetFrame = -minFrameGap - 1; + + for (int f = 1; f < (int)_detectionFunction.size() - 1; f++) { + // Peak picking: local maximum above absolute threshold with min gap + if (_detectionFunction[f] > absThreshold && + _detectionFunction[f] >= _detectionFunction[f - 1] && + _detectionFunction[f] >= _detectionFunction[f + 1] && + (f - lastOnsetFrame) >= minFrameGap) { + + float timeSec = (float)f * _detectionFunctionHopSec; + int timeMS = (int)(timeSec * 1000.0f); + _onsetTimesMS.push_back(timeMS); + lastOnsetFrame = f; + } + } + + UpdatePreviewMarkers(); + UpdateStatusText(); +} + +void StemOnsetDialog::UpdatePreviewMarkers() +{ + if (_waveform != nullptr) { + _waveform->SetOnsetMarkers(_onsetTimesMS); + _waveform->render(); + } +} + +void StemOnsetDialog::UpdateStatusText() +{ + _statusText->SetLabel(wxString::Format("Detected %d onsets.", (int)_onsetTimesMS.size())); + _createButton->Enable(!_onsetTimesMS.empty()); +} + +void StemOnsetDialog::OnCreateTimingTrack(wxCommandEvent& event) +{ + if (_onsetTimesMS.empty() || _xlFrame == nullptr) return; + + std::string trackName = _trackNameCtrl->GetValue().ToStdString(); + if (trackName.empty()) { + wxMessageBox("Please enter a timing track name.", "Error", wxOK | wxICON_ERROR, this); + return; + } + + int frameMS = 50; + if (xLightsFrame::CurrentSeqXmlFile != nullptr) { + frameMS = xLightsFrame::CurrentSeqXmlFile->GetFrameMS(); + } + double frequency = 1000.0 / frameMS; + + TimingElement* element = _xlFrame->AddTimingElement(trackName); + if (element == nullptr) return; + + EffectLayer* effectLayer = element->GetEffectLayer(0); + if (effectLayer == nullptr) { + effectLayer = element->AddEffectLayer(); + } + + for (size_t i = 0; i < _onsetTimesMS.size(); i++) { + int startMS = TimeLine::RoundToMultipleOfPeriod(_onsetTimesMS[i], frequency); + int endMS; + if (i + 1 < _onsetTimesMS.size()) { + endMS = TimeLine::RoundToMultipleOfPeriod(_onsetTimesMS[i + 1], frequency); + } else { + int seqEnd = 0; + if (xLightsFrame::CurrentSeqXmlFile != nullptr) { + seqEnd = xLightsFrame::CurrentSeqXmlFile->GetSequenceDurationMS(); + } + endMS = (seqEnd > startMS) ? seqEnd : startMS + frameMS; + } + if (endMS <= startMS) { + endMS = startMS + frameMS; + } + effectLayer->AddEffect(0, "", "", "", startMS, endMS, EFFECT_NOT_SELECTED, false); + } + + wxCommandEvent eventRowHeaderChanged(EVT_ROW_HEADINGS_CHANGED); + wxPostEvent(_xlFrame, eventRowHeaderChanged); + + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + _waveform->Refresh(false); + } + + _statusText->SetLabel(wxString::Format("Created timing track '%s' with %d marks.", + trackName, (int)_onsetTimesMS.size())); + + Destroy(); +} + +void StemOnsetDialog::OnCancel(wxCommandEvent& event) +{ + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + _waveform->Refresh(false); + } + Destroy(); +} + +void StemOnsetDialog::OnClose(wxCloseEvent& event) +{ + _debounceTimer.Stop(); + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + _waveform->Refresh(false); + } + Destroy(); +} + +void StemOnsetDialog::OnThresholdChanged(wxCommandEvent& event) +{ + float val = (float)_thresholdSlider->GetValue() / 100.0f; + _thresholdLabel->SetLabel(wxString::Format("%.2f", val)); + if (_detected) { + ApplyThreshold(); + } +} + +void StemOnsetDialog::OnMinIntervalChanged(wxCommandEvent& event) +{ + int val = _minIntervalSlider->GetValue(); + _minIntervalLabel->SetLabel(wxString::Format("%d ms", val)); + if (_detected) { + ApplyThreshold(); + } +} + +void StemOnsetDialog::OnPresetChanged(wxCommandEvent& event) +{ + int idx = _presetChoice->GetSelection(); + if (idx < 0 || idx >= NUM_PRESETS) return; + + // Update frequency sliders to preset values + _lowFreqSlider->SetValue(FreqToSlider(PRESETS[idx].lowHz)); + _lowFreqLabel->SetLabel(FormatFrequency(PRESETS[idx].lowHz)); + _highFreqSlider->SetValue(FreqToSlider(PRESETS[idx].highHz)); + _highFreqLabel->SetLabel(FormatFrequency(PRESETS[idx].highHz)); + + // Apply preset defaults for threshold and interval + int threshVal = (int)(PRESETS[idx].defaultThreshold * 100.0f); + _thresholdSlider->SetValue(threshVal); + _thresholdLabel->SetLabel(wxString::Format("%.2f", PRESETS[idx].defaultThreshold)); + + _minIntervalSlider->SetValue(PRESETS[idx].defaultMinIntervalMS); + _minIntervalLabel->SetLabel(wxString::Format("%d ms", PRESETS[idx].defaultMinIntervalMS)); + + // Update track name + _trackNameCtrl->SetValue(_stemName + " - " + PRESETS[idx].name); + + // Clear previous detection and re-detect with new preset + _detected = false; + _detectionFunction.clear(); + _onsetTimesMS.clear(); + _createButton->Enable(false); + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + } + + RunOnsetDetection(); +} + +void StemOnsetDialog::OnLowFreqChanged(wxCommandEvent& event) +{ + float hz = SliderToFreq(_lowFreqSlider->GetValue()); + _lowFreqLabel->SetLabel(FormatFrequency(hz)); + + // Ensure low doesn't exceed high + float highHz = SliderToFreq(_highFreqSlider->GetValue()); + if (hz > highHz) { + _highFreqSlider->SetValue(_lowFreqSlider->GetValue()); + _highFreqLabel->SetLabel(FormatFrequency(hz)); + } + + // Switch to Custom preset if not already + if (_presetChoice->GetSelection() != CUSTOM_PRESET_INDEX) { + _presetChoice->SetSelection(CUSTOM_PRESET_INDEX); + _trackNameCtrl->SetValue(_stemName + " - Custom"); + } + + // Debounce: restart timer for stage 1 re-detection + _debounceTimer.Start(150, wxTIMER_ONE_SHOT); +} + +void StemOnsetDialog::OnHighFreqChanged(wxCommandEvent& event) +{ + float hz = SliderToFreq(_highFreqSlider->GetValue()); + _highFreqLabel->SetLabel(FormatFrequency(hz)); + + // Ensure high doesn't go below low + float lowHz = SliderToFreq(_lowFreqSlider->GetValue()); + if (hz < lowHz) { + _lowFreqSlider->SetValue(_highFreqSlider->GetValue()); + _lowFreqLabel->SetLabel(FormatFrequency(hz)); + } + + // Switch to Custom preset if not already + if (_presetChoice->GetSelection() != CUSTOM_PRESET_INDEX) { + _presetChoice->SetSelection(CUSTOM_PRESET_INDEX); + _trackNameCtrl->SetValue(_stemName + " - Custom"); + } + + // Debounce: restart timer for stage 1 re-detection + _debounceTimer.Start(150, wxTIMER_ONE_SHOT); +} + +void StemOnsetDialog::OnDebounceTimer(wxTimerEvent& event) +{ + _detected = false; + _detectionFunction.clear(); + _onsetTimesMS.clear(); + _createButton->Enable(false); + if (_waveform != nullptr) { + _waveform->ClearOnsetMarkers(); + } + + RunOnsetDetection(); +} diff --git a/src-ui-wx/StemOnsetDialog.h b/src-ui-wx/StemOnsetDialog.h new file mode 100644 index 0000000000..f363c096a1 --- /dev/null +++ b/src-ui-wx/StemOnsetDialog.h @@ -0,0 +1,106 @@ +#pragma once + +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include + +class StemWaveform; +class xLightsFrame; + +struct FrequencyBandPreset { + std::string name; + float lowHz; + float highHz; + float defaultThreshold; + int defaultMinIntervalMS; + std::string onsetMethod; +}; + +class StemOnsetDialog : public wxDialog +{ +public: + StemOnsetDialog(wxWindow* parent, StemWaveform* waveform, + xLightsFrame* frame, const std::string& stemName); + virtual ~StemOnsetDialog(); + + const std::vector& GetOnsetTimesMS() const { return _onsetTimesMS; } + +private: + void OnCreateTimingTrack(wxCommandEvent& event); + void OnCancel(wxCommandEvent& event); + void OnThresholdChanged(wxCommandEvent& event); + void OnMinIntervalChanged(wxCommandEvent& event); + void OnPresetChanged(wxCommandEvent& event); + void OnLowFreqChanged(wxCommandEvent& event); + void OnHighFreqChanged(wxCommandEvent& event); + void OnDebounceTimer(wxTimerEvent& event); + void OnClose(wxCloseEvent& event); + + void RunOnsetDetection(); + void ApplyThreshold(); + void UpdatePreviewMarkers(); + void UpdateStatusText(); + + void ApplyBiquadBandpass(const float* input, float* output, long numSamples, + int sampleRate, float lowHz, float highHz); + + static float SliderToFreq(int val); + static int FreqToSlider(float hz); + static wxString FormatFrequency(float hz); + + StemWaveform* _waveform = nullptr; + xLightsFrame* _xlFrame = nullptr; + std::string _stemName; + + wxChoice* _presetChoice = nullptr; + wxSlider* _lowFreqSlider = nullptr; + wxStaticText* _lowFreqLabel = nullptr; + wxSlider* _highFreqSlider = nullptr; + wxStaticText* _highFreqLabel = nullptr; + wxSlider* _thresholdSlider = nullptr; + wxStaticText* _thresholdLabel = nullptr; + wxSlider* _minIntervalSlider = nullptr; + wxStaticText* _minIntervalLabel = nullptr; + wxTextCtrl* _trackNameCtrl = nullptr; + wxButton* _createButton = nullptr; + wxButton* _cancelButton = nullptr; + wxStaticText* _statusText = nullptr; + + wxTimer _debounceTimer; + + // Cached detection function from stage 1 (aubio spectral descriptor output) + std::vector _detectionFunction; + float _detectionFunctionHopSec = 0.0f; + + // Final onset times after thresholding (stage 2) + std::vector _onsetTimesMS; + + bool _detected = false; + + static const FrequencyBandPreset PRESETS[]; + static const int NUM_PRESETS; + static const int CUSTOM_PRESET_INDEX; + + static const long ID_CHOICE_PRESET; + static const long ID_SLIDER_LOW_FREQ; + static const long ID_SLIDER_HIGH_FREQ; + static const long ID_SLIDER_THRESHOLD; + static const long ID_SLIDER_MIN_INTERVAL; + static const long ID_TEXT_TRACK_NAME; + static const long ID_BTN_CREATE; + static const long ID_BTN_CANCEL; + static const long ID_DEBOUNCE_TIMER; + + DECLARE_EVENT_TABLE() +}; diff --git a/src-ui-wx/import_export/SeqFileUtilities.cpp b/src-ui-wx/import_export/SeqFileUtilities.cpp index 6168687452..a174e61771 100644 --- a/src-ui-wx/import_export/SeqFileUtilities.cpp +++ b/src-ui-wx/import_export/SeqFileUtilities.cpp @@ -1217,6 +1217,8 @@ bool xLightsFrame::CloseSequence() if (mainSequencer != nullptr) { if (mainSequencer->PanelWaveForm != nullptr) mainSequencer->PanelWaveForm->CloseMedia(); + if (mainSequencer->GetStemsPanel() != nullptr) + mainSequencer->GetStemsPanel()->ClearRowsUiOnly(); if (mainSequencer->ViewChoice != nullptr) { mainSequencer->ViewChoice->Clear(); mainSequencer->ViewChoice->Show(); diff --git a/src-ui-wx/sequencer/MainSequencer.cpp b/src-ui-wx/sequencer/MainSequencer.cpp index 1f6139679e..d5b2ebd9bb 100755 --- a/src-ui-wx/sequencer/MainSequencer.cpp +++ b/src-ui-wx/sequencer/MainSequencer.cpp @@ -19,6 +19,7 @@ #include #include "MainSequencer.h" +#include "StemsPanel.h" #include "render/SequenceElements.h" #include "xLightsMain.h" #include "xLightsApp.h" @@ -239,15 +240,21 @@ MainSequencer::MainSequencer(wxWindow* parent, bool smallWaveform, wxWindowID id spdlog::debug(" Creating main sequencer"); //(*Initialize(MainSequencer) - wxFlexGridSizer* FlexGridSizer1; wxFlexGridSizer* FlexGridSizer2; wxFlexGridSizer* FlexGridSizer4; wxStaticText* StaticText1; Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxWANTS_CHARS, _T("wxID_ANY")); - FlexGridSizer1 = new wxFlexGridSizer(3, 3, 0, 0); - FlexGridSizer1->AddGrowableCol(1); - FlexGridSizer1->AddGrowableRow(1); + + // Create stems panel first (hidden coordinator that creates sub-panels) + PanelStems = new StemsPanel(this, wxID_ANY); + + // 5-row grid: timeline/waveform | stems | resize handle | effects grid | scrollbar + _mainSizer = new wxFlexGridSizer(5, 3, 0, 0); + _mainSizer->AddGrowableCol(1); + _mainSizer->AddGrowableRow(3); // effects grid row + + // Row 0: view choice area | timeline + waveform | spacer FlexGridSizer2 = new wxFlexGridSizer(0, 1, 0, 0); FlexGridSizer2->AddGrowableCol(0); ViewLabel = new wxStaticText(this, wxID_ANY, _("View:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY")); @@ -256,10 +263,9 @@ MainSequencer::MainSequencer(wxWindow* parent, bool smallWaveform, wxWindowID id ViewChoice = new wxChoice(this, ID_CHOICE_VIEW_CHOICE, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_VIEW_CHOICE")); FlexGridSizer2->Add(ViewChoice, 1, wxBOTTOM|wxLEFT|wxRIGHT|wxEXPAND, 0); FlexGridSizer2->Add(-1,-1,1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); - FlexGridSizer1->Add(FlexGridSizer2, 0, wxEXPAND, 0); + _mainSizer->Add(FlexGridSizer2, 0, wxEXPAND, 0); FlexGridSizer4 = new wxFlexGridSizer(2, 0, 0, 0); FlexGridSizer4->AddGrowableCol(0); - FlexGridSizer4->AddGrowableRow(1); PanelTimeLine = new TimeLine(this, ID_PANEL1, wxDefaultPosition, wxDLG_UNIT(this,wxSize(-1,15)), wxTAB_TRAVERSAL, _T("ID_PANEL1")); PanelTimeLine->SetMinSize(wxDLG_UNIT(this,wxSize(-1,15))); PanelTimeLine->SetMaxSize(wxDLG_UNIT(this,wxSize(-1,15))); @@ -268,24 +274,39 @@ MainSequencer::MainSequencer(wxWindow* parent, bool smallWaveform, wxWindowID id PanelWaveForm->SetMinSize(wxDLG_UNIT(this,wxSize(-1,40))); PanelWaveForm->SetMaxSize(wxDLG_UNIT(this,wxSize(-1,40))); FlexGridSizer4->Add(PanelWaveForm, 1, wxALL|wxEXPAND, 0); - FlexGridSizer1->Add(FlexGridSizer4, 1, wxALL|wxEXPAND, 0); - FlexGridSizer1->Add(-1,-1,1, wxALL|wxEXPAND, 5); + _mainSizer->Add(FlexGridSizer4, 1, wxALL|wxEXPAND, 0); + _mainSizer->Add(-1,-1,1, wxALL|wxEXPAND, 5); + + // Row 1: stem headers | stem waveforms | spacer (hidden when no stems) + _mainSizer->Add(PanelStems->GetStemHeadersPanel(), 0, wxEXPAND, 0); + _mainSizer->Add(PanelStems->GetStemWaveformsPanel(), 1, wxEXPAND, 0); + _mainSizer->Add(0, 0, 0, 0, 0); + + // Row 2: resize handle spanning col 0+1 (hidden when stems hidden) + _mainSizer->Add(0, 0, 0, 0, 0); // empty col 0 + _mainSizer->Add(PanelStems->GetResizeHandle(), 1, wxEXPAND, 0); + _mainSizer->Add(0, 0, 0, 0, 0); // empty col 2 + + // Row 3: row headings | effects grid | vertical scrollbar PanelRowHeadings = new RowHeading(this, ID_PANEL6, wxDefaultPosition, wxDLG_UNIT(this,wxSize(90,-1)), wxTAB_TRAVERSAL, _T("ID_PANEL6")); PanelRowHeadings->SetMinSize(wxDLG_UNIT(this,wxSize(90,-1))); PanelRowHeadings->SetMaxSize(wxDLG_UNIT(this,wxSize(90,-1))); - FlexGridSizer1->Add(PanelRowHeadings, 1, wxALL|wxEXPAND, 0); + PanelStems->SetHeaderWidth(PanelRowHeadings->GetMinSize().GetWidth()); + _mainSizer->Add(PanelRowHeadings, 1, wxALL|wxEXPAND, 0); PanelEffectGrid = new EffectsGrid(this, ID_PANEL2, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxFULL_REPAINT_ON_RESIZE, _T("ID_PANEL2")); - FlexGridSizer1->Add(PanelEffectGrid, 1, wxALL|wxEXPAND, 0); + _mainSizer->Add(PanelEffectGrid, 1, wxALL|wxEXPAND, 0); ScrollBarEffectsVertical = new wxScrollBar(this, ID_SCROLLBAR_EFFECTS_VERTICAL, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL|wxALWAYS_SHOW_SB, wxDefaultValidator, _T("ID_SCROLLBAR_EFFECTS_VERTICAL")); ScrollBarEffectsVertical->SetScrollbar(0, 1, 10, 1); - FlexGridSizer1->Add(ScrollBarEffectsVertical, 1, wxALL|wxEXPAND, 0); + _mainSizer->Add(ScrollBarEffectsVertical, 1, wxALL|wxEXPAND, 0); + + // Row 4: suspend render checkbox | horizontal scrollbar | empty CheckBox_SuspendRender = new wxCheckBox(this, ID_CHECKBOX1, _("Suspend Render"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX1")); CheckBox_SuspendRender->SetValue(false); - FlexGridSizer1->Add(CheckBox_SuspendRender, 1, wxALL|wxEXPAND, 0); + _mainSizer->Add(CheckBox_SuspendRender, 1, wxALL|wxEXPAND, 0); ScrollBarEffectsHorizontal = new wxScrollBar(this, ID_SCROLLBAR_EFFECT_GRID_HORZ, wxDefaultPosition, wxDefaultSize, wxSB_HORIZONTAL|wxALWAYS_SHOW_SB, wxDefaultValidator, _T("ID_SCROLLBAR_EFFECT_GRID_HORZ")); ScrollBarEffectsHorizontal->SetScrollbar(0, 1, 100, 1); - FlexGridSizer1->Add(ScrollBarEffectsHorizontal, 1, wxALL|wxEXPAND, 0); - SetSizer(FlexGridSizer1); + _mainSizer->Add(ScrollBarEffectsHorizontal, 1, wxALL|wxEXPAND, 0); + SetSizer(_mainSizer); Connect(ID_SCROLLBAR_EFFECTS_VERTICAL, wxEVT_SCROLL_TOP|wxEVT_SCROLL_BOTTOM|wxEVT_SCROLL_LINEUP|wxEVT_SCROLL_LINEDOWN|wxEVT_SCROLL_PAGEUP|wxEVT_SCROLL_PAGEDOWN|wxEVT_SCROLL_THUMBTRACK|wxEVT_SCROLL_THUMBRELEASE|wxEVT_SCROLL_CHANGED, (wxObjectEventFunction)&MainSequencer::OnScrollBarEffectsVerticalScrollChanged); Connect(ID_SCROLLBAR_EFFECTS_VERTICAL, wxEVT_SCROLL_TOP, (wxObjectEventFunction)&MainSequencer::OnScrollBarEffectsVerticalScrollChanged); @@ -2068,10 +2089,13 @@ void MainSequencer::TimelineChanged( wxCommandEvent& event) TimelineChangeArguments *tla = (TimelineChangeArguments*)(event.GetClientData()); PanelWaveForm->SetZoomLevel(tla->ZoomLevel); PanelWaveForm->SetStartPixelOffset(tla->StartPixelOffset); + PanelStems->SetZoomLevel(tla->ZoomLevel); + PanelStems->SetStartPixelOffset(tla->StartPixelOffset); UpdateTimeDisplay(tla->CurrentTimeMS, {}); PanelTimeLine->Refresh(); PanelTimeLine->Update(); PanelWaveForm->render(); + PanelStems->ForceRedraw(); PanelEffectGrid->SetStartPixelOffset(tla->StartPixelOffset); PanelEffectGrid->Draw(); UpdateEffectGridHorizontalScrollBar(); @@ -2082,12 +2106,15 @@ void MainSequencer::UpdateEffectGridHorizontalScrollBar() { PanelWaveForm->SetZoomLevel(PanelTimeLine->GetZoomLevel()); PanelWaveForm->SetStartPixelOffset(PanelTimeLine->GetStartPixelOffset()); + PanelStems->SetZoomLevel(PanelTimeLine->GetZoomLevel()); + PanelStems->SetStartPixelOffset(PanelTimeLine->GetStartPixelOffset()); UpdateTimeDisplay(PanelTimeLine->GetCurrentPlayMarkerMS(), {}); //printf("%d\n", PanelTimeLine->GetStartPixelOffset()); PanelTimeLine->Refresh(); PanelTimeLine->Update(); PanelWaveForm->render(); + PanelStems->ForceRedraw(); PanelEffectGrid->SetStartPixelOffset(PanelTimeLine->GetStartPixelOffset()); PanelEffectGrid->Draw(); diff --git a/src-ui-wx/sequencer/MainSequencer.h b/src-ui-wx/sequencer/MainSequencer.h index f31fb7760a..4c971f6f3d 100644 --- a/src-ui-wx/sequencer/MainSequencer.h +++ b/src-ui-wx/sequencer/MainSequencer.h @@ -18,6 +18,7 @@ #include "RowHeading.h" #include "EffectsGrid.h" #include "Waveform.h" +#include "StemsPanel.h" #include "app-shell/KeyBindings.h" #if __has_include("osxUtils/TouchBars.h") @@ -91,6 +92,8 @@ class MainSequencer: public wxPanel void SetShowAlternateTimingMark(bool b); int GetActiveAudioTrackIndex() const { return PanelWaveForm ? PanelWaveForm->GetActiveAudioTrackIndex() : 0; } + StemsPanel* GetStemsPanel() { return PanelStems; } + void TouchButtonEvent(wxCommandEvent &event); void ToggleHousePreview(); void ToggleModelPreview(); @@ -101,6 +104,7 @@ class MainSequencer: public wxPanel RowHeading* PanelRowHeadings; TimeLine* PanelTimeLine; Waveform* PanelWaveForm; + StemsPanel* PanelStems; wxCheckBox* CheckBox_SuspendRender; wxChoice* ViewChoice; wxStaticText* ViewLabel; @@ -159,6 +163,7 @@ class MainSequencer: public wxPanel void RestorePosition(); wxWindow *mParent; + wxFlexGridSizer* _mainSizer = nullptr; SequenceElements* mSequenceElements; wxSearchCtrl* _seqFilterCtrl = nullptr; diff --git a/src-ui-wx/sequencer/RowHeading.cpp b/src-ui-wx/sequencer/RowHeading.cpp index 93b7d2b176..457ed903aa 100644 --- a/src-ui-wx/sequencer/RowHeading.cpp +++ b/src-ui-wx/sequencer/RowHeading.cpp @@ -310,6 +310,11 @@ void RowHeading::SetWidth(int w) if (w < _minRowHeadingWidth) w = _minRowHeadingWidth; if (minSize.GetWidth() != w) { SetMinSize(wxSize(w, -1)); + // Propagate width to stems header panel + MainSequencer* ms = dynamic_cast(GetParent()); + if (ms && ms->GetStemsPanel()) { + ms->GetStemsPanel()->SetHeaderWidth(w); + } GetParent()->Layout(); } } diff --git a/src-ui-wx/sequencer/StemWaveform.cpp b/src-ui-wx/sequencer/StemWaveform.cpp new file mode 100644 index 0000000000..1ad5b29941 --- /dev/null +++ b/src-ui-wx/sequencer/StemWaveform.cpp @@ -0,0 +1,553 @@ +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include + +#include "StemWaveform.h" +#include "TimeLine.h" +#include "Waveform.h" +#include "xLightsApp.h" +#include "xLightsMain.h" +#include "color/ColorManager.h" + + +BEGIN_EVENT_TABLE(StemWaveform, GRAPHICS_BASE_CLASS) +EVT_LEFT_DOWN(StemWaveform::mouseLeftDown) +EVT_LEFT_UP(StemWaveform::mouseLeftUp) +EVT_MOTION(StemWaveform::mouseMoved) +EVT_MOUSE_CAPTURE_LOST(StemWaveform::OnLostMouseCapture) +EVT_LEAVE_WINDOW(StemWaveform::mouseLeftWindow) +EVT_MOUSEWHEEL(StemWaveform::mouseWheelMoved) +EVT_SIZE(StemWaveform::Resized) +EVT_PAINT(StemWaveform::Paint) +END_EVENT_TABLE() + +StemWaveform::StemWaveform(wxPanel* parent, wxWindowID id, + const wxPoint& pos, const wxSize& size) + : GRAPHICS_BASE_CLASS(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, "StemWaveform"), + _parent(parent), + _stemColor(130, 178, 207) +{ + _dragging = false; + _rowHeight = 32; +} + +StemWaveform::~StemWaveform() +{ + CloseMedia(); +} + +bool StemWaveform::LoadMedia(const std::string& filepath, wxString& error) +{ + spdlog::debug("[DEBUG: StemWaveform::LoadMedia starting '{}']", filepath); + CloseMedia(); + + spdlog::debug("[DEBUG: StemWaveform::LoadMedia creating AudioManager]"); + _media = new AudioManager(filepath); + if (!_media->IsOk()) { + error = wxString::Format("Failed to load stem audio: %s", filepath); + delete _media; + _media = nullptr; + return false; + } + + _stemFilePath = filepath; + _pendingMediaInit = true; + _shimmerPhase = 0.0f; + spdlog::debug("[DEBUG: StemWaveform::LoadMedia done, pending async init]"); + return true; +} + +void StemWaveform::CloseMedia() +{ + _overviewBuckets.clear(); + _overviewReady = false; + _pendingMediaInit = false; + _waveBackground.reset(); + _waveOutline.reset(); + _cacheRenderStart = -1; + _cacheRenderSize = 0; + if (_media != nullptr) { + delete _media; + _media = nullptr; + } +} + +void StemWaveform::ComputeOverviewBuckets() +{ + if (!_media || !_media->IsOk()) return; + + long trackSize = _media->GetTrackSize(); + if (trackSize <= 0) return; + + // Get raw data pointer directly — avoids 32768 GetLeftDataMinMax calls + // each of which acquires a mutex and does a linear search + FilteredAudioData* fad = _media->GetFilteredAudioData(AUDIOSAMPLETYPE::RAW, -1, -1); + if (!fad || !fad->data0) return; + + float* data = fad->data0; + _samplesPerBucket = (float)trackSize / (float)OVERVIEW_BUCKET_COUNT; + _overviewBuckets.resize(OVERVIEW_BUCKET_COUNT); + + for (int b = 0; b < OVERVIEW_BUCKET_COUNT; b++) { + long start = (long)((float)b * _samplesPerBucket); + long end = (long)((float)(b + 1) * _samplesPerBucket); + if (end > trackSize) end = trackSize; + if (start >= trackSize) { + _overviewBuckets[b].min = 0; + _overviewBuckets[b].max = 0; + continue; + } + + float minVal = 0.0f; + float maxVal = 0.0f; + for (long j = start; j < end; j++) { + float v = data[j]; + if (v < minVal) minVal = v; + if (v > maxVal) maxVal = v; + } + _overviewBuckets[b].min = minVal; + _overviewBuckets[b].max = maxVal; + } + + _overviewReady = true; +} + +void StemWaveform::GetMinMaxForPixel(int pixel, float& minVal, float& maxVal) +{ + float samplesPerPixel = GetSamplesPerLineFromZoomLevel(_zoomLevel); + float pixelOffset = translateOffset(_startPixelOffset); + float startSample = (pixel + pixelOffset) * samplesPerPixel; + float endSample = startSample + samplesPerPixel; + + long trackSize = _media->GetTrackSize(); + if (startSample >= trackSize || endSample <= 0) { + minVal = 0; + maxVal = 0; + return; + } + if (startSample < 0) startSample = 0; + if (endSample > trackSize) endSample = (float)trackSize; + + if (_overviewReady && samplesPerPixel > _samplesPerBucket) { + int startBucket = (int)(startSample / _samplesPerBucket); + int endBucket = (int)(endSample / _samplesPerBucket); + if (startBucket < 0) startBucket = 0; + if (endBucket >= OVERVIEW_BUCKET_COUNT) endBucket = OVERVIEW_BUCKET_COUNT - 1; + + minVal = 0.0f; + maxVal = 0.0f; + for (int b = startBucket; b <= endBucket; b++) { + if (_overviewBuckets[b].min < minVal) minVal = _overviewBuckets[b].min; + if (_overviewBuckets[b].max > maxVal) maxVal = _overviewBuckets[b].max; + } + } else { + // Direct data access for zoomed-in view (few samples per pixel) + FilteredAudioData* fad = _media->GetFilteredAudioData(AUDIOSAMPLETYPE::RAW, -1, -1); + if (!fad || !fad->data0) { + minVal = 0; + maxVal = 0; + return; + } + minVal = 0.0f; + maxVal = 0.0f; + long s = (long)startSample; + long e = std::min((long)endSample, trackSize); + for (long j = s; j < e; j++) { + float v = fad->data0[j]; + if (v < minVal) minVal = v; + if (v > maxVal) maxVal = v; + } + } +} + +void StemWaveform::SetZoomLevel(int level) +{ + _zoomLevel = level; + if (_pendingMediaInit) return; + InvalidateCache(); +} + +int StemWaveform::SetStartPixelOffset(int offset) +{ + if (_startPixelOffset != offset) { + _startPixelOffset = offset; + InvalidateCache(); + } + return _startPixelOffset; +} + +void StemWaveform::SetTimeFrequency(int frequency) +{ + _frequency = frequency; +} + +void StemWaveform::SetRowHeight(int height) +{ + _rowHeight = height; + SetMinSize(wxSize(-1, _rowHeight)); + SetMaxSize(wxSize(-1, _rowHeight)); + + // Don't set mWindowHeight directly — base class Resized handler + // applies GetContentScaleFactor() for Retina displays + mWindowResized = true; + + InvalidateCache(); + Refresh(false); +} + +void StemWaveform::ForceRedraw() +{ + InvalidateCache(); +} + +void StemWaveform::UpdatePlayMarker() +{ + render(); +} + +xlColor StemWaveform::ClearBackgroundColor() const +{ + return ColorManager::instance()->GetColor(ColorManager::COLOR_WAVEFORM_BACKGROUND); +} + +void StemWaveform::Paint(wxPaintEvent& event) +{ + wxPaintDC(this); + render(); +} + +float StemWaveform::translateOffset(float f) +{ + if (drawingUsingLogicalSize()) { + return f; + } + return translateToBacking(f); +} + +float StemWaveform::GetSamplesPerLineFromZoomLevel(int zoomLevel) const +{ + int periodsPerMajorHash = TimeLine::ZoomLevelValues[zoomLevel]; + float timePerPixel = ((float)periodsPerMajorHash / (float)_frequency) / (float)PIXELS_PER_MAJOR_HASH; + if (!drawingUsingLogicalSize()) { + timePerPixel /= GetContentScaleFactor(); + } + if (_media != nullptr) { + return timePerPixel * (float)_media->GetRate(); + } + return 0.0f; +} + +void StemWaveform::InvalidateCache() +{ + _cacheRenderStart = -1; + _cacheRenderSize = 0; + Refresh(false); +} + +void StemWaveform::render() +{ + if (!IsShown()) return; + + // Check if we're within visible bounds of the outer container + wxWindow* outerPanel = GetParent() ? GetParent()->GetParent() : nullptr; + if (outerPanel) { + wxPoint pos = GetParent()->GetPosition(); + wxPoint myPos = GetPosition(); + int absY = pos.y + myPos.y; + int outerH = outerPanel->GetSize().GetHeight(); + if (absY + _rowHeight < 0 || absY > outerH) return; + } + + if (!mIsInitialized) { + PrepareCanvas(); + } + + // Deferred media init — complete once audio data has finished loading + if (_pendingMediaInit && _media != nullptr) { + if (_media->IsDataLoaded()) { + if (_media->IsOk()) { + _media->SwitchTo(AUDIOSAMPLETYPE::RAW); + ComputeOverviewBuckets(); + } + _pendingMediaInit = false; + _shimmerPhase = 0.0f; + } + } + + xlGraphicsContext* ctx = PrepareContextForDrawing(); + if (ctx == nullptr) return; + ctx->SetViewport(0, 0, mWindowWidth, mWindowHeight); + + if (_pendingMediaInit) { + // Draw loading shimmer while audio is being decoded + float w = (float)mWindowWidth; + float h = (float)mWindowHeight; + + float bandWidth = w * 0.25f; + float bandCenter = _shimmerPhase * (w + bandWidth) - bandWidth * 0.5f; + + xlColor bgColor(30, 30, 35); + xlColor shimmerColor(55, 55, 65); + + auto* vca = ctx->createVertexColorAccumulator(); + vca->PreAlloc(18); + + float leftEdge = std::max(0.0f, bandCenter - bandWidth * 0.5f); + if (leftEdge > 0) { + vca->AddVertex(0, 0, bgColor); vca->AddVertex(leftEdge, 0, bgColor); vca->AddVertex(0, h, bgColor); + vca->AddVertex(leftEdge, 0, bgColor); vca->AddVertex(leftEdge, h, bgColor); vca->AddVertex(0, h, bgColor); + } + + float sl = std::max(0.0f, bandCenter - bandWidth * 0.5f); + float sm = std::min(w, std::max(0.0f, bandCenter)); + float sr = std::min(w, bandCenter + bandWidth * 0.5f); + + vca->AddVertex(sl, 0, bgColor); vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sl, h, bgColor); + vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sm, h, shimmerColor); vca->AddVertex(sl, h, bgColor); + vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sr, 0, bgColor); vca->AddVertex(sm, h, shimmerColor); + vca->AddVertex(sr, 0, bgColor); vca->AddVertex(sr, h, bgColor); vca->AddVertex(sm, h, shimmerColor); + + float rightEdge = std::min(w, bandCenter + bandWidth * 0.5f); + if (rightEdge < w) { + vca->AddVertex(rightEdge, 0, bgColor); vca->AddVertex(w, 0, bgColor); vca->AddVertex(rightEdge, h, bgColor); + vca->AddVertex(w, 0, bgColor); vca->AddVertex(w, h, bgColor); vca->AddVertex(rightEdge, h, bgColor); + } + + vca->Finalize(false, false); + ctx->drawTriangles(vca); + delete vca; + + _shimmerPhase += 0.02f; + if (_shimmerPhase > 1.0f) _shimmerPhase = 0.0f; + CallAfter([this]() { Refresh(); }); + } else if (_overviewReady) { + DrawWaveform(ctx); + } + + FinishDrawing(ctx); +} + +void StemWaveform::DrawWaveform(xlGraphicsContext* ctx) +{ + // Draw border + if (!_border) { + _border = ctx->createVertexAccumulator(); + _border->PreAlloc(5); + _border->AddVertex(0.25, 0.25, 0); + _border->AddVertex(mWindowWidth - 0.5, 0.25, 0); + _border->AddVertex(mWindowWidth - 0.5, mWindowHeight - 0.5, 0); + _border->AddVertex(0.25, mWindowHeight - 0.5, 0); + _border->AddVertex(0.25, 0.25, 0); + _border->Finalize(true); + } else if (mWindowResized) { + _border->SetVertex(1, mWindowWidth - 0.5, 0.25, 0); + _border->SetVertex(2, mWindowWidth - 0.5, mWindowHeight - 0.5, 0); + _border->SetVertex(3, 0.25, mWindowHeight - 0.5, 0); + _border->FlushRange(1, 3); + } + + xlColor borderColor(80, 80, 80); + ctx->drawLineStrip(_border, borderColor); + + int max_wave_ht = mWindowHeight - 4; + + if (_media != nullptr && _overviewReady) { + if (_startPixelOffset != _cacheRenderStart || (int)mWindowWidth != _cacheRenderSize) { + if (!_waveBackground) { + _waveBackground.reset(ctx->createVertexAccumulator()); + _waveBackground->SetName("StemFill"); + _waveOutline.reset(ctx->createVertexAccumulator()); + _waveOutline->SetName("StemLines"); + } + _waveBackground->Reset(); + _waveOutline->Reset(); + _waveBackground->PreAlloc((mWindowWidth + 2) * 2); + _waveOutline->PreAlloc((mWindowWidth + 2) + 4); + + std::vector vertexes(mWindowWidth + 2); + + for (int x = 0; x < (int)mWindowWidth; x++) { + float minVal, maxVal; + GetMinMaxForPixel(x, minVal, maxVal); + + double y1 = minVal * ((double)max_wave_ht / 2.0) + (mWindowHeight / 2.0); + double y2 = maxVal * ((double)max_wave_ht / 2.0) + (mWindowHeight / 2.0); + + _waveBackground->AddVertex(x, y1); + _waveBackground->AddVertex(x, y2); + _waveOutline->AddVertex(x, y1); + vertexes[x] = y2; + } + for (int x = (int)mWindowWidth - 1; x >= 0; x--) { + _waveOutline->AddVertex(x, vertexes[x]); + } + + _cacheRenderSize = (int)mWindowWidth; + _cacheRenderStart = _startPixelOffset; + _waveBackground->FlushRange(0, _waveBackground->getCount()); + _waveOutline->FlushRange(0, _waveOutline->getCount()); + } + + xlColor fillColor = _stemColor; + fillColor.SetAlpha(180); + if (_waveBackground && _waveBackground->getCount()) { + ctx->enableBlending(); + ctx->drawTriangleStrip(_waveBackground.get(), fillColor); + ctx->disableBlending(); + } + + xlColor outlineColor = _stemColor; + if (_waveOutline && _waveOutline->getCount()) { + ctx->drawLineStrip(_waveOutline.get(), outlineColor); + } + } + + // Draw onset preview markers + if (!_onsetMarkersMS.empty() && _timeline != nullptr) { + xlVertexColorAccumulator* onsetVac = ctx->createVertexColorAccumulator(); + xlColor onsetColor(0, 220, 220, 200); // cyan + for (int ms : _onsetMarkersMS) { + int pos = _timeline->GetPositionFromTimeMS(ms); + if (pos >= 0 && pos < (int)mWindowWidth) { + float f = translateOffset(pos); + onsetVac->AddVertex(f, 1, 0, onsetColor); + onsetVac->AddVertex(f, mWindowHeight - 1, 0, onsetColor); + } + } + if (onsetVac->getCount() > 0) { + ctx->enableBlending(); + ctx->drawLines(onsetVac); + ctx->disableBlending(); + } + delete onsetVac; + } + + // Draw play marker + if (_timeline != nullptr) { + xlVertexColorAccumulator* vac = ctx->createVertexColorAccumulator(); + int play_marker = _timeline->GetPlayMarker(); + if (play_marker != -1) { + xlColor c(0, 0, 0, 255); + float f = translateOffset(play_marker); + vac->AddVertex(f, 1, 0, c); + vac->AddVertex(f, mWindowHeight - 1, 0, c); + } + + int mouse_marker = _timeline->GetMousePosition(); + if (mouse_marker != -1) { + xlColor mc(0, 0, 255, 128); + if (xLightsApp::GetFrame() != nullptr) { + mc = xLightsApp::GetFrame()->color_mgr.GetColor(ColorManager::COLOR_WAVEFORM_MOUSE_MARKER); + mc.SetAlpha(128); + } + float f = translateOffset(mouse_marker); + vac->AddVertex(f, 1, 0, mc); + vac->AddVertex(f, mWindowHeight - 1, 0, mc); + } + + if (vac->getCount() > 0) { + ctx->drawLines(vac); + } + delete vac; + } + + mWindowResized = false; +} + +void StemWaveform::mouseLeftDown(wxMouseEvent& event) +{ + if (!mIsInitialized || _timeline == nullptr) return; + + if (!_dragging) { + _dragging = true; + CaptureMouse(); + } + _timeline->SetSelectedPositionStart(event.GetX()); + SetFocus(); + Refresh(false); + + wxCommandEvent eventSelected(EVT_WAVE_FORM_HIGHLIGHT); + eventSelected.SetInt(0); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventSelected); +} + +void StemWaveform::mouseLeftUp(wxMouseEvent& event) +{ + if (_dragging) { + ReleaseMouse(); + _dragging = false; + } + if (_timeline != nullptr) { + _timeline->LatchSelectedPositions(); + } + Refresh(false); +} + +void StemWaveform::mouseMoved(wxMouseEvent& event) +{ + if (!mIsInitialized || _timeline == nullptr) return; + + if (_dragging) { + _timeline->SetSelectedPositionEnd(event.GetX()); + Refresh(false); + + wxCommandEvent eventSelected(EVT_WAVE_FORM_HIGHLIGHT); + eventSelected.SetInt(abs(_timeline->GetNewStartTimeMS() - _timeline->GetNewEndTimeMS())); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventSelected); + } + + int mouseTimeMS = _timeline->GetAbsoluteTimeMSfromPosition(event.GetX()); + wxCommandEvent eventMousePos(EVT_MOUSE_POSITION); + eventMousePos.SetInt(mouseTimeMS); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventMousePos); +} + +void StemWaveform::mouseWheelMoved(wxMouseEvent& event) +{ + if (event.CmdDown()) { + int i = event.GetWheelRotation(); + wxCommandEvent eventZoom(EVT_ZOOM); + eventZoom.SetInt(i < 0 ? ZOOM_OUT : ZOOM_IN); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventZoom); +#ifdef __WXOSX__ + } else if (event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL) { + int i = event.GetWheelRotation(); + wxCommandEvent eventScroll(EVT_GSCROLL); + eventScroll.SetInt(i > 0 ? SCROLL_RIGHT : SCROLL_LEFT); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventScroll); +#endif + } else if (event.ShiftDown()) { + int i = event.GetWheelRotation(); + wxCommandEvent eventScroll(EVT_GSCROLL); + eventScroll.SetInt(i < 0 ? SCROLL_RIGHT : SCROLL_LEFT); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventScroll); + } else { + // Forward vertical scroll to StemsPanel for stem area scrolling + // Go up from waveform → _waveformsInner → _stemWaveformsOuter → find handler + wxWindow* target = _eventTarget ? _eventTarget : _parent; + if (target) { + target->GetEventHandler()->SafelyProcessEvent(event); + } + } +} + +void StemWaveform::OnLostMouseCapture(wxMouseCaptureLostEvent& event) +{ + _dragging = false; +} + +void StemWaveform::mouseLeftWindow(wxMouseEvent& event) +{ + wxCommandEvent eventMousePos(EVT_MOUSE_POSITION); + eventMousePos.SetInt(-1); + wxPostEvent(_eventTarget ? _eventTarget : _parent, eventMousePos); +} diff --git a/src-ui-wx/sequencer/StemWaveform.h b/src-ui-wx/sequencer/StemWaveform.h new file mode 100644 index 0000000000..eb7c486a06 --- /dev/null +++ b/src-ui-wx/sequencer/StemWaveform.h @@ -0,0 +1,122 @@ +#pragma once + +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include + +#include "graphics/xlGraphicsBase.h" +#include "graphics/xlGraphicsContext.h" +#include "media/AudioManager.h" + +class TimeLine; + +class StemWaveform : public GRAPHICS_BASE_CLASS +{ +public: + StemWaveform(wxPanel* parent, wxWindowID id, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize); + virtual ~StemWaveform(); + + bool LoadMedia(const std::string& filepath, wxString& error); + void CloseMedia(); + + void SetStemName(const std::string& name) { _stemName = name; } + std::string GetStemName() const { return _stemName; } + + void SetStemColor(const xlColor& color) { _stemColor = color; InvalidateCache(); } + xlColor GetStemColor() const { return _stemColor; } + + void SetStemFilePath(const std::string& path) { _stemFilePath = path; } + std::string GetStemFilePath() const { return _stemFilePath; } + + void SetTimeline(TimeLine* timeline) { _timeline = timeline; } + void SetEventTarget(wxWindow* target) { _eventTarget = target; } + void SetZoomLevel(int level); + int SetStartPixelOffset(int offset); + void SetTimeFrequency(int frequency); + + void SetRowHeight(int height); + int GetRowHeight() const { return _rowHeight; } + + void ForceRedraw(); + void UpdatePlayMarker(); + + AudioManager* GetMedia() const { return _media; } + + // Onset detection preview markers + void SetOnsetMarkers(const std::vector& timesMS) { _onsetMarkersMS = timesMS; } + void ClearOnsetMarkers() { _onsetMarkersMS.clear(); } + bool HasOnsetMarkers() const { return !_onsetMarkersMS.empty(); } + + virtual xlColor ClearBackgroundColor() const override; + void render() override; + +protected: + DECLARE_EVENT_TABLE() + +private: + struct OverviewBucket { + float min; + float max; + }; + + void ComputeOverviewBuckets(); + void GetMinMaxForPixel(int pixel, float& minVal, float& maxVal); + void DrawWaveform(xlGraphicsContext* ctx); + void Paint(wxPaintEvent& event); + void mouseLeftDown(wxMouseEvent& event); + void mouseLeftUp(wxMouseEvent& event); + void mouseMoved(wxMouseEvent& event); + void mouseWheelMoved(wxMouseEvent& event); + void OnLostMouseCapture(wxMouseCaptureLostEvent& event); + void mouseLeftWindow(wxMouseEvent& event); + void InvalidateCache(); + float GetSamplesPerLineFromZoomLevel(int zoomLevel) const; + float translateOffset(float f); + + xlVertexAccumulator* _border = nullptr; + std::unique_ptr _waveBackground; + std::unique_ptr _waveOutline; + int _cacheRenderStart = -1; + int _cacheRenderSize = 0; + + TimeLine* _timeline = nullptr; + wxPanel* _parent = nullptr; + wxWindow* _eventTarget = nullptr; + AudioManager* _media = nullptr; + + std::string _stemName; + std::string _stemFilePath; + xlColor _stemColor; + + int _zoomLevel = 0; + int _startPixelOffset = 0; + int _frequency = 40; + int _rowHeight = 32; + bool _dragging = false; + + // Async loading + bool _pendingMediaInit = false; + float _shimmerPhase = 0.0f; + + // Overview buckets for smooth zooming + static const int OVERVIEW_BUCKET_COUNT = 32768; + std::vector _overviewBuckets; + bool _overviewReady = false; + float _samplesPerBucket = 0.0f; + + // Onset detection preview markers + std::vector _onsetMarkersMS; +}; diff --git a/src-ui-wx/sequencer/StemsPanel.cpp b/src-ui-wx/sequencer/StemsPanel.cpp new file mode 100644 index 0000000000..17f3905c41 --- /dev/null +++ b/src-ui-wx/sequencer/StemsPanel.cpp @@ -0,0 +1,1360 @@ +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StemsPanel.h" +#include "StemWaveform.h" +#include "TimeLine.h" +#include "Waveform.h" +#include "RowHeading.h" +#include "MainSequencer.h" +#include "UtilFunctions.h" +#include "StemOnsetDialog.h" +#include "xLightsApp.h" +#include "xLightsMain.h" +#include "shared/utils/wxUtilities.h" +#include "../../src-core/render/SequenceFile.h" + + +wxDEFINE_EVENT(EVT_STEMS_CHANGED, wxCommandEvent); + +const long StemsPanel::ID_MNU_IMPORT_FILES = wxNewId(); +const long StemsPanel::ID_MNU_IMPORT_FOLDER = wxNewId(); +const long StemsPanel::ID_MNU_REMOVE_STEM = wxNewId(); +const long StemsPanel::ID_MNU_REMOVE_ALL = wxNewId(); +const long StemsPanel::ID_MNU_MOVE_UP = wxNewId(); +const long StemsPanel::ID_MNU_MOVE_DOWN = wxNewId(); +const long StemsPanel::ID_MNU_RENAME = wxNewId(); +const long StemsPanel::ID_MNU_RECOLOR = wxNewId(); +const long StemsPanel::ID_MNU_SET_HEIGHT = wxNewId(); +const long StemsPanel::ID_MNU_ONSET_DETECT = wxNewId(); + +BEGIN_EVENT_TABLE(StemsPanel, wxPanel) +END_EVENT_TABLE() + +static const xlColor DEFAULT_STEM_COLORS[] = { + xlColor(0xFF, 0x44, 0x44), // Red - Vocals + xlColor(0x44, 0xFF, 0x44), // Green - Drums + xlColor(0x44, 0x88, 0xFF), // Blue - Bass + xlColor(0xFF, 0xAA, 0x22), // Orange - Other + xlColor(0xCC, 0x44, 0xFF), // Purple + xlColor(0x22, 0xFF, 0xCC), // Teal + xlColor(0xFF, 0xFF, 0x44), // Yellow + xlColor(0xFF, 0x44, 0xAA), // Pink +}; +static const int NUM_DEFAULT_COLORS = sizeof(DEFAULT_STEM_COLORS) / sizeof(DEFAULT_STEM_COLORS[0]); + +static const int RESIZE_HANDLE_HEIGHT = 5; +static const int MIN_PANEL_HEIGHT = 24; +static const int MAX_PANEL_HEIGHT = 400; +static const int COLLAPSED_HEIGHT = 6; + +// Font sizing to match RowHeading exactly +static float ComputeStemFontSize() { + float fontSize = 15.0f * DEFAULT_ROW_HEADING_HEIGHT / 22.0f; + if (fontSize < 9) fontSize = 8; + return fontSize; +} + +#ifndef __WXMSW__ +static void SetStemFontPixelSize(wxFont &font, float f) { + float i = font.GetPixelSize().y; + float p = font.GetFractionalPointSize(); + float points = f * p / i; + font.SetFractionalPointSize(points); +} +#else +static void SetStemFontPixelSize(wxFont &font, float f) { + wxSize sz(0, (int)(std::round(f * 0.8f))); + font.SetPixelSize(sz); +} +#endif + +// ========================================================================= +// StemsPanel +// ========================================================================= + +StemsPanel::StemsPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) + : wxPanel(parent, id, pos, size, wxTAB_TRAVERSAL | wxBORDER_NONE) +{ + Hide(); + + // Outer headers panel: clips content, no scrollbar + _stemHeadersOuter = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxTAB_TRAVERSAL | wxBORDER_NONE | wxCLIP_CHILDREN); + _stemHeadersOuter->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); + _headerWindow = new StemHeaderWindow(_stemHeadersOuter, this); + wxBoxSizer* hdrSizer = new wxBoxSizer(wxVERTICAL); + hdrSizer->Add(_headerWindow, 1, wxEXPAND, 0); + _stemHeadersOuter->SetSizer(hdrSizer); + _stemHeadersOuter->Hide(); + + // Outer waveforms panel: clips content, no sizer — inner panel sized/positioned manually + _stemWaveformsOuter = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxTAB_TRAVERSAL | wxBORDER_NONE | wxCLIP_CHILDREN); + _stemWaveformsOuter->SetBackgroundColour(wxColour(30, 30, 35)); + _waveformsInner = new wxPanel(_stemWaveformsOuter, wxID_ANY); + _waveformsInner->SetBackgroundColour(wxColour(30, 30, 35)); + _waveformsSizer = new wxBoxSizer(wxVERTICAL); + _waveformsInner->SetSizer(_waveformsSizer); + // No outer sizer — we manage _waveformsInner position/size manually for scrolling + _stemWaveformsOuter->Hide(); + + // Resize handle between stems and effects grid + _resizeHandle = new StemResizeHandle(parent, this); + _resizeHandle->Hide(); + + // When outer waveforms panel resizes, update inner panel width + _stemWaveformsOuter->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) { + evt.Skip(); + wxSize sz = evt.GetSize(); + if (sz.GetWidth() > 0 && !_stems.empty()) { + int contentHeight = GetTotalContentHeight(); + _waveformsInner->SetSize(sz.GetWidth(), contentHeight); + _waveformsInner->Layout(); + } + }); + + // Mouse wheel on both outer panels for scrolling + _stemHeadersOuter->Bind(wxEVT_MOUSEWHEEL, &StemsPanel::OnMouseWheel, this); + _stemWaveformsOuter->Bind(wxEVT_MOUSEWHEEL, &StemsPanel::OnMouseWheel, this); + + // Right-click on waveforms panel background + _stemWaveformsOuter->Bind(wxEVT_RIGHT_DOWN, &StemsPanel::OnRightDown, this); +} + +StemsPanel::~StemsPanel() +{ + // Tear down rows without touching SequenceFile::alt_tracks — the panel + // is a view; the model owns the data and will free it on its own + // teardown. Calling RemoveAllStems() here would mutate the sequence on + // shutdown and dirty it. + ClearRowsUiOnly(); +} + +xlColor StemsPanel::GetDefaultStemColor(int index) const +{ + return DEFAULT_STEM_COLORS[index % NUM_DEFAULT_COLORS]; +} + +static SequenceFile* CurrentSeqFile() +{ + auto* frame = xLightsApp::GetFrame(); + return frame != nullptr ? frame->CurrentSeqXmlFile : nullptr; +} + +static Waveform* MainWaveform() +{ + auto* frame = xLightsApp::GetFrame(); + if (frame == nullptr) return nullptr; + auto* ms = frame->GetMainSequencer(); + return ms != nullptr ? ms->PanelWaveForm : nullptr; +} + +bool StemsPanel::AddStem(const std::string& name, const std::string& filepath, const xlColor& color) +{ + spdlog::debug("[DEBUG: StemsPanel::AddStem '{}' from '{}']", name, filepath); + + SequenceFile* sf = CurrentSeqFile(); + if (sf == nullptr) { + spdlog::warn("StemsPanel::AddStem called with no current sequence"); + return false; + } + + sf->AddAltTrack(_showDir, filepath, name); + int altIdx = sf->GetAltTrackCount() - 1; + if (altIdx < 0) return false; + + // The on-disk path may have been canonicalised by AddAltTrack (FixFile). + std::string resolvedPath = sf->GetAltTrack(altIdx).path; + std::string finalName = sf->GetAltTrackDisplayName(altIdx); + + StemInfo stem; + stem.name = finalName; + stem.path = resolvedPath; + stem.color = color; + stem.altTrackIdx = altIdx; + + StemWaveform* wf = new StemWaveform(_waveformsInner, wxID_ANY); + wf->SetTimeline(_timeline); + wf->SetEventTarget(GetParent()); + wf->SetStemName(finalName); + wf->SetStemColor(color); + wf->SetTimeFrequency(_frequency); + wf->SetZoomLevel(_zoomLevel); + wf->SetStartPixelOffset(_startPixelOffset); + + wxString error; + if (!wf->LoadMedia(resolvedPath, error)) { + spdlog::error("Failed to load stem '{}': {}", finalName, (const char*)error.c_str()); + delete wf; + // Roll back the alt-track since we can't display it. + sf->RemoveAltTrack(altIdx); + return false; + } + + wf->SetRowHeight(_stemRowHeight); + stem.waveform = wf; + _stems.push_back(stem); + + wf->Bind(wxEVT_MOUSEWHEEL, &StemsPanel::OnMouseWheel, this); + + RebuildLayout(); + UpdateVisibility(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); + return true; +} + +void StemsPanel::RemoveStem(int index) +{ + if (index < 0 || index >= (int)_stems.size()) return; + + int altIdx = _stems[index].altTrackIdx; + + if (_stems[index].waveform) { + _stems[index].waveform->CloseMedia(); + _stems[index].waveform->Destroy(); + } + _stems.erase(_stems.begin() + index); + + if (SequenceFile* sf = CurrentSeqFile()) { + if (altIdx >= 0 && altIdx < sf->GetAltTrackCount()) { + // If main waveform is currently following this alt track, reset to Main first. + if (Waveform* wf = MainWaveform()) { + if (wf->GetActiveAudioTrackIndex() == altIdx + 1) { + wf->SetActiveAudioTrack(0); + } + } + sf->RemoveAltTrack(altIdx); + } + } + + // Re-sequence altTrackIdx for stems that came after the removed one. + for (auto& s : _stems) { + if (s.altTrackIdx > altIdx) s.altTrackIdx--; + } + + if (_stems.empty()) _scrollOffset = 0; + + RebuildLayout(); + UpdateVisibility(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +void StemsPanel::RemoveAllStems() +{ + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->CloseMedia(); + stem.waveform->Destroy(); + } + } + if (SequenceFile* sf = CurrentSeqFile()) { + // Reset playback to Main if any alt track was active. + if (Waveform* wf = MainWaveform()) { + if (wf->GetActiveAudioTrackIndex() != 0) wf->SetActiveAudioTrack(0); + } + // Remove only the alt-tracks that the panel owned (matching altTrackIdx). + // Iterate in descending order so indexes stay valid. + std::vector indices; + indices.reserve(_stems.size()); + for (auto& s : _stems) { + if (s.altTrackIdx >= 0) indices.push_back(s.altTrackIdx); + } + std::sort(indices.begin(), indices.end(), std::greater()); + for (int idx : indices) sf->RemoveAltTrack(idx); + } + _stems.clear(); + _scrollOffset = 0; + + RebuildLayout(); + UpdateVisibility(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +void StemsPanel::ClearRowsUiOnly() +{ + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->CloseMedia(); + stem.waveform->Destroy(); + } + } + _stems.clear(); + _scrollOffset = 0; + RebuildLayout(); + UpdateVisibility(); +} + +void StemsPanel::MoveStemUp(int index) +{ + if (index <= 0 || index >= (int)_stems.size()) return; + if (SequenceFile* sf = CurrentSeqFile()) { + sf->MoveAltTrack(_stems[index].altTrackIdx, _stems[index - 1].altTrackIdx); + } + std::swap(_stems[index], _stems[index - 1]); + std::swap(_stems[index].altTrackIdx, _stems[index - 1].altTrackIdx); + RebuildLayout(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +void StemsPanel::MoveStemDown(int index) +{ + if (index < 0 || index >= (int)_stems.size() - 1) return; + if (SequenceFile* sf = CurrentSeqFile()) { + sf->MoveAltTrack(_stems[index].altTrackIdx, _stems[index + 1].altTrackIdx); + } + std::swap(_stems[index], _stems[index + 1]); + std::swap(_stems[index].altTrackIdx, _stems[index + 1].altTrackIdx); + RebuildLayout(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +StemInfo* StemsPanel::GetStem(int index) +{ + if (index < 0 || index >= (int)_stems.size()) return nullptr; + return &_stems[index]; +} + +void StemsPanel::RenameStem(int index, const std::string& name) +{ + if (index < 0 || index >= (int)_stems.size()) return; + std::string finalName = name; + if (SequenceFile* sf = CurrentSeqFile()) { + sf->SetAltTrackShortname(_stems[index].altTrackIdx, name); + finalName = sf->GetAltTrackDisplayName(_stems[index].altTrackIdx); + } + _stems[index].name = finalName; + if (_stems[index].waveform) { + _stems[index].waveform->SetStemName(finalName); + } + _headerWindow->Refresh(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +void StemsPanel::RecolorStem(int index, const xlColor& color) +{ + if (index < 0 || index >= (int)_stems.size()) return; + _stems[index].color = color; + if (_stems[index].waveform) { + _stems[index].waveform->SetStemColor(color); + } + _headerWindow->Refresh(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); +} + +void StemsPanel::ImportStemFiles() +{ + wxFileDialog dlg(GetParent(), "Select Stem Audio Files", _showDir, "", + "Audio files (*.wav;*.mp3;*.ogg;*.flac;*.m4a)|*.wav;*.mp3;*.ogg;*.flac;*.m4a|All files (*.*)|*.*", + wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + + if (dlg.ShowModal() == wxID_OK) { + wxArrayString paths; + dlg.GetPaths(paths); + for (size_t i = 0; i < paths.size(); i++) { + wxFileName fn(paths[i]); + std::string name = fn.GetName().ToStdString(); + xlColor color = GetDefaultStemColor((int)_stems.size() + (int)i); + AddStem(name, paths[i].ToStdString(), color); + } + } +} + +void StemsPanel::ImportStemFolder() +{ + wxDirDialog dlg(GetParent(), "Select Folder Containing Stem Files", _showDir, + wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + + if (dlg.ShowModal() == wxID_OK) { + wxString folder = dlg.GetPath(); + wxDir dir(folder); + if (!dir.IsOpened()) return; + + wxString filename; + wxArrayString files; + const wxString patterns[] = { "*.wav", "*.mp3", "*.flac", "*.ogg", "*.m4a" }; + for (const auto& pat : patterns) { + bool cont = dir.GetFirst(&filename, pat, wxDIR_FILES); + while (cont) { + files.Add(folder + wxFileName::GetPathSeparator() + filename); + cont = dir.GetNext(&filename); + } + } + + files.Sort(); + for (size_t i = 0; i < files.size(); i++) { + wxFileName fn(files[i]); + std::string name = fn.GetName().ToStdString(); + xlColor color = GetDefaultStemColor((int)_stems.size() + (int)i); + AddStem(name, files[i].ToStdString(), color); + } + } +} + +void StemsPanel::SetStemRowHeight(int height) +{ + if (height < 16) height = 16; + if (height > 80) height = 80; + _stemRowHeight = height; + + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->SetRowHeight(_stemRowHeight); + } + } + + RebuildLayout(); + UpdateVisibility(); +} + +void StemsPanel::SetPanelHeight(int height) +{ + if (height < MIN_PANEL_HEIGHT) height = MIN_PANEL_HEIGHT; + if (height > MAX_PANEL_HEIGHT) height = MAX_PANEL_HEIGHT; + _panelHeight = height; + _collapsed = false; + UpdateVisibility(); +} + +void StemsPanel::SetCollapsed(bool collapsed) +{ + _collapsed = collapsed; + UpdateVisibility(); +} + +void StemsPanel::ToggleCollapsed() +{ + _collapsed = !_collapsed; + UpdateVisibility(); +} + +void StemsPanel::SetHeaderWidth(int width) +{ + _headerWidth = width; + if (_headerWidth < 158) _headerWidth = 158; + + _stemHeadersOuter->SetMinSize(wxSize(_headerWidth, -1)); + _stemHeadersOuter->SetMaxSize(wxSize(_headerWidth, -1)); + + _stemHeadersOuter->Layout(); + _headerWindow->Refresh(); + if (_stemHeadersOuter->GetParent()) { + _stemHeadersOuter->GetParent()->Layout(); + } +} + +void StemsPanel::SetUserVisible(bool visible) +{ + _userVisible = visible; + UpdateVisibility(); +} + +int StemsPanel::GetTotalContentHeight() const +{ + if (_stems.empty()) return MIN_PANEL_HEIGHT; + return (int)_stems.size() * _stemRowHeight; +} + +void StemsPanel::UpdateVisibility() +{ + bool show = _userVisible; + + if (!show) { + _stemHeadersOuter->SetMinSize(wxSize(0, 0)); + _stemHeadersOuter->SetMaxSize(wxSize(0, 0)); + _stemWaveformsOuter->SetMinSize(wxSize(0, 0)); + _stemWaveformsOuter->SetMaxSize(wxSize(0, 0)); + _resizeHandle->SetMinSize(wxSize(0, 0)); + _resizeHandle->SetMaxSize(wxSize(0, 0)); + } else if (_collapsed) { + _stemHeadersOuter->SetMinSize(wxSize(0, 0)); + _stemHeadersOuter->SetMaxSize(wxSize(0, 0)); + _stemWaveformsOuter->SetMinSize(wxSize(0, 0)); + _stemWaveformsOuter->SetMaxSize(wxSize(0, 0)); + _resizeHandle->SetMinSize(wxSize(-1, RESIZE_HANDLE_HEIGHT)); + _resizeHandle->SetMaxSize(wxSize(-1, RESIZE_HANDLE_HEIGHT)); + } else { + int displayHeight; + if (_stems.empty()) { + displayHeight = MIN_PANEL_HEIGHT; + } else { + int contentHeight = GetTotalContentHeight(); + displayHeight = std::min(_panelHeight, contentHeight); + } + + _stemHeadersOuter->SetMinSize(wxSize(_headerWidth, displayHeight)); + _stemHeadersOuter->SetMaxSize(wxSize(_headerWidth, displayHeight)); + _stemWaveformsOuter->SetMinSize(wxSize(-1, displayHeight)); + _stemWaveformsOuter->SetMaxSize(wxSize(-1, displayHeight)); + _resizeHandle->SetMinSize(wxSize(-1, RESIZE_HANDLE_HEIGHT)); + _resizeHandle->SetMaxSize(wxSize(-1, RESIZE_HANDLE_HEIGHT)); + + // Clamp scroll offset + int maxScroll = std::max(0, GetTotalContentHeight() - displayHeight); + if (_scrollOffset > maxScroll) _scrollOffset = maxScroll; + if (_scrollOffset < 0) _scrollOffset = 0; + + SyncScrollPositions(); + } + + _stemHeadersOuter->Show(show && !_collapsed); + _stemWaveformsOuter->Show(show && !_collapsed); + _resizeHandle->Show(show); + + wxWindow* parent = _stemHeadersOuter->GetParent(); + if (parent) parent->Layout(); +} + +void StemsPanel::SetScrollOffset(int offset) +{ + int contentHeight = GetTotalContentHeight(); + int displayHeight = _stemWaveformsOuter->GetSize().GetHeight(); + int maxScroll = std::max(0, contentHeight - displayHeight); + _scrollOffset = std::clamp(offset, 0, maxScroll); + SyncScrollPositions(); +} + +void StemsPanel::SyncScrollPositions() +{ + // Set inner panel to full content height and outer panel's width + int outerWidth = _stemWaveformsOuter->GetSize().GetWidth(); + if (outerWidth <= 0) outerWidth = _stemWaveformsOuter->GetMinSize().GetWidth(); + int contentHeight = GetTotalContentHeight(); + _waveformsInner->SetSize(outerWidth, contentHeight); + _waveformsInner->Layout(); + + // Position for scrolling + _waveformsInner->SetPosition(wxPoint(0, -_scrollOffset)); + _headerWindow->Refresh(); + _stemWaveformsOuter->Refresh(); +} + +void StemsPanel::OnMouseWheel(wxMouseEvent& event) +{ + if (_collapsed) return; + +#ifdef __WXOSX__ + // macOS trackpad horizontal swipe → forward as horizontal scroll + if (event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL) { + int i = event.GetWheelRotation(); + wxCommandEvent eventScroll(EVT_GSCROLL); + eventScroll.SetInt(i > 0 ? SCROLL_RIGHT : SCROLL_LEFT); + wxPostEvent(GetParent(), eventScroll); + return; + } +#endif + + // Cmd+wheel → zoom + if (event.CmdDown()) { + int i = event.GetWheelRotation(); + wxCommandEvent eventZoom(EVT_ZOOM); + eventZoom.SetInt(i < 0 ? ZOOM_OUT : ZOOM_IN); + wxPostEvent(GetParent(), eventZoom); + return; + } + + // Shift+wheel → horizontal scroll + if (event.ShiftDown()) { + int i = event.GetWheelRotation(); + wxCommandEvent eventScroll(EVT_GSCROLL); + eventScroll.SetInt(i < 0 ? SCROLL_RIGHT : SCROLL_LEFT); + wxPostEvent(GetParent(), eventScroll); + return; + } + + // Plain vertical scroll → scroll stems area + if (_stems.empty()) return; + int contentHeight = GetTotalContentHeight(); + int displayHeight = _stemWaveformsOuter->GetSize().GetHeight(); + if (contentHeight <= displayHeight) return; + + int delta = event.GetWheelRotation(); + int scrollStep = _stemRowHeight / 2; + if (scrollStep < 8) scrollStep = 8; + + if (delta > 0) { + SetScrollOffset(_scrollOffset - scrollStep); + } else if (delta < 0) { + SetScrollOffset(_scrollOffset + scrollStep); + } +} + +void StemsPanel::SetZoomLevel(int level) +{ + _zoomLevel = level; + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->SetZoomLevel(level); + } + } +} + +void StemsPanel::SetStartPixelOffset(int offset) +{ + _startPixelOffset = offset; + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->SetStartPixelOffset(offset); + } + } +} + +void StemsPanel::SetTimeFrequency(int frequency) +{ + _frequency = frequency; + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->SetTimeFrequency(frequency); + } + } +} + +void StemsPanel::UpdatePlayMarker() +{ + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->UpdatePlayMarker(); + } + } +} + +void StemsPanel::ForceRedraw() +{ + for (auto& stem : _stems) { + if (stem.waveform) { + stem.waveform->ForceRedraw(); + stem.waveform->render(); + } + } + _headerWindow->Refresh(); +} + +bool StemsPanel::LoadFromXml(wxXmlNode* stemsNode, const wxString& showDir) +{ + spdlog::debug("[DEBUG: StemsPanel::LoadFromXml starting]"); + if (stemsNode == nullptr || stemsNode->GetName() != "Stems") return false; + + // Don't call RemoveAllStems() — it would also delete the new sequence's + // alt_tracks. We only want to clear leftover UI rows from a prior sequence. + ClearRowsUiOnly(); + + _stemRowHeight = wxAtoi(stemsNode->GetAttribute("rowHeight", "32")); + _panelHeight = wxAtoi(stemsNode->GetAttribute("panelHeight", "96")); + _collapsed = stemsNode->GetAttribute("collapsed", "0") == "1"; + _userVisible = stemsNode->GetAttribute("visible", "1") != "0"; + + SequenceFile* sf = CurrentSeqFile(); + + // Legacy migration: entries owned the audio. New canonical + // location is SequenceFile::alt_tracks. AddStem will route through AddAltTrack. + // For new-format files the node will only carry colors keyed by index. + int colorIdx = 0; + for (wxXmlNode* child = stemsNode->GetChildren(); child != nullptr; child = child->GetNext()) { + if (child->GetName() != "Stem") continue; + + std::string name = child->GetAttribute("name", "Stem").ToStdString(); + std::string path = child->GetAttribute("path", "").ToStdString(); + std::string colorStr = child->GetAttribute("color", "").ToStdString(); + xlColor color = colorStr.empty() ? GetDefaultStemColor(colorIdx) : xlColor(colorStr); + + if (!path.empty()) { + // Legacy form — bring the path into alt_tracks. + std::string resolvedPath = ResolveRelativePath(path, showDir.ToStdString()); + AddStem(name, resolvedPath, color); + } + // New form: just remember the color slot; the actual stems come from alt_tracks + // via RefreshFromAltTracks below. We'll re-apply colors after refresh. + colorIdx++; + } + + // If the panel currently has no stems (i.e. either empty XML or new-format + // overlay without legacy paths), pull stems from SequenceFile::alt_tracks. + if (_stems.empty() && sf != nullptr && sf->GetAltTrackCount() > 0) { + RefreshFromAltTracks(); + + // Apply colors from the overlay back onto the refreshed rows. + int idx = 0; + for (wxXmlNode* child = stemsNode->GetChildren(); child != nullptr; child = child->GetNext()) { + if (child->GetName() != "Stem") continue; + if (idx >= (int)_stems.size()) break; + std::string colorStr = child->GetAttribute("color", "").ToStdString(); + if (!colorStr.empty()) { + xlColor c(colorStr); + _stems[idx].color = c; + if (_stems[idx].waveform) _stems[idx].waveform->SetStemColor(c); + } + idx++; + } + } + + SetStemRowHeight(_stemRowHeight); + UpdateVisibility(); + + spdlog::debug("[DEBUG: StemsPanel::LoadFromXml done, {} stems loaded]", _stems.size()); + return true; +} + +wxXmlNode* StemsPanel::SaveToXml(const wxString& /*showDir*/) const +{ + wxXmlNode* node = new wxXmlNode(wxXML_ELEMENT_NODE, "Stems"); + node->AddAttribute("rowHeight", wxString::Format("%d", _stemRowHeight)); + node->AddAttribute("panelHeight", wxString::Format("%d", _panelHeight)); + node->AddAttribute("collapsed", _collapsed ? "1" : "0"); + node->AddAttribute("visible", _userVisible ? "1" : "0"); + + // UI overlay: just colors, indexed by alt-track position. Audio paths and + // shortnames live in SequenceFile::alt_tracks ( XML). + for (const auto& stem : _stems) { + wxXmlNode* stemNode = new wxXmlNode(wxXML_ELEMENT_NODE, "Stem"); + stemNode->AddAttribute("color", wxString::Format("#%02X%02X%02X", + (int)stem.color.red, (int)stem.color.green, (int)stem.color.blue)); + node->AddChild(stemNode); + } + + return node; +} + +void StemsPanel::RefreshFromAltTracks() +{ + SequenceFile* sf = CurrentSeqFile(); + if (sf == nullptr) return; + + int n = sf->GetAltTrackCount(); + + // Drop excess rows. + while ((int)_stems.size() > n) { + if (_stems.back().waveform) { + _stems.back().waveform->CloseMedia(); + _stems.back().waveform->Destroy(); + } + _stems.pop_back(); + } + + // Update existing rows, append new ones. + for (int i = 0; i < n; i++) { + const AlternateAudioTrack& at = sf->GetAltTrack(i); + std::string name = sf->GetAltTrackDisplayName(i); + + if (i < (int)_stems.size()) { + StemInfo& s = _stems[i]; + s.altTrackIdx = i; + if (s.path != at.path) { + if (s.waveform) { + wxString error; + s.waveform->LoadMedia(at.path, error); + } + s.path = at.path; + } + if (s.name != name) { + s.name = name; + if (s.waveform) s.waveform->SetStemName(name); + } + continue; + } + + StemInfo stem; + stem.name = name; + stem.path = at.path; + stem.color = GetDefaultStemColor(i); + stem.altTrackIdx = i; + + StemWaveform* wf = new StemWaveform(_waveformsInner, wxID_ANY); + wf->SetTimeline(_timeline); + wf->SetEventTarget(GetParent()); + wf->SetStemName(name); + wf->SetStemColor(stem.color); + wf->SetTimeFrequency(_frequency); + wf->SetZoomLevel(_zoomLevel); + wf->SetStartPixelOffset(_startPixelOffset); + wxString error; + if (!wf->LoadMedia(at.path, error)) { + spdlog::error("RefreshFromAltTracks: failed to load '{}': {}", + at.path, (const char*)error.c_str()); + delete wf; + continue; + } + wf->SetRowHeight(_stemRowHeight); + stem.waveform = wf; + wf->Bind(wxEVT_MOUSEWHEEL, &StemsPanel::OnMouseWheel, this); + _stems.push_back(stem); + } + + RebuildLayout(); + UpdateVisibility(); +} + +void StemsPanel::SoloStem(int stemIndex) +{ + if (stemIndex < 0 || stemIndex >= (int)_stems.size()) return; + Waveform* wf = MainWaveform(); + if (wf == nullptr) return; + + int altIdx = _stems[stemIndex].altTrackIdx; + int targetTrack = altIdx + 1; // 0 = Main + + // Toggle: clicking the active solo returns to Main. + if (wf->GetActiveAudioTrackIndex() == targetTrack) { + wf->SetActiveAudioTrack(0); + } else { + wf->SetActiveAudioTrack(targetTrack); + } + if (_headerWindow) _headerWindow->Refresh(); +} + +int StemsPanel::GetActiveStemIndex() const +{ + Waveform* wf = MainWaveform(); + if (wf == nullptr) return -1; + int track = wf->GetActiveAudioTrackIndex(); + if (track <= 0) return -1; + int altIdx = track - 1; + for (int i = 0; i < (int)_stems.size(); i++) { + if (_stems[i].altTrackIdx == altIdx) return i; + } + return -1; +} + +void StemsPanel::RebuildLayout() +{ + _waveformsSizer->Clear(false); + + // Detach all waveforms from sizer (don't destroy) + for (auto& stem : _stems) { + if (stem.waveform) { + _waveformsSizer->Add(stem.waveform, 0, wxEXPAND, 0); + } + } + + int contentHeight = GetTotalContentHeight(); + _waveformsInner->SetMinSize(wxSize(-1, contentHeight)); + _waveformsInner->Layout(); + + _headerWindow->Refresh(); +} + +int StemsPanel::HitTestStemIndex(int y) +{ + if (_stems.empty() || _stemRowHeight <= 0) return -1; + int adjustedY = y + _scrollOffset; + int index = adjustedY / _stemRowHeight; + if (index < 0 || index >= (int)_stems.size()) return -1; + return index; +} + +void StemsPanel::OnRightDown(wxMouseEvent& event) +{ + _contextMenuStemIndex = -1; + + // Try to determine which stem was clicked + wxWindow* src = dynamic_cast(event.GetEventObject()); + if (src == _headerWindow) { + _contextMenuStemIndex = HitTestStemIndex(event.GetY()); + } else if (src == _stemWaveformsOuter) { + _contextMenuStemIndex = HitTestStemIndex(event.GetY()); + } + + wxMenu menu; + menu.Append(ID_MNU_IMPORT_FILES, "Import Stem Files..."); + menu.Append(ID_MNU_IMPORT_FOLDER, "Import Stem Folder..."); + menu.AppendSeparator(); + + if (_contextMenuStemIndex >= 0 && _contextMenuStemIndex < (int)_stems.size()) { + menu.Append(ID_MNU_RENAME, wxString::Format("Rename '%s'...", _stems[_contextMenuStemIndex].name)); + menu.Append(ID_MNU_RECOLOR, wxString::Format("Change Color of '%s'...", _stems[_contextMenuStemIndex].name)); + menu.AppendSeparator(); + menu.Append(ID_MNU_ONSET_DETECT, "Create Timing Track from Transients..."); + menu.AppendSeparator(); + menu.Append(ID_MNU_MOVE_UP, "Move Up"); + menu.Append(ID_MNU_MOVE_DOWN, "Move Down"); + menu.Enable(ID_MNU_MOVE_UP, _contextMenuStemIndex > 0); + menu.Enable(ID_MNU_MOVE_DOWN, _contextMenuStemIndex < (int)_stems.size() - 1); + menu.AppendSeparator(); + menu.Append(ID_MNU_REMOVE_STEM, wxString::Format("Remove '%s'", _stems[_contextMenuStemIndex].name)); + } + + if (!_stems.empty()) { + menu.Append(ID_MNU_REMOVE_ALL, "Remove All Stems"); + menu.AppendSeparator(); + } + + menu.Append(ID_MNU_SET_HEIGHT, "Set Row Height..."); + + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&StemsPanel::OnPopupMenu, nullptr, this); + + if (src) { + src->PopupMenu(&menu); + } else { + _stemHeadersOuter->PopupMenu(&menu); + } + + _contextMenuStemIndex = -1; +} + +void StemsPanel::OnPopupMenu(wxCommandEvent& event) +{ + int id = event.GetId(); + if (id == ID_MNU_IMPORT_FILES) { + ImportStemFiles(); + } else if (id == ID_MNU_IMPORT_FOLDER) { + ImportStemFolder(); + } else if (id == ID_MNU_REMOVE_STEM) { + if (_contextMenuStemIndex >= 0) { + RemoveStem(_contextMenuStemIndex); + } + } else if (id == ID_MNU_REMOVE_ALL) { + RemoveAllStems(); + } else if (id == ID_MNU_MOVE_UP) { + if (_contextMenuStemIndex >= 0) { + MoveStemUp(_contextMenuStemIndex); + } + } else if (id == ID_MNU_MOVE_DOWN) { + if (_contextMenuStemIndex >= 0) { + MoveStemDown(_contextMenuStemIndex); + } + } else if (id == ID_MNU_RENAME) { + if (_contextMenuStemIndex >= 0 && _contextMenuStemIndex < (int)_stems.size()) { + wxTextEntryDialog dlg(GetParent(), "Enter new name:", "Rename Stem", + _stems[_contextMenuStemIndex].name); + if (dlg.ShowModal() == wxID_OK) { + RenameStem(_contextMenuStemIndex, dlg.GetValue().ToStdString()); + } + } + } else if (id == ID_MNU_RECOLOR) { + if (_contextMenuStemIndex >= 0 && _contextMenuStemIndex < (int)_stems.size()) { + xlColor c = _stems[_contextMenuStemIndex].color; + wxColourData data; + data.SetColour(wxColour(c.red, c.green, c.blue)); + wxColourDialog dlg(GetParent(), &data); + if (dlg.ShowModal() == wxID_OK) { + wxColour nc = dlg.GetColourData().GetColour(); + RecolorStem(_contextMenuStemIndex, xlColor(nc.Red(), nc.Green(), nc.Blue())); + } + } + } else if (id == ID_MNU_SET_HEIGHT) { + long height = wxGetNumberFromUser("Set stem row height (16-80):", "Height", + "Stem Row Height", _stemRowHeight, 16, 80, GetParent()); + if (height != -1) { + SetStemRowHeight((int)height); + } + } else if (id == ID_MNU_ONSET_DETECT) { + if (_contextMenuStemIndex >= 0 && _contextMenuStemIndex < (int)_stems.size()) { + StemInfo& stem = _stems[_contextMenuStemIndex]; + xLightsFrame* frame = xLightsApp::GetFrame(); + if (frame != nullptr && stem.waveform != nullptr) { + StemOnsetDialog* dlg = new StemOnsetDialog(this, stem.waveform, frame, stem.name); + dlg->Show(); + } + } + } +} + +void StemsPanel::OnStemHeaderDClick(int stemIndex) +{ + if (stemIndex < 0 || stemIndex >= (int)_stems.size()) return; + + wxTextEntryDialog dlg(GetParent(), "Enter new name:", "Rename Stem", + _stems[stemIndex].name); + if (dlg.ShowModal() == wxID_OK) { + RenameStem(stemIndex, dlg.GetValue().ToStdString()); + } +} + +void StemsPanel::BeginDragReorder(int stemIndex, int mouseY) +{ + if (stemIndex < 0 || stemIndex >= (int)_stems.size()) return; + _dragging = true; + _dragSourceIndex = stemIndex; + _dragInsertIndex = stemIndex; + _dragStartY = mouseY; + _headerWindow->Refresh(); +} + +void StemsPanel::UpdateDragReorder(int mouseY) +{ + if (!_dragging) return; + int adjustedY = mouseY + _scrollOffset; + int newIndex = adjustedY / _stemRowHeight; + if (newIndex < 0) newIndex = 0; + if (newIndex >= (int)_stems.size()) newIndex = (int)_stems.size() - 1; + if (newIndex != _dragInsertIndex) { + _dragInsertIndex = newIndex; + _headerWindow->Refresh(); + } +} + +void StemsPanel::EndDragReorder() +{ + if (!_dragging) return; + _dragging = false; + + if (_dragSourceIndex != _dragInsertIndex && + _dragSourceIndex >= 0 && _dragSourceIndex < (int)_stems.size() && + _dragInsertIndex >= 0 && _dragInsertIndex < (int)_stems.size()) { + + if (SequenceFile* sf = CurrentSeqFile()) { + sf->MoveAltTrack(_stems[_dragSourceIndex].altTrackIdx, + _stems[_dragInsertIndex].altTrackIdx); + } + + StemInfo moving = _stems[_dragSourceIndex]; + _stems.erase(_stems.begin() + _dragSourceIndex); + _stems.insert(_stems.begin() + _dragInsertIndex, moving); + + // Re-anchor altTrackIdx so each stem matches its new position. + for (int i = 0; i < (int)_stems.size(); i++) { + _stems[i].altTrackIdx = i; + } + + RebuildLayout(); + + wxCommandEvent evt(EVT_STEMS_CHANGED); + wxPostEvent(GetParent(), evt); + } + + _dragSourceIndex = -1; + _dragInsertIndex = -1; + _headerWindow->Refresh(); +} + +std::string StemsPanel::MakeRelativePath(const std::string& absPath, const std::string& showDir) const +{ + if (showDir.empty() || absPath.empty()) return absPath; + + wxFileName fn(absPath); + if (fn.MakeRelativeTo(showDir)) { + return fn.GetFullPath().ToStdString(); + } + return absPath; +} + +std::string StemsPanel::ResolveRelativePath(const std::string& relPath, const std::string& showDir) const +{ + if (relPath.empty()) return relPath; + + wxFileName fn(relPath); + if (fn.IsRelative() && !showDir.empty()) { + fn.MakeAbsolute(showDir); + } + return fn.GetFullPath().ToStdString(); +} + +// ========================================================================= +// StemHeaderWindow — custom drawn header (matches RowHeading style) +// ========================================================================= + +wxRect StemHeaderWindow::SoloButtonRect(int headerW, int startY, int rowHeight) +{ + int btnSize = std::min(rowHeight - 6, 22); + if (btnSize < 12) btnSize = 12; + int x = headerW - btnSize - 6; + int y = startY + (rowHeight - btnSize) / 2; + return wxRect(x, y, btnSize, btnSize); +} + +StemHeaderWindow::StemHeaderWindow(wxWindow* parent, StemsPanel* stemsPanel) + : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) + , _stemsPanel(stemsPanel) +{ + SetBackgroundStyle(wxBG_STYLE_PAINT); + + Bind(wxEVT_PAINT, &StemHeaderWindow::OnPaint, this); + Bind(wxEVT_LEFT_DOWN, &StemHeaderWindow::OnLeftDown, this); + Bind(wxEVT_LEFT_UP, &StemHeaderWindow::OnLeftUp, this); + Bind(wxEVT_MOTION, &StemHeaderWindow::OnMotion, this); + Bind(wxEVT_LEFT_DCLICK, &StemHeaderWindow::OnLeftDClick, this); + Bind(wxEVT_RIGHT_DOWN, &StemHeaderWindow::OnRightDown, this); +} + +void StemHeaderWindow::OnPaint(wxPaintEvent& event) +{ + wxAutoBufferedPaintDC dc(this); + wxCoord w, h; + dc.GetSize(&w, &h); + + bool isDark = IsDarkMode(); + xlColor outlineCol(32, 32, 32); + if (isDark) outlineCol.Set(55, 55, 55); + wxPen penOutline(xlColorToWxColour(outlineCol)); + + // Font matching RowHeading + auto font = dc.GetFont(); + auto fontSize = ComputeStemFontSize(); + SetStemFontPixelSize(font, fontSize); + dc.SetFont(font); + + int scrollOffset = _stemsPanel->GetScrollOffset(); + int rowHeight = _stemsPanel->GetStemRowHeight(); + int stemCount = _stemsPanel->GetStemCount(); + int dragSource = _stemsPanel->GetDragSourceIndex(); + int dragInsert = _stemsPanel->GetDragInsertIndex(); + bool dragging = _stemsPanel->IsDragging(); + + // Background + xlColor bgColor = xlColor(60, 60, 65); + dc.SetBrush(wxBrush(xlColorToWxColour(bgColor))); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(0, 0, w, h); + + if (stemCount == 0) { + // Empty state hint + dc.SetTextForeground(wxColour(120, 120, 120)); + wxFont hintFont = dc.GetFont(); + hintFont.SetPointSize(9); + dc.SetFont(hintFont); + wxRect r(DEFAULT_ROW_HEADING_MARGIN, 0, w - DEFAULT_ROW_HEADING_MARGIN, h); + dc.DrawLabel("Right-click to import", r, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + return; + } + + int activeStem = _stemsPanel->GetActiveStemIndex(); + + for (int i = 0; i < stemCount; i++) { + StemInfo* stem = _stemsPanel->GetStem(i); + if (!stem) continue; + + int startY = i * rowHeight - scrollOffset; + if (startY + rowHeight < 0) continue; + if (startY > h) break; + + // Stem color stripe on left edge (3px) + wxColour stemColor(stem->color.red, stem->color.green, stem->color.blue); + dc.SetBrush(wxBrush(stemColor)); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(0, startY, 4, rowHeight); + + // Row background + xlColor rowBg(48, 48, 52); + if (dragging && i == dragSource) { + rowBg.Set(35, 35, 40); // dimmed source row + } + dc.SetBrush(wxBrush(xlColorToWxColour(rowBg))); + dc.SetPen(penOutline); + dc.DrawRectangle(4, startY, w - 4, rowHeight); + + // Grab handle dots (6px from left edge, centered vertically) + int handleX = 8; + int cy = startY + rowHeight / 2; + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxBrush(wxColour(100, 100, 105))); + for (int dot = -1; dot <= 1; dot++) { + dc.DrawCircle(handleX, cy + dot * 4, 1); + dc.DrawCircle(handleX + 4, cy + dot * 4, 1); + } + + // Solo (S) button on the right edge — DAW-style. Active = filled yellow. + wxRect soloRect = StemHeaderWindow::SoloButtonRect(w, startY, rowHeight); + bool soloActive = (i == activeStem); + wxColour soloFill = soloActive ? wxColour(240, 200, 40) : wxColour(70, 70, 75); + wxColour soloText = soloActive ? wxColour(20, 20, 20) : wxColour(200, 200, 200); + dc.SetBrush(wxBrush(soloFill)); + dc.SetPen(wxPen(wxColour(30, 30, 30))); + dc.DrawRoundedRectangle(soloRect, 2); + dc.SetTextForeground(soloText); + wxFont btnFont = dc.GetFont(); + btnFont.SetWeight(wxFONTWEIGHT_BOLD); + dc.SetFont(btnFont); + dc.DrawLabel("S", soloRect, wxALIGN_CENTER); + + // Text — same margin as RowHeading + dc.SetTextForeground(*wxWHITE); + // Reset to stem font (non-bold) + auto f2 = dc.GetFont(); + f2.SetWeight(wxFONTWEIGHT_NORMAL); + SetStemFontPixelSize(f2, fontSize); + dc.SetFont(f2); + + int textRight = soloRect.GetX() - 4; + wxRect textRect(DEFAULT_ROW_HEADING_MARGIN, startY, + std::max(0, textRight - DEFAULT_ROW_HEADING_MARGIN), rowHeight); + dc.DrawLabel(stem->name, textRect, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + } + + // Drag insert indicator line + if (dragging && dragInsert >= 0) { + int lineY = dragInsert * rowHeight - scrollOffset; + if (dragInsert > dragSource) lineY += rowHeight; + dc.SetPen(wxPen(wxColour(255, 180, 0), 2)); + dc.DrawLine(0, lineY, w, lineY); + } +} + +void StemHeaderWindow::OnLeftDown(wxMouseEvent& event) +{ + int idx = _stemsPanel->HitTestStemIndex(event.GetY()); + if (idx >= 0) { + int rowHeight = _stemsPanel->GetStemRowHeight(); + int startY = idx * rowHeight - _stemsPanel->GetScrollOffset(); + wxCoord w, h; + GetClientSize(&w, &h); + wxRect soloRect = SoloButtonRect(w, startY, rowHeight); + if (soloRect.Contains(event.GetX(), event.GetY())) { + _stemsPanel->SoloStem(idx); + return; // do NOT start drag-reorder + } + } + _mouseDown = true; + _mouseDownY = event.GetY(); + _mouseDownIndex = idx; + CaptureMouse(); +} + +void StemHeaderWindow::OnLeftUp(wxMouseEvent& event) +{ + if (HasCapture()) ReleaseMouse(); + + if (_stemsPanel->IsDragging()) { + _stemsPanel->EndDragReorder(); + } + + _mouseDown = false; + _mouseDownIndex = -1; +} + +void StemHeaderWindow::OnMotion(wxMouseEvent& event) +{ + if (!_mouseDown) return; + + int dy = std::abs(event.GetY() - _mouseDownY); + + if (!_stemsPanel->IsDragging() && dy > 4 && _mouseDownIndex >= 0) { + _stemsPanel->BeginDragReorder(_mouseDownIndex, event.GetY()); + } + + if (_stemsPanel->IsDragging()) { + _stemsPanel->UpdateDragReorder(event.GetY()); + } +} + +void StemHeaderWindow::OnLeftDClick(wxMouseEvent& event) +{ + int index = _stemsPanel->HitTestStemIndex(event.GetY()); + if (index >= 0) { + _stemsPanel->OnStemHeaderDClick(index); + } +} + +void StemHeaderWindow::OnRightDown(wxMouseEvent& event) +{ + _stemsPanel->OnRightDown(event); +} + +// ========================================================================= +// StemResizeHandle — drag to resize stems area +// ========================================================================= + +StemResizeHandle::StemResizeHandle(wxWindow* parent, StemsPanel* stemsPanel) + : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, RESIZE_HANDLE_HEIGHT)) + , _stemsPanel(stemsPanel) +{ + SetBackgroundStyle(wxBG_STYLE_PAINT); + SetCursor(wxCURSOR_SIZENS); + + Bind(wxEVT_PAINT, &StemResizeHandle::OnPaint, this); + Bind(wxEVT_LEFT_DOWN, &StemResizeHandle::OnLeftDown, this); + Bind(wxEVT_LEFT_UP, &StemResizeHandle::OnLeftUp, this); + Bind(wxEVT_MOTION, &StemResizeHandle::OnMotion, this); + Bind(wxEVT_LEFT_DCLICK, &StemResizeHandle::OnLeftDClick, this); + Bind(wxEVT_ENTER_WINDOW, &StemResizeHandle::OnEnter, this); + Bind(wxEVT_LEAVE_WINDOW, &StemResizeHandle::OnLeave, this); +} + +void StemResizeHandle::OnPaint(wxPaintEvent& event) +{ + wxAutoBufferedPaintDC dc(this); + wxCoord w, h; + dc.GetSize(&w, &h); + + bool collapsed = _stemsPanel->IsCollapsed(); + + // Dark bar with subtle grip indicator + dc.SetBrush(wxBrush(wxColour(45, 45, 50))); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(0, 0, w, h); + + int cx = w / 2; + int cy = h / 2; + + if (collapsed) { + // Show expand indicator: small triangle pointing down + dc.SetBrush(wxBrush(wxColour(120, 120, 130))); + dc.SetPen(*wxTRANSPARENT_PEN); + wxPoint tri[3] = { {cx - 4, cy - 1}, {cx + 4, cy - 1}, {cx, cy + 2} }; + dc.DrawPolygon(3, tri); + } else { + // Center grip dots + dc.SetBrush(wxBrush(wxColour(90, 90, 95))); + for (int i = -2; i <= 2; i++) { + dc.DrawCircle(cx + i * 6, cy, 1); + } + } +} + +void StemResizeHandle::OnLeftDown(wxMouseEvent& event) +{ + _resizing = true; + _resizeStartY = event.GetPosition().y + GetPosition().y; + _resizeStartHeight = _stemsPanel->GetPanelHeight(); + CaptureMouse(); +} + +void StemResizeHandle::OnLeftUp(wxMouseEvent& event) +{ + if (HasCapture()) ReleaseMouse(); + _resizing = false; +} + +void StemResizeHandle::OnMotion(wxMouseEvent& event) +{ + if (!_resizing) return; + + int currentY = event.GetPosition().y + GetPosition().y; + int delta = currentY - _resizeStartY; + int newHeight = _resizeStartHeight + delta; + _stemsPanel->SetPanelHeight(newHeight); +} + +void StemResizeHandle::OnLeftDClick(wxMouseEvent& event) +{ + _stemsPanel->ToggleCollapsed(); +} + +void StemResizeHandle::OnEnter(wxMouseEvent& event) +{ + SetCursor(wxCURSOR_SIZENS); +} + +void StemResizeHandle::OnLeave(wxMouseEvent& event) +{ + SetCursor(wxCURSOR_DEFAULT); +} diff --git a/src-ui-wx/sequencer/StemsPanel.h b/src-ui-wx/sequencer/StemsPanel.h new file mode 100644 index 0000000000..8bffdd401c --- /dev/null +++ b/src-ui-wx/sequencer/StemsPanel.h @@ -0,0 +1,234 @@ +#pragma once + +/*************************************************************** + * This source files comes from the xLights project + * https://www.xlights.org + * https://github.com/xLightsSequencer/xLights + * See the github commit history for a record of contributing + * developers. + * Copyright claimed based on commit dates recorded in Github + * License: https://github.com/xLightsSequencer/xLights/blob/master/License.txt + **************************************************************/ + +#include +#include +#include +#include +#include + +#include "Color.h" + +class StemWaveform; +class TimeLine; +class StemHeaderWindow; +class StemResizeHandle; + +wxDECLARE_EVENT(EVT_STEMS_CHANGED, wxCommandEvent); + +struct StemInfo { + std::string name; + std::string path; + xlColor color; + StemWaveform* waveform = nullptr; + int altTrackIdx = -1; // index into SequenceFile::alt_tracks; kept aligned with _stems index +}; + +class StemsPanel : public wxPanel +{ +public: + StemsPanel(wxWindow* parent, wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize); + virtual ~StemsPanel(); + + void SetTimeline(TimeLine* timeline) { _timeline = timeline; } + + // Sub-panels for split layout (children of parent, not of this panel) + wxWindow* GetStemHeadersPanel() { return _stemHeadersOuter; } + wxWindow* GetStemWaveformsPanel() { return _stemWaveformsOuter; } + StemResizeHandle* GetResizeHandle() { return _resizeHandle; } + + // Stem management + bool AddStem(const std::string& name, const std::string& filepath, const xlColor& color); + void RemoveStem(int index); + void RemoveAllStems(); + // Tear down UI rows without mutating SequenceFile::alt_tracks. Used on + // sequence-open to discard stale rows from the previous sequence. + void ClearRowsUiOnly(); + void MoveStemUp(int index); + void MoveStemDown(int index); + int GetStemCount() const { return (int)_stems.size(); } + StemInfo* GetStem(int index); + + // Rename/recolor + void RenameStem(int index, const std::string& name); + void RecolorStem(int index, const xlColor& color); + + // Import + void ImportStemFiles(); + void ImportStemFolder(); + + // Row height + void SetStemRowHeight(int height); + int GetStemRowHeight() const { return _stemRowHeight; } + + // Panel height (the visible container area) + void SetPanelHeight(int height); + int GetPanelHeight() const { return _panelHeight; } + + // Collapse / expand + void SetCollapsed(bool collapsed); + bool IsCollapsed() const { return _collapsed; } + void ToggleCollapsed(); + + // Sync header width with row headings + void SetHeaderWidth(int width); + + // Visibility management + void SetUserVisible(bool visible); + bool IsUserVisible() const { return _userVisible; } + void UpdateVisibility(); + bool IsStemsVisible() const { return _userVisible && !_stems.empty(); } + + // Scroll offset sync between headers and waveforms + void SetScrollOffset(int offset); + int GetScrollOffset() const { return _scrollOffset; } + int GetTotalContentHeight() const; + + // Sync with main timeline + void SetZoomLevel(int level); + void SetStartPixelOffset(int offset); + void SetTimeFrequency(int frequency); + void UpdatePlayMarker(); + void ForceRedraw(); + + // XML persistence (UI overlay: colors, row height, panel state). Audio paths + // live in SequenceFile::alt_tracks. LoadFromXml also migrates legacy + // nodes (which embedded audio paths) into alt_tracks. + bool LoadFromXml(wxXmlNode* stemsNode, const wxString& showDir); + wxXmlNode* SaveToXml(const wxString& showDir) const; + + // Rebuild rows from SequenceFile::alt_tracks, creating/destroying StemWaveforms + // as needed and preserving UI state (color, waveform pointer) where possible. + void RefreshFromAltTracks(); + + // Solo: switch the main waveform's active track to this stem (alt-track index + 1). + // Click again on the active stem to switch back to Main. + void SoloStem(int stemIndex); + int GetActiveStemIndex() const; // -1 if Main is active + + // Show directory for relative path resolution + void SetShowDirectory(const std::string& dir) { _showDir = dir; } + + // Drag reorder support + void BeginDragReorder(int stemIndex, int mouseY); + void UpdateDragReorder(int mouseY); + void EndDragReorder(); + int GetDragSourceIndex() const { return _dragSourceIndex; } + int GetDragInsertIndex() const { return _dragInsertIndex; } + bool IsDragging() const { return _dragging; } + +private: + void RebuildLayout(); + void OnRightDown(wxMouseEvent& event); + void OnPopupMenu(wxCommandEvent& event); + void OnStemHeaderDClick(int stemIndex); + void OnMouseWheel(wxMouseEvent& event); + int HitTestStemIndex(int y); + std::string MakeRelativePath(const std::string& absPath, const std::string& showDir) const; + std::string ResolveRelativePath(const std::string& relPath, const std::string& showDir) const; + xlColor GetDefaultStemColor(int index) const; + void SyncScrollPositions(); + + // Outer containers (children of MainSequencer, placed in grid sizer) + wxPanel* _stemHeadersOuter = nullptr; + wxPanel* _stemWaveformsOuter = nullptr; + StemResizeHandle* _resizeHandle = nullptr; + + // Inner scrolling content + StemHeaderWindow* _headerWindow = nullptr; + wxPanel* _waveformsInner = nullptr; + wxBoxSizer* _waveformsSizer = nullptr; + + TimeLine* _timeline = nullptr; + std::vector _stems; + + std::string _showDir; + int _stemRowHeight = 32; + int _panelHeight = 96; + int _headerWidth = 158; + bool _userVisible = false; + bool _collapsed = false; + int _scrollOffset = 0; + int _zoomLevel = 0; + int _startPixelOffset = 0; + int _frequency = 40; + + // Drag reorder state + bool _dragging = false; + int _dragSourceIndex = -1; + int _dragInsertIndex = -1; + int _dragStartY = 0; + + static const long ID_MNU_IMPORT_FILES; + static const long ID_MNU_IMPORT_FOLDER; + static const long ID_MNU_REMOVE_STEM; + static const long ID_MNU_REMOVE_ALL; + static const long ID_MNU_MOVE_UP; + static const long ID_MNU_MOVE_DOWN; + static const long ID_MNU_RENAME; + static const long ID_MNU_RECOLOR; + static const long ID_MNU_SET_HEIGHT; + static const long ID_MNU_ONSET_DETECT; + + int _contextMenuStemIndex = -1; + + friend class StemHeaderWindow; + + DECLARE_EVENT_TABLE() +}; + +// Custom-drawn stem header area (matches RowHeading style) +class StemHeaderWindow : public wxWindow +{ +public: + StemHeaderWindow(wxWindow* parent, StemsPanel* stemsPanel); + + // Geometry of the per-row Solo (S) button. Caller passes the row's startY + // (already adjusted for scroll), rowHeight, and the header window width. + static wxRect SoloButtonRect(int headerW, int startY, int rowHeight); + +private: + void OnPaint(wxPaintEvent& event); + void OnLeftDown(wxMouseEvent& event); + void OnLeftUp(wxMouseEvent& event); + void OnMotion(wxMouseEvent& event); + void OnLeftDClick(wxMouseEvent& event); + void OnRightDown(wxMouseEvent& event); + + StemsPanel* _stemsPanel; + bool _mouseDown = false; + int _mouseDownY = 0; + int _mouseDownIndex = -1; +}; + +// Drag handle between stems row and effects grid row +class StemResizeHandle : public wxWindow +{ +public: + StemResizeHandle(wxWindow* parent, StemsPanel* stemsPanel); + +private: + void OnPaint(wxPaintEvent& event); + void OnLeftDown(wxMouseEvent& event); + void OnLeftUp(wxMouseEvent& event); + void OnMotion(wxMouseEvent& event); + void OnLeftDClick(wxMouseEvent& event); + void OnEnter(wxMouseEvent& event); + void OnLeave(wxMouseEvent& event); + + StemsPanel* _stemsPanel; + bool _resizing = false; + int _resizeStartY = 0; + int _resizeStartHeight = 0; +}; diff --git a/src-ui-wx/sequencer/Waveform.cpp b/src-ui-wx/sequencer/Waveform.cpp index 0b791f2f6b..0e3155eef7 100644 --- a/src-ui-wx/sequencer/Waveform.cpp +++ b/src-ui-wx/sequencer/Waveform.cpp @@ -30,6 +30,9 @@ #include "xLightsApp.h" #include "xLightsMain.h" #include "MainSequencer.h" +#include "StemsPanel.h" +#include "utils/WavWriter.h" +#include "utils/ExternalHooks.h" #include "sequencer/NoteRangeDialog.h" #include "media/OnsetDetector.h" #include "media/PitchDetector.h" @@ -258,7 +261,25 @@ void Waveform::rightClick(wxMouseEvent& event) mnuWave.AppendSeparator(); } - mnuWave.AppendRadioItem(ID_WAVE_MNU_RAW, "Raw waveform")->Check(_type == AUDIOSAMPLETYPE::RAW); + // Effective type: when the active alt track is one of the auto-named + // stem tracks (Drums/Bass/Vocals/Other), report it as STEM_* so the + // radio group highlights the right entry even though `_type` is RAW. + AUDIOSAMPLETYPE effectiveType = _type; + if (_type == AUDIOSAMPLETYPE::RAW && _activeAudioTrackIndex > 0) { + if (auto* frame = xLightsApp::GetFrame()) { + if (auto* sf = frame->CurrentSeqXmlFile) { + int altIdx = _activeAudioTrackIndex - 1; + if (altIdx < sf->GetAltTrackCount()) { + std::string n = sf->GetAltTrackDisplayName(altIdx); + if (n == "Drums") effectiveType = AUDIOSAMPLETYPE::STEM_DRUMS; + else if (n == "Bass") effectiveType = AUDIOSAMPLETYPE::STEM_BASS; + else if (n == "Vocals") effectiveType = AUDIOSAMPLETYPE::STEM_VOCALS; + else if (n == "Other") effectiveType = AUDIOSAMPLETYPE::STEM_OTHER; + } + } + } + } + mnuWave.AppendRadioItem(ID_WAVE_MNU_RAW, "Raw waveform")->Check(effectiveType == AUDIOSAMPLETYPE::RAW); mnuWave.AppendRadioItem(ID_WAVE_MNU_BASS, "Bass waveform")->Check(_type == AUDIOSAMPLETYPE::BASS); mnuWave.AppendRadioItem(ID_WAVE_MNU_TREBLE, "Treble waveform")->Check(_type == AUDIOSAMPLETYPE::TREBLE); mnuWave.AppendRadioItem(ID_WAVE_MNU_ALTO, "Alto waveform")->Check(_type == AUDIOSAMPLETYPE::ALTO); @@ -274,23 +295,23 @@ void Waveform::rightClick(wxMouseEvent& event) // MLMultiArray I/O the model uses isn't available before. if (__builtin_available(macOS 12.0, *)) { mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_DRUMS, "Stem — Drums") - ->Check(_type == AUDIOSAMPLETYPE::STEM_DRUMS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_DRUMS); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_BASS, "Stem — Bass") - ->Check(_type == AUDIOSAMPLETYPE::STEM_BASS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_BASS); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_OTHER, "Stem — Other") - ->Check(_type == AUDIOSAMPLETYPE::STEM_OTHER); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_OTHER); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_VOCALS, "Stem — Vocals (ML)") - ->Check(_type == AUDIOSAMPLETYPE::STEM_VOCALS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_VOCALS); } #elif defined(HAVE_OPENVINO) || defined(HAVE_ORT) mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_DRUMS, "Stem — Drums") - ->Check(_type == AUDIOSAMPLETYPE::STEM_DRUMS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_DRUMS); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_BASS, "Stem — Bass") - ->Check(_type == AUDIOSAMPLETYPE::STEM_BASS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_BASS); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_OTHER, "Stem — Other") - ->Check(_type == AUDIOSAMPLETYPE::STEM_OTHER); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_OTHER); mnuWave.AppendRadioItem(ID_WAVE_MNU_STEM_VOCALS, "Stem — Vocals (ML)") - ->Check(_type == AUDIOSAMPLETYPE::STEM_VOCALS); + ->Check(effectiveType == AUDIOSAMPLETYPE::STEM_VOCALS); #endif #ifdef __APPLE__ // Keep the classify entry inside the same radio group so its @@ -359,6 +380,25 @@ void Waveform::OnGridPopup(wxCommandEvent& event) RenderCommandEvent rcEvent("", mTimeline->GetSelectedPositionStartMS(), mTimeline->GetSelectedPositionEndMS(), true, false); wxPostEvent(mParent, rcEvent); } else if (id == ID_WAVE_MNU_RAW) { + // If we're currently playing one of the auto-named stem alt tracks + // (Drums/Bass/Vocals/Other), "Raw waveform" should mean "raw main + // audio" — not "raw drums". Switch back to Main first. + if (_activeAudioTrackIndex > 0) { + if (auto* frame = xLightsApp::GetFrame()) { + if (auto* sf = frame->CurrentSeqXmlFile) { + int altIdx = _activeAudioTrackIndex - 1; + if (altIdx < sf->GetAltTrackCount()) { + std::string n = sf->GetAltTrackDisplayName(altIdx); + if (n == "Drums" || n == "Bass" || n == "Vocals" || n == "Other") { + SetActiveAudioTrack(0); + ForceRedraw(); + Refresh(); + return; + } + } + } + } + } _type = AUDIOSAMPLETYPE::RAW; } else if (id == ID_WAVE_MNU_BASS) { _type = AUDIOSAMPLETYPE::BASS; @@ -429,20 +469,51 @@ void Waveform::OnGridPopup(wxCommandEvent& event) ForceRedraw(); Refresh(); return; -#if defined(__APPLE__) || defined(HAVE_OPENVINO) || defined(HAVE_ORT) +#if defined(__APPLE__) || defined(HAVE_OPENVINO) || defined(HAVE_ORT) } else if (id == ID_WAVE_MNU_STEM_DRUMS || id == ID_WAVE_MNU_STEM_BASS || id == ID_WAVE_MNU_STEM_OTHER || id == ID_WAVE_MNU_STEM_VOCALS) { if (_media == nullptr) return; - if (!_media->HasStemData()) { + const char* stemName = + id == ID_WAVE_MNU_STEM_DRUMS ? "Drums" : + id == ID_WAVE_MNU_STEM_BASS ? "Bass" : + id == ID_WAVE_MNU_STEM_OTHER ? "Other" : + "Vocals"; + // Fast path: if PersistStemsAsAltTracks already produced a matching + // alt track on a previous run (or in a previous session — alt_tracks + // round-trip through the .xsq XML), just switch to it. Skipping + // PrepareStemData here is the whole reason we persist: HTDemucs takes + // tens of seconds and we already have the answer on disk. + auto findStemAlt = [stemName]() -> int { + auto* frame = xLightsApp::GetFrame(); + if (frame == nullptr || frame->CurrentSeqXmlFile == nullptr) return -1; + auto* sf = frame->CurrentSeqXmlFile; + for (int i = 0; i < sf->GetAltTrackCount(); i++) { + if (sf->GetAltTrackDisplayName(i) == stemName) return i; + } + return -1; + }; + + int altIdx = findStemAlt(); + if (altIdx < 0) { + // No persisted stem yet — run HTDemucs (which writes the WAVs and + // AddAltTracks them via PersistStemsAsAltTracks) and look again. if (!PrepareStemData()) { - // User cancelled or error — bail without switching. ForceRedraw(); Refresh(); return; } + altIdx = findStemAlt(); } + if (altIdx >= 0) { + SetActiveAudioTrack(altIdx + 1); + ForceRedraw(); + Refresh(); + return; + } + // Last-resort fallback — persistence failed. Use the in-memory STEM_* + // visualization on the main media. AUDIOSAMPLETYPE stem = id == ID_WAVE_MNU_STEM_DRUMS ? AUDIOSAMPLETYPE::STEM_DRUMS : id == ID_WAVE_MNU_STEM_BASS ? AUDIOSAMPLETYPE::STEM_BASS : @@ -451,7 +522,6 @@ void Waveform::OnGridPopup(wxCommandEvent& event) _type = stem; views.clear(); mCurrentWaveView = NO_WAVE_VIEW_SELECTED; - // Fall through to the shared SwitchTo / rebuild path below. #endif // __APPLE__ || HAVE_OPENVINO || HAVE_ORT #ifdef __APPLE__ } else if (id == ID_WAVE_MNU_CLASSIFY) { @@ -517,15 +587,7 @@ void Waveform::OnGridPopup(wxCommandEvent& event) EnsureAudioTrackIds(); for (int i = 0; i < 32; i++) { if (id == _audioTrackIdPool[i]) { - _activeAudioTrackIndex = i; - auto* frame = xLightsApp::GetFrame(); - if (frame != nullptr && frame->CurrentSeqXmlFile != nullptr) { - wxString err; - AudioManager* newMedia = (i == 0) - ? frame->CurrentSeqXmlFile->GetMedia() - : frame->CurrentSeqXmlFile->GetAltTrackMedia(i - 1); - OpenfileMedia(newMedia, err); - } + SetActiveAudioTrack(i); return; } } @@ -651,6 +713,10 @@ void Waveform::mouseWheelMoved(wxMouseEvent& event) } } +#if defined(__APPLE__) || defined(HAVE_OPENVINO) || defined(HAVE_ORT) +static void PersistStemsAsAltTracks(const StemOutput& stems); +#endif + #if defined(HAVE_OPENVINO) || defined(HAVE_ORT) // Non-Apple PrepareStemData: // ONNX Runtime or OpenVINO @@ -744,9 +810,10 @@ bool Waveform::PrepareStemData() stems.bassL, stems.bassR, stems.otherL, stems.otherR, stems.vocalsL, stems.vocalsR); + PersistStemsAsAltTracks(stems); return true; } -#endif // HAVE_OPENVINO || HAVE_ORT +#endif // HAVE_OPENVINO || HAVE_ORT #ifdef __APPLE__ // A8 first-run helper: make sure the HTDemucs model is present @@ -957,10 +1024,120 @@ bool Waveform::PrepareStemData() stems.bassL, stems.bassR, stems.otherL, stems.otherR, stems.vocalsL, stems.vocalsR); + PersistStemsAsAltTracks(stems); return true; } #endif +#if defined(__APPLE__) || defined(HAVE_OPENVINO) || defined(HAVE_ORT) +// Persist the four HTDemucs output buffers as wav files in +// /audio-stems//, register each via SequenceFile:: +// AddAltTrack, and refresh the stems panel. Stems whose shortname is already +// an alt track are skipped — re-running stem separation on the same sequence +// just refreshes the buffers in-memory, it doesn't pile up duplicates. +static void PersistStemsAsAltTracks(const StemOutput& stems) +{ + if (stems.sampleRate <= 0) return; + auto* frame = xLightsApp::GetFrame(); + if (frame == nullptr || frame->CurrentSeqXmlFile == nullptr) return; + SequenceFile* sf = frame->CurrentSeqXmlFile; + + std::string showDir = frame->GetShowDirectory(); + if (showDir.empty()) return; + + std::string seqName = sf->GetName(); + if (seqName.empty()) seqName = "sequence"; + + std::filesystem::path dir = std::filesystem::path(showDir) / "audio-stems" / seqName; + std::error_code ec; + std::filesystem::create_directories(dir, ec); + if (ec) { + spdlog::warn("PersistStemsAsAltTracks: couldn't create {}: {}", + dir.string(), ec.message()); + return; + } + ObtainAccessToURL(dir.string(), true); + + struct OneStem { + const char* name; + const std::vector* L; + const std::vector* R; + }; + const OneStem all[] = { + { "Drums", &stems.drumsL, &stems.drumsR }, + { "Bass", &stems.bassL, &stems.bassR }, + { "Vocals", &stems.vocalsL, &stems.vocalsR }, + { "Other", &stems.otherL, &stems.otherR }, + }; + + auto altTrackHasName = [sf](const std::string& name) { + for (int i = 0; i < sf->GetAltTrackCount(); i++) { + if (sf->GetAltTrackDisplayName(i) == name) return true; + } + return false; + }; + + for (const OneStem& s : all) { + if (s.L->empty() || s.L->size() != s.R->size()) continue; + if (altTrackHasName(s.name)) continue; + + std::filesystem::path wavPath = dir / (std::string(s.name) + ".wav"); + if (!xlights::wav::WriteStereoFloatWav(wavPath.string(), *s.L, *s.R, + static_cast(stems.sampleRate))) { + spdlog::error("PersistStemsAsAltTracks: failed to write {}", wavPath.string()); + continue; + } + sf->AddAltTrack(showDir, wavPath.string(), s.name); + } + + if (auto* ms = frame->GetMainSequencer()) { + if (auto* sp = ms->GetStemsPanel()) sp->RefreshFromAltTracks(); + } +} +#endif + +bool Waveform::SetActiveAudioTrack(int idx) +{ + auto* frame = xLightsApp::GetFrame(); + if (frame == nullptr || frame->CurrentSeqXmlFile == nullptr) return false; + if (idx < 0) return false; + if (idx > 0 && idx - 1 >= frame->CurrentSeqXmlFile->GetAltTrackCount()) return false; + + // Capture the old audio's playback state before we swap. If the user is + // mid-playback we want to keep going on the new track from the same + // position rather than forcing a stop+replay. + AudioManager* oldMedia = _media; + bool wasPlaying = (oldMedia != nullptr && oldMedia->IsPlaying()); + long playPosMS = (oldMedia != nullptr) ? oldMedia->Tell() : -1; + if (oldMedia != nullptr) oldMedia->Pause(); + + _activeAudioTrackIndex = idx; + AudioManager* newMedia = (idx == 0) + ? frame->CurrentSeqXmlFile->GetMedia() + : frame->CurrentSeqXmlFile->GetAltTrackMedia(idx - 1); + wxString err; + OpenfileMedia(newMedia, err); + // Force the waveform to repaint with the new media. Without this, the + // canvas stays stale until the next external trigger (e.g. play button). + ForceRedraw(); + Refresh(); + // Let the stems panel repaint its solo indicators. + if (auto* ms = frame->GetMainSequencer()) { + if (auto* sp = ms->GetStemsPanel()) { + if (auto* hdr = sp->GetStemHeadersPanel()) hdr->Refresh(); + } + } + + // Resume on the new track at the same position so a live solo toggle + // doesn't drop the user out of the song. Skip the seek/play if there + // was no prior position (initial load) or the new track failed to open. + if (newMedia != nullptr && wasPlaying && playPosMS >= 0) { + newMedia->Seek(playPosMS); + newMedia->Play(); + } + return true; +} + // Open Media file and return elapsed time in milliseconds int Waveform::OpenfileMedia(AudioManager* media, wxString& error) { @@ -971,12 +1148,13 @@ int Waveform::OpenfileMedia(AudioManager* media, wxString& error) views.clear(); ResetAnalysisState(); if (_media != nullptr) { - _media->SwitchTo(_type); - float samplesPerLine = GetSamplesPerLineFromZoomLevel(mZoomLevel); - views.emplace_back(mZoomLevel, samplesPerLine, media, _type, _lowNote, _highNote); - mCurrentWaveView = 0; + // Defer SwitchTo and view creation to first render — avoids blocking + // on audio data load (which runs on a background thread). + _pendingMediaInit = true; + mCurrentWaveView = NO_WAVE_VIEW_SELECTED; return media->LengthMS(); } else { + _pendingMediaInit = false; mCurrentWaveView = NO_WAVE_VIEW_SELECTED; SetZoomLevel(GetZoomLevel()); return 0; @@ -1005,13 +1183,73 @@ void Waveform::render() SetZoomLevel(mZoomLevel); } + // Deferred waveform init — complete once audio data has finished loading + if (_pendingMediaInit && _media != nullptr) { + if (_media->IsDataLoaded()) { + _media->SwitchTo(_type); + float samplesPerLine = GetSamplesPerLineFromZoomLevel(mZoomLevel); + views.emplace_back(mZoomLevel, samplesPerLine, _media, _type, _lowNote, _highNote); + mCurrentWaveView = 0; + _pendingMediaInit = false; + _shimmerPhase = 0.0f; + } + } + xlGraphicsContext *ctx = PrepareContextForDrawing(); if (ctx == nullptr) { return; } ctx->SetViewport(0, 0, mWindowWidth, mWindowHeight); - if (mCurrentWaveView >= 0) { + if (_pendingMediaInit) { + // Draw loading shimmer while audio is being decoded + float w = (float)mWindowWidth; + float h = (float)mWindowHeight; + + // Shimmer band sweeps left to right + float bandWidth = w * 0.25f; + float bandCenter = _shimmerPhase * (w + bandWidth) - bandWidth * 0.5f; + + xlColor bgColor(30, 30, 35); + xlColor shimmerColor(55, 55, 65); + + auto* vca = ctx->createVertexColorAccumulator(); + vca->PreAlloc(12); + + // Left section (bg) + float leftEdge = std::max(0.0f, bandCenter - bandWidth * 0.5f); + if (leftEdge > 0) { + vca->AddVertex(0, 0, bgColor); vca->AddVertex(leftEdge, 0, bgColor); vca->AddVertex(0, h, bgColor); + vca->AddVertex(leftEdge, 0, bgColor); vca->AddVertex(leftEdge, h, bgColor); vca->AddVertex(0, h, bgColor); + } + + // Shimmer band (gradient: bg → shimmer → bg) + float sl = std::max(0.0f, bandCenter - bandWidth * 0.5f); + float sm = std::min(w, std::max(0.0f, bandCenter)); + float sr = std::min(w, bandCenter + bandWidth * 0.5f); + + // Left fade-in + vca->AddVertex(sl, 0, bgColor); vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sl, h, bgColor); + vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sm, h, shimmerColor); vca->AddVertex(sl, h, bgColor); + // Right fade-out + vca->AddVertex(sm, 0, shimmerColor); vca->AddVertex(sr, 0, bgColor); vca->AddVertex(sm, h, shimmerColor); + vca->AddVertex(sr, 0, bgColor); vca->AddVertex(sr, h, bgColor); vca->AddVertex(sm, h, shimmerColor); + + // Right section (bg) + float rightEdge = std::min(w, bandCenter + bandWidth * 0.5f); + if (rightEdge < w) { + vca->AddVertex(rightEdge, 0, bgColor); vca->AddVertex(w, 0, bgColor); vca->AddVertex(rightEdge, h, bgColor); + vca->AddVertex(w, 0, bgColor); vca->AddVertex(w, h, bgColor); vca->AddVertex(rightEdge, h, bgColor); + } + + vca->Finalize(false, false); + ctx->drawTriangles(vca); + delete vca; + + _shimmerPhase += 0.02f; + if (_shimmerPhase > 1.0f) _shimmerPhase = 0.0f; + CallAfter([this]() { Refresh(); }); + } else if (mCurrentWaveView >= 0) { DrawWaveView(ctx, views[mCurrentWaveView]); } @@ -1385,6 +1623,9 @@ void Waveform::SetZoomLevel(int level) if (!mIsInitialized) return; + // Don't create views while media init is deferred — will be done on first render + if (_pendingMediaInit) return; + mCurrentWaveView = NO_WAVE_VIEW_SELECTED; for (size_t i = 0; i < views.size(); i++) { if (views[i].GetZoomLevel() == mZoomLevel && views[i].GetType() == _type) { diff --git a/src-ui-wx/sequencer/Waveform.h b/src-ui-wx/sequencer/Waveform.h index a4c4529565..96f31cb9ac 100644 --- a/src-ui-wx/sequencer/Waveform.h +++ b/src-ui-wx/sequencer/Waveform.h @@ -80,6 +80,11 @@ class Waveform : public GRAPHICS_BASE_CLASS #endif int GetActiveAudioTrackIndex() const { return _activeAudioTrackIndex; } + // Switch playback to a track: 0 = main, 1..N = alt track index + 1. + // Returns true when the index is valid and the switch is initiated; + // the actual media open is async (see deferred-init path in render()), + // so true does not guarantee the new track has finished loading. + bool SetActiveAudioTrack(int idx); Waveform(wxPanel* parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize,long style=0, const wxString &name=wxPanelNameStr); @@ -117,6 +122,8 @@ class Waveform : public GRAPHICS_BASE_CLASS bool m_dragging; DRAG_MODE m_drag_mode; AudioManager* _media; + bool _pendingMediaInit = false; + float _shimmerPhase = 0.0f; AUDIOSAMPLETYPE _type = AUDIOSAMPLETYPE::RAW; int _lowNote = -1; int _highNote = -1; diff --git a/src-ui-wx/sequencer/tabSequencer.cpp b/src-ui-wx/sequencer/tabSequencer.cpp index 8a0de6ebc4..994d091553 100644 --- a/src-ui-wx/sequencer/tabSequencer.cpp +++ b/src-ui-wx/sequencer/tabSequencer.cpp @@ -91,6 +91,7 @@ void xLightsFrame::CreateSequencer() spdlog::debug(" Set timeline."); mainSequencer->PanelWaveForm->SetTimeline(mainSequencer->PanelTimeLine); + mainSequencer->PanelStems->SetTimeline(mainSequencer->PanelTimeLine); mainSequencer->PanelTimeLine->SetSequenceElements(&_sequenceElements); mainSequencer->PanelTimeLine->SyncTagsFrom(_sequenceElements); @@ -921,6 +922,18 @@ void xLightsFrame::LoadSequencer(SequenceFile& xml_file, pugi::xml_document& doc spdlog::debug("Loading the audio data"); LoadAudioData(xml_file); + spdlog::debug("Loading audio stems"); + { + // SequenceFile::alt_tracks is the source of truth for stem audio data. + // The UI overlay (panel visibility, row colors) is not yet + // round-tripped through master's pugi-based xsq writer, so we always + // rebuild the panel from alt_tracks on open. + mainSequencer->PanelStems->SetShowDirectory(GetShowDirectory()); + mainSequencer->PanelStems->SetTimeFrequency(xml_file.GetFrequency()); + mainSequencer->PanelStems->ClearRowsUiOnly(); + mainSequencer->PanelStems->RefreshFromAltTracks(); + } + spdlog::debug("Preparing views"); _sequenceElements.PrepareViews(xml_file); @@ -2795,6 +2808,7 @@ bool xLightsFrame::TimerRgbSeq(long msec) if (mainSequencer->PanelTimeLine->SetPlayMarkerMS(current_play_time)) { if (NeedToRenderFrame(mainSequencer->PanelWaveForm, OutputTimer, didRender)) { mainSequencer->PanelWaveForm->UpdatePlayMarker(); + mainSequencer->PanelStems->UpdatePlayMarker(); mainSequencer->PanelWaveForm->CheckNeedToScroll(); mainSequencer->PanelEffectGrid->ForceRefresh(); } @@ -3454,6 +3468,15 @@ void xLightsFrame::ShowHideHousePreview(wxCommandEvent& event) UpdateViewMenu(); } +void xLightsFrame::OnMenuItemToggleStemsPanel(wxCommandEvent& event) +{ + InitSequencer(); + StemsPanel* stems = mainSequencer->GetStemsPanel(); + stems->SetUserVisible(!stems->IsUserVisible()); + mainSequencer->Layout(); + UpdateViewMenu(); +} + void xLightsFrame::ShowHideEffectDropper(wxCommandEvent& event) { InitSequencer(); diff --git a/src-ui-wx/wxsmith/xLightsframe.wxs b/src-ui-wx/wxsmith/xLightsframe.wxs index bdf235d135..18f3171300 100755 --- a/src-ui-wx/wxsmith/xLightsframe.wxs +++ b/src-ui-wx/wxsmith/xLightsframe.wxs @@ -1475,6 +1475,11 @@ 1 + + + + 1 + diff --git a/src-ui-wx/xLightsMain.cpp b/src-ui-wx/xLightsMain.cpp index 1b41df6d3d..68120d598c 100644 --- a/src-ui-wx/xLightsMain.cpp +++ b/src-ui-wx/xLightsMain.cpp @@ -336,6 +336,7 @@ const wxWindowID xLightsFrame::ID_MENUITEM_SEARCH_EFFECTS = wxNewId(); const wxWindowID xLightsFrame::ID_MENUITEM_VIDEOPREVIEW = wxNewId(); const wxWindowID xLightsFrame::ID_MNU_JUKEBOX = wxNewId(); const wxWindowID xLightsFrame::ID_MNU_FINDDATA = wxNewId(); +const wxWindowID xLightsFrame::ID_MENU_TOGGLE_STEMS_PANEL = wxNewId(); const wxWindowID xLightsFrame::ID_MNU_SUPPRESSDOCK_HP = wxNewId(); const wxWindowID xLightsFrame::ID_MNU_SUPPRESSDOCK_MP = wxNewId(); const wxWindowID xLightsFrame::ID_MENUITEM3 = wxNewId(); @@ -1204,6 +1205,8 @@ xLightsFrame::xLightsFrame(wxWindow* parent, int ab, wxWindowID id, bool renderO MenuItem18->Append(MenuItemJukebox); MenuItemFindData = new wxMenuItem(MenuItem18, ID_MNU_FINDDATA, _("Find Effect Data"), wxEmptyString, wxITEM_CHECK); MenuItem18->Append(MenuItemFindData); + MenuItemStemsPanel = new wxMenuItem(MenuItem18, ID_MENU_TOGGLE_STEMS_PANEL, _("Audio Stems"), wxEmptyString, wxITEM_CHECK); + MenuItem18->Append(MenuItemStemsPanel); MenuItem18->AppendSeparator(); MenuItem1 = new wxMenu(); MenuItem_SD_HP = new wxMenuItem(MenuItem1, ID_MNU_SUPPRESSDOCK_HP, _("House Preview"), wxEmptyString, wxITEM_CHECK); @@ -1433,6 +1436,7 @@ xLightsFrame::xLightsFrame(wxWindow* parent, int ab, wxWindowID id, bool renderO Connect(ID_MENUITEM_VIDEOPREVIEW, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItemShowHideVideoPreview); Connect(ID_MNU_JUKEBOX, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_JukeboxSelected); Connect(ID_MNU_FINDDATA, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItemFindDataSelected); + Connect(ID_MENU_TOGGLE_STEMS_PANEL, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItemToggleStemsPanel); Connect(ID_MNU_SUPPRESSDOCK_HP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_SuppressDock); Connect(ID_MNU_SUPPRESSDOCK_MP, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_SuppressDock); Connect(ID_MENUITEM_WINDOWS_PERSPECTIVE, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::ShowHidePerspectivesWindow); @@ -4129,6 +4133,7 @@ void xLightsFrame::UpdateSequenceLength() mainSequencer->PanelWaveForm->SetZoomLevel(maxZoom); mainSequencer->PanelTimeLine->RaiseChangeTimeline(); mainSequencer->PanelWaveForm->UpdatePlayMarker(); + mainSequencer->PanelStems->UpdatePlayMarker(); } } @@ -10253,6 +10258,10 @@ void xLightsFrame::UpdateViewMenu() } } } + + if (mainSequencer != nullptr && mainSequencer->GetStemsPanel() != nullptr) { + MenuItemStemsPanel->Check(mainSequencer->GetStemsPanel()->IsUserVisible()); + } } void xLightsFrame::OnMenuItem_ColorReplaceSelected(wxCommandEvent& event) diff --git a/src-ui-wx/xLightsMain.h b/src-ui-wx/xLightsMain.h index 43288008e0..622a4890da 100755 --- a/src-ui-wx/xLightsMain.h +++ b/src-ui-wx/xLightsMain.h @@ -534,6 +534,7 @@ class xLightsFrame: public xlFrame, public RenderContext, public UICallbacks void OnNotebook1PageChanged1(wxAuiNotebookEvent& event); void ShowHideModelPreview(wxCommandEvent& event); void ShowHideHousePreview(wxCommandEvent& event); + void OnMenuItemToggleStemsPanel(wxCommandEvent& event); void OnAuiToolBarItemPlayButtonClick(wxCommandEvent& event); void OnAuiToolBarItemPauseButtonClick(wxCommandEvent& event); void OnAuiToolBarItemStopClick(wxCommandEvent& event); @@ -860,6 +861,7 @@ private : static const wxWindowID ID_MENUITEM_VIDEOPREVIEW; static const wxWindowID ID_MNU_JUKEBOX; static const wxWindowID ID_MNU_FINDDATA; + static const wxWindowID ID_MENU_TOGGLE_STEMS_PANEL; static const wxWindowID ID_MNU_SUPPRESSDOCK_HP; static const wxWindowID ID_MNU_SUPPRESSDOCK_MP; static const wxWindowID ID_MENUITEM3; @@ -1008,6 +1010,7 @@ private : wxMenuItem* MenuItemUserDict; wxMenuItem* MenuItemValueCurves; wxMenuItem* MenuItemVideoPreview; + wxMenuItem* MenuItemStemsPanel; wxMenuItem* MenuItemViewSaveAsPerspective; wxMenuItem* MenuItemViewSavePerspective; wxMenuItem* MenuItem_ACLIghts; diff --git a/xLights/Xlights.vcxproj b/xLights/Xlights.vcxproj index 6a19cef9aa..90799d7c0b 100644 --- a/xLights/Xlights.vcxproj +++ b/xLights/Xlights.vcxproj @@ -118,35 +118,35 @@ true - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)\..\src-core;$(SolutionDir)\..\src-ui-wx;$(SolutionDir)\..\src-core\render;$(SolutionDir)\..\src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D;..\include\microsoft.ml.onnxruntime.directml.1.19.2\build\native\include + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)\..\src-core;$(SolutionDir)\..\src-ui-wx;$(SolutionDir)\..\src-core\render;$(SolutionDir)\..\src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D;..\include\microsoft.ml.onnxruntime.directml.1.19.2\build\native\include $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;./ffmpeg-dev/lib;$(VCToolsInstallDir)lib\x64;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs;..\include\microsoft.ml.onnxruntime.directml.1.19.2\runtimes\win-x64\native true true - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;./ffmpeg-dev/lib;$(VCToolsInstallDir)lib\x64;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs true true - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include;..\include\ffmpeg-6\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\dependencies\lua\src;..\include\sol2-3.5.0\;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;./ffmpeg-dev/lib;$(VCToolsInstallDir)lib\x64;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs true false - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)\..\src-core;$(SolutionDir)\..\src-ui-wx;$(SolutionDir)\..\src-core\render;$(SolutionDir)\..\src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D;..\include\microsoft.ml.onnxruntime.directml.1.19.2\build\native\include + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)\..\src-core;$(SolutionDir)\..\src-ui-wx;$(SolutionDir)\..\src-core\render;$(SolutionDir)\..\src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D;..\include\microsoft.ml.onnxruntime.directml.1.19.2\build\native\include $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs;..\include\microsoft.ml.onnxruntime.directml.1.19.2\runtimes\win-x64\native false - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs false - $(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D + ..\dependencies\aubio\src;$(WXWIDGETS_ROOT)\include;$(WXWIDGETS_ROOT)\include\msvc;$(WXWIDGETS_ROOT)\3rdparty;..\..\wxWidgets\include;..\..\wxWidgets\include\msvc;..\..\wxWidgets\3rdparty;$(IncludePath);..\include\ffmpeg-6\include;..\include;$(SolutionDir)src-core;$(SolutionDir)src-ui-wx;$(SolutionDir)src-core\render;$(SolutionDir)src-core\utils;..\include\zlib;..\dependencies;..\dependencies\libxlsxwriter\include;..\include\sol2-3.5.0\;..\dependencies\lua\src;..\dependencies\pybind11\include;..\dependencies\pugixml\src;$(Python_ROOT_DIR)\include;..\dependencies\spdlog\include;..\dependencies\midifile\include;..\dependencies\liquidfun\liquidfun\Box2D $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;..\..\wxWidgets\lib\vc_x64_lib;$(WXWIDGETS_ROOT)\lib\vc_x64_lib;..\lib\windows64;GL;..\dependencies\lua\src;$(Python_ROOT_DIR)\libs @@ -918,6 +918,32 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r + + + + + + + + + + + + + + + + $(IntDir)aubio_fft.obj + + + + + + + + + + @@ -1491,6 +1517,10 @@ xcopy "$(SolutionDir)..\bin64\Vamp\" "$(TargetDir)Vamp\" /e /y /i /r + + + + diff --git a/xLights/Xlights.vcxproj.filters b/xLights/Xlights.vcxproj.filters index e4e5eafb1e..320a39d368 100644 --- a/xLights/Xlights.vcxproj.filters +++ b/xLights/Xlights.vcxproj.filters @@ -694,6 +694,78 @@ sequencer + + sequencer + + + sequencer + + + sequencer + + + utils + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + + + aubio + render @@ -2038,6 +2110,18 @@ sequencer + + sequencer + + + sequencer + + + sequencer + + + utils + render @@ -2831,6 +2915,9 @@ {c1416434-1c5e-4d35-9335-0e5286dca853} + + {7a8b6c5d-4e3f-2a1b-9c8d-7e6f5a4b3c2d} + {6ee588bc-dc96-462e-b373-e36fbb8a27e0} diff --git a/xLights/xLights.cbp b/xLights/xLights.cbp index 041856fc53..9c0938136c 100644 --- a/xLights/xLights.cbp +++ b/xLights/xLights.cbp @@ -49,6 +49,7 @@ + @@ -114,6 +115,7 @@ + @@ -1220,6 +1222,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +