# HG changeset patch # User lbajardsilogic # Date 1178874494 0 # Node ID fc9323a41f5a8f03278e9e23ada89d4072e0cdec start base : Sonic Visualiser sv1-1.0rc1 diff -r 000000000000 -r fc9323a41f5a COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff -r 000000000000 -r fc9323a41f5a README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,125 @@ + +Sonic Visualiser +================ + +Sonic Visualiser is a program for viewing and analysing the contents +of music audio files. + +With Sonic Visualiser you can: + + * Load audio files in various formats and view their waveforms + + * Look at audio visualisations such as spectrogram views, with +interactive adjustment of display parameters + + * Annotate audio data by adding labelled time points and defining +segments, point values and curves + + * Run feature-extraction plugins to calculate annotations +automatically, using algorithms such as beat trackers, pitch +detectors and so on + + * Import annotation data from various text formats and MIDI files + + * Play back the original audio with synthesised annotations, taking +care to synchronise playback with the display position + + * Slow down and speed up playback and loop segments of interest, +including seamless looping of complex non-contiguous areas + + * Export annotations and audio selections to external files. + +Sonic Visualiser can also be controlled remotely using the Open Sound +Control (OSC) protocol. + + +To compile from source +---------------------- + +First, please consider getting one of the ready-to-run binary +distributions available from http://www.sonicvisualiser.org/ . + +The following additional libraries are required or optional when +building Sonic Visualiser: + +REQUIRED Vamp Plugin SDK http://www.sonicvisualiser.org/ + +REQUIRED Qt4 Free Edition http://www.trolltech.com/ +REQUIRED libsndfile http://www.mega-nerd.com/libsndfile/ +REQUIRED libsamplerate http://www.mega-nerd.com/SRC/ +REQUIRED FFTW3 http://www.fftw.org/ +REQUIRED bzip2 library http://www.bzip.org/ + +Optional JACK http://www.jackaudio.org/ +Optional PortAudio v18 or v19 http://www.portaudio.com/ +Optional MAD mp3 decoder http://www.underbit.com/products/mad/ +Optional Oggz and fishsound http://www.annodex.net/software/libraries.html +Optional liblo OSC library http://www.plugin.org.uk/liblo/ + +Although JACK and PortAudio are individually optional, you will need +to have one or the other of them in order to get any audio playback. +Usually JACK is preferred on Linux and PortAudio elsewhere. + +If you happen to be using a Debian-based Linux, you probably want to +apt-get install the following packages: libqt4-dev libsndfile1-dev +libsamplerate0-dev fftw3-dev libbz2-dev libjack0.100.0-dev libmad0-dev +liboggz1-dev libfishsound1-dev liblo0-dev. + +If you are building on a Unix-like system that supports pkg-config and +uses it for all appropriate libraries (such as a modern Linux +distribution) then you should be able to just run "qmake" (being +careful to ensure it is the Qt 4 version of qmake you are running, and +not a Qt 3 version). + +If you do not have pkg-config, you should first edit the file sv.prf +and comment out any of the optional HAVE_* lines (in the section +starting "If you don't have pkg-config...") for library dependencies +that you aren't using. Then run the Qt 4 "qmake" command. This will +create the proper Makefile. + +(If you're on the Mac and you have Qt3 installed as well, you must +export QMAKESPEC=macx-g++ before you do this, or Qt will get confused.) + +Then type "make". The program will then either build, or not build. + + +Qt Library Version Requirements +------------------------------- + +Sonic Visualiser requires Qt version 4.x. It can not be built with +Qt 3 or earlier. + +You can build Sonic Visualiser using Qt 4.0, 4.1, or 4.2, but the +build scripts provided will only work with Qt 4.2 because of an +incompatible change in the Qt build utilities. Please read +README.Qt41 if you want to build with Qt 4.0 or 4.1. + + +Credits +------- + +Sonic Visualiser was developed at the Centre for Digital Music, +Queen Mary, University of London. + +http://www.elec.qmul.ac.uk/digitalmusic/ + +The main program is by Chris Cannam, with additional DSP and program +design work by Christian Landone. Code copyright 2005-2007 Chris +Cannam with parts copyright 2006-2007 Queen Mary, University of +London, except where indicated in the individual source files. + +This project was partially funded by the European Commission through +the SIMAC project IST-FP6-507142 and the EASAIER project IST-FP6-033902. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. See the file COPYING included with +this distribution for more information. + + +More information +---------------- + +http://www.sonicvisualiser.org/ + diff -r 000000000000 -r fc9323a41f5a README.OSC --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.OSC Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,262 @@ + +OSC control of Sonic Visualiser +=============================== + +Sonic Visualiser can be controlled remotely using the Open Sound +Control protocol. This facility requires Steve Harris's liblo (Lite +OSC) library to have been available when Sonic Visualiser was built. + +Sonic Visualiser opens a single OSC port on startup. The URL of this +port is printed to standard output on startup, or can be read from the +About box on the Help menu. + +OSC commands accepted by Sonic Visualiser take the form: + + ://:/ [ ...] + +For example, "osc.udp://localhost:12654/play 2.0" will play the +current session from time 2.0 seconds. + +Methods that manipulate panes or layers act on the currently selected +pane or layer. Use the setcurrent method to choose the right target +for subsequent such methods. + +There is a small program sv/osc/sv-osc-send.c that sends an OSC method +and arguments to a given URL, and a small shell script sv/osc/sv-command +that provides a basic command shell for Sonic Visualiser. + + +OSC methods available +===================== + +Main window methods +------------------- + + /open + + Open a new file (of type determined by Sonic Visualiser). + If it is an audio file, use it to replace the existing main + audio file (if any). + + /openadditional + + Open a new file. If it is an audio file, open it in a new + pane in addition to the existing audio file (if any). + + /recent + /last + + Open the 'th most recent file from the Recent Files menu, + counting from 1 for the most recent file opened. "last" is a + synonym for "recent 1". + + /save + + Save the current session in as an SV session file. + This action will try to fail rather than overwrite an existing + file, but you probably shouldn't rely on that. + + /export + + Export the (first) selected area of the main audio file + (or all of it, if there is no selection) in , as a + WAV file. This action will try to fail rather than overwrite + an existing file, but you probably shouldn't rely on that. + + /jump + /jump end + /jump selection + + Jump the playback position to time (in seconds); or to + the end of the file; or to the start of the current selection. + + /play + /play + /play selection + + Start playback. If a time is given, start from that time + in seconds. If the word "selection" is given instead, play + the current selection. + + /stop + + Stop playback. + + /loop on + /loop off + + Switch playback loop mode on or off. + + /select + /select all + /select none + + Select the region from times to in seconds; or select + the whole file; or clear the selection. If there is a layer + selected that can be used as a snap guide for the selection, then + the selection will be snapped to it (in the same manner as when + making selections interactively). + + /addselect + + Make an additional selection (leaving any existing selection + in place) from times to in seconds. + + /undo + /redo + + Undo the last editing operation; redo the last undone operation. + Note that most of the classic editing operations (copy and paste + etc) are not controllable via OSC, but undo may still be useful + because Sonic Visualiser considers actions such as adding a pane + to be undoable editing operations as well. + + /add + /add + + Add a new pane containing a layer of the given type, based on + the main audio file. If no is specified, use all + available channels. Useful s are: + + waveform + spectrogram + spectrum + timeruler + + The following s are less useful, because they create + empty layers which there is currently no OSC support for editing: + + timeinstants + timevalues + notes + text + colour3dplot + + /set + /set pane + /set layer + + Set a main window control; a property of the current pane; or a + property of the current layer. + + Accepted main window s are: + + gain + whose values are linear multipliers (i.e. 1.0 == unity gain). + + speedup + takes a value of a percentage increase in playback + speed, so 0 is the default playback speed, 100 sets double + the default speed, and -100 sets half the default speed + (yes, I know, it's nonsense, sorry). + + overlays + controls the verbosity level of the text overlays on + each pane, from 0 (everything off) to 2 (everything on). + + zoomwheels + controls whether the zoom wheels are displayed (1) or not (0). + + propertyboxes + controls whether the property boxes are displayed (1) or not (0). + + For pane and layer properties, the control name is the displayed + name of the given property (though you may use "-" or "_" in place + of any spaces in the name if it's easier for you). The value may + be the displayed value or underlying integer for the property. + + Some examples: + + /set pane Global-Scroll off + /set pane Follow_Playback Scroll + /set layer Colour Blue + /set layer Scale-Units dB + /set layer Frequency-Scale Log + + Note that while you can use "-" or "_" in place of spaces in the + property name, you cannot currently do so in the value text. If + this is a problem for you, you might be able to set the value + as an integer instead (all layer properties can be set this way). + + /setcurrent + /setcurrent + + Make the given (a number counting from 1 for the topmost + pane) and optionally the given on that pane (a number + counting from 1 for the "frontmost" layer) the current pane and + layer for subsequent pane and layer operations. + + /delete pane + /delete layer + + Delete the current pane or layer. + + /zoom + /zoom in + /zoom out + /zoom default + + Zoom to a given zoom , given in audio sample frames per + pixel; or zoom in or out one step from the current level; or + return to the default zoom level. This method acts on the + current pane (it only affects all panes if set to Global Zoom, + which is the default). + + /zoomvertical + /zoomvertical in + /zoomvertical out + /zoomvertical default + + Change the vertical zoom and origin so as to show the value + range from to in the vertical scale; or zoom in or + out vertically; or return to the default vertical zoom level. + The effect of this method is heavily dependent on the current + layer. + + /transform + + Transform the current main audio file using the named transform. + Transforms are named according to the scheme + + type:source:plugin:output + + For example, the percussion onset detector from the Vamp example + plugin set can be invoked via + + /transform vamp:vamp-example-plugins:percussiononsets:onsets + + If the output is omitted, the first is used. Note that you + need to use the plugin and output name, not description: in + this case "percussiononsets" rather than "Simple Percussion + Onset Detector". + + There is not yet any way to run a transform via OSC on any but + the main audio file, nor with any but its default parameters, + processing block/step size, or channel selection. + + /resize + /resize pane + + Resize the main window to width and height (if the + window system permits); resize the current pane to height + if possible (!!! not yet working). + + /quit + + Exit the program abruptly without saving. + +Handy things still missing from the OSC interface include: + + * the ability to run transforms with non-default parameters or + starting from different source models + * the ability to add layers to a pane (without transform) + * the ability to add panes (and layers) showing any but the + main model + * the ability to set play parameters on a layer/model and show/hide it + * the ability to set the vertical zoom range (vital for spectrogram) + * the ability to import and export layers + * a working pane resize + * quick shortcuts to Melodic Range Spectrogram, Peak Frequency Spectrogram + * the ability to rename a layer + + diff -r 000000000000 -r fc9323a41f5a README.Qt41 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.Qt41 Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,54 @@ + +Building Sonic Visualiser using Qt 4.0 or 4.1 +============================================= + +The Sonic Visualiser source code should build fine using Qt versions +4.0, 4.1 or 4.2. + +However, TrollTech introduced an incompatible change to the handling +of quoted text in version 4.2 of the qmake utility, that makes it +impossible for us to provide build scripts that will work with both +older and newer versions. + +The provided scripts are for Qt 4.2. If you try to build using Qt 4.0 +or 4.1, you will get errors like these: + +main/MainWindow.cpp:3737: error: parse error before `:' token +main/MainWindow.cpp:3737:66: invalid suffix "M" on integer constant +main/MainWindow.cpp:3761:95: too many decimal points in number + +To fix this, you will need to patch the file sv.prf in this directory. +The easiest way to do that if you have a suitable patch utility to +hand, is to feed this file (README.Qt41) directly into patch, like this: + + patch < README.Qt41 + +If that succeeds, it will modify the file "sv.prf" from a Qt 4.2 +compatible format into one that is compatible with Qt 4.0 and 4.1. + +If you want to apply the patch some other way, here it is, in unified +diff format: + +--- sv.prf 2006-11-10 11:08:04.000000000 +0000 ++++ sv.prf.qt41 2006-11-10 11:08:31.000000000 +0000 +@@ -83,9 +83,9 @@ + system(pkg-config --exists $$PKG) { + VERSION = $$system(pkg-config --modversion $$PKG) + PACKAGE_SYMBOL = $$system(echo $$PKG | tr '[a-z-]' '[A-Z_]') +- VERSION_SYMBOL = $$PACKAGE_SYMBOL'_VERSION' ++ VERSION_SYMBOL = $$system(echo $$PKG"_VERSION" | tr '[a-z-]' '[A-Z_]') + DEFINES += HAVE_$$PACKAGE_SYMBOL +- QMAKE_CXXFLAGS += -D"'"$$VERSION_SYMBOL='"'$$VERSION'"'"'" ++ QMAKE_CXXFLAGS += -D'$$VERSION_SYMBOL="$$VERSION"' + QMAKE_CXXFLAGS += $$system(pkg-config --cflags $$PKG) + LIBS += $$system(pkg-config --libs $$PKG) + message("Using pkg-config package $$PKG with version $$VERSION") +@@ -142,7 +142,7 @@ + contains(SV_UNIT_PACKAGES, sndfile):!contains(DEFINES, HAVE_SNDFILE):error("sndfile library required") + contains(SV_UNIT_PACKAGES, samplerate):!contains(DEFINES, HAVE_SAMPLERATE):error("libsamplerate required") + +-VERSION_CFLAGS += -D"'"SVNREV='"'$$system(svnversion -n .)'"'"'" ++VERSION_CFLAGS += -D'SVNREV="$$system(svnversion -n .)"' + + QMAKE_CXXFLAGS_DEBUG += -DBUILD_DEBUG $$VERSION_CFLAGS + QMAKE_CXXFLAGS_RELEASE += -DBUILD_RELEASE $$VERSION_CFLAGS diff -r 000000000000 -r fc9323a41f5a base/AudioLevel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/AudioLevel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,291 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include "AudioLevel.h" +#include +#include +#include +#include +#include + +const float AudioLevel::DB_FLOOR = -1000.f; + +struct FaderDescription +{ + FaderDescription(float _minDb, float _maxDb, float _zeroPoint) : + minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { } + + float minDb; + float maxDb; + float zeroPoint; // as fraction of total throw +}; + +static const FaderDescription faderTypes[] = { + FaderDescription(-40.f, +6.f, 0.75f), // short + FaderDescription(-70.f, +10.f, 0.80f), // long + FaderDescription(-70.f, 0.f, 1.00f), // IEC268 + FaderDescription(-70.f, +10.f, 0.80f), // IEC268 long + FaderDescription(-40.f, 0.f, 1.00f), // preview +}; + +//typedef std::vector LevelList; +//static std::map previewLevelCache; +//static const LevelList &getPreviewLevelCache(int levels); + +float +AudioLevel::multiplier_to_dB(float multiplier) +{ + if (multiplier == 0.f) return DB_FLOOR; + else if (multiplier < 0.f) return multiplier_to_dB(-multiplier); + float dB = 10 * log10f(multiplier); + return dB; +} + +float +AudioLevel::dB_to_multiplier(float dB) +{ + if (dB == DB_FLOOR) return 0.f; + float m = powf(10.f, dB / 10.f); + return m; +} + +/* IEC 60-268-18 fader levels. Thanks to Steve Harris. */ + +static float iec_dB_to_fader(float db) +{ + float def = 0.0f; // Meter deflection %age + + if (db < -70.0f) { + def = 0.0f; + } else if (db < -60.0f) { + def = (db + 70.0f) * 0.25f; + } else if (db < -50.0f) { + def = (db + 60.0f) * 0.5f + 5.0f; + } else if (db < -40.0f) { + def = (db + 50.0f) * 0.75f + 7.5f; + } else if (db < -30.0f) { + def = (db + 40.0f) * 1.5f + 15.0f; + } else if (db < -20.0f) { + def = (db + 30.0f) * 2.0f + 30.0f; + } else { + def = (db + 20.0f) * 2.5f + 50.0f; + } + + return def; +} + +static float iec_fader_to_dB(float def) // Meter deflection %age +{ + float db = 0.0f; + + if (def >= 50.0f) { + db = (def - 50.0f) / 2.5f - 20.0f; + } else if (def >= 30.0f) { + db = (def - 30.0f) / 2.0f - 30.0f; + } else if (def >= 15.0f) { + db = (def - 15.0f) / 1.5f - 40.0f; + } else if (def >= 7.5f) { + db = (def - 7.5f) / 0.75f - 50.0f; + } else if (def >= 5.0f) { + db = (def - 5.0f) / 0.5f - 60.0f; + } else { + db = (def / 0.25f) - 70.0f; + } + + return db; +} + +float +AudioLevel::fader_to_dB(int level, int maxLevel, FaderType type) +{ + if (level == 0) return DB_FLOOR; + + if (type == IEC268Meter || type == IEC268LongMeter) { + + float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb); + float percent = float(level) * maxPercent / float(maxLevel); + float dB = iec_fader_to_dB(percent); + return dB; + + } else { // scale proportional to sqrt(fabs(dB)) + + int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint); + + if (level >= zeroLevel) { + + float value = level - zeroLevel; + float scale = float(maxLevel - zeroLevel) / + sqrtf(faderTypes[type].maxDb); + value /= scale; + float dB = powf(value, 2.f); + return dB; + + } else { + + float value = zeroLevel - level; + float scale = zeroLevel / sqrtf(0.f - faderTypes[type].minDb); + value /= scale; + float dB = powf(value, 2.f); + return 0.f - dB; + } + } +} + + +int +AudioLevel::dB_to_fader(float dB, int maxLevel, FaderType type) +{ + if (dB == DB_FLOOR) return 0; + + if (type == IEC268Meter || type == IEC268LongMeter) { + + // The IEC scale gives a "percentage travel" for a given dB + // level, but it reaches 100% at 0dB. So we want to treat the + // result not as a percentage, but as a scale between 0 and + // whatever the "percentage" for our (possibly >0dB) max dB is. + + float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb); + float percent = iec_dB_to_fader(dB); + int faderLevel = int((maxLevel * percent) / maxPercent + 0.01f); + + if (faderLevel < 0) faderLevel = 0; + if (faderLevel > maxLevel) faderLevel = maxLevel; + return faderLevel; + + } else { + + int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint); + + if (dB >= 0.f) { + + if (faderTypes[type].maxDb <= 0.f) { + + return maxLevel; + + } else { + + float value = sqrtf(dB); + float scale = (maxLevel - zeroLevel) / sqrtf(faderTypes[type].maxDb); + value *= scale; + int level = int(value + 0.01f) + zeroLevel; + if (level > maxLevel) level = maxLevel; + return level; + } + + } else { + + dB = 0.f - dB; + float value = sqrtf(dB); + float scale = zeroLevel / sqrtf(0.f - faderTypes[type].minDb); + value *= scale; + int level = zeroLevel - int(value + 0.01f); + if (level < 0) level = 0; + return level; + } + } +} + + +float +AudioLevel::fader_to_multiplier(int level, int maxLevel, FaderType type) +{ + if (level == 0) return 0.f; + return dB_to_multiplier(fader_to_dB(level, maxLevel, type)); +} + +int +AudioLevel::multiplier_to_fader(float multiplier, int maxLevel, FaderType type) +{ + if (multiplier == 0.f) return 0; + float dB = multiplier_to_dB(multiplier); + int fader = dB_to_fader(dB, maxLevel, type); + return fader; +} + +/* +const LevelList & +getPreviewLevelCache(int levels) +{ + LevelList &ll = previewLevelCache[levels]; + if (ll.empty()) { + for (int i = 0; i <= levels; ++i) { + float m = AudioLevel::fader_to_multiplier + (i + levels/4, levels + levels/4, AudioLevel::PreviewLevel); + if (levels == 1) m /= 100; // noise + ll.push_back(m); + } + } + return ll; +} +*/ + +int +AudioLevel::multiplier_to_preview(float m, int levels) +{ + assert(levels > 0); + return multiplier_to_fader(m, levels, PreviewLevel); + + /* The original multiplier_to_preview which follows is not thread-safe. + + if (m < 0.f) return -multiplier_to_preview(-m, levels); + + const LevelList &ll = getPreviewLevelCache(levels); + int result = -1; + + int lo = 0, hi = levels; + + // binary search + int level = -1; + while (result < 0) { + int newlevel = (lo + hi) / 2; + if (newlevel == level || + newlevel == 0 || + newlevel == levels) { + result = newlevel; + break; + } + level = newlevel; + if (ll[level] >= m) { + hi = level; + } else if (ll[level+1] >= m) { + result = level; + } else { + lo = level; + } + } + + return result; + + */ +} + +float +AudioLevel::preview_to_multiplier(int level, int levels) +{ + assert(levels > 0); + return fader_to_multiplier(level, levels, PreviewLevel); +/* + if (level < 0) return -preview_to_multiplier(-level, levels); + const LevelList &ll = getPreviewLevelCache(levels); + return ll[level]; +*/ +} + + diff -r 000000000000 -r fc9323a41f5a base/AudioLevel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/AudioLevel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,65 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _AUDIO_LEVEL_H_ +#define _AUDIO_LEVEL_H_ + +/** + * AudioLevel converts audio sample levels between various scales: + * + * - dB values (-inf -> 0dB) + * - floating-point values (-1.0 -> 1.0) such as used for a + * multiplier for gain or in floating-point WAV files + * - integer values intended to correspond to pixels on a fader + * or vu level scale. + */ + +class AudioLevel +{ +public: + + static const float DB_FLOOR; + + enum FaderType { + ShortFader = 0, // -40 -> +6 dB + LongFader = 1, // -70 -> +10 dB + IEC268Meter = 2, // -70 -> 0 dB + IEC268LongMeter = 3, // -70 -> +10 dB (0dB aligns with LongFader) + PreviewLevel = 4 + }; + + static float multiplier_to_dB(float multiplier); + static float dB_to_multiplier(float dB); + + static float fader_to_dB(int level, int maxLevel, FaderType type); + static int dB_to_fader(float dB, int maxFaderLevel, FaderType type); + + static float fader_to_multiplier(int level, int maxLevel, FaderType type); + static int multiplier_to_fader(float multiplier, int maxFaderLevel, + FaderType type); + + // fast if "levels" doesn't change often -- for audio segment previews + static int multiplier_to_preview(float multiplier, int levels); + static float preview_to_multiplier(int level, int levels); +}; + + +#endif + diff -r 000000000000 -r fc9323a41f5a base/AudioPlaySource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/AudioPlaySource.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_PLAY_SOURCE_H_ +#define _AUDIO_PLAY_SOURCE_H_ + +/** + * Simple interface for audio playback. This should be all that the + * ViewManager needs to know about to synchronise with playback by + * sample frame, but it doesn't provide enough to determine what is + * actually being played or how. See the audioio directory for a + * concrete subclass. + */ + +class AudioPlaySource +{ +public: + virtual ~AudioPlaySource() { } + + /** + * Start playing from the given frame. If playback is already + * under way, reseek to the given frame and continue. + */ + virtual void play(size_t startFrame) = 0; + + /** + * Stop playback. + */ + virtual void stop() = 0; + + /** + * Return whether playback is currently supposed to be happening. + */ + virtual bool isPlaying() const = 0; + + /** + * Return the frame number that is currently expected to be coming + * out of the speakers. (i.e. compensating for playback latency.) + */ + virtual size_t getCurrentPlayingFrame() = 0; + + /** + * Return the current (or thereabouts) output levels in the range + * 0.0 -> 1.0, for metering purposes. + */ + virtual bool getOutputLevels(float &left, float &right) = 0; + + /** + * Return the sample rate of the source material -- any material + * that wants to play at a different rate will sound wrong. + */ + virtual size_t getSourceSampleRate() const = 0; + + /** + * Return the sample rate set by the target audio device (or the + * source sample rate if the target hasn't set one). If the + * source and target sample rates differ, resampling will occur. + */ + virtual size_t getTargetSampleRate() const = 0; + +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/Clipboard.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Clipboard.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,158 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Clipboard.h" + +Clipboard::Point::Point(long frame, QString label) : + m_haveFrame(true), + m_frame(frame), + m_haveValue(false), + m_haveDuration(false), + m_haveLabel(true), + m_label(label) +{ +} + +Clipboard::Point::Point(long frame, float value, QString label) : + m_haveFrame(true), + m_frame(frame), + m_haveValue(true), + m_value(value), + m_haveDuration(false), + m_haveLabel(true), + m_label(label) +{ +} + +Clipboard::Point::Point(long frame, float value, size_t duration, QString label) : + m_haveFrame(true), + m_frame(frame), + m_haveValue(true), + m_value(value), + m_haveDuration(true), + m_duration(duration), + m_haveLabel(true), + m_label(label) +{ +} + +Clipboard::Point::Point(const Point &point) : + m_haveFrame(point.m_haveFrame), + m_frame(point.m_frame), + m_haveValue(point.m_haveValue), + m_value(point.m_value), + m_haveDuration(point.m_haveDuration), + m_duration(point.m_duration), + m_haveLabel(point.m_haveLabel), + m_label(point.m_label) +{ +} + +Clipboard::Point & +Clipboard::Point::operator=(const Point &point) +{ + if (this == &point) return *this; + m_haveFrame = point.m_haveFrame; + m_frame = point.m_frame; + m_haveValue = point.m_haveValue; + m_value = point.m_value; + m_haveDuration = point.m_haveDuration; + m_duration = point.m_duration; + m_haveLabel = point.m_haveLabel; + m_label = point.m_label; + return *this; +} + +bool +Clipboard::Point::haveFrame() const +{ + return m_haveFrame; +} + +long +Clipboard::Point::getFrame() const +{ + return m_frame; +} + +bool +Clipboard::Point::haveValue() const +{ + return m_haveValue; +} + +float +Clipboard::Point::getValue() const +{ + return m_value; +} + +bool +Clipboard::Point::haveDuration() const +{ + return m_haveDuration; +} + +size_t +Clipboard::Point::getDuration() const +{ + return m_duration; +} + +bool +Clipboard::Point::haveLabel() const +{ + return m_haveLabel; +} + +QString +Clipboard::Point::getLabel() const +{ + return m_label; +} + +Clipboard::Clipboard() { } +Clipboard::~Clipboard() { } + +void +Clipboard::clear() +{ + m_points.clear(); +} + +bool +Clipboard::empty() const +{ + return m_points.empty(); +} + +const Clipboard::PointList & +Clipboard::getPoints() const +{ + return m_points; +} + +void +Clipboard::setPoints(const PointList &pl) +{ + m_points = pl; +} + +void +Clipboard::addPoint(const Point &point) +{ + m_points.push_back(point); +} + diff -r 000000000000 -r fc9323a41f5a base/Clipboard.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Clipboard.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _CLIPBOARD_H_ +#define _CLIPBOARD_H_ + +#include +#include + +class Clipboard +{ +public: + class Point + { + public: + Point(long frame, QString label); + Point(long frame, float value, QString label); + Point(long frame, float value, size_t duration, QString label); + Point(const Point &point); + Point &operator=(const Point &point); + + bool haveFrame() const; + long getFrame() const; + + bool haveValue() const; + float getValue() const; + + bool haveDuration() const; + size_t getDuration() const; + + bool haveLabel() const; + QString getLabel() const; + + private: + bool m_haveFrame; + long m_frame; + bool m_haveValue; + float m_value; + bool m_haveDuration; + size_t m_duration; + bool m_haveLabel; + QString m_label; + }; + + Clipboard(); + ~Clipboard(); + + typedef std::vector PointList; + + void clear(); + bool empty() const; + const PointList &getPoints() const; + void setPoints(const PointList &points); + void addPoint(const Point &point); + +protected: + PointList m_points; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/Command.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Command.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,83 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Command.h" + +MacroCommand::MacroCommand(QString name) : + m_name(name) +{ +} + +MacroCommand::~MacroCommand() +{ + for (size_t i = 0; i < m_commands.size(); ++i) { + delete m_commands[i]; + } +} + +void +MacroCommand::addCommand(Command *command) +{ + m_commands.push_back(command); +} + +void +MacroCommand::deleteCommand(Command *command) +{ + for (std::vector::iterator i = m_commands.begin(); + i != m_commands.end(); ++i) { + + if (*i == command) { + m_commands.erase(i); + delete command; + return; + } + } +} + +bool +MacroCommand::haveCommands() const +{ + return !m_commands.empty(); +} + +void +MacroCommand::execute() +{ + for (size_t i = 0; i < m_commands.size(); ++i) { + m_commands[i]->execute(); + } +} + +void +MacroCommand::unexecute() +{ + for (size_t i = 0; i < m_commands.size(); ++i) { + m_commands[m_commands.size() - i - 1]->unexecute(); + } +} + +QString +MacroCommand::getName() const +{ + return m_name; +} + +void +MacroCommand::setName(QString name) +{ + m_name = name; +} + diff -r 000000000000 -r fc9323a41f5a base/Command.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Command.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _COMMAND_H_ +#define _COMMAND_H_ + +#include +#include + +class Command +{ +public: + virtual ~Command() { } + + virtual void execute() = 0; + virtual void unexecute() = 0; + virtual QString getName() const = 0; +}; + +class MacroCommand : public Command +{ +public: + MacroCommand(QString name); + virtual ~MacroCommand(); + + virtual void addCommand(Command *command); + virtual void deleteCommand(Command *command); + virtual bool haveCommands() const; + + virtual void execute(); + virtual void unexecute(); + + virtual QString getName() const; + virtual void setName(QString name); + +protected: + QString m_name; + std::vector m_commands; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a base/CommandHistory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/CommandHistory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,480 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the Rosegarden + MIDI and audio sequencer and notation editor, copyright 2000-2006 + Chris Cannam, distributed under the GNU General Public License. + + This file contains traces of the KCommandHistory class from the KDE + project, copyright 2000 Werner Trobin and David Faure and + distributed under the GNU Lesser General Public License. +*/ + +#include "CommandHistory.h" + +#include "Command.h" + +#include +#include +#include +#include +#include + +#include + +CommandHistory *CommandHistory::m_instance = 0; + +CommandHistory::CommandHistory() : + m_undoLimit(50), + m_redoLimit(50), + m_menuLimit(15), + m_savedAt(0), + m_currentCompound(0), + m_executeCompound(false), + m_currentBundle(0), + m_bundleTimer(0), + m_bundleTimeout(5000) +{ + m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); + m_undoAction->setShortcut(tr("Ctrl+Z")); + m_undoAction->setStatusTip(tr("Undo the last editing operation")); + connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); + + m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); + connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); + + m_undoMenu = new QMenu(tr("&Undo")); + m_undoMenuAction->setMenu(m_undoMenu); + connect(m_undoMenu, SIGNAL(triggered(QAction *)), + this, SLOT(undoActivated(QAction*))); + + m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); + m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); + m_redoAction->setStatusTip(tr("Redo the last operation that was undone")); + connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); + + m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); + connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); + + m_redoMenu = new QMenu(tr("Re&do")); + m_redoMenuAction->setMenu(m_redoMenu); + connect(m_redoMenu, SIGNAL(triggered(QAction *)), + this, SLOT(redoActivated(QAction*))); +} + +CommandHistory::~CommandHistory() +{ + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); + + delete m_undoMenu; + delete m_redoMenu; +} + +CommandHistory * +CommandHistory::getInstance() +{ + if (!m_instance) m_instance = new CommandHistory(); + return m_instance; +} + +void +CommandHistory::clear() +{ +// std::cerr << "CommandHistory::clear()" << std::endl; + closeBundle(); + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); + updateActions(); +} + +void +CommandHistory::registerMenu(QMenu *menu) +{ + menu->addAction(m_undoAction); + menu->addAction(m_redoAction); +} + +void +CommandHistory::registerToolbar(QToolBar *toolbar) +{ + toolbar->addAction(m_undoMenuAction); + toolbar->addAction(m_redoMenuAction); +} + +void +CommandHistory::addCommand(Command *command) +{ + if (!command) return; + + if (m_currentCompound) { + addToCompound(command, m_executeCompound); + return; + } + + addCommand(command, true); +} + +void +CommandHistory::addCommand(Command *command, bool execute, bool bundle) +{ + if (!command) return; + + if (m_currentCompound) { + addToCompound(command, execute); + return; + } + + if (bundle) { + addToBundle(command, execute); + return; + } else if (m_currentBundle) { + closeBundle(); + } + +// std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; + + // We can't redo after adding a command +// std::cerr << "CommandHistory::clearing redo stack" << std::endl; + clearStack(m_redoStack); + + // can we reach savedAt? + if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope + + m_undoStack.push(command); + clipCommands(); + + if (execute) { + command->execute(); + } + + // Emit even if we aren't executing the command, because + // someone must have executed it for this to make any sense + emit commandExecuted(); + emit commandExecuted(command); + + updateActions(); +} + +void +CommandHistory::addToBundle(Command *command, bool execute) +{ + if (m_currentBundle) { + if (!command || (command->getName() != m_currentBundleName)) { + closeBundle(); + } + } + + if (!command) return; + + if (!m_currentBundle) { + // need to addCommand before setting m_currentBundle, as addCommand + // with bundle false will reset m_currentBundle to 0 + MacroCommand *mc = new MacroCommand(command->getName()); + addCommand(mc, false); + m_currentBundle = mc; + m_currentBundleName = command->getName(); + } + + if (execute) command->execute(); + m_currentBundle->addCommand(command); + + delete m_bundleTimer; + m_bundleTimer = new QTimer(this); + connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout())); + m_bundleTimer->start(m_bundleTimeout); +} + +void +CommandHistory::closeBundle() +{ + m_currentBundle = 0; + m_currentBundleName = ""; +} + +void +CommandHistory::bundleTimerTimeout() +{ + closeBundle(); +} + +void +CommandHistory::addToCompound(Command *command, bool execute) +{ +// std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl; + + if (execute) command->execute(); + m_currentCompound->addCommand(command); +} + +void +CommandHistory::startCompoundOperation(QString name, bool execute) +{ + if (m_currentCompound) { + std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl; + std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl; + } + + closeBundle(); + + m_currentCompound = new MacroCommand(name); + m_executeCompound = execute; +} + +void +CommandHistory::endCompoundOperation() +{ + if (!m_currentCompound) { + std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl; + } + + MacroCommand *toAdd = m_currentCompound; + m_currentCompound = 0; + + if (toAdd->haveCommands()) { + + // We don't execute the macro command here, because we have + // been executing the individual commands as we went along if + // m_executeCompound was true. + addCommand(toAdd, false); + } +} + +void +CommandHistory::addExecutedCommand(Command *command) +{ + addCommand(command, false); +} + +void +CommandHistory::addCommandAndExecute(Command *command) +{ + addCommand(command, true); +} + +void +CommandHistory::undo() +{ + if (m_undoStack.empty()) return; + + closeBundle(); + + Command *command = m_undoStack.top(); + command->unexecute(); + emit commandExecuted(); + emit commandUnexecuted(command); + + m_redoStack.push(command); + m_undoStack.pop(); + + clipCommands(); + updateActions(); + + if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); +} + +void +CommandHistory::redo() +{ + if (m_redoStack.empty()) return; + + closeBundle(); + + Command *command = m_redoStack.top(); + command->execute(); + emit commandExecuted(); + emit commandExecuted(command); + + m_undoStack.push(command); + m_redoStack.pop(); + // no need to clip + + updateActions(); + + if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); +} + +void +CommandHistory::setUndoLimit(int limit) +{ + if (limit > 0 && limit != m_undoLimit) { + m_undoLimit = limit; + clipCommands(); + } +} + +void +CommandHistory::setRedoLimit(int limit) +{ + if (limit > 0 && limit != m_redoLimit) { + m_redoLimit = limit; + clipCommands(); + } +} + +void +CommandHistory::setMenuLimit(int limit) +{ + m_menuLimit = limit; + updateActions(); +} + +void +CommandHistory::setBundleTimeout(int ms) +{ + m_bundleTimeout = ms; +} + +void +CommandHistory::documentSaved() +{ + closeBundle(); + m_savedAt = m_undoStack.size(); +} + +void +CommandHistory::clipCommands() +{ + if ((int)m_undoStack.size() > m_undoLimit) { + m_savedAt -= (m_undoStack.size() - m_undoLimit); + } + + clipStack(m_undoStack, m_undoLimit); + clipStack(m_redoStack, m_redoLimit); +} + +void +CommandHistory::clipStack(CommandStack &stack, int limit) +{ + int i; + + if ((int)stack.size() > limit) { + + CommandStack tempStack; + + for (i = 0; i < limit; ++i) { +// Command *command = stack.top(); +// std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; + tempStack.push(stack.top()); + stack.pop(); + } + + clearStack(stack); + + for (i = 0; i < m_undoLimit; ++i) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + +void +CommandHistory::clearStack(CommandStack &stack) +{ + while (!stack.empty()) { + Command *command = stack.top(); + // Not safe to call getName() on a command about to be deleted +// std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl; + delete command; + stack.pop(); + } +} + +void +CommandHistory::undoActivated(QAction *action) +{ + int pos = m_actionCounts[action]; + for (int i = 0; i <= pos; ++i) { + undo(); + } +} + +void +CommandHistory::redoActivated(QAction *action) +{ + int pos = m_actionCounts[action]; + for (int i = 0; i <= pos; ++i) { + redo(); + } +} + +void +CommandHistory::updateActions() +{ + m_actionCounts.clear(); + + for (int undo = 0; undo <= 1; ++undo) { + + QAction *action(undo ? m_undoAction : m_redoAction); + QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); + QMenu *menu(undo ? m_undoMenu : m_redoMenu); + CommandStack &stack(undo ? m_undoStack : m_redoStack); + + if (stack.empty()) { + + QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); + + action->setEnabled(false); + action->setText(text); + + menuAction->setEnabled(false); + menuAction->setText(text); + + } else { + + action->setEnabled(true); + menuAction->setEnabled(true); + + QString commandName = stack.top()->getName(); + commandName.replace(QRegExp("&"), ""); + + QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) + .arg(commandName); + + action->setText(text); + menuAction->setText(text); + } + + menu->clear(); + + CommandStack tempStack; + int j = 0; + + while (j < m_menuLimit && !stack.empty()) { + + Command *command = stack.top(); + tempStack.push(command); + stack.pop(); + + QString commandName = command->getName(); + commandName.replace(QRegExp("&"), ""); + + QString text; + if (undo) text = tr("&Undo %1").arg(commandName); + else text = tr("Re&do %1").arg(commandName); + + QAction *action = menu->addAction(text); + m_actionCounts[action] = j++; + } + + while (!tempStack.empty()) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + diff -r 000000000000 -r fc9323a41f5a base/CommandHistory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/CommandHistory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,226 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the Rosegarden + MIDI and audio sequencer and notation editor, copyright 2000-2006 + Chris Cannam, distributed under the GNU General Public License. + + This file contains traces of the KCommandHistory class from the KDE + project, copyright 2000 Werner Trobin and David Faure and + distributed under the GNU Lesser General Public License. +*/ + +#ifndef _MULTI_VIEW_COMMAND_HISTORY_H_ +#define _MULTI_VIEW_COMMAND_HISTORY_H_ + +#include +#include + +#include +#include +#include + +class Command; +class MacroCommand; +class QAction; +class QMenu; +class QToolBar; +class QTimer; + +/** + * The CommandHistory class stores a list of executed commands and + * maintains Undo and Redo actions synchronised with those commands. + * + * CommandHistory allows you to associate more than one Undo and Redo + * menu or toolbar with the same command history, and it keeps them + * all up-to-date at once. This makes it effective in systems where + * multiple views may be editing the same data. + */ + +class CommandHistory : public QObject +{ + Q_OBJECT + +public: + virtual ~CommandHistory(); + + static CommandHistory *getInstance(); + + void clear(); + + void registerMenu(QMenu *menu); + void registerToolbar(QToolBar *toolbar); + + /** + * Add a command to the command history. + * + * The command will normally be executed before being added; but + * if a compound operation is in use (see startCompoundOperation + * below), the execute status of the compound operation will + * determine whether the command is executed or not. + */ + void addCommand(Command *command); + + /** + * Add a command to the command history. + * + * If execute is true, the command will be executed before being + * added. Otherwise it will be assumed to have been already + * executed -- a command should not be added to the history unless + * its work has actually been done somehow! + * + * If a compound operation is in use (see startCompoundOperation + * below), the execute value passed to this method will override + * the execute status of the compound operation. In this way it's + * possible to have a compound operation mixing both to-execute + * and pre-executed commands. + * + * If bundle is true, the command will be a candidate for bundling + * with any adjacent bundeable commands that have the same name, + * into a single compound command. This is useful for small + * commands that may be executed repeatedly altering the same data + * (e.g. type text, set a parameter) whose number and extent is + * not known in advance. The bundle parameter will be ignored if + * a compound operation is already in use. + */ + void addCommand(Command *command, bool execute, bool bundle = false); + + /// Return the maximum number of items in the undo history. + int getUndoLimit() const { return m_undoLimit; } + + /// Set the maximum number of items in the undo history. + void setUndoLimit(int limit); + + /// Return the maximum number of items in the redo history. + int getRedoLimit() const { return m_redoLimit; } + + /// Set the maximum number of items in the redo history. + void setRedoLimit(int limit); + + /// Return the maximum number of items visible in undo and redo menus. + int getMenuLimit() const { return m_menuLimit; } + + /// Set the maximum number of items in the menus. + void setMenuLimit(int limit); + + /// Return the time after which a bundle will be closed if nothing is added. + int getBundleTimeout() const { return m_bundleTimeout; } + + /// Set the time after which a bundle will be closed if nothing is added. + void setBundleTimeout(int msec); + + /// Start recording commands to batch up into a single compound command. + void startCompoundOperation(QString name, bool execute); + + /// Finish recording commands and store the compound command. + void endCompoundOperation(); + +public slots: + /** + * Checkpoint function that should be called when the document is + * saved. If the undo/redo stack later returns to the point at + * which the document was saved, the documentRestored signal will + * be emitted. + */ + virtual void documentSaved(); + + /** + * Add a command to the history that has already been executed, + * without executing it again. Equivalent to addCommand(command, false). + */ + void addExecutedCommand(Command *); + + /** + * Add a command to the history and also execute it. Equivalent + * to addCommand(command, true). + */ + void addCommandAndExecute(Command *); + + void undo(); + void redo(); + +protected slots: + void undoActivated(QAction *); + void redoActivated(QAction *); + void bundleTimerTimeout(); + +signals: + /** + * Emitted whenever a command has just been executed or + * unexecuted, whether by addCommand, undo, or redo. + */ + void commandExecuted(); + + /** + * Emitted whenever a command has just been executed, whether by + * addCommand or redo. + */ + void commandExecuted(Command *); + + /** + * Emitted whenever a command has just been unexecuted, whether by + * addCommand or undo. + */ + void commandUnexecuted(Command *); + + /** + * Emitted when the undo/redo stack has reached the same state at + * which the documentSaved slot was last called. + */ + void documentRestored(); + +protected: + CommandHistory(); + static CommandHistory *m_instance; + + QAction *m_undoAction; + QAction *m_redoAction; + QAction *m_undoMenuAction; + QAction *m_redoMenuAction; + QMenu *m_undoMenu; + QMenu *m_redoMenu; + + std::map m_actionCounts; + + typedef std::stack CommandStack; + CommandStack m_undoStack; + CommandStack m_redoStack; + + int m_undoLimit; + int m_redoLimit; + int m_menuLimit; + int m_savedAt; + + MacroCommand *m_currentCompound; + bool m_executeCompound; + void addToCompound(Command *command, bool execute); + + MacroCommand *m_currentBundle; + QString m_currentBundleName; + QTimer *m_bundleTimer; + int m_bundleTimeout; + void addToBundle(Command *command, bool execute); + void closeBundle(); + + void updateActions(); + + void clipCommands(); + + void clipStack(CommandStack &stack, int limit); + void clearStack(CommandStack &stack); +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a base/Exceptions.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Exceptions.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,109 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Exceptions.h" + +#include + +FileNotFound::FileNotFound(QString file) throw() : + m_file(file) +{ + std::cerr << "ERROR: File not found: " + << file.toStdString() << std::endl; +} + +const char * +FileNotFound::what() const throw() +{ + return QString("File \"%1\" not found") + .arg(m_file).toLocal8Bit().data(); +} + +FailedToOpenFile::FailedToOpenFile(QString file) throw() : + m_file(file) +{ + std::cerr << "ERROR: Failed to open file: " + << file.toStdString() << std::endl; +} + +const char * +FailedToOpenFile::what() const throw() +{ + return QString("Failed to open file \"%1\"") + .arg(m_file).toLocal8Bit().data(); +} + +DirectoryCreationFailed::DirectoryCreationFailed(QString directory) throw() : + m_directory(directory) +{ + std::cerr << "ERROR: Directory creation failed for directory: " + << directory.toStdString() << std::endl; +} + +const char * +DirectoryCreationFailed::what() const throw() +{ + return QString("Directory creation failed for \"%1\"") + .arg(m_directory).toLocal8Bit().data(); +} + +FileReadFailed::FileReadFailed(QString file) throw() : + m_file(file) +{ + std::cerr << "ERROR: File read failed for file: " + << file.toStdString() << std::endl; +} + +const char * +FileReadFailed::what() const throw() +{ + return QString("File read failed for \"%1\"") + .arg(m_file).toLocal8Bit().data(); +} + +FileOperationFailed::FileOperationFailed(QString file, QString op) throw() : + m_file(file), + m_operation(op) +{ + std::cerr << "ERROR: File " << op.toStdString() << " failed for file: " + << file.toStdString() << std::endl; +} + +const char * +FileOperationFailed::what() const throw() +{ + return QString("File %1 failed for \"%2\"") + .arg(m_operation).arg(m_file).toLocal8Bit().data(); +} + +InsufficientDiscSpace::InsufficientDiscSpace(QString directory, + size_t required, + size_t available) throw() : + m_directory(directory), + m_required(required), + m_available(available) +{ + std::cerr << "ERROR: Not enough disc space available in " + << directory.toStdString() << ": need " << required + << ", only have " << available << std::endl; +} + +const char * +InsufficientDiscSpace::what() const throw() +{ + return QString("Not enough space available in \"%1\": need %2, have %3") + .arg(m_directory).arg(m_required).arg(m_available).toLocal8Bit().data(); +} + diff -r 000000000000 -r fc9323a41f5a base/Exceptions.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Exceptions.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,96 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _EXCEPTIONS_H_ +#define _EXCEPTIONS_H_ + +#include + +#include + +class FileNotFound : virtual public std::exception +{ +public: + FileNotFound(QString file) throw(); + virtual ~FileNotFound() throw() { } + virtual const char *what() const throw(); + +protected: + QString m_file; +}; + +class FailedToOpenFile : virtual public std::exception +{ +public: + FailedToOpenFile(QString file) throw(); + virtual ~FailedToOpenFile() throw() { } + virtual const char *what() const throw(); + +protected: + QString m_file; +}; + +class DirectoryCreationFailed : virtual public std::exception +{ +public: + DirectoryCreationFailed(QString directory) throw(); + virtual ~DirectoryCreationFailed() throw() { } + virtual const char *what() const throw(); + +protected: + QString m_directory; +}; + +class FileReadFailed : virtual public std::exception +{ +public: + FileReadFailed(QString file) throw(); + virtual ~FileReadFailed() throw() { } + virtual const char *what() const throw(); + +protected: + QString m_file; +}; + +class FileOperationFailed : virtual public std::exception +{ +public: + FileOperationFailed(QString file, QString operation) throw(); + virtual ~FileOperationFailed() throw() { } + virtual const char *what() const throw(); + +protected: + QString m_file; + QString m_operation; +}; + +class InsufficientDiscSpace : virtual public std::exception +{ +public: + InsufficientDiscSpace(QString directory, + size_t required, size_t available) throw(); + virtual ~InsufficientDiscSpace() throw() { } + virtual const char *what() const throw(); + + size_t getRequired() const { return m_required; } + size_t getAvailable() const { return m_available; } + +protected: + QString m_directory; + size_t m_required; + size_t m_available; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/LogRange.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/LogRange.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,60 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LogRange.h" + +#include +#include + +void +LogRange::mapRange(float &min, float &max, float logthresh) +{ + if (min > max) std::swap(min, max); + if (max == min) max = min + 1; + + if (min >= 0.f) { + + max = log10f(max); // we know max != 0 + + if (min == 0.f) min = std::min(logthresh, max); + else min = log10f(min); + + } else if (max <= 0.f) { + + min = log10f(-min); // we know min != 0 + + if (max == 0.f) max = std::min(logthresh, min); + else max = log10f(-max); + + std::swap(min, max); + + } else { + + // min < 0 and max > 0 + + max = log10f(std::max(max, -min)); + min = std::min(logthresh, max); + } + + if (min == max) min = max - 1; +} + +float +LogRange::map(float value, float thresh) +{ + if (value == 0.f) return thresh; + return log10f(fabsf(value)); +} + diff -r 000000000000 -r fc9323a41f5a base/LogRange.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/LogRange.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,38 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LOG_RANGE_H_ +#define _LOG_RANGE_H_ + +class LogRange +{ +public: + /** + * Map a linear range onto a logarithmic range. min and max are + * passed as the extents of the linear range and returned as the + * extents of the logarithmic range. thresh is the minimum value + * for the log range, to be used if the linear range spans zero. + */ + static void mapRange(float &min, float &max, float thresh = -10); + + /** + * Map a value onto a logarithmic range. This just means taking + * the base-10 log of the absolute value, or using the threshold + * value if the absolute value is zero. + */ + static float map(float value, float thresh = -10); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/Pitch.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Pitch.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,104 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "system/System.h" +#include "Pitch.h" +#include "Preferences.h" + +#include + +float +Pitch::getFrequencyForPitch(int midiPitch, + float centsOffset, + float concertA) +{ + if (concertA <= 0.0) { + concertA = Preferences::getInstance()->getTuningFrequency(); + } + float p = float(midiPitch) + (centsOffset / 100); + return concertA * powf(2.0, (p - 69.0) / 12.0); +} + +int +Pitch::getPitchForFrequency(float frequency, + float *centsOffsetReturn, + float concertA) +{ + if (concertA <= 0.0) { + concertA = Preferences::getInstance()->getTuningFrequency(); + } + float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0; + + int midiPitch = int(p + 0.00001); + float centsOffset = (p - midiPitch) * 100.0; + + if (centsOffset >= 50.0) { + midiPitch = midiPitch + 1; + centsOffset = -(100.0 - centsOffset); + } + + if (centsOffsetReturn) *centsOffsetReturn = centsOffset; + return midiPitch; +} + +static QString notes[] = { + "C%1", "C#%1", "D%1", "D#%1", + "E%1", "F%1", "F#%1", "G%1", + "G#%1", "A%1", "A#%1", "B%1" +}; + +static QString flatNotes[] = { + "C%1", "Db%1", "D%1", "Eb%1", + "E%1", "F%1", "Gb%1", "G%1", + "Ab%1", "A%1", "Bb%1", "B%1" +}; + +QString +Pitch::getPitchLabel(int midiPitch, + float centsOffset, + bool useFlats) +{ + int octave = -2; + + if (midiPitch < 0) { + while (midiPitch < 0) { + midiPitch += 12; + --octave; + } + } else { + octave = midiPitch / 12 - 2; + } + + QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave); + + int ic = lrintf(centsOffset); + if (ic == 0) return plain; + else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic); + else return QString("%1%2c").arg(plain).arg(ic); +} + +QString +Pitch::getPitchLabelForFrequency(float frequency, + float concertA, + bool useFlats) +{ + if (concertA <= 0.0) { + concertA = Preferences::getInstance()->getTuningFrequency(); + } + float centsOffset = 0.0; + int midiPitch = getPitchForFrequency(frequency, ¢sOffset, concertA); + return getPitchLabel(midiPitch, centsOffset, useFlats); +} + diff -r 000000000000 -r fc9323a41f5a base/Pitch.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Pitch.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,44 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PITCH_H_ +#define _PITCH_H_ + +#include + +class Pitch +{ +public: + /* If concertA <= 0, it will be taken from current preferences */ + + static float getFrequencyForPitch(int midiPitch, + float centsOffset = 0, + float concertA = 0.0); + + static int getPitchForFrequency(float frequency, + float *centsOffsetReturn = 0, + float concertA = 0.0); + + static QString getPitchLabel(int midiPitch, + float centsOffset = 0, + bool useFlats = false); + + static QString getPitchLabelForFrequency(float frequency, + float concertA = 0.0, + bool useFlats = false); +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a base/PlayParameterRepository.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PlayParameterRepository.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,138 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PlayParameterRepository.h" +#include "PlayParameters.h" + +//!!! shouldn't be including this here -- restructure needed + +//!!! should the AudioGenerator actually implement all this stuff itself? do we even want this class? +#include "sv/audioio/AudioGenerator.h" + +#include + +PlayParameterRepository * +PlayParameterRepository::m_instance = new PlayParameterRepository; + +PlayParameterRepository * +PlayParameterRepository::getInstance() +{ + return m_instance; +} + +PlayParameterRepository::~PlayParameterRepository() +{ +} + +void +PlayParameterRepository::addModel(const Model *model) +{ +// std::cerr << "PlayParameterRepository:addModel " << model << std::endl; + + if (!getPlayParameters(model)) { + + // Give all models the same type of play parameters for the + // moment, provided they can be played at all + + if (AudioGenerator::canPlay(model)) { + +// std::cerr << "PlayParameterRepository: Adding play parameters for " << model << std::endl; + + PlayParameters *params = new PlayParameters; + m_playParameters[model] = params; + + params->setPlayPluginId + (AudioGenerator::getDefaultPlayPluginId(model)); + + params->setPlayPluginConfiguration + (AudioGenerator::getDefaultPlayPluginConfiguration(model)); + + connect(params, SIGNAL(playParametersChanged()), + this, SLOT(playParametersChanged())); + + connect(params, SIGNAL(playPluginIdChanged(QString)), + this, SLOT(playPluginIdChanged(QString))); + + connect(params, SIGNAL(playPluginConfigurationChanged(QString)), + this, SLOT(playPluginConfigurationChanged(QString))); + +// std::cerr << "Connected play parameters " << params << " for model " +// << model << " to this " << this << std::endl; + + } else { + +// std::cerr << "PlayParameterRepository: Model " << model << " not playable" << std::endl; + } + } +} + +void +PlayParameterRepository::removeModel(const Model *model) +{ + delete m_playParameters[model]; + m_playParameters.erase(model); +} + +PlayParameters * +PlayParameterRepository::getPlayParameters(const Model *model) const +{ + if (m_playParameters.find(model) == m_playParameters.end()) return 0; + return m_playParameters.find(model)->second; +} + +void +PlayParameterRepository::playParametersChanged() +{ + PlayParameters *params = dynamic_cast(sender()); + emit playParametersChanged(params); +} + +void +PlayParameterRepository::playPluginIdChanged(QString id) +{ + PlayParameters *params = dynamic_cast(sender()); + for (ModelParameterMap::iterator i = m_playParameters.begin(); + i != m_playParameters.end(); ++i) { + if (i->second == params) { + emit playPluginIdChanged(i->first, id); + return; + } + } +} + +void +PlayParameterRepository::playPluginConfigurationChanged(QString config) +{ + PlayParameters *params = dynamic_cast(sender()); +// std::cerr << "PlayParameterRepository::playPluginConfigurationChanged" << std::endl; + for (ModelParameterMap::iterator i = m_playParameters.begin(); + i != m_playParameters.end(); ++i) { + if (i->second == params) { + emit playPluginConfigurationChanged(i->first, config); + return; + } + } +} + +void +PlayParameterRepository::clear() +{ +// std::cerr << "PlayParameterRepository: PlayParameterRepository::clear" << std::endl; + while (!m_playParameters.empty()) { + delete m_playParameters.begin()->second; + m_playParameters.erase(m_playParameters.begin()); + } +} + diff -r 000000000000 -r fc9323a41f5a base/PlayParameterRepository.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PlayParameterRepository.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLAY_PARAMETER_REPOSITORY_H_ +#define _PLAY_PARAMETER_REPOSITORY_H_ + +class PlayParameters; +class Model; + +#include + +#include + +class PlayParameterRepository : public QObject +{ + Q_OBJECT + +public: + static PlayParameterRepository *getInstance(); + + virtual ~PlayParameterRepository(); + + void addModel(const Model *model); + void removeModel(const Model *model); + + PlayParameters *getPlayParameters(const Model *model) const; + + void clear(); + +signals: + void playParametersChanged(PlayParameters *); + void playPluginIdChanged(const Model *, QString); + void playPluginConfigurationChanged(const Model *, QString); + +protected slots: + void playParametersChanged(); + void playPluginIdChanged(QString); + void playPluginConfigurationChanged(QString); + +protected: + typedef std::map ModelParameterMap; + ModelParameterMap m_playParameters; + + static PlayParameterRepository *m_instance; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/PlayParameters.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PlayParameters.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,99 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PlayParameters.h" + +#include + +QString +PlayParameters::toXmlString(QString indent, + QString extraAttributes) const +{ + QString s; + s += indent; + s += QString("\n"; + } else { + s += "/>\n"; + } + return s; +} + +void +PlayParameters::setPlayMuted(bool muted) +{ +// std::cerr << "PlayParameters: setPlayMuted(" << muted << ")" << std::endl; + m_playMuted = muted; + emit playMutedChanged(muted); + emit playAudibleChanged(!muted); + emit playParametersChanged(); +} + +void +PlayParameters::setPlayAudible(bool audible) +{ +// std::cerr << "PlayParameters(" << this << "): setPlayAudible(" << audible << ")" << std::endl; + setPlayMuted(!audible); +} + +void +PlayParameters::setPlayPan(float pan) +{ + if (m_playPan != pan) { + m_playPan = pan; + emit playPanChanged(pan); + emit playParametersChanged(); + } +} + +void +PlayParameters::setPlayGain(float gain) +{ + if (m_playGain != gain) { + m_playGain = gain; + emit playGainChanged(gain); + emit playParametersChanged(); + } +} + +void +PlayParameters::setPlayPluginId(QString id) +{ + if (m_playPluginId != id) { + m_playPluginId = id; + emit playPluginIdChanged(id); + emit playParametersChanged(); + } +} + +void +PlayParameters::setPlayPluginConfiguration(QString configuration) +{ + if (m_playPluginConfiguration != configuration) { + m_playPluginConfiguration = configuration; +// std::cerr << "PlayParameters(" << this << "): setPlayPluginConfiguration to \"" << configuration.toStdString() << "\"" << std::endl; + emit playPluginConfigurationChanged(configuration); + emit playParametersChanged(); + } +} + + diff -r 000000000000 -r fc9323a41f5a base/PlayParameters.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PlayParameters.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLAY_PARAMETERS_H_ +#define _PLAY_PARAMETERS_H_ + +#include + +#include "XmlExportable.h" + +class PlayParameters : public QObject, public XmlExportable +{ + Q_OBJECT + +public: + PlayParameters() : m_playMuted(false), m_playPan(0.0), m_playGain(1.0) { } + + virtual bool isPlayMuted() const { return m_playMuted; } + virtual float getPlayPan() const { return m_playPan; } // -1.0 -> 1.0 + virtual float getPlayGain() const { return m_playGain; } + + virtual QString getPlayPluginId() const { return m_playPluginId; } + virtual QString getPlayPluginConfiguration() const { return m_playPluginConfiguration; } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +public slots: + virtual void setPlayMuted(bool muted); + virtual void setPlayAudible(bool nonMuted); + virtual void setPlayPan(float pan); + virtual void setPlayGain(float gain); + virtual void setPlayPluginId(QString id); + virtual void setPlayPluginConfiguration(QString configuration); + +signals: + void playParametersChanged(); + void playMutedChanged(bool); + void playAudibleChanged(bool); + void playPanChanged(float); + void playGainChanged(float); + void playPluginIdChanged(QString); + void playPluginConfigurationChanged(QString); + +protected: + bool m_playMuted; + float m_playPan; + float m_playGain; + QString m_playPluginId; + QString m_playPluginConfiguration; +}; + +#endif + + + + diff -r 000000000000 -r fc9323a41f5a base/Preferences.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Preferences.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,286 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Preferences.h" + +#include "Exceptions.h" + +#include "TempDirectory.h" + +#include +#include +#include +#include + +Preferences * +Preferences::m_instance = 0; + +Preferences * +Preferences::getInstance() +{ + if (!m_instance) m_instance = new Preferences(); + return m_instance; +} + +Preferences::Preferences() : + m_spectrogramSmoothing(SpectrogramZeroPadded), + m_tuningFrequency(440), + m_propertyBoxLayout(VerticallyStacked), + m_windowType(HanningWindow), + m_resampleQuality(1) +{ + QSettings settings; + settings.beginGroup("Preferences"); + m_spectrogramSmoothing = SpectrogramSmoothing + (settings.value("spectrogram-smoothing", int(m_spectrogramSmoothing)).toInt()); + m_tuningFrequency = settings.value("tuning-frequency", 440.f).toDouble(); + m_propertyBoxLayout = PropertyBoxLayout + (settings.value("property-box-layout", int(VerticallyStacked)).toInt()); + m_windowType = WindowType + (settings.value("window-type", int(HanningWindow)).toInt()); + m_resampleQuality = settings.value("resample-quality", 1).toInt(); + settings.endGroup(); +} + +Preferences::~Preferences() +{ +} + +Preferences::PropertyList +Preferences::getProperties() const +{ + PropertyList props; + props.push_back("Spectrogram Smoothing"); + props.push_back("Tuning Frequency"); + props.push_back("Property Box Layout"); + props.push_back("Window Type"); + props.push_back("Resample Quality"); + return props; +} + +QString +Preferences::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Spectrogram Smoothing") { + return tr("Spectrogram y-axis smoothing:"); + } + if (name == "Tuning Frequency") { + return tr("Frequency of concert A"); + } + if (name == "Property Box Layout") { + return tr("Property box layout"); + } + if (name == "Window Type") { + return tr("Spectral analysis window shape"); + } + if (name == "Resample Quality") { + return tr("Playback resampler type"); + } + return name; +} + +Preferences::PropertyType +Preferences::getPropertyType(const PropertyName &name) const +{ + if (name == "Spectrogram Smoothing") { + return ValueProperty; + } + if (name == "Tuning Frequency") { + return RangeProperty; + } + if (name == "Property Box Layout") { + return ValueProperty; + } + if (name == "Window Type") { + return ValueProperty; + } + if (name == "Resample Quality") { + return ValueProperty; + } + return InvalidProperty; +} + +int +Preferences::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + if (name == "Spectrogram Smoothing") { + if (min) *min = 0; + if (max) *max = 2; + if (deflt) *deflt = int(SpectrogramZeroPadded); + return int(m_spectrogramSmoothing); + } + + //!!! freq mapping + + if (name == "Property Box Layout") { + if (min) *min = 0; + if (max) *max = 1; + if (deflt) *deflt = 0; + return m_propertyBoxLayout == Layered ? 1 : 0; + } + + if (name == "Window Type") { + if (min) *min = int(RectangularWindow); + if (max) *max = int(BlackmanHarrisWindow); + if (deflt) *deflt = int(HanningWindow); + return int(m_windowType); + } + + if (name == "Resample Quality") { + if (min) *min = 0; + if (max) *max = 2; + if (deflt) *deflt = 1; + return m_resampleQuality; + } + + return 0; +} + +QString +Preferences::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Property Box Layout") { + if (value == 0) return tr("Show boxes for all panes"); + else return tr("Show box for current pane only"); + } + if (name == "Window Type") { + switch (WindowType(value)) { + case RectangularWindow: return tr("Rectangular"); + case BartlettWindow: return tr("Triangular"); + case HammingWindow: return tr("Hamming"); + case HanningWindow: return tr("Hanning"); + case BlackmanWindow: return tr("Blackman"); + case GaussianWindow: return tr("Gaussian"); + case ParzenWindow: return tr("Parzen"); + case NuttallWindow: return tr("Nuttall"); + case BlackmanHarrisWindow: return tr("Blackman-Harris"); + } + } + if (name == "Resample Quality") { + switch (value) { + case 0: return tr("Fastest"); + case 1: return tr("Standard"); + case 2: return tr("Highest quality"); + } + } + if (name == "Spectrogram Smoothing") { + switch (value) { + case NoSpectrogramSmoothing: return tr("None - blocky but accurate"); + case SpectrogramInterpolated: return tr("Interpolate - fast but fuzzy"); + case SpectrogramZeroPadded: return tr("Zero pad FFT - slow but clear"); + } + } + + return ""; +} + +QString +Preferences::getPropertyContainerName() const +{ + return tr("Preferences"); +} + +QString +Preferences::getPropertyContainerIconName() const +{ + return "preferences"; +} + +void +Preferences::setProperty(const PropertyName &name, int value) +{ + if (name == "Spectrogram Smoothing") { + setSpectrogramSmoothing(SpectrogramSmoothing(value)); + } else if (name == "Tuning Frequency") { + //!!! + } else if (name == "Property Box Layout") { + setPropertyBoxLayout(value == 0 ? VerticallyStacked : Layered); + } else if (name == "Window Type") { + setWindowType(WindowType(value)); + } else if (name == "Resample Quality") { + setResampleQuality(value); + } +} + +void +Preferences::setSpectrogramSmoothing(SpectrogramSmoothing smoothing) +{ + if (m_spectrogramSmoothing != smoothing) { + + // "smoothing" is one of those words that looks increasingly + // ridiculous the more you see it. Smoothing smoothing smoothing. + m_spectrogramSmoothing = smoothing; + + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("spectrogram-smoothing", int(smoothing)); + settings.endGroup(); + emit propertyChanged("Spectrogram Smoothing"); + } +} + +void +Preferences::setTuningFrequency(float freq) +{ + if (m_tuningFrequency != freq) { + m_tuningFrequency = freq; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("tuning-frequency", freq); + settings.endGroup(); + emit propertyChanged("Tuning Frequency"); + } +} + +void +Preferences::setPropertyBoxLayout(PropertyBoxLayout layout) +{ + if (m_propertyBoxLayout != layout) { + m_propertyBoxLayout = layout; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("property-box-layout", int(layout)); + settings.endGroup(); + emit propertyChanged("Property Box Layout"); + } +} + +void +Preferences::setWindowType(WindowType type) +{ + if (m_windowType != type) { + m_windowType = type; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("window-type", int(type)); + settings.endGroup(); + emit propertyChanged("Window Type"); + } +} + +void +Preferences::setResampleQuality(int q) +{ + if (m_resampleQuality != q) { + m_resampleQuality = q; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("resample-quality", q); + settings.endGroup(); + emit propertyChanged("Resample Quality"); + } +} diff -r 000000000000 -r fc9323a41f5a base/Preferences.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Preferences.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,79 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PREFERENCES_H_ +#define _PREFERENCES_H_ + +#include "PropertyContainer.h" + +#include "Window.h" + +class Preferences : public PropertyContainer +{ + Q_OBJECT + +public: + static Preferences *getInstance(); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, int *, int *, int *) const; + virtual QString getPropertyValueLabel(const PropertyName &, int value) const; + virtual QString getPropertyContainerName() const; + virtual QString getPropertyContainerIconName() const; + + enum SpectrogramSmoothing { + NoSpectrogramSmoothing, + SpectrogramInterpolated, + SpectrogramZeroPadded, + SpectrogramZeroPaddedAndInterpolated + }; + + SpectrogramSmoothing getSpectrogramSmoothing() const { return m_spectrogramSmoothing; } + float getTuningFrequency() const { return m_tuningFrequency; } + WindowType getWindowType() const { return m_windowType; } + int getResampleQuality() const { return m_resampleQuality; } + + //!!! harmonise with PaneStack + enum PropertyBoxLayout { + VerticallyStacked, + Layered + }; + PropertyBoxLayout getPropertyBoxLayout() const { return m_propertyBoxLayout; } + +public slots: + virtual void setProperty(const PropertyName &, int); + + void setSpectrogramSmoothing(SpectrogramSmoothing smoothing); + void setTuningFrequency(float freq); + void setPropertyBoxLayout(PropertyBoxLayout layout); + void setWindowType(WindowType type); + void setResampleQuality(int quality); + +private: + Preferences(); // may throw DirectoryCreationFailed + virtual ~Preferences(); + + static Preferences *m_instance; + + SpectrogramSmoothing m_spectrogramSmoothing; + float m_tuningFrequency; + PropertyBoxLayout m_propertyBoxLayout; + WindowType m_windowType; + int m_resampleQuality; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/Profiler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Profiler.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,220 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam, Guillaume Laurent, + and QMUL. +*/ + +#include +#include "Profiler.h" + +#include +#include +#include +#include + +using std::cerr; +using std::endl; + +Profiles* Profiles::m_instance = 0; + +Profiles* Profiles::getInstance() +{ + if (!m_instance) m_instance = new Profiles(); + + return m_instance; +} + +Profiles::Profiles() +{ +} + +Profiles::~Profiles() +{ + dump(); +} + +void Profiles::accumulate( +#ifndef NO_TIMING + const char* id, clock_t time, RealTime rt +#else + const char*, clock_t, RealTime +#endif +) +{ +#ifndef NO_TIMING + ProfilePair &pair(m_profiles[id]); + ++pair.first; + pair.second.first += time; + pair.second.second = pair.second.second + rt; + + TimePair &lastPair(m_lastCalls[id]); + lastPair.first = time; + lastPair.second = rt; + + TimePair &worstPair(m_worstCalls[id]); + if (time > worstPair.first) { + worstPair.first = time; + } + if (rt > worstPair.second) { + worstPair.second = rt; + } +#endif +} + +void Profiles::dump() const +{ +#ifndef NO_TIMING + + fprintf(stderr, "Profiling points:\n"); + + fprintf(stderr, "\nBy name:\n"); + + typedef std::set > StringSet; + + StringSet profileNames; + for (ProfileMap::const_iterator i = m_profiles.begin(); + i != m_profiles.end(); ++i) { + profileNames.insert(i->first); + } + + for (StringSet::const_iterator i = profileNames.begin(); + i != profileNames.end(); ++i) { + + ProfileMap::const_iterator j = m_profiles.find(*i); + + if (j == m_profiles.end()) continue; + + const ProfilePair &pp(j->second); + + fprintf(stderr, "%s(%d):\n", *i, pp.first); + + fprintf(stderr, "\tCPU: \t%.9g ms/call \t[%d ms total]\n", + (((double)pp.second.first * 1000.0 / + (double)pp.first) / CLOCKS_PER_SEC), + int((pp.second.first * 1000.0) / CLOCKS_PER_SEC)); + + fprintf(stderr, "\tReal: \t%s ms \t[%s ms total]\n", + ((pp.second.second / pp.first) * 1000).toString().c_str(), + (pp.second.second * 1000).toString().c_str()); + + WorstCallMap::const_iterator k = m_worstCalls.find(*i); + if (k == m_worstCalls.end()) continue; + + const TimePair &wc(k->second); + + fprintf(stderr, "\tWorst:\t%s ms/call \t[%d ms CPU]\n", + (wc.second * 1000).toString().c_str(), + int((wc.first * 1000.0) / CLOCKS_PER_SEC)); + } + + typedef std::multimap TimeRMap; + typedef std::multimap IntRMap; + + TimeRMap totmap, avgmap, worstmap; + IntRMap ncallmap; + + for (ProfileMap::const_iterator i = m_profiles.begin(); + i != m_profiles.end(); ++i) { + totmap.insert(TimeRMap::value_type(i->second.second.second, i->first)); + avgmap.insert(TimeRMap::value_type(i->second.second.second / + i->second.first, i->first)); + ncallmap.insert(IntRMap::value_type(i->second.first, i->first)); + } + + for (WorstCallMap::const_iterator i = m_worstCalls.begin(); + i != m_worstCalls.end(); ++i) { + worstmap.insert(TimeRMap::value_type(i->second.second, + i->first)); + } + + + fprintf(stderr, "\nBy total:\n"); + for (TimeRMap::const_iterator i = totmap.end(); i != totmap.begin(); ) { + --i; + fprintf(stderr, "%-40s %s ms\n", i->second, + (i->first * 1000).toString().c_str()); + } + + fprintf(stderr, "\nBy average:\n"); + for (TimeRMap::const_iterator i = avgmap.end(); i != avgmap.begin(); ) { + --i; + fprintf(stderr, "%-40s %s ms\n", i->second, + (i->first * 1000).toString().c_str()); + } + + fprintf(stderr, "\nBy worst case:\n"); + for (TimeRMap::const_iterator i = worstmap.end(); i != worstmap.begin(); ) { + --i; + fprintf(stderr, "%-40s %s ms\n", i->second, + (i->first * 1000).toString().c_str(), i->second); + } + + fprintf(stderr, "\nBy number of calls:\n"); + for (IntRMap::const_iterator i = ncallmap.end(); i != ncallmap.begin(); ) { + --i; + fprintf(stderr, "%-40s %d\n", i->second, i->first); + } + +#endif +} + +#ifndef NO_TIMING + +Profiler::Profiler(const char* c, bool showOnDestruct) + : m_c(c), + m_showOnDestruct(showOnDestruct) +{ + m_startCPU = clock(); + + struct timeval tv; + (void)gettimeofday(&tv, 0); + m_startTime = RealTime::fromTimeval(tv); +} + +void +Profiler::update() const +{ + clock_t elapsedCPU = clock() - m_startCPU; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + RealTime elapsedTime = RealTime::fromTimeval(tv) - m_startTime; + + cerr << "Profiler : id = " << m_c + << " - elapsed so far = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC) + << "ms CPU, " << elapsedTime << " real" << endl; +} + +Profiler::~Profiler() +{ + clock_t elapsedCPU = clock() - m_startCPU; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + RealTime elapsedTime = RealTime::fromTimeval(tv) - m_startTime; + + Profiles::getInstance()->accumulate(m_c, elapsedCPU, elapsedTime); + + if (m_showOnDestruct) + cerr << "Profiler : id = " << m_c + << " - elapsed = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC) + << "ms CPU, " << elapsedTime << " real" << endl; +} + +#endif + diff -r 000000000000 -r fc9323a41f5a base/Profiler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Profiler.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,122 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam, Guillaume Laurent, + and QMUL. +*/ + + +#ifndef _PROFILER_H_ +#define _PROFILER_H_ + +#include "system/System.h" + +#include +//#include +#include + +#include "RealTime.h" + +//#define NO_TIMING 1 + +#define WANT_TIMING 1 + +#ifdef NDEBUG +#ifndef WANT_TIMING +#define NO_TIMING 1 +#endif +#endif + +/** + * Profiling classes + */ + +/** + * The class holding all profiling data + * + * This class is a singleton + */ +class Profiles +{ +public: + static Profiles* getInstance(); + ~Profiles(); + + void accumulate(const char* id, clock_t time, RealTime rt); + void dump() const; + +protected: + Profiles(); + + typedef std::pair TimePair; + typedef std::pair ProfilePair; + typedef std::map ProfileMap; + typedef std::map LastCallMap; + typedef std::map WorstCallMap; + ProfileMap m_profiles; + LastCallMap m_lastCalls; + WorstCallMap m_worstCalls; + + static Profiles* m_instance; +}; + +#ifndef NO_TIMING + +/** + * Profile point instance class. Construct one of these on the stack + * at the start of a function, in order to record the time consumed + * within that function. The profiler object should be effectively + * optimised out if NO_TIMING is defined, so any overhead in a release + * build should be negligible so long as you remember to define that. + */ +class Profiler +{ +public: + /** + * Create a profile point instance that records time consumed + * against the given profiling point name. If showOnDestruct is + * true, the time consumed will be printed to stderr when the + * object is destroyed; otherwise, only the accumulated, mean and + * worst-case times will be shown when the program exits or + * Profiles::dump() is called. + */ + Profiler(const char *name, bool showOnDestruct = false); + ~Profiler(); + + void update() const; + +protected: + const char* m_c; + clock_t m_startCPU; + RealTime m_startTime; + bool m_showOnDestruct; +}; + +#else + +class Profiler +{ +public: + Profiler(const char *, bool) { } + ~Profiler() { } + + void update() { } +}; + +#endif + +#endif diff -r 000000000000 -r fc9323a41f5a base/PropertyContainer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PropertyContainer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,260 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PropertyContainer.h" +#include "CommandHistory.h" +#include "RangeMapper.h" +#include "UnitDatabase.h" + +#include + +#include + +PropertyContainer::PropertyList +PropertyContainer::getProperties() const +{ + return PropertyList(); +} + +//QString +//PropertyContainer::getPropertyLabel(const PropertyName &) const +//{ +// return ""; +//} + +PropertyContainer::PropertyType +PropertyContainer::getPropertyType(const PropertyName &) const +{ + return InvalidProperty; +} + +QString +PropertyContainer::getPropertyGroupName(const PropertyName &) const +{ + return QString(); +} + +int +PropertyContainer::getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const +{ + if (min) *min = 0; + if (max) *max = 0; + if (deflt) *deflt = 0; + return 0; +} + +QString +PropertyContainer::getPropertyValueLabel(const PropertyName &, int) const +{ + return QString(); +} + +RangeMapper * +PropertyContainer::getNewPropertyRangeMapper(const PropertyName &) const +{ + return 0; +} + +void +PropertyContainer::setProperty(const PropertyName &name, int) +{ + std::cerr << "WARNING: PropertyContainer[" << getPropertyContainerName().toStdString() << "]::setProperty(" << name.toStdString() << "): no implementation in subclass!" << std::endl; +} + +void +PropertyContainer::setPropertyWithCommand(const PropertyName &name, int value) +{ + int currentValue = getPropertyRangeAndValue(name, 0, 0, 0); + if (value == currentValue) return; + + CommandHistory::getInstance()->addCommand + (new SetPropertyCommand(this, name, value), true, true); // bundled +} + +void +PropertyContainer::setProperty(QString nameString, QString valueString) +{ + PropertyName name; + int value; + if (!convertPropertyStrings(nameString, valueString, name, value)) { + std::cerr << "WARNING: PropertyContainer::setProperty(\"" + << nameString.toStdString() << "\", \"" + << valueString.toStdString() + << "\"): Name and value conversion failed" << std::endl; + return; + } + setProperty(name, value); +} + +void +PropertyContainer::setPropertyWithCommand(QString nameString, QString valueString) +{ + PropertyName name; + int value; + if (!convertPropertyStrings(nameString, valueString, name, value)) { + std::cerr << "WARNING: PropertyContainer::setPropertyWithCommand(\"" + << nameString.toStdString() << "\", \"" + << valueString.toStdString() + << "\"): Name and value conversion failed" << std::endl; + return; + } + setPropertyWithCommand(name, value); +} + +bool +PropertyContainer::convertPropertyStrings(QString nameString, QString valueString, + PropertyName &name, int &value) +{ + PropertyList pl = getProperties(); + + QString adjusted = nameString.trimmed(); + adjusted.replace('_', ' '); + adjusted.replace('-', ' '); + + name = ""; + + for (PropertyList::iterator pli = pl.begin(); pli != pl.end(); ++pli) { + + QString label = getPropertyLabel(*pli); + + if (label != "" && (nameString == label || adjusted == label)) { + name = *pli; + break; + } else if (nameString == *pli) { + name = *pli; + break; + } + } + + if (name == "") { + std::cerr << "PropertyContainer::convertPropertyStrings: Unable to match name string \"" << nameString.toStdString() << "\"" << std::endl; + return false; + } + + value = 0; + bool success = false; + + bool isDouble = false; + double dval = valueString.toDouble(&isDouble); + + switch (getPropertyType(name)) { + + case ToggleProperty: + if (valueString == tr("yes") || + valueString == tr("on") || + valueString == tr("true")) { + value = 1; success = true; + } else if (valueString == tr("no") || + valueString == tr("off") || + valueString == tr("false")) { + value = 0; success = true; + } + break; + + case RangeProperty: + if (isDouble) { + RangeMapper *mapper = getNewPropertyRangeMapper(name); + if (mapper) { + value = mapper->getPositionForValue(dval); + delete mapper; + success = true; + } + } + break; + + case ValueProperty: + { + int min, max; + getPropertyRangeAndValue(name, &min, &max, 0); + for (int i = min; i <= max; ++i) { + if (valueString == getPropertyValueLabel(name, i)) { + value = i; + success = true; + break; + } + } + break; + } + + case ColourProperty: + { + QColor c(valueString); + if (c.isValid()) { + value = c.rgb(); + success = true; + } + break; + } + + case UnitsProperty: + value = UnitDatabase::getInstance()->getUnitId(valueString, false); + if (value >= 0) success = true; + else value = 0; + break; + + case InvalidProperty: + std::cerr << "PropertyContainer::convertPropertyStrings: Invalid property name \"" << name.toStdString() << "\"" << std::endl; + return false; + } + + if (success) return true; + + int min, max; + getPropertyRangeAndValue(name, &min, &max, 0); + + bool ok = false; + int i = valueString.toInt(&ok); + if (!ok) { + std::cerr << "PropertyContainer::convertPropertyStrings: Unable to parse value string \"" << valueString.toStdString() << "\"" << std::endl; + return false; + } else if (i < min || i > max) { + std::cerr << "PropertyContainer::convertPropertyStrings: Property value \"" << i << "\" outside valid range " << min << " to " << max << std::endl; + return false; + } + + value = i; + return true; +} + +PropertyContainer::SetPropertyCommand::SetPropertyCommand(PropertyContainer *pc, + const PropertyName &pn, + int value) : + m_pc(pc), + m_pn(pn), + m_value(value), + m_oldValue(0) +{ +} + +void +PropertyContainer::SetPropertyCommand::execute() +{ + m_oldValue = m_pc->getPropertyRangeAndValue(m_pn, 0, 0, 0); + m_pc->setProperty(m_pn, m_value); +} + +void +PropertyContainer::SetPropertyCommand::unexecute() +{ + m_pc->setProperty(m_pn, m_oldValue); +} + +QString +PropertyContainer::SetPropertyCommand::getName() const +{ + return tr("Set %1 Property").arg(m_pn); +} + diff -r 000000000000 -r fc9323a41f5a base/PropertyContainer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/PropertyContainer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,165 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PROPERTY_CONTAINER_H_ +#define _PROPERTY_CONTAINER_H_ + +#include "Command.h" + +#include +#include +#include + +class PlayParameters; +class RangeMapper; + +class PropertyContainer : public QObject +{ + Q_OBJECT + +public: + virtual ~PropertyContainer() { } + + typedef QString PropertyName; + typedef std::vector PropertyList; + + enum PropertyType { + ToggleProperty, // on or off + RangeProperty, // range of integers + ValueProperty, // range of integers given string labels + ColourProperty, // colours, get/set as qRgb + UnitsProperty, // unit from UnitDatabase, get/set unit id + InvalidProperty, // property not found! + }; + + /** + * Get a list of the names of all the supported properties on this + * container. These should be fixed (i.e. not internationalized). + */ + virtual PropertyList getProperties() const; + + /** + * Return the human-readable (and i18n'ised) name of a property. + */ + virtual QString getPropertyLabel(const PropertyName &) const = 0; + + /** + * Return the type of the given property, or InvalidProperty if + * the property is not supported on this container. + */ + virtual PropertyType getPropertyType(const PropertyName &) const; + + /** + * If this property has something in common with other properties + * on this container, return a name that can be used to group them + * (in order to save screen space, for example). e.g. "Window + * Type" and "Window Size" might both have a group name of "Window". + * If this property is not groupable, return the empty string. + */ + virtual QString getPropertyGroupName(const PropertyName &) const; + + /** + * Return the minimum and maximum values for the given property + * and its current value in this container. Min and/or max may be + * passed as NULL if their values are not required. + */ + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + + /** + * If the given property is a ValueProperty, return the display + * label to be used for the given value for that property. + */ + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + + /** + * If the given property is a RangeProperty, return a new + * RangeMapper object mapping its integer range onto an underlying + * floating point value range for human-intelligible display, if + * appropriate. The RangeMapper should be allocated with new, and + * the caller takes responsibility for deleting it. Return NULL + * (as in the default implementation) if there is no such mapping. + */ + virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; + + virtual QString getPropertyContainerName() const = 0; + virtual QString getPropertyContainerIconName() const = 0; + + virtual PlayParameters *getPlayParameters() { return 0; } + +signals: + void propertyChanged(PropertyContainer::PropertyName); + +public slots: + /** + * Set a property. This is used for all property types. For + * boolean properties, zero is false and non-zero true; for + * colours, the integer value should be treated as a qRgb. + */ + virtual void setProperty(const PropertyName &, int value); + + /** + * Set a property using a command, supporting undo and redo. + * The default implementation should work for most subclasses. + */ + virtual void setPropertyWithCommand(const PropertyName &, int value); + + /** + * Set a property using a fuzzy match. Compare nameString with + * the property labels and underlying names, and if it matches one + * (with preference given to labels), try to convert valueString + * appropriately and set it. The valueString should contain a + * value label for value properties, a mapped value for range + * properties, "on" or "off" for toggle properties, a colour or + * unit name, or the underlying integer value for the property. + * + * Note that as property and value labels may be translatable, the + * results of this function may vary by locale. It is intended + * for handling user-originated strings, _not_ persistent storage. + * + * The default implementation should work for most subclasses. + */ + virtual void setProperty(QString nameString, QString valueString); + + /** + * As above, but using a command. + */ + virtual void setPropertyWithCommand(QString nameString, QString valueString); + +protected: + + class SetPropertyCommand : public Command + { + public: + SetPropertyCommand(PropertyContainer *pc, const PropertyName &pn, int); + virtual ~SetPropertyCommand() { } + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + protected: + PropertyContainer *m_pc; + PropertyName m_pn; + int m_value; + int m_oldValue; + }; + + virtual bool convertPropertyStrings(QString nameString, QString valueString, + PropertyName &name, int &value); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/RangeMapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RangeMapper.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "system/System.h" +#include "RangeMapper.h" + +#include +#include + +#include + +LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos, + float minval, float maxval, + QString unit) : + m_minpos(minpos), + m_maxpos(maxpos), + m_minval(minval), + m_maxval(maxval), + m_unit(unit) +{ + assert(m_maxval != m_minval); + assert(m_maxpos != m_minpos); +} + +int +LinearRangeMapper::getPositionForValue(float value) const +{ + int position = m_minpos + + lrintf(((value - m_minval) / (m_maxval - m_minval)) + * (m_maxpos - m_minpos)); + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; +// std::cerr << "LinearRangeMapper::getPositionForValue: " << value << " -> " +// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << std::endl; + return position; +} + +float +LinearRangeMapper::getValueForPosition(int position) const +{ + float value = m_minval + + ((float(position - m_minpos) / float(m_maxpos - m_minpos)) + * (m_maxval - m_minval)); + if (value < m_minval) value = m_minval; + if (value > m_maxval) value = m_maxval; +// std::cerr << "LinearRangeMapper::getValueForPosition: " << position << " -> " +// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << std::endl; + return value; +} + +LogRangeMapper::LogRangeMapper(int minpos, int maxpos, + float ratio, float minlog, + QString unit) : + m_minpos(minpos), + m_maxpos(maxpos), + m_ratio(ratio), + m_minlog(minlog), + m_unit(unit) +{ + assert(m_maxpos != m_minpos); + + m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog; +} + +int +LogRangeMapper::getPositionForValue(float value) const +{ + float mapped = m_ratio * log10(value); + int position = lrintf(((mapped - m_minlog) / (m_maxlog - m_minlog)) + * (m_maxpos - m_minpos)); + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; +// std::cerr << "LogRangeMapper::getPositionForValue: " << value << " -> " +// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << std::endl; + return position; +} + +float +LogRangeMapper::getValueForPosition(int position) const +{ + float value = powf(10, (position - m_minpos) / m_ratio + m_minlog); +// std::cerr << "LogRangeMapper::getValueForPosition: " << position << " -> " +// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << std::endl; + return value; +} + diff -r 000000000000 -r fc9323a41f5a base/RangeMapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RangeMapper.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RANGE_MAPPER_H_ +#define _RANGE_MAPPER_H_ + +#include + + +class RangeMapper +{ +public: + virtual ~RangeMapper() { } + virtual int getPositionForValue(float value) const = 0; + virtual float getValueForPosition(int position) const = 0; + virtual QString getUnit() const { return ""; } +}; + + +class LinearRangeMapper : public RangeMapper +{ +public: + LinearRangeMapper(int minpos, int maxpos, + float minval, float maxval, + QString unit = ""); + + virtual int getPositionForValue(float value) const; + virtual float getValueForPosition(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + int m_minpos; + int m_maxpos; + float m_minval; + float m_maxval; + QString m_unit; +}; + + +class LogRangeMapper : public RangeMapper +{ +public: + LogRangeMapper(int minpos, int maxpos, + float ratio, float minlog, + QString m_unit = ""); + + virtual int getPositionForValue(float value) const; + virtual float getValueForPosition(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + int m_minpos; + int m_maxpos; + float m_ratio; + float m_minlog; + float m_maxlog; + QString m_unit; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a base/RealTime.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RealTime.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,273 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +using std::cerr; +using std::endl; + +#include "RealTime.h" +#ifdef USE_VC +#include "winsock2.h" +#else +#include "sys/time.h" +#endif +// A RealTime consists of two ints that must be at least 32 bits each. +// A signed 32-bit int can store values exceeding +/- 2 billion. This +// means we can safely use our lower int for nanoseconds, as there are +// 1 billion nanoseconds in a second and we need to handle double that +// because of the implementations of addition etc that we use. +// +// The maximum valid RealTime on a 32-bit system is somewhere around +// 68 years: 999999999 nanoseconds longer than the classic Unix epoch. + +#define ONE_BILLION 1000000000 + +RealTime::RealTime(int s, int n) : + sec(s), nsec(n) +{ + if (sec == 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + } else if (sec < 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec > 0) { nsec -= ONE_BILLION; ++sec; } + } else { + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + while (nsec < 0) { nsec += ONE_BILLION; --sec; } + } +} + +RealTime +RealTime::fromSeconds(double sec) +{ + return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); +} + +RealTime +RealTime::fromMilliseconds(int msec) +{ + return RealTime(msec / 1000, (msec % 1000) * 1000000); +} + +RealTime +RealTime::fromTimeval(const struct timeval &tv) +{ + return RealTime(tv.tv_sec, tv.tv_usec * 1000); +} + +std::ostream &operator<<(std::ostream &out, const RealTime &rt) +{ + if (rt < RealTime::zeroTime) { + out << "-"; + } else { + out << " "; + } + + int s = (rt.sec < 0 ? -rt.sec : rt.sec); + int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec); + + out << s << "."; + + int nn(n); + if (nn == 0) out << "00000000"; + else while (nn < (ONE_BILLION / 10)) { + out << "0"; + nn *= 10; + } + + out << n << "R"; + return out; +} + +std::string +RealTime::toString(bool align) const +{ + std::stringstream out; + out << *this; + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + std::string s = out.str(); + + if (!align && *this >= RealTime::zeroTime) { + // remove leading " " + s = s.substr(1, s.length() - 1); + } + + // remove trailing R + return s.substr(0, s.length() - 1); +} + +std::string +RealTime::toText(bool fixedDp) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp); + + std::stringstream out; + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + out << (sec % 3600) / 60 << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + int ms = msec(); + + if (ms != 0) { + out << "."; + out << (ms / 100); + ms = ms % 100; + if (ms != 0) { + out << (ms / 10); + ms = ms % 10; + } else if (fixedDp) { + out << "0"; + } + if (ms != 0) { + out << ms; + } else if (fixedDp) { + out << "0"; + } + } else if (fixedDp) { + out << ".000"; + } + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + std::string s = out.str(); + + return s; +} + +std::string +RealTime::toSecText() const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText(); + + std::stringstream out; + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + out << (sec % 3600) / 60 << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + if (sec < 60) { + out << "s"; + } + + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + std::string s = out.str(); + + return s; +} + +RealTime +RealTime::operator*(int m) const +{ + double t = (double(nsec) / ONE_BILLION) * m; + t += sec * m; + return fromSeconds(t); +} + +RealTime +RealTime::operator/(int d) const +{ + int secdiv = sec / d; + int secrem = sec % d; + + double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d; + + return RealTime(secdiv, int(nsecdiv + 0.5)); +} + +double +RealTime::operator/(const RealTime &r) const +{ + double lTotal = double(sec) * ONE_BILLION + double(nsec); + double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec); + + if (rTotal == 0) return 0.0; + else return lTotal/rTotal; +} + +long +RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate) +{ + if (time < zeroTime) return -realTime2Frame(-time, sampleRate); + + // We like integers. The last term is always zero unless the + // sample rate is greater than 1MHz, but hell, you never know... + + long frame = + time.sec * sampleRate + + (time.msec() * sampleRate) / 1000 + + ((time.usec() - 1000 * time.msec()) * sampleRate) / 1000000 + + ((time.nsec - 1000 * time.usec()) * sampleRate) / 1000000000; + + return frame; +} + +RealTime +RealTime::frame2RealTime(long frame, unsigned int sampleRate) +{ + if (frame < 0) return -frame2RealTime(-frame, sampleRate); + + RealTime rt; + rt.sec = frame / long(sampleRate); + frame -= rt.sec * long(sampleRate); + rt.nsec = (int)(((float(frame) * 1000000) / long(sampleRate)) * 1000); + return rt; +} + +const RealTime RealTime::zeroTime(0,0); + diff -r 000000000000 -r fc9323a41f5a base/RealTime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RealTime.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,129 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _REAL_TIME_H_ +#define _REAL_TIME_H_ + +#include +#include + +struct timeval; + + +/** + * RealTime represents time values to nanosecond precision + * with accurate arithmetic and frame-rate conversion functions. + */ + +struct RealTime +{ + int sec; + int nsec; + + int usec() const { return nsec / 1000; } + int msec() const { return nsec / 1000000; } + + RealTime(): sec(0), nsec(0) {} + RealTime(int s, int n); + + RealTime(const RealTime &r) : + sec(r.sec), nsec(r.nsec) { } + + static RealTime fromSeconds(double sec); + static RealTime fromMilliseconds(int msec); + static RealTime fromTimeval(const struct timeval &); + + RealTime &operator=(const RealTime &r) { + sec = r.sec; nsec = r.nsec; return *this; + } + + RealTime operator+(const RealTime &r) const { + return RealTime(sec + r.sec, nsec + r.nsec); + } + RealTime operator-(const RealTime &r) const { + return RealTime(sec - r.sec, nsec - r.nsec); + } + RealTime operator-() const { + return RealTime(-sec, -nsec); + } + + bool operator <(const RealTime &r) const { + if (sec == r.sec) return nsec < r.nsec; + else return sec < r.sec; + } + + bool operator >(const RealTime &r) const { + if (sec == r.sec) return nsec > r.nsec; + else return sec > r.sec; + } + + bool operator==(const RealTime &r) const { + return (sec == r.sec && nsec == r.nsec); + } + + bool operator!=(const RealTime &r) const { + return !(r == *this); + } + + bool operator>=(const RealTime &r) const { + if (sec == r.sec) return nsec >= r.nsec; + else return sec >= r.sec; + } + + bool operator<=(const RealTime &r) const { + if (sec == r.sec) return nsec <= r.nsec; + else return sec <= r.sec; + } + + RealTime operator*(int m) const; + RealTime operator/(int d) const; + + // Find the fractional difference between times + // + double operator/(const RealTime &r) const; + + // Return a human-readable debug-type string to full precision + // (probably not a format to show to a user directly). If align + // is true, prepend " " to the start of positive values so that + // they line up with negative ones (which start with "-"). + // + std::string toString(bool align = false) const; + + // Return a user-readable string to the nearest millisecond + // in a form like HH:MM:SS.mmm + // + std::string toText(bool fixedDp = false) const; + + // Return a user-readable string to the nearest second in a form + // like "6s" (for less than a minute) or "2:21" (for more). + // + std::string toSecText() const; + + // Convenience functions for handling sample frames + // + static long realTime2Frame(const RealTime &r, unsigned int sampleRate); + static RealTime frame2RealTime(long frame, unsigned int sampleRate); + + static const RealTime zeroTime; +}; + +std::ostream &operator<<(std::ostream &out, const RealTime &rt); + +#endif diff -r 000000000000 -r fc9323a41f5a base/RecentFiles.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RecentFiles.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,127 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RecentFiles.h" + +#include +#include +#include + +RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount) : + m_settingsGroup(settingsGroup), + m_maxCount(maxCount) +{ + read(); +} + +RecentFiles::~RecentFiles() +{ + // nothing +} + +void +RecentFiles::read() +{ + m_names.clear(); + QSettings settings; + settings.beginGroup(m_settingsGroup); + + for (size_t i = 0; i < 100; ++i) { + QString key = QString("recent-%1").arg(i); + QString name = settings.value(key, "").toString(); + if (name == "") break; + if (i < m_maxCount) m_names.push_back(name); + else settings.setValue(key, ""); + } + + settings.endGroup(); +} + +void +RecentFiles::write() +{ + QSettings settings; + settings.beginGroup(m_settingsGroup); + + for (size_t i = 0; i < m_maxCount; ++i) { + QString key = QString("recent-%1").arg(i); + QString name = ""; + if (i < m_names.size()) name = m_names[i]; + settings.setValue(key, name); + } + + settings.endGroup(); +} + +void +RecentFiles::truncateAndWrite() +{ + while (m_names.size() > m_maxCount) { + m_names.pop_back(); + } + write(); +} + +std::vector +RecentFiles::getRecent() const +{ + std::vector names; + for (size_t i = 0; i < m_maxCount; ++i) { + if (i < m_names.size()) { + names.push_back(m_names[i]); + } + } + return names; +} + +void +RecentFiles::add(QString name) +{ + bool have = false; + for (size_t i = 0; i < m_names.size(); ++i) { + if (m_names[i] == name) { + have = true; + break; + } + } + + if (!have) { + m_names.push_front(name); + } else { + std::deque newnames; + newnames.push_back(name); + for (size_t i = 0; i < m_names.size(); ++i) { + if (m_names[i] == name) continue; + newnames.push_back(m_names[i]); + } + m_names = newnames; + } + + truncateAndWrite(); + emit recentChanged(); +} + +void +RecentFiles::addFile(QString name) +{ + static QRegExp schemeRE("^[a-zA-Z]{2,5}://"); + if (schemeRE.indexIn(name) == 0) { + add(name); + } else { + add(QFileInfo(name).absoluteFilePath()); + } +} + + diff -r 000000000000 -r fc9323a41f5a base/RecentFiles.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RecentFiles.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,76 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RECENT_FILES_H_ +#define _RECENT_FILES_H_ + +#include +#include +#include +#include + +/** + * RecentFiles manages a list of the names of recently-used objects, + * saving and restoring that list via QSettings. The names do not + * actually have to refer to files. + */ + +class RecentFiles : public QObject +{ + Q_OBJECT + +public: + /** + * Construct a RecentFiles object that saves and restores in the + * given QSettings group and truncates when the given count of + * strings is reached. + */ + RecentFiles(QString settingsGroup = "RecentFiles", size_t maxCount = 10); + + virtual ~RecentFiles(); + + QString getSettingsGroup() const { return m_settingsGroup; } + + int getMaxCount() const { return m_maxCount; } + + std::vector getRecent() const; + + /** + * Add a name that should be treated as a literal string. + */ + void add(QString name); + + /** + * Add a name that is known to be either a file path or a URL. If + * it looks like a URL, add it literally; otherwise treat it as a + * file path and canonicalise it appropriately. + */ + void addFile(QString name); + +signals: + void recentChanged(); + +protected: + QString m_settingsGroup; + size_t m_maxCount; + + std::deque m_names; + + void read(); + void write(); + void truncateAndWrite(); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/ResizeableBitset.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ResizeableBitset.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RESIZEABLE_BITMAP_H_ +#define _RESIZEABLE_BITMAP_H_ + +#include +//#include +#include "system/System.h" + +class ResizeableBitset { + +public: + ResizeableBitset() : m_bits(0) { + } + ResizeableBitset(size_t size) : m_bits(new std::vector) { + m_bits->assign((size >> 3) + 1, 0); + } + ResizeableBitset(const ResizeableBitset &b) { + m_bits = new std::vector(*b.m_bits); + } + ResizeableBitset &operator=(const ResizeableBitset &b) { + if (&b != this) return *this; + delete m_bits; + m_bits = new std::vector(*b.m_bits); + return *this; + } + ~ResizeableBitset() { + delete m_bits; + } + + void resize(size_t bits) { // losing all data + if (!m_bits || bits < m_bits->size()) { + delete m_bits; + m_bits = new std::vector; + } + m_bits->assign((bits >> 3) + 1, 0); + } + + bool get(size_t column) const { + return ((*m_bits)[column >> 3]) & (1u << (column & 0x07)); + } + + void set(size_t column) { + ((*m_bits)[column >> 3]) |= (uint8_t(1) << (column & 0x07)); + } + + void reset(size_t column) { + ((*m_bits)[column >> 3]) &= ~(uint8_t(1) << (column & 0x07)); + } + + void copy(size_t source, size_t dest) { + get(source) ? set(dest) : reset(dest); + } + +private: + std::vector *m_bits; +}; + + +#endif + diff -r 000000000000 -r fc9323a41f5a base/RingBuffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RingBuffer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,546 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _RINGBUFFER_H_ +#define _RINGBUFFER_H_ + +#include + +#include "system/System.h" +#include "Scavenger.h" + +//#define DEBUG_RINGBUFFER 1 + +#ifdef DEBUG_RINGBUFFER +#include +#endif + +/** + * RingBuffer implements a lock-free ring buffer for one writer and N + * readers, that is to be used to store a sample type T. + * + * For efficiency, RingBuffer frequently initialises samples by + * writing zeroes into their memory space, so T should normally be a + * simple type that can safely be set to zero using memset. + */ + +template +class RingBuffer +{ +public: + /** + * Create a ring buffer with room to write n samples. + * + * Note that the internal storage size will actually be n+1 + * samples, as one element is unavailable for administrative + * reasons. Since the ring buffer performs best if its size is a + * power of two, this means n should ideally be some power of two + * minus one. + */ + RingBuffer(size_t n); + + virtual ~RingBuffer(); + + /** + * Return the total capacity of the ring buffer in samples. + * (This is the argument n passed to the constructor.) + */ + size_t getSize() const; + + /** + * Resize the ring buffer. This also empties it. Actually swaps + * in a new, larger buffer; the old buffer is scavenged after a + * seemly delay. Should be called from the write thread. + */ + void resize(size_t newSize); + + /** + * Lock the ring buffer into physical memory. Returns true + * for success. + */ + bool mlock(); + + /** + * Reset read and write pointers, thus emptying the buffer. + * Should be called from the write thread. + */ + void reset(); + + /** + * Return the amount of data available for reading by reader R, in + * samples. + */ + size_t getReadSpace(int R = 0) const; + + /** + * Return the amount of space available for writing, in samples. + */ + size_t getWriteSpace() const; + + /** + * Read n samples from the buffer, for reader R. If fewer than n + * are available, the remainder will be zeroed out. Returns the + * number of samples actually read. + */ + size_t read(T *destination, size_t n, int R = 0); + + /** + * Read n samples from the buffer, for reader R, adding them to + * the destination. If fewer than n are available, the remainder + * will be left alone. Returns the number of samples actually + * read. + */ + size_t readAdding(T *destination, size_t n, int R = 0); + + /** + * Read one sample from the buffer, for reader R. If no sample is + * available, this will silently return zero. Calling this + * repeatedly is obviously slower than calling read once, but it + * may be good enough if you don't want to allocate a buffer to + * read into. + */ + T readOne(int R = 0); + + /** + * Read n samples from the buffer, if available, for reader R, + * without advancing the read pointer -- i.e. a subsequent read() + * or skip() will be necessary to empty the buffer. If fewer than + * n are available, the remainder will be zeroed out. Returns the + * number of samples actually read. + */ + size_t peek(T *destination, size_t n, int R = 0) const; + + /** + * Read one sample from the buffer, if available, without + * advancing the read pointer -- i.e. a subsequent read() or + * skip() will be necessary to empty the buffer. Returns zero if + * no sample was available. + */ + T peekOne(int R = 0) const; + + /** + * Pretend to read n samples from the buffer, for reader R, + * without actually returning them (i.e. discard the next n + * samples). Returns the number of samples actually available for + * discarding. + */ + size_t skip(size_t n, int R = 0); + + /** + * Write n samples to the buffer. If insufficient space is + * available, not all samples may actually be written. Returns + * the number of samples actually written. + */ + size_t write(const T *source, size_t n); + + /** + * Write n zero-value samples to the buffer. If insufficient + * space is available, not all zeros may actually be written. + * Returns the number of zeroes actually written. + */ + size_t zero(size_t n); + +protected: + T *m_buffer; + volatile size_t m_writer; + volatile size_t m_readers[N]; + size_t m_size; + bool m_mlocked; + + static Scavenger > m_scavenger; + +private: + RingBuffer(const RingBuffer &); // not provided + RingBuffer &operator=(const RingBuffer &); // not provided +}; + +template +Scavenger > RingBuffer::m_scavenger; + +template +RingBuffer::RingBuffer(size_t n) : + m_buffer(new T[n + 1]), + m_writer(0), + m_size(n + 1), + m_mlocked(false) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::RingBuffer(" << n << ")" << std::endl; +#endif + + for (int i = 0; i < N; ++i) m_readers[i] = 0; + + m_scavenger.scavenge(); +} + +template +RingBuffer::~RingBuffer() +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::~RingBuffer" << std::endl; +#endif + + if (m_mlocked) { + MUNLOCK((void *)m_buffer, m_size * sizeof(T)); + } + delete[] m_buffer; + + m_scavenger.scavenge(); +} + +template +size_t +RingBuffer::getSize() const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getSize(): " << m_size-1 << std::endl; +#endif + + return m_size - 1; +} + +template +void +RingBuffer::resize(size_t newSize) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::resize(" << newSize << ")" << std::endl; +#endif + + m_scavenger.scavenge(); + + if (m_mlocked) { + MUNLOCK((void *)m_buffer, m_size * sizeof(T)); + } + + m_scavenger.claim(new ScavengerArrayWrapper(m_buffer)); + + reset(); + m_buffer = new T[newSize + 1]; + m_size = newSize + 1; + + if (m_mlocked) { + if (MLOCK((void *)m_buffer, m_size * sizeof(T))) { + m_mlocked = false; + } + } +} + +template +bool +RingBuffer::mlock() +{ + if (MLOCK((void *)m_buffer, m_size * sizeof(T))) return false; + m_mlocked = true; + return true; +} + +template +void +RingBuffer::reset() +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::reset" << std::endl; +#endif + + m_writer = 0; + for (int i = 0; i < N; ++i) m_readers[i] = 0; +} + +template +size_t +RingBuffer::getReadSpace(int R) const +{ + size_t writer = m_writer; + size_t reader = m_readers[R]; + size_t space = 0; + + if (writer > reader) space = writer - reader; + else space = ((writer + m_size) - reader) % m_size; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getReadSpace(" << R << "): " << space << std::endl; +#endif + + return space; +} + +template +size_t +RingBuffer::getWriteSpace() const +{ + size_t space = 0; + for (int i = 0; i < N; ++i) { + size_t here = (m_readers[i] + m_size - m_writer - 1) % m_size; + if (i == 0 || here < space) space = here; + } + +#ifdef DEBUG_RINGBUFFER + size_t rs(getReadSpace()), rp(m_readers[0]); + + std::cerr << "RingBuffer: write space " << space << ", read space " + << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl; + std::cerr << "RingBuffer: reader " << rp << ", writer " << m_writer << std::endl; +#endif + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getWriteSpace(): " << space << std::endl; +#endif + + return space; +} + +template +size_t +RingBuffer::read(T *destination, size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::read(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + memset(destination + available, 0, (n - available) * sizeof(T)); + n = available; + } + if (n == 0) return n; + + size_t here = m_size - m_readers[R]; + if (here >= n) { + memcpy(destination, m_buffer + m_readers[R], n * sizeof(T)); + } else { + memcpy(destination, m_buffer + m_readers[R], here * sizeof(T)); + memcpy(destination + here, m_buffer, (n - here) * sizeof(T)); + } + + m_readers[R] = (m_readers[R] + n) % m_size; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::read: read " << n << ", reader now " << m_readers[R] << std::endl; +#endif + + return n; +} + +template +size_t +RingBuffer::readAdding(T *destination, size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::readAdding(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t here = m_size - m_readers[R]; + + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + destination[i] += (m_buffer + m_readers[R])[i]; + } + } else { + for (size_t i = 0; i < here; ++i) { + destination[i] += (m_buffer + m_readers[R])[i]; + } + for (size_t i = 0; i < (n - here); ++i) { + destination[i + here] += m_buffer[i]; + } + } + + m_readers[R] = (m_readers[R] + n) % m_size; + return n; +} + +template +T +RingBuffer::readOne(int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::readOne(" << R << ")" << std::endl; +#endif + + if (m_writer == m_readers[R]) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: No sample available" + << std::endl; +#endif + T t; + memset(&t, 0, sizeof(T)); + return t; + } + T value = m_buffer[m_readers[R]]; + if (++m_readers[R] == m_size) m_readers[R] = 0; + return value; +} + +template +size_t +RingBuffer::peek(T *destination, size_t n, int R) const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + memset(destination + available, 0, (n - available) * sizeof(T)); + n = available; + } + if (n == 0) return n; + + size_t here = m_size - m_readers[R]; + if (here >= n) { + memcpy(destination, m_buffer + m_readers[R], n * sizeof(T)); + } else { + memcpy(destination, m_buffer + m_readers[R], here * sizeof(T)); + memcpy(destination + here, m_buffer, (n - here) * sizeof(T)); + } + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek: read " << n << std::endl; +#endif + + return n; +} + +template +T +RingBuffer::peekOne(int R) const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek(" << R << ")" << std::endl; +#endif + + if (m_writer == m_readers[R]) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: No sample available" + << std::endl; +#endif + T t; + memset(&t, 0, sizeof(T)); + return t; + } + T value = m_buffer[m_readers[R]]; + return value; +} + +template +size_t +RingBuffer::skip(size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::skip(" << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + m_readers[R] = (m_readers[R] + n) % m_size; + return n; +} + +template +size_t +RingBuffer::write(const T *source, size_t n) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::write(" << n << ")" << std::endl; +#endif + + size_t available = getWriteSpace(); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only room for " << available << " samples" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t here = m_size - m_writer; + if (here >= n) { + memcpy(m_buffer + m_writer, source, n * sizeof(T)); + } else { + memcpy(m_buffer + m_writer, source, here * sizeof(T)); + memcpy(m_buffer, source + here, (n - here) * sizeof(T)); + } + + m_writer = (m_writer + n) % m_size; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::write: wrote " << n << ", writer now " << m_writer << std::endl; +#endif + + return n; +} + +template +size_t +RingBuffer::zero(size_t n) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::zero(" << n << ")" << std::endl; +#endif + + size_t available = getWriteSpace(); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only room for " << available << " samples" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t here = m_size - m_writer; + if (here >= n) { + memset(m_buffer + m_writer, 0, n * sizeof(T)); + } else { + memset(m_buffer + m_writer, 0, here * sizeof(T)); + memset(m_buffer, 0, (n - here) * sizeof(T)); + } + + m_writer = (m_writer + n) % m_size; + return n; +} + +#endif // _RINGBUFFER_H_ diff -r 000000000000 -r fc9323a41f5a base/Scavenger.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Scavenger.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,203 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _SCAVENGER_H_ +#define _SCAVENGER_H_ + +#include "system/System.h" + +#include +#include +//#include +#include +#include + +/** + * A very simple class that facilitates running things like plugins + * without locking, by collecting unwanted objects and deleting them + * after a delay so as to be sure nobody's in the middle of using + * them. Requires scavenge() to be called regularly from a non-RT + * thread. + * + * This is currently not at all suitable for large numbers of objects + * -- it's just a quick hack for use with things like plugins. + */ + +template +class Scavenger +{ +public: + Scavenger(int sec = 2, int defaultObjectListSize = 200); + ~Scavenger(); + + /** + * Call from an RT thread etc., to pass ownership of t to us. + * Only one thread should be calling this on any given scavenger. + */ + void claim(T *t); + + /** + * Call from a non-RT thread. + * Only one thread should be calling this on any given scavenger. + */ + void scavenge(bool clearNow = false); + +protected: + typedef std::pair ObjectTimePair; + typedef std::vector ObjectTimeList; + ObjectTimeList m_objects; + int m_sec; + + typedef std::list ObjectList; + ObjectList m_excess; + int m_lastExcess; + QMutex m_excessMutex; + void pushExcess(T *); + void clearExcess(int); + + unsigned int m_claimed; + unsigned int m_scavenged; +}; + +/** + * A wrapper to permit arrays to be scavenged. + */ + +template +class ScavengerArrayWrapper +{ +public: + ScavengerArrayWrapper(T *array) : m_array(array) { } + ~ScavengerArrayWrapper() { delete[] m_array; } + +private: + T *m_array; +}; + + +template +Scavenger::Scavenger(int sec, int defaultObjectListSize) : + m_objects(ObjectTimeList(defaultObjectListSize)), + m_sec(sec), + m_lastExcess(0), + m_claimed(0), + m_scavenged(0) +{ +} + +template +Scavenger::~Scavenger() +{ + if (m_scavenged < m_claimed) { + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (pair.first != 0) { + T *ot = pair.first; + pair.first = 0; + delete ot; + ++m_scavenged; + } + } + } + + clearExcess(0); +} + +template +void +Scavenger::claim(T *t) +{ +// std::cerr << "Scavenger::claim(" << t << ")" << std::endl; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + int sec = tv.tv_sec; + + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (pair.first == 0) { + pair.second = sec; + pair.first = t; + ++m_claimed; + return; + } + } + + std::cerr << "WARNING: Scavenger::claim(" << t << "): run out of slots, " + << "using non-RT-safe method" << std::endl; + pushExcess(t); +} + +template +void +Scavenger::scavenge(bool clearNow) +{ +// std::cerr << "Scavenger::scavenge: scavenged " << m_scavenged << ", claimed " << m_claimed << std::endl; + + if (m_scavenged >= m_claimed) return; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + int sec = tv.tv_sec; + + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (clearNow || + (pair.first != 0 && pair.second + m_sec < sec)) { + T *ot = pair.first; + pair.first = 0; + delete ot; + ++m_scavenged; + } + } + + if (sec > m_lastExcess + m_sec) { + clearExcess(sec); + } +} + +template +void +Scavenger::pushExcess(T *t) +{ + m_excessMutex.lock(); + m_excess.push_back(t); + struct timeval tv; + (void)gettimeofday(&tv, 0); + m_lastExcess = tv.tv_sec; + m_excessMutex.unlock(); +} + +template +void +Scavenger::clearExcess(int sec) +{ + m_excessMutex.lock(); + for (typename ObjectList::iterator i = m_excess.begin(); + i != m_excess.end(); ++i) { + delete *i; + } + m_excess.clear(); + m_lastExcess = sec; + m_excessMutex.unlock(); +} + +#endif diff -r 000000000000 -r fc9323a41f5a base/Selection.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Selection.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,210 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Selection.h" + +Selection::Selection() : + m_startFrame(0), + m_endFrame(0) +{ +} + +Selection::Selection(size_t startFrame, size_t endFrame) : + m_startFrame(startFrame), + m_endFrame(endFrame) +{ + if (m_startFrame > m_endFrame) { + size_t tmp = m_endFrame; + m_endFrame = m_startFrame; + m_startFrame = tmp; + } +} + +Selection::Selection(const Selection &s) : + m_startFrame(s.m_startFrame), + m_endFrame(s.m_endFrame) +{ +} + +Selection & +Selection::operator=(const Selection &s) +{ + if (this != &s) { + m_startFrame = s.m_startFrame; + m_endFrame = s.m_endFrame; + } + return *this; +} + +Selection::~Selection() +{ +} + +bool +Selection::isEmpty() const +{ + return m_startFrame == m_endFrame; +} + +size_t +Selection::getStartFrame() const +{ + return m_startFrame; +} + +size_t +Selection::getEndFrame() const +{ + return m_endFrame; +} + +bool +Selection::contains(size_t frame) const +{ + return (frame >= m_startFrame) && (frame < m_endFrame); +} + +bool +Selection::operator<(const Selection &s) const +{ + if (isEmpty()) { + if (s.isEmpty()) return false; + else return true; + } else { + if (s.isEmpty()) return false; + else return (m_startFrame < s.m_startFrame); + } +} + +bool +Selection::operator==(const Selection &s) const +{ + if (isEmpty()) return s.isEmpty(); + + return (m_startFrame == s.m_startFrame && + m_endFrame == s.m_endFrame); +} + + +MultiSelection::MultiSelection() +{ +} + +MultiSelection::~MultiSelection() +{ +} + +const MultiSelection::SelectionList & +MultiSelection::getSelections() const +{ + return m_selections; +} + +void +MultiSelection::setSelection(const Selection &selection) +{ + clearSelections(); + addSelection(selection); +} + +void +MultiSelection::addSelection(const Selection &selection) +{ + m_selections.insert(selection); + + // Cope with a sitation where the new selection overlaps one or + // more existing ones. This is a terribly inefficient way to do + // this, but that probably isn't significant in real life. + + // It's essential for the correct operation of + // getContainingSelection that the selections do not overlap, so + // this is not just a frill. + + for (SelectionList::iterator i = m_selections.begin(); + i != m_selections.end(); ) { + + SelectionList::iterator j = i; + if (++j == m_selections.end()) break; + + if (i->getEndFrame() >= j->getStartFrame()) { + Selection merged(i->getStartFrame(), + std::max(i->getEndFrame(), j->getEndFrame())); + m_selections.erase(i); + m_selections.erase(j); + m_selections.insert(merged); + i = m_selections.begin(); + } else { + ++i; + } + } +} + +void +MultiSelection::removeSelection(const Selection &selection) +{ + //!!! Likewise this needs to cope correctly with the situation + //where selection is not one of the original selection set but + //simply overlaps one of them (cutting down the original selection + //appropriately) + + if (m_selections.find(selection) != m_selections.end()) { + m_selections.erase(selection); + } +} + +void +MultiSelection::clearSelections() +{ + if (!m_selections.empty()) { + m_selections.clear(); + } +} + +Selection +MultiSelection::getContainingSelection(size_t frame, bool defaultToFollowing) const +{ + // This scales very badly with the number of selections, but it's + // more efficient for very small numbers of selections than a more + // scalable method, and I think that may be what we need + + for (SelectionList::const_iterator i = m_selections.begin(); + i != m_selections.end(); ++i) { + + if (i->contains(frame)) return *i; + + if (i->getStartFrame() > frame) { + if (defaultToFollowing) return *i; + else return Selection(); + } + } + + return Selection(); +} + +QString +MultiSelection::toXmlString(QString indent, + QString extraAttributes) const +{ + QString s; + s += indent + QString("\n").arg(extraAttributes); + for (SelectionList::const_iterator i = m_selections.begin(); + i != m_selections.end(); ++i) { + s += indent + QString(" \n") + .arg(i->getStartFrame()).arg(i->getEndFrame()); + } + s += indent + "\n"; + return s; +} + diff -r 000000000000 -r fc9323a41f5a base/Selection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Selection.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,76 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SELECTION_H_ +#define _SELECTION_H_ + +#include +#include + +#include "XmlExportable.h" + +class Selection +{ +public: + Selection(); + Selection(size_t startFrame, size_t endFrame); + Selection(const Selection &); + Selection &operator=(const Selection &); + virtual ~Selection(); + + bool isEmpty() const; + size_t getStartFrame() const; + size_t getEndFrame() const; + bool contains(size_t frame) const; + + bool operator<(const Selection &) const; + bool operator==(const Selection &) const; + +protected: + size_t m_startFrame; + size_t m_endFrame; +}; + +class MultiSelection : public XmlExportable +{ +public: + MultiSelection(); + virtual ~MultiSelection(); + + typedef std::set SelectionList; + + const SelectionList &getSelections() const; + void setSelection(const Selection &selection); + void addSelection(const Selection &selection); + void removeSelection(const Selection &selection); + void clearSelections(); + + /** + * Return the selection that contains a given frame. + * If defaultToFollowing is true, and if the frame is not in a + * selected area, return the next selection after the given frame. + * Return the empty selection if no appropriate selection is found. + */ + Selection getContainingSelection(size_t frame, bool defaultToFollowing) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +protected: + SelectionList m_selections; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a base/StorageAdviser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/StorageAdviser.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,187 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "StorageAdviser.h" + +#include "Exceptions.h" +#include "TempDirectory.h" + +#include "system/System.h" + +#include + +long StorageAdviser::m_discPlanned = 0; +long StorageAdviser::m_memoryPlanned = 0; + +StorageAdviser::Recommendation +StorageAdviser::recommend(Criteria criteria, + int minimumSize, + int maximumSize) +{ + std::cerr << "StorageAdviser::recommend: Criteria " << criteria + << ", minimumSize " << minimumSize + << ", maximumSize " << maximumSize << std::endl; + + QString path = TempDirectory::getInstance()->getPath(); + int discFree = GetDiscSpaceMBAvailable(path.toLocal8Bit()); + int memoryFree, memoryTotal; + GetRealMemoryMBAvailable(memoryFree, memoryTotal); + + if (discFree > m_discPlanned / 1024 + 1) { + discFree -= m_discPlanned / 1024 + 1; + } else if (discFree > 0) { // can also be -1 for unknown + discFree = 0; + } + + if (memoryFree > m_memoryPlanned / 1024 + 1) { + memoryFree -= m_memoryPlanned / 1024 + 1; + } else if (memoryFree > 0) { // can also be -1 for unknown + memoryFree = 0; + } + + std::cerr << "Disc space: " << discFree << ", memory free: " << memoryFree << ", memory total: " << memoryTotal << std::endl; + + //!!! We have a potentially serious problem here if multiple + //recommendations are made in advance of any of the resulting + //allocations, as the allocations that have been recommended for + //won't be taken into account in subsequent recommendations. + + enum StorageStatus { + Unknown, + Insufficient, + Marginal, + Sufficient + }; + + StorageStatus memoryStatus = Unknown; + StorageStatus discStatus = Unknown; + + int minmb = minimumSize / 1024 + 1; + int maxmb = maximumSize / 1024 + 1; + + if (memoryFree == -1) memoryStatus = Unknown; + else if (minmb > (memoryFree * 3) / 4) memoryStatus = Insufficient; + else if (maxmb > (memoryFree * 3) / 4) memoryStatus = Marginal; + else if (minmb > (memoryFree / 3)) memoryStatus = Marginal; + else if (memoryTotal == -1 || + minmb > (memoryTotal / 10)) memoryStatus = Marginal; + else memoryStatus = Sufficient; + + if (discFree == -1) discStatus = Unknown; + else if (minmb > (discFree * 3) / 4) discStatus = Insufficient; + else if (maxmb > (discFree / 4)) discStatus = Marginal; + else if (minmb > (discFree / 10)) discStatus = Marginal; + else discStatus = Sufficient; + + std::cerr << "Memory status: " << memoryStatus << ", disc status " + << discStatus << std::endl; + + int recommendation = NoRecommendation; + + if (memoryStatus == Insufficient || memoryStatus == Unknown) { + + recommendation |= UseDisc; + + if (discStatus == Insufficient && minmb > discFree) { + throw InsufficientDiscSpace(path, minmb, discFree); + } + + if (discStatus == Insufficient || discStatus == Marginal) { + recommendation |= ConserveSpace; + } else if (discStatus == Unknown && !(criteria & PrecisionCritical)) { + recommendation |= ConserveSpace; + } else { + recommendation |= UseAsMuchAsYouLike; + } + + } else if (memoryStatus == Marginal) { + + if (((criteria & SpeedCritical) || + (criteria & FrequentLookupLikely)) && + !(criteria & PrecisionCritical) && + !(criteria & LongRetentionLikely)) { + + // requirements suggest a preference for memory + + if (discStatus != Insufficient) { + recommendation |= PreferMemory; + } else { + recommendation |= UseMemory; + } + + recommendation |= ConserveSpace; + + } else { + + if (discStatus == Insufficient) { + recommendation |= (UseMemory | ConserveSpace); + } else if (discStatus == Marginal) { + recommendation |= (PreferMemory | ConserveSpace); + } else if (discStatus == Unknown) { + recommendation |= (PreferDisc | ConserveSpace); + } else { + recommendation |= (UseDisc | UseAsMuchAsYouLike); + } + } + + } else { + + if (discStatus == Insufficient) { + recommendation |= (UseMemory | ConserveSpace); + } else if (discStatus != Sufficient) { + recommendation |= (PreferMemory | ConserveSpace); + } else { + + if ((criteria & SpeedCritical) || + (criteria & FrequentLookupLikely)) { + recommendation |= PreferMemory; + if (criteria & PrecisionCritical) { + recommendation |= UseAsMuchAsYouLike; + } else { + recommendation |= ConserveSpace; + } + } else { + recommendation |= PreferDisc; + recommendation |= UseAsMuchAsYouLike; + } + } + } + + return Recommendation(recommendation); +} + +void +StorageAdviser::notifyPlannedAllocation(AllocationArea area, int size) +{ + if (area == MemoryAllocation) m_memoryPlanned += size; + else if (area == DiscAllocation) m_discPlanned += size; +// std::cerr << "storage planned up: memory: " << m_memoryPlanned << ", disc " +// << m_discPlanned << std::endl; +} + +void +StorageAdviser::notifyDoneAllocation(AllocationArea area, int size) +{ + if (area == MemoryAllocation) { + if (m_memoryPlanned > size) m_memoryPlanned -= size; + else m_memoryPlanned = 0; + } else if (area == DiscAllocation) { + if (m_discPlanned > size) m_discPlanned -= size; + else m_discPlanned = 0; + } +// std::cerr << "storage planned down: memory: " << m_memoryPlanned << ", disc " +// << m_discPlanned << std::endl; +} + diff -r 000000000000 -r fc9323a41f5a base/StorageAdviser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/StorageAdviser.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,86 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _STORAGE_ADVISER_H_ +#define _STORAGE_ADVISER_H_ + +/** + * A utility class designed to help decide whether to store cache data + * (for example FFT outputs) in memory or on disk in the TempDirectory. + * This is basically a compendium of simple rules of thumb. + */ + +class StorageAdviser +{ +public: + // pass to recommend() zero or more of these OR'd together + enum Criteria { + NoCriteria = 0, + SpeedCritical = 1, + PrecisionCritical = 2, + LongRetentionLikely = 4, + FrequentLookupLikely = 8 + }; + + // recommend() returns one or two of these OR'd together + enum Recommendation { + NoRecommendation = 0, + UseMemory = 1, // Disc is strongly contraindicated + PreferMemory = 2, // Either would do; memory probably better + PreferDisc = 4, // Either would do; disc probably better + UseDisc = 8, // Probably won't fit in memory + ConserveSpace = 16,// Whatever you choose, keep it compact + UseAsMuchAsYouLike = 32 // Take my advice and there'll be space for all + }; + + /** + * Recommend where to store some data, given certain storage and + * recall criteria. The minimum size is the approximate amount of + * data in kilobytes that will be stored if the recommendation is + * to ConserveSpace; the maximum size is approximately the amount + * that will be used if UseAsMuchAsYouLike is returned. + * + * May throw InsufficientDiscSpace exception if there appears to + * be nowhere the minimum amount of data can be stored. + */ + static Recommendation recommend(Criteria criteria, + int minimumSize, + int maximumSize); + + enum AllocationArea { + MemoryAllocation, + DiscAllocation + }; + + /** + * Specify that we are planning to use a given amount of storage + * (in kilobytes), but haven't allocated it yet. + */ + static void notifyPlannedAllocation(AllocationArea area, int size); + + /** + * Specify that we have now allocated, or abandoned the allocation + * of, the given amount (in kilobytes) of a storage area that was + * previously notified using notifyPlannedAllocation. + */ + static void notifyDoneAllocation(AllocationArea area, int size); + +private: + static long m_discPlanned; + static long m_memoryPlanned; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a base/TempDirectory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/TempDirectory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,257 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TempDirectory.h" +#include "system/System.h" +#include "Exceptions.h" + +#include +#include +#include +#include + +#include +#include + +TempDirectory * +TempDirectory::m_instance = new TempDirectory; + +TempDirectory * +TempDirectory::getInstance() +{ + return m_instance; +} + +TempDirectory::TempDirectory() : + m_tmpdir("") +{ +} + +TempDirectory::~TempDirectory() +{ + std::cerr << "TempDirectory::~TempDirectory" << std::endl; + + cleanup(); +} + +void +TempDirectory::cleanup() +{ + cleanupDirectory(""); +} + +QString +TempDirectory::getPath() +{ + QMutexLocker locker(&m_mutex); + + if (m_tmpdir != "") return m_tmpdir; + + QSettings settings; + settings.beginGroup("TempDirectory"); + QString svDirParent = settings.value("create-in", "$HOME").toString(); + settings.endGroup(); + + svDirParent.replace("$HOME", QDir::home().absolutePath()); + + QString svDirBase = ".sv1"; + QString svDir = QDir(svDirParent).filePath(svDirBase); + if (!QFileInfo(svDir).exists()) { + if (!QDir(svDirParent).mkdir(svDirBase)) { + throw DirectoryCreationFailed(QString("%1 directory in %2") + .arg(svDirBase).arg(svDirParent)); + } + } else if (!QFileInfo(svDir).isDir()) { + throw DirectoryCreationFailed(QString("%1/%2 is not a directory") + .arg(svDirParent).arg(svDirBase)); + } + + cleanupAbandonedDirectories(svDir); + + return createTempDirectoryIn(svDir); +} + +QString +TempDirectory::createTempDirectoryIn(QString dir) +{ + // Entered with mutex held. + + QDir tempDirBase(dir); + + // Generate a temporary directory. Qt4.1 doesn't seem to be able + // to do this for us, and mkdtemp is not standard. This method is + // based on the way glibc does mkdtemp. + + static QString chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + QString suffix; + int padlen = 6, attempts = 100; + unsigned int r = time(0) ^ getpid(); + + for (int i = 0; i < padlen; ++i) { + suffix += "X"; + } + + for (int j = 0; j < attempts; ++j) { + + unsigned int v = r; + + for (int i = 0; i < padlen; ++i) { + suffix[i] = chars[v % 62]; + v /= 62; + } + + QString candidate = QString("sv_%1").arg(suffix); + + if (tempDirBase.mkpath(candidate)) { + m_tmpdir = tempDirBase.filePath(candidate); + break; + } + + r = r + 7777; + } + + if (m_tmpdir == "") { + throw DirectoryCreationFailed(QString("temporary subdirectory in %1") + .arg(tempDirBase.canonicalPath())); + } + + QString pidpath = QDir(m_tmpdir).filePath(QString("%1.pid").arg(getpid())); + QFile pidfile(pidpath); + + if (!pidfile.open(QIODevice::WriteOnly)) { + throw DirectoryCreationFailed(QString("pid file creation in %1") + .arg(m_tmpdir)); + } else { + pidfile.close(); + } + + return m_tmpdir; +} + +QString +TempDirectory::getSubDirectoryPath(QString subdir) +{ + QString tmpdirpath = getPath(); + + QMutexLocker locker(&m_mutex); + + QDir tmpdir(tmpdirpath); + QFileInfo fi(tmpdir.filePath(subdir)); + + if (!fi.exists()) { + if (!tmpdir.mkdir(subdir)) { + throw DirectoryCreationFailed(fi.filePath()); + } else { + return fi.filePath(); + } + } else if (fi.isDir()) { + return fi.filePath(); + } else { + throw DirectoryCreationFailed(fi.filePath()); + } +} + +void +TempDirectory::cleanupDirectory(QString tmpdir) +{ + bool isRoot = false; + + if (tmpdir == "") { + + m_mutex.lock(); + + isRoot = true; + tmpdir = m_tmpdir; + + if (tmpdir == "") { + m_mutex.unlock(); + return; + } + } + + QDir dir(tmpdir); + dir.setFilter(QDir::Dirs | QDir::Files); + + for (unsigned int i = 0; i < dir.count(); ++i) { + + if (dir[i] == "." || dir[i] == "..") continue; + QFileInfo fi(dir.filePath(dir[i])); + + if (fi.isDir()) { + cleanupDirectory(fi.absoluteFilePath()); + } else { + if (!QFile(fi.absoluteFilePath()).remove()) { + std::cerr << "WARNING: TempDirectory::cleanup: " + << "Failed to unlink file \"" + << fi.absoluteFilePath().toStdString() << "\"" + << std::endl; + } + } + } + + QString dirname = dir.dirName(); + if (dirname != "") { + if (!dir.cdUp()) { + std::cerr << "WARNING: TempDirectory::cleanup: " + << "Failed to cd to parent directory of " + << tmpdir.toStdString() << std::endl; + return; + } + if (!dir.rmdir(dirname)) { + std::cerr << "WARNING: TempDirectory::cleanup: " + << "Failed to remove directory " + << dirname.toStdString() << std::endl; + } + } + + if (isRoot) { + m_tmpdir = ""; + m_mutex.unlock(); + } +} + +void +TempDirectory::cleanupAbandonedDirectories(QString svDir) +{ + QDir dir(svDir, "sv_*", QDir::Name, QDir::Dirs); + + for (unsigned int i = 0; i < dir.count(); ++i) { + + QDir subdir(dir.filePath(dir[i]), "*.pid", QDir::Name, QDir::Files); + + for (unsigned int j = 0; j < subdir.count(); ++j) { + + bool ok = false; + int pid = QFileInfo(subdir[j]).baseName().toInt(&ok); + if (!ok) continue; + + if (GetProcessStatus(pid) == ProcessNotRunning) { + std::cerr << "INFO: Found abandoned temporary directory from " + << "an old Sonic Visualiser process\n(pid=" << pid + << ", directory=\"" + << dir.filePath(dir[i]).toStdString() + << "\"). Removing it..." << std::endl; + cleanupDirectory(dir.filePath(dir[i])); + std::cerr << "...done." << std::endl; + break; + } + } + } +} + + + diff -r 000000000000 -r fc9323a41f5a base/TempDirectory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/TempDirectory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TEMP_DIRECTORY_H_ +#define _TEMP_DIRECTORY_H_ + +#include +#include + +#include + +/** + * A class that manages the creation and removal of a temporary + * directory tree to store data during the program run. There is one + * root temporary directory for the program, created on demand and + * deleted when the program exits. + * + * This class is thread safe. + */ + +class TempDirectory +{ +public: + static TempDirectory *getInstance(); + + virtual ~TempDirectory(); + + /** + * Create the root temporary directory if necessary, and return + * its path. Throw DirectoryCreationFailed if the directory + * cannot be created. + */ + QString getPath(); + + /** + * Create an immediate subdirectory of the root temporary + * directory of the given name, if it doesn't already exist, and + * return its path. Throw DirectoryCreationFailed if the + * directory cannot be created. + */ + QString getSubDirectoryPath(QString subdir); + + /** + * Delete the temporary directory (before exiting). + */ + void cleanup(); + +protected: + TempDirectory(); + + QString createTempDirectoryIn(QString inDir); + void cleanupDirectory(QString tmpDir); + void cleanupAbandonedDirectories(QString svDir); + + QString m_tmpdir; + QMutex m_mutex; + + static TempDirectory *m_instance; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a base/Thread.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Thread.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Thread.h" + +#ifndef _WIN32 +#include +#endif + +//#define DEBUG_MUTEX_LOCKER 1 + +#include + +Thread::Thread(Type type, QObject *parent) : + QThread(parent), + m_type(type) +{ + setStackSize(512 * 1024); +} + +void +Thread::start() +{ + QThread::start(); + +#ifndef _WIN32 + struct sched_param param; + ::memset(¶m, 0, sizeof(param)); + + if (m_type == RTThread) { + + param.sched_priority = 5; + + if (::pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m)) { + ::perror("INFO: pthread_setschedparam to SCHED_FIFO failed"); + } + + } else { + + if (::pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m)) { + ::perror("WARNING: pthread_setschedparam to SCHED_OTHER failed"); + } + } + +#endif +} + +MutexLocker::MutexLocker(QMutex *mutex, const char *name) : + m_printer(name), + m_locker(mutex) +{ +#ifdef DEBUG_MUTEX_LOCKER + std::cerr << "... locked mutex " << mutex << std::endl; +#endif +} + +MutexLocker::~MutexLocker() +{ +} + +MutexLocker::Printer::Printer(const char *name) : + m_name(name) +{ +#ifdef DEBUG_MUTEX_LOCKER + std::cerr << "MutexLocker: Locking \"" << m_name << "\" in " + << (void *)QThread::currentThreadId() << std::endl; +#endif +} + +MutexLocker::Printer::~Printer() +{ +#ifdef DEBUG_MUTEX_LOCKER + std::cerr << "MutexLocker: Unlocking \"" << m_name + << "\" in " << (void *)QThread::currentThreadId() << std::endl; +#endif +} + diff -r 000000000000 -r fc9323a41f5a base/Thread.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Thread.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _THREAD_H_ +#define _THREAD_H_ + +#include +#include + +class Thread : public QThread +{ +Q_OBJECT + +public: + enum Type { RTThread, NonRTThread }; + + Thread(Type type = NonRTThread, QObject *parent = 0); + +public slots: + void start(); + +private: + Type m_type; +}; + + +class MutexLocker +{ +public: + MutexLocker(QMutex *mutex, const char *name); + ~MutexLocker(); + +private: + class Printer { + public: + Printer(const char *name); + ~Printer(); + + private: + const char *m_name; + }; + + Printer m_printer; + QMutexLocker m_locker; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/UnitDatabase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/UnitDatabase.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "UnitDatabase.h" + +UnitDatabase +UnitDatabase::m_instance; + +UnitDatabase * +UnitDatabase::getInstance() +{ + return &m_instance; +} + +UnitDatabase::UnitDatabase() : + m_nextId(0) +{ +} + +QStringList +UnitDatabase::getKnownUnits() const +{ + QStringList list; + for (UnitMap::const_iterator i = m_units.begin(); i != m_units.end(); ++i) { + list.push_back(i->first); + } + return list; +} + +void +UnitDatabase::registerUnit(QString unit) +{ + if (m_units.find(unit) == m_units.end()) { + m_units[unit] = m_nextId++; + } + emit unitDatabaseChanged(); +} + +int +UnitDatabase::getUnitId(QString unit, bool registerNew) +{ + if (m_units.find(unit) == m_units.end()) { + if (registerNew) registerUnit(unit); + else return -1; + } + return m_units[unit]; +} + +QString +UnitDatabase::getUnitById(int id) +{ + for (UnitMap::const_iterator i = m_units.begin(); i != m_units.end(); ++i) { + if (i->second == id) return i->first; + } + return ""; +} + diff -r 000000000000 -r fc9323a41f5a base/UnitDatabase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/UnitDatabase.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _UNIT_DATABASE_H_ +#define _UNIT_DATABASE_H_ + +#include +#include +#include +#include + +// This grandly named class is just a list of the names of known scale +// units for the various models, for use as the set of fixed values in +// unit dropdown menus etc. Of course, the user should be allowed to +// enter their own as well. + +class UnitDatabase : public QObject +{ + Q_OBJECT + +public: + static UnitDatabase *getInstance(); + + QStringList getKnownUnits() const; + void registerUnit(QString unit); + + /** + * Return the reference id for a given unit name. If registerNew is + * true and the unit is not known, register it and return its new + * id. If register is false and the unit is not known, return -1. + */ + int getUnitId(QString unit, bool registerNew = true); + + QString getUnitById(int id); + +signals: + void unitDatabaseChanged(); + +protected: + UnitDatabase(); + + typedef std::map UnitMap; + UnitMap m_units; + int m_nextId; + + static UnitDatabase m_instance; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a base/Window.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Window.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,162 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WINDOW_H_ +#define _WINDOW_H_ + +#include +#include +#include + +enum WindowType { + RectangularWindow, + BartlettWindow, + HammingWindow, + HanningWindow, + BlackmanWindow, + GaussianWindow, + ParzenWindow, + NuttallWindow, + BlackmanHarrisWindow +}; + +template +class Window +{ +public: + /** + * Construct a windower of the given type. + */ + Window(WindowType type, size_t size) : m_type(type), m_size(size) { encache(); } + Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); } + Window &operator=(const Window &w) { + if (&w == this) return *this; + m_type = w.m_type; + m_size = w.m_size; + encache(); + return *this; + } + virtual ~Window() { delete[] m_cache; } + + void cut(T *src) const { cut(src, src); } + void cut(T *src, T *dst) const { + for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + } + + T getArea() { return m_area; } + T getValue(size_t i) { return m_cache[i]; } + + WindowType getType() const { return m_type; } + size_t getSize() const { return m_size; } + +protected: + WindowType m_type; + size_t m_size; + T *m_cache; + T m_area; + + void encache(); + void cosinewin(T *, T, T, T, T); +}; + +template +void Window::encache() +{ + int n = int(m_size); + T *mult = new T[n]; + int i; + for (i = 0; i < n; ++i) mult[i] = 1.0; + + switch (m_type) { + + case RectangularWindow: + for (i = 0; i < n; ++i) { + mult[i] *= 0.5; + } + break; + + case BartlettWindow: + for (i = 0; i < n/2; ++i) { + mult[i] *= (i / T(n/2)); + mult[i + n/2] *= (1.0 - (i / T(n/2))); + } + break; + + case HammingWindow: + cosinewin(mult, 0.54, 0.46, 0.0, 0.0); + break; + + case HanningWindow: + cosinewin(mult, 0.50, 0.50, 0.0, 0.0); + break; + + case BlackmanWindow: + cosinewin(mult, 0.42, 0.50, 0.08, 0.0); + break; + + case GaussianWindow: + for (i = 0; i < n; ++i) { + mult[i] *= pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2)); + } + break; + + case ParzenWindow: + { + int N = n-1; + for (i = 0; i < N/4; ++i) { + T m = 2 * pow(1.0 - (T(N)/2 - i) / (T(N)/2), 3); + mult[i] *= m; + mult[N-i] *= m; + } + for (i = N/4; i <= N/2; ++i) { + int wn = i - N/2; + T m = 1.0 - 6 * pow(wn / (T(N)/2), 2) * (1.0 - abs(wn) / (T(N)/2)); + mult[i] *= m; + mult[N-i] *= m; + } + break; + } + + case NuttallWindow: + cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411); + break; + + case BlackmanHarrisWindow: + cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168); + break; + } + + m_cache = mult; + + m_area = 0; + for (int i = 0; i < n; ++i) { + m_area += m_cache[i]; + } + m_area /= n; +} + +template +void Window::cosinewin(T *mult, T a0, T a1, T a2, T a3) +{ + int n = int(m_size); + for (int i = 0; i < n; ++i) { + mult[i] *= (a0 + - a1 * cos(2 * M_PI * i / n) + + a2 * cos(4 * M_PI * i / n) + - a3 * cos(6 * M_PI * i / n)); + } +} + +#endif diff -r 000000000000 -r fc9323a41f5a base/XmlExportable.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/XmlExportable.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "XmlExportable.h" +#include +#include +#include +#include + +void +XmlExportable::toXml(QTextStream &stream, QString indent, + QString extraAttributes) const +{ + stream << toXmlString(indent, extraAttributes); +} + +QString +XmlExportable::encodeEntities(QString s) +{ + s + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + + return s; +} + +QString +XmlExportable::encodeColour(QColor c) +{ + QString r, g, b; + + r.setNum(c.red(), 16); + if (c.red() < 16) r = "0" + r; + + g.setNum(c.green(), 16); + if (c.green() < 16) g = "0" + g; + + b.setNum(c.blue(), 16); + if (c.blue() < 16) b = "0" + b; + + return "#" + r + g + b; +} + +int +XmlExportable::getObjectExportId(const void * object) +{ + static QMutex mutex; + QMutexLocker locker(&mutex); + + static std::map idMap; + static int maxId = 0; + + if (idMap.find(object) == idMap.end()) { + idMap[object] = maxId++; + } + + return idMap[object]; +} + + diff -r 000000000000 -r fc9323a41f5a base/XmlExportable.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/XmlExportable.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _XML_EXPORTABLE_H_ +#define _XML_EXPORTABLE_H_ + +#include +#include + +class QTextStream; + +class XmlExportable +{ +public: + virtual ~XmlExportable() { } + + /** + * Stream this exportable object out to XML on a text stream. + * + * The default implementation calls toXmlString and streams the + * resulting string. This is only appropriate for objects with + * short representations. Bigger objects should override this + * method so as to write to the stream directly and override + * toXmlString with a method that calls this one, so that the + * direct streaming method can be used when appropriate. + */ + virtual void toXml(QTextStream &stream, + QString indent = "", + QString extraAttributes = "") const; + + /** + * Convert this exportable object to XML in a string. + */ + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const = 0; + + static QString encodeEntities(QString); + + static QString encodeColour(QColor); + + static int getObjectExportId(const void *); // thread-safe +}; + +#endif diff -r 000000000000 -r fc9323a41f5a base/ZoomConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ZoomConstraint.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _ZOOM_CONSTRAINT_H_ +#define _ZOOM_CONSTRAINT_H_ + +#include + +/** + * ZoomConstraint is a simple interface that describes a limitation on + * the available zoom sizes for a view, for example based on cache + * strategy or a (processing) window-size limitation. + * + * The default ZoomConstraint imposes no actual constraint except for + * a nominal maximum. + */ + +class ZoomConstraint +{ +public: + virtual ~ZoomConstraint() { } + + enum RoundingDirection { + RoundDown, + RoundUp, + RoundNearest + }; + + /** + * Given the "ideal" block size (frames per pixel) for a given + * zoom level, return the nearest viable block size for this + * constraint. + * + * For example, if a block size of 1523 frames per pixel is + * requested but the underlying model only supports value + * summaries at powers-of-two block sizes, return 1024 or 2048 + * depending on the rounding direction supplied. + */ + virtual size_t getNearestBlockSize(size_t requestedBlockSize, + RoundingDirection = RoundNearest) + const + { + if (requestedBlockSize > getMaxZoomLevel()) return getMaxZoomLevel(); + else return requestedBlockSize; + } + + /** + * Return the maximum zoom level within range for this constraint. + */ + virtual size_t getMaxZoomLevel() const { return 262144; } +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a base/base.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/base.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,63 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions + +TARGET = svbase + +DEPENDPATH += . +INCLUDEPATH += . .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += AudioLevel.h \ + AudioPlaySource.h \ + Clipboard.h \ + Command.h \ + CommandHistory.h \ + Exceptions.h \ + LogRange.h \ + Pitch.h \ + PlayParameterRepository.h \ + PlayParameters.h \ + Preferences.h \ + Profiler.h \ + PropertyContainer.h \ + RangeMapper.h \ + RealTime.h \ + RecentFiles.h \ + ResizeableBitset.h \ + RingBuffer.h \ + Scavenger.h \ + Selection.h \ + StorageAdviser.h \ + TempDirectory.h \ + Thread.h \ + UnitDatabase.h \ + Window.h \ + XmlExportable.h \ + ZoomConstraint.h +SOURCES += AudioLevel.cpp \ + Clipboard.cpp \ + Command.cpp \ + CommandHistory.cpp \ + Exceptions.cpp \ + LogRange.cpp \ + Pitch.cpp \ + PlayParameterRepository.cpp \ + PlayParameters.cpp \ + Preferences.cpp \ + Profiler.cpp \ + PropertyContainer.cpp \ + RangeMapper.cpp \ + RealTime.cpp \ + RecentFiles.cpp \ + Selection.cpp \ + StorageAdviser.cpp \ + TempDirectory.cpp \ + Thread.cpp \ + UnitDatabase.cpp \ + XmlExportable.cpp diff -r 000000000000 -r fc9323a41f5a base/svbase.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/svbase.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,595 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a data/data.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/data.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,83 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = fftw3f sndfile mad oggz fishsound +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += network + +TARGET = svdata + +DEPENDPATH += fft fileio model .. +INCLUDEPATH += . fft model fileio .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += fft/FFTapi.h \ + fft/FFTCache.h \ + fft/FFTDataServer.h \ + fft/FFTFileCache.h \ + fft/FFTMemoryCache.h \ + fileio/AudioFileReader.h \ + fileio/AudioFileReaderFactory.h \ + fileio/BZipFileDevice.h \ + fileio/CodedAudioFileReader.h \ + fileio/CSVFileReader.h \ + fileio/CSVFileWriter.h \ + fileio/DataFileReader.h \ + fileio/DataFileReaderFactory.h \ + fileio/FileFinder.h \ + fileio/FileReadThread.h \ + fileio/MatrixFile.h \ + fileio/MIDIFileReader.h \ + fileio/MP3FileReader.h \ + fileio/OggVorbisFileReader.h \ + fileio/RemoteFile.h \ + fileio/WavFileReader.h \ + fileio/WavFileWriter.h \ + model/DenseThreeDimensionalModel.h \ + model/DenseTimeValueModel.h \ + model/EditableDenseThreeDimensionalModel.h \ + model/FFTModel.h \ + model/Model.h \ + model/NoteModel.h \ + model/PowerOfSqrtTwoZoomConstraint.h \ + model/PowerOfTwoZoomConstraint.h \ + model/RangeSummarisableTimeValueModel.h \ + model/SparseModel.h \ + model/SparseOneDimensionalModel.h \ + model/SparseTimeValueModel.h \ + model/SparseValueModel.h \ + model/TextModel.h \ + model/WaveFileModel.h \ + model/WritableWaveFileModel.h +SOURCES += fft/FFTapi.cpp \ + fft/FFTDataServer.cpp \ + fft/FFTFileCache.cpp \ + fft/FFTMemoryCache.cpp \ + fileio/AudioFileReader.cpp \ + fileio/AudioFileReaderFactory.cpp \ + fileio/BZipFileDevice.cpp \ + fileio/CodedAudioFileReader.cpp \ + fileio/CSVFileReader.cpp \ + fileio/CSVFileWriter.cpp \ + fileio/DataFileReaderFactory.cpp \ + fileio/FileFinder.cpp \ + fileio/FileReadThread.cpp \ + fileio/MatrixFile.cpp \ + fileio/MIDIFileReader.cpp \ + fileio/MP3FileReader.cpp \ + fileio/OggVorbisFileReader.cpp \ + fileio/RemoteFile.cpp \ + fileio/WavFileReader.cpp \ + fileio/WavFileWriter.cpp \ + model/DenseTimeValueModel.cpp \ + model/EditableDenseThreeDimensionalModel.cpp \ + model/FFTModel.cpp \ + model/Model.cpp \ + model/NoteModel.cpp \ + model/PowerOfSqrtTwoZoomConstraint.cpp \ + model/PowerOfTwoZoomConstraint.cpp \ + model/WaveFileModel.cpp \ + model/WritableWaveFileModel.cpp diff -r 000000000000 -r fc9323a41f5a data/fft/FFTCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTCache.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_CACHE_H_ +#define _FFT_CACHE_H_ + +#include +#include + +//#include +#include "system/System.h" + +class FFTCache +{ +public: + virtual ~FFTCache() { } + + virtual size_t getWidth() const = 0; + virtual size_t getHeight() const = 0; + + virtual void resize(size_t width, size_t height) = 0; + virtual void reset() = 0; // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const = 0; + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const = 0; + virtual float getMaximumMagnitudeAt(size_t x) const = 0; + virtual float getPhaseAt(size_t x, size_t y) const = 0; + + virtual void getValuesAt(size_t x, size_t y, float &real, float &imaginary) const = 0; + + virtual bool haveSetColumnAt(size_t x) const = 0; + + // may modify argument arrays + virtual void setColumnAt(size_t x, float *mags, float *phases, float factor) = 0; + + // may modify argument arrays + virtual void setColumnAt(size_t x, float *reals, float *imags) = 0; + + virtual void suspend() { } + +protected: + FFTCache() { } +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a data/fft/FFTDataServer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTDataServer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1218 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTDataServer.h" + +#include "FFTFileCache.h" +#include "FFTMemoryCache.h" + +#include "model/DenseTimeValueModel.h" + +#include "system/System.h" + +#include "base/StorageAdviser.h" +#include "base/Exceptions.h" +#include "base/Profiler.h" +#include "base/Thread.h" // for debug mutex locker + +#include +#include + +//#define DEBUG_FFT_SERVER 1 +//#define DEBUG_FFT_SERVER_FILL 1 + +#ifdef DEBUG_FFT_SERVER_FILL +#ifndef DEBUG_FFT_SERVER +#define DEBUG_FFT_SERVER 1 +#endif +#endif + + +FFTDataServer::ServerMap FFTDataServer::m_servers; +FFTDataServer::ServerQueue FFTDataServer::m_releasedServers; +QMutex FFTDataServer::m_serverMapMutex; + +FFTDataServer * +FFTDataServer::getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + QString n = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar); + + FFTDataServer *server = 0; + + MutexLocker locker(&m_serverMapMutex, "FFTDataServer::m_serverMapMutex[getInstance]"); + + if ((server = findServer(n))) { + return server; + } + + QString npn = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + !polar); + + if ((server = findServer(npn))) { + return server; + } + + try { + server = new FFTDataServer(n, + model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + } catch (InsufficientDiscSpace) { + delete server; + server = 0; + } + + if (server) { + m_servers[n] = ServerCountPair(server, 1); + } + + return server; +} + +FFTDataServer * +FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + // Fuzzy matching: + // + // -- if we're asked for polar and have non-polar, use it (and + // vice versa). This one is vital, and we do it for non-fuzzy as + // well (above). + // + // -- if we're asked for an instance with a given fft size and we + // have one already with a multiple of that fft size but the same + // window size and type (and model), we can draw the results from + // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the + // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the + // same window plus zero padding). + // + // -- if we're asked for an instance with a given window type and + // size and fft size and we have one already the same but with a + // smaller increment, we can draw the results from it (provided + // our increment is a multiple of its) + // + // The FFTModel knows how to interpret these things. In + // both cases we require that the larger one is a power-of-two + // multiple of the smaller (e.g. even though in principle you can + // draw the results at increment 256 from those at increment 768 + // or 1536, the model doesn't support this). + + { + MutexLocker locker(&m_serverMapMutex, "FFTDataServer::m_serverMapMutex[getFuzzyInstance]"); + + ServerMap::iterator best = m_servers.end(); + int bestdist = -1; + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + + FFTDataServer *server = i->second.first; + + if (server->getModel() == model && + (server->getChannel() == channel || model->getChannelCount() == 1) && + server->getWindowType() == windowType && + server->getWindowSize() == windowSize && + server->getWindowIncrement() <= windowIncrement && + server->getFFTSize() >= fftSize) { + + if ((windowIncrement % server->getWindowIncrement()) != 0) continue; + int ratio = windowIncrement / server->getWindowIncrement(); + bool poweroftwo = true; + while (ratio > 1) { + if (ratio & 0x1) { + poweroftwo = false; + break; + } + ratio >>= 1; + } + if (!poweroftwo) continue; + + if ((server->getFFTSize() % fftSize) != 0) continue; + ratio = server->getFFTSize() / fftSize; + while (ratio > 1) { + if (ratio & 0x1) { + poweroftwo = false; + break; + } + ratio >>= 1; + } + if (!poweroftwo) continue; + + int distance = 0; + + if (server->getPolar() != polar) distance += 1; + + distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15; + distance += ((server->getFFTSize() / fftSize) - 1) * 10; + + if (server->getFillCompletion() < 50) distance += 100; + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl; +#endif + + if (bestdist == -1 || distance < bestdist) { + bestdist = distance; + best = i; + } + } + } + + if (bestdist >= 0) { + FFTDataServer *server = best->second.first; +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::endl; +#endif + claimInstance(server, false); + return server; + } + } + + // Nothing found, make a new one + + return getInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); +} + +FFTDataServer * +FFTDataServer::findServer(QString n) +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\")" << std::endl; +#endif + + if (m_servers.find(n) != m_servers.end()) { + + FFTDataServer *server = m_servers[n].first; + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): found " << server << std::endl; +#endif + + claimInstance(server, false); + + return server; + } + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): not found" << std::endl; +#endif + + return 0; +} + +void +FFTDataServer::claimInstance(FFTDataServer *server) +{ + claimInstance(server, true); +} + +void +FFTDataServer::claimInstance(FFTDataServer *server, bool needLock) +{ + MutexLocker locker(needLock ? &m_serverMapMutex : 0, + "FFTDataServer::m_serverMapMutex[claimInstance]"); + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::endl; +#endif + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + if (i->second.first == server) { + + for (ServerQueue::iterator j = m_releasedServers.begin(); + j != m_releasedServers.end(); ++j) { + + if (*j == server) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl; +#endif + m_releasedServers.erase(j); + break; + } + } + + ++i->second.second; + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::endl; +#endif + + return; + } + } + + std::cerr << "ERROR: FFTDataServer::claimInstance: instance " + << server << " unknown!" << std::endl; +} + +void +FFTDataServer::releaseInstance(FFTDataServer *server) +{ + releaseInstance(server, true); +} + +void +FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock) +{ + MutexLocker locker(needLock ? &m_serverMapMutex : 0, + "FFTDataServer::m_serverMapMutex[releaseInstance]"); + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; +#endif + + // -- if ref count > 0, decrement and return + // -- if the instance hasn't been used at all, delete it immediately + // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts, + // leave them hanging around + // -- if N instances with zero refcounts remain, delete the one that + // was last released first + // -- if we run out of disk space when allocating an instance, go back + // and delete the spare N instances before trying again + // -- have an additional method to indicate that a model has been + // destroyed, so that we can delete all of its fft server instances + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + if (i->second.first == server) { + if (i->second.second == 0) { + std::cerr << "ERROR: FFTDataServer::releaseInstance(" + << server << "): instance not allocated" << std::endl; + } else if (--i->second.second == 0) { + if (server->m_lastUsedCache == -1) { // never used +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance: instance " + << server << " has never been used, erasing" + << std::endl; +#endif + delete server; + m_servers.erase(i); + } else { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance: instance " + << server << " no longer in use, marking for possible collection" + << std::endl; +#endif + bool found = false; + for (ServerQueue::iterator j = m_releasedServers.begin(); + j != m_releasedServers.end(); ++j) { + if (*j == server) { + std::cerr << "ERROR: FFTDataServer::releaseInstance(" + << server << "): server is already in " + << "released servers list" << std::endl; + found = true; + } + } + if (!found) m_releasedServers.push_back(server); + server->suspend(); + purgeLimbo(); + } + } else { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance: instance " + << server << " now has refcount " << i->second.second + << std::endl; +#endif + } + return; + } + } + + std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " + << "instance not found" << std::endl; +} + +void +FFTDataServer::purgeLimbo(int maxSize) +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " + << m_releasedServers.size() << " candidates" << std::endl; +#endif + + while (int(m_releasedServers.size()) > maxSize) { + + FFTDataServer *server = *m_releasedServers.begin(); + + bool found = false; + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::purgeLimbo: considering candidate " + << server << std::endl; +#endif + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + + if (i->second.first == server) { + found = true; + if (i->second.second > 0) { + std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server " + << server << " is in released queue, but still has non-zero refcount " + << i->second.second << std::endl; + // ... so don't delete it + break; + } +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it" + << std::endl; +#endif + + m_servers.erase(i); + delete server; + break; + } + } + + if (!found) { + std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server " + << server << " is in released queue, but not in server map!" + << std::endl; + delete server; + } + + m_releasedServers.pop_front(); + } + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " + << m_releasedServers.size() << " remain" << std::endl; +#endif + +} + +void +FFTDataServer::modelAboutToBeDeleted(Model *model) +{ + MutexLocker locker(&m_serverMapMutex, + "FFTDataServer::m_serverMapMutex[modelAboutToBeDeleted]"); + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")" + << std::endl; +#endif + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + + FFTDataServer *server = i->second.first; + + if (server->getModel() == model) { + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is " + << server << std::endl; +#endif + + if (i->second.second > 0) { + std::cerr << "ERROR: FFTDataServer::modelAboutToBeDeleted: Model " << model << " (\"" << model->objectName().toStdString() << "\") is about to be deleted, but is still being referred to by FFT server " << server << " with non-zero refcount " << i->second.second << std::endl; + } + for (ServerQueue::iterator j = m_releasedServers.begin(); + j != m_releasedServers.end(); ++j) { + if (*j == server) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl; +#endif + m_releasedServers.erase(j); + break; + } + } +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl; +#endif + m_servers.erase(i); + delete server; + return; + } + } +} + +FFTDataServer::FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + m_fileBaseName(fileBaseName), + m_model(model), + m_channel(channel), + m_windower(windowType, windowSize), + m_windowSize(windowSize), + m_windowIncrement(windowIncrement), + m_fftSize(fftSize), + m_polar(polar), + m_width(0), + m_height(0), + m_cacheWidth(0), + m_memoryCache(false), + m_compactCache(false), + m_lastUsedCache(-1), + m_fftInput(0), + m_exiting(false), + m_suspended(true), //!!! or false? + m_fillThread(0) +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << std::endl; +#endif + + size_t start = m_model->getStartFrame(); + size_t end = m_model->getEndFrame(); + + m_width = (end - start) / m_windowIncrement + 1; + m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2 + +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): dimensions are " + << m_width << "x" << m_height << std::endl; +#endif + + size_t maxCacheSize = 20 * 1024 * 1024; + size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample); + if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width; + else m_cacheWidth = maxCacheSize / columnSize; + + int bits = 0; + while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; } + m_cacheWidth = 2; + while (bits) { m_cacheWidth <<= 1; --bits; } + + //!!! Need to pass in what this server is intended for + // (e.g. playback processing, spectrogram, feature extraction), + // or pass in something akin to the storage adviser criteria. + // That probably goes alongside the polar argument. + // For now we'll assume "spectrogram" criteria for polar ffts, + // and "feature extraction" criteria for rectangular ones. + + StorageAdviser::Criteria criteria; + if (m_polar) { + criteria = StorageAdviser::Criteria + (StorageAdviser::SpeedCritical | StorageAdviser::LongRetentionLikely); + } else { + criteria = StorageAdviser::Criteria(StorageAdviser::PrecisionCritical); + } + + int cells = m_width * m_height; + int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb + int maximumSize = (cells / 1024) * sizeof(float); // kb + + StorageAdviser::Recommendation recommendation; + + try { + + recommendation = + StorageAdviser::recommend(criteria, minimumSize, maximumSize); + + } catch (InsufficientDiscSpace s) { + + // Delete any unused servers we may have been leaving around + // in case we wanted them again + + purgeLimbo(0); + + // This time we don't catch InsufficientDiscSpace -- we + // haven't allocated anything yet and can safely let the + // exception out to indicate to the caller that we can't + // handle it. + + recommendation = + StorageAdviser::recommend(criteria, minimumSize, maximumSize); + } + +// std::cerr << "Recommendation was: " << recommendation << std::endl; + + m_memoryCache = ((recommendation & StorageAdviser::UseMemory) || + (recommendation & StorageAdviser::PreferMemory)); + + m_compactCache = (recommendation & StorageAdviser::ConserveSpace); + +#ifdef DEBUG_FFT_SERVER + std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl; +#endif + + StorageAdviser::notifyPlannedAllocation + (m_memoryCache ? StorageAdviser::MemoryAllocation : + StorageAdviser::DiscAllocation, + m_compactCache ? minimumSize : maximumSize); + + for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) { + m_caches.push_back(0); + } + + m_fftInput = (fftsample *) + fftf_malloc(fftSize * sizeof(fftsample)); + + m_fftOutput = (fftf_complex *) + fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex)); + + m_workbuffer = (float *) + fftf_malloc((fftSize+2) * sizeof(float)); + + m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize, + m_fftInput, + m_fftOutput, + FFTW_ESTIMATE); + + if (!m_fftPlan) { + std::cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl; + throw(0); + } + + m_fillThread = new FillThread(*this, fillFromColumn); +} + +FFTDataServer::~FFTDataServer() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << std::endl; +#endif + + m_suspended = false; + m_exiting = true; + m_condition.wakeAll(); + if (m_fillThread) { + m_fillThread->wait(); + delete m_fillThread; + } + + MutexLocker locker(&m_writeMutex, + "FFTDataServer::m_writeMutex[~FFTDataServer]"); + + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + if (*i) { + delete *i; + } else { + StorageAdviser::notifyDoneAllocation + (m_memoryCache ? StorageAdviser::MemoryAllocation : + StorageAdviser::DiscAllocation, + m_cacheWidth * m_height * + (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1); + } + } + + deleteProcessingData(); +} + +void +FFTDataServer::deleteProcessingData() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << std::endl; +#endif + if (m_fftInput) { + fftf_destroy_plan(m_fftPlan); + fftf_free(m_fftInput); + fftf_free(m_fftOutput); + fftf_free(m_workbuffer); + } + m_fftInput = 0; +} + +void +FFTDataServer::suspend() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << std::endl; +#endif + Profiler profiler("FFTDataServer::suspend", false); + + MutexLocker locker(&m_writeMutex, + "FFTDataServer::m_writeMutex[suspend]"); + m_suspended = true; + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + if (*i) (*i)->suspend(); + } +} + +void +FFTDataServer::suspendWrites() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << std::endl; +#endif + Profiler profiler("FFTDataServer::suspendWrites", false); + + m_suspended = true; +} + +void +FFTDataServer::resume() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << std::endl; +#endif + Profiler profiler("FFTDataServer::resume", false); + + m_suspended = false; + if (m_fillThread) { + if (m_fillThread->isFinished()) { + delete m_fillThread; + m_fillThread = 0; + deleteProcessingData(); + } else { + m_condition.wakeAll(); + } + } +} + +FFTCache * +FFTDataServer::getCacheAux(size_t c) +{ + Profiler profiler("FFTDataServer::getCacheAux", false); +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::getCacheAux" << std::endl; +#endif + + MutexLocker locker(&m_writeMutex, + "FFTDataServer::m_writeMutex[getCacheAux]"); + + if (m_lastUsedCache == -1) { + m_fillThread->start(); + } + + if (int(c) != m_lastUsedCache) { + +// std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl; + + for (IntQueue::iterator i = m_dormantCaches.begin(); + i != m_dormantCaches.end(); ++i) { + if (*i == int(c)) { + m_dormantCaches.erase(i); + break; + } + } + + if (m_lastUsedCache >= 0) { + bool inDormant = false; + for (size_t i = 0; i < m_dormantCaches.size(); ++i) { + if (m_dormantCaches[i] == m_lastUsedCache) { + inDormant = true; + break; + } + } + if (!inDormant) { + m_dormantCaches.push_back(m_lastUsedCache); + } + while (m_dormantCaches.size() > 4) { + int dc = m_dormantCaches.front(); + m_dormantCaches.pop_front(); + m_caches[dc]->suspend(); + } + } + } + + if (m_caches[c]) { + m_lastUsedCache = c; + return m_caches[c]; + } + + QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); + + FFTCache *cache = 0; + + size_t width = m_cacheWidth; + if (c * m_cacheWidth + width > m_width) { + width = m_width - c * m_cacheWidth; + } + + try { + + if (m_memoryCache) { + + cache = new FFTMemoryCache(); + + } else if (m_compactCache) { + + cache = new FFTFileCache(name, MatrixFile::ReadWrite, + FFTFileCache::Compact); + + } else { + + cache = new FFTFileCache(name, MatrixFile::ReadWrite, + m_polar ? FFTFileCache::Polar : + FFTFileCache::Rectangular); + } + + cache->resize(width, m_height); + cache->reset(); + + } catch (std::bad_alloc) { + + delete cache; + cache = 0; + + if (m_memoryCache) { + + std::cerr << "WARNING: Memory allocation failed when resizing" + << " FFT memory cache no. " << c << " to " << width + << "x" << m_height << " (of total width " << m_width + << "): falling back to disc cache" << std::endl; + + try { + + cache = new FFTFileCache(name, MatrixFile::ReadWrite, + FFTFileCache::Compact); + + cache->resize(width, m_height); + cache->reset(); + + } catch (std::bad_alloc) { + + delete cache; + cache = 0; + } + } + + if (cache) { + std::cerr << "ERROR: Memory allocation failed when resizing" + << " FFT file cache no. " << c << " to " << width + << "x" << m_height << " (of total width " << m_width + << "): abandoning this cache" << std::endl; + } + + //!!! Shouldn't be using QtGui here. Need a better way to report this. + QMessageBox::critical + (0, QApplication::tr("FFT cache resize failed"), + QApplication::tr + ("Failed to create or resize an FFT model slice.\n" + "There may be insufficient memory or disc space to continue.")); + } + + StorageAdviser::notifyDoneAllocation + (m_memoryCache ? StorageAdviser::MemoryAllocation : + StorageAdviser::DiscAllocation, + width * m_height * + (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1); + + m_caches[c] = cache; + m_lastUsedCache = c; + return cache; +} + +float +FFTDataServer::getMagnitudeAt(size_t x, size_t y) +{ + Profiler profiler("FFTDataServer::getMagnitudeAt", false); + + if (x >= m_width || y >= m_height) return 0; + + size_t col; + FFTCache *cache = getCache(x, col); + if (!cache) return 0; + + if (!cache->haveSetColumnAt(col)) { + std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn(" + << x << ")" << std::endl; + fillColumn(x); + } + return cache->getMagnitudeAt(col, y); +} + +float +FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y) +{ + Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false); + + if (x >= m_width || y >= m_height) return 0; + + size_t col; + FFTCache *cache = getCache(x, col); + if (!cache) return 0; + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getNormalizedMagnitudeAt(col, y); +} + +float +FFTDataServer::getMaximumMagnitudeAt(size_t x) +{ + Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false); + + if (x >= m_width) return 0; + + size_t col; + FFTCache *cache = getCache(x, col); + if (!cache) return 0; + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getMaximumMagnitudeAt(col); +} + +float +FFTDataServer::getPhaseAt(size_t x, size_t y) +{ + Profiler profiler("FFTDataServer::getPhaseAt", false); + + if (x >= m_width || y >= m_height) return 0; + + size_t col; + FFTCache *cache = getCache(x, col); + if (!cache) return 0; + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getPhaseAt(col, y); +} + +void +FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary) +{ + Profiler profiler("FFTDataServer::getValuesAt", false); + + if (x >= m_width || y >= m_height) { + real = 0; + imaginary = 0; + return; + } + + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache) { + real = 0; + imaginary = 0; + return; + } + + if (!cache->haveSetColumnAt(col)) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; +#endif + fillColumn(x); + } + float magnitude = cache->getMagnitudeAt(col, y); + float phase = cache->getPhaseAt(col, y); + real = magnitude * cosf(phase); + imaginary = magnitude * sinf(phase); +} + +bool +FFTDataServer::isColumnReady(size_t x) +{ + Profiler profiler("FFTDataServer::isColumnReady", false); + + if (x >= m_width) return true; + + if (!haveCache(x)) { + if (m_lastUsedCache == -1) { + if (m_suspended) { + std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl; + resume(); + } + m_fillThread->start(); + } + return false; + } + + size_t col; + FFTCache *cache = getCache(x, col); + if (!cache) return true; + + return cache->haveSetColumnAt(col); +} + +void +FFTDataServer::fillColumn(size_t x) +{ + Profiler profiler("FFTDataServer::fillColumn", false); + + if (!m_fftInput) { + std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " + << "input has already been completed and discarded?" + << std::endl; + return; + } + + if (x >= m_width) { + std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " + << "x > width (" << x << " > " << m_width << ")" + << std::endl; + return; + } + + size_t col; +#ifdef DEBUG_FFT_SERVER_FILL + std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl; +#endif + FFTCache *cache = getCache(x, col); + if (!cache) return; + + MutexLocker locker(&m_writeMutex, + "FFTDataServer::m_writeMutex[fillColumn]"); + + if (cache->haveSetColumnAt(col)) return; + + int startFrame = m_windowIncrement * x; + int endFrame = startFrame + m_windowSize; + + startFrame -= int(m_windowSize - m_windowIncrement) / 2; + endFrame -= int(m_windowSize - m_windowIncrement) / 2; + size_t pfx = 0; + + size_t off = (m_fftSize - m_windowSize) / 2; + + for (size_t i = 0; i < off; ++i) { + m_fftInput[i] = 0.0; + m_fftInput[m_fftSize - i - 1] = 0.0; + } + + if (startFrame < 0) { + pfx = size_t(-startFrame); + for (size_t i = 0; i < pfx; ++i) { + m_fftInput[off + i] = 0.0; + } + } + +#ifdef DEBUG_FFT_SERVER_FILL + std::cerr << "FFTDataServer::fillColumn: requesting frames " + << startFrame + pfx << " -> " << endFrame << " ( = " + << endFrame - (startFrame + pfx) << ") at index " + << off + pfx << " in buffer of size " << m_fftSize + << " with window size " << m_windowSize + << " from channel " << m_channel << std::endl; +#endif + + size_t got = m_model->getValues(m_channel, startFrame + pfx, + endFrame, m_fftInput + off + pfx); + + while (got + pfx < m_windowSize) { + m_fftInput[off + got + pfx] = 0.0; + ++got; + } + + if (m_channel == -1) { + int channels = m_model->getChannelCount(); + if (channels > 1) { + for (size_t i = 0; i < m_windowSize; ++i) { + m_fftInput[off + i] /= channels; + } + } + } + + m_windower.cut(m_fftInput + off); + + for (size_t i = 0; i < m_fftSize/2; ++i) { + fftsample temp = m_fftInput[i]; + m_fftInput[i] = m_fftInput[i + m_fftSize/2]; + m_fftInput[i + m_fftSize/2] = temp; + } + + fftf_execute(m_fftPlan); + + fftsample factor = 0.0; + + for (size_t i = 0; i <= m_fftSize/2; ++i) { + + fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] + + m_fftOutput[i][1] * m_fftOutput[i][1]); + mag /= m_windowSize / 2; + + if (mag > factor) factor = mag; + + fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]); + phase = princargf(phase); + + m_workbuffer[i] = mag; + m_workbuffer[i + m_fftSize/2+1] = phase; + } + + cache->setColumnAt(col, + m_workbuffer, + m_workbuffer + m_fftSize/2+1, + factor); + + if (m_suspended) { +// std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl; +// resume(); + } +} + +size_t +FFTDataServer::getFillCompletion() const +{ + if (m_fillThread) return m_fillThread->getCompletion(); + else return 100; +} + +size_t +FFTDataServer::getFillExtent() const +{ + if (m_fillThread) return m_fillThread->getExtent(); + else return m_model->getEndFrame(); +} + +QString +FFTDataServer::generateFileBasename() const +{ + return generateFileBasename(m_model, m_channel, m_windower.getType(), + m_windowSize, m_windowIncrement, m_fftSize, + m_polar); +} + +QString +FFTDataServer::generateFileBasename(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar) +{ + char buffer[200]; + + sprintf(buffer, "%u-%u-%u-%u-%u-%u%s", + (unsigned int)XmlExportable::getObjectExportId(model), + (unsigned int)(channel + 1), + (unsigned int)windowType, + (unsigned int)windowSize, + (unsigned int)windowIncrement, + (unsigned int)fftSize, + polar ? "-p" : "-r"); + + return buffer; +} + +void +FFTDataServer::FillThread::run() +{ + m_extent = 0; + m_completion = 0; + + size_t start = m_server.m_model->getStartFrame(); + size_t end = m_server.m_model->getEndFrame(); + size_t remainingEnd = end; + + int counter = 0; + int updateAt = 1; + int maxUpdateAt = (end / m_server.m_windowIncrement) / 20; + if (maxUpdateAt < 100) maxUpdateAt = 100; + + if (m_fillFrom > start) { + + for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl; +#endif + { + MutexLocker locker(&m_server.m_writeMutex, + "FFTDataServer::m_writeMutex[run/1]"); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + } +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << std::endl; +#endif + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = size_t(100 * fabsf(float(f - m_fillFrom) / + float(end - start))); + counter = 0; + if (updateAt < maxUpdateAt) { + updateAt *= 2; + if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; + } + } + } + + remainingEnd = m_fillFrom; + if (remainingEnd > start) --remainingEnd; + else remainingEnd = start; + } + + size_t baseCompletion = m_completion; + + for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl; +#endif + { + MutexLocker locker(&m_server.m_writeMutex, + "FFTDataServer::m_writeMutex[run/2]"); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + } + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = baseCompletion + + size_t(100 * fabsf(float(f - start) / + float(end - start))); + counter = 0; + if (updateAt < maxUpdateAt) { + updateAt *= 2; + if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; + } + } + } + + m_completion = 100; + m_extent = end; +} + diff -r 000000000000 -r fc9323a41f5a data/fft/FFTDataServer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTDataServer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,215 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_DATA_SERVER_H_ +#define _FFT_DATA_SERVER_H_ + +#include "base/Window.h" +#include "base/Thread.h" + +#include "FFTapi.h" + +#include +#include +#include + +#include +#include + +class DenseTimeValueModel; +class Model; +class FFTCache; + +class FFTDataServer +{ +public: + static FFTDataServer *getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static void claimInstance(FFTDataServer *); + static void releaseInstance(FFTDataServer *); + + static void modelAboutToBeDeleted(Model *); + + const DenseTimeValueModel *getModel() const { return m_model; } + int getChannel() const { return m_channel; } + WindowType getWindowType() const { return m_windower.getType(); } + size_t getWindowSize() const { return m_windowSize; } + size_t getWindowIncrement() const { return m_windowIncrement; } + size_t getFFTSize() const { return m_fftSize; } + bool getPolar() const { return m_polar; } + + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + + float getMagnitudeAt(size_t x, size_t y); + float getNormalizedMagnitudeAt(size_t x, size_t y); + float getMaximumMagnitudeAt(size_t x); + float getPhaseAt(size_t x, size_t y); + void getValuesAt(size_t x, size_t y, float &real, float &imaginary); + bool isColumnReady(size_t x); + + void suspend(); + void suspendWrites(); + void resume(); // also happens automatically if new data needed + + // Convenience functions: + + bool isLocalPeak(size_t x, size_t y) { + float mag = getMagnitudeAt(x, y); + if (y > 0 && mag < getMagnitudeAt(x, y - 1)) return false; + if (y < getHeight()-1 && mag < getMagnitudeAt(x, y + 1)) return false; + return true; + } + bool isOverThreshold(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const; + size_t getFillExtent() const; + +private: + FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + virtual ~FFTDataServer(); + + FFTDataServer(const FFTDataServer &); // not implemented + FFTDataServer &operator=(const FFTDataServer &); // not implemented + + typedef float fftsample; + + QString m_fileBaseName; + const DenseTimeValueModel *m_model; + int m_channel; + + Window m_windower; + + size_t m_windowSize; + size_t m_windowIncrement; + size_t m_fftSize; + bool m_polar; + + size_t m_width; + size_t m_height; + size_t m_cacheWidth; + size_t m_cacheWidthPower; + size_t m_cacheWidthMask; + bool m_memoryCache; + bool m_compactCache; + + typedef std::vector CacheVector; + CacheVector m_caches; + + typedef std::deque IntQueue; + IntQueue m_dormantCaches; + + int m_lastUsedCache; + FFTCache *getCache(size_t x, size_t &col) { + col = x % m_cacheWidth; + int c = x / m_cacheWidth; + // The only use of m_lastUsedCache without a lock is to + // establish whether a cache has been created at all (they're + // created on demand, but not destroyed until the server is). + if (c == m_lastUsedCache) return m_caches[c]; + else return getCacheAux(c); + } + bool haveCache(size_t x) { + int c = x / m_cacheWidth; + if (c == m_lastUsedCache) return true; + else return (m_caches[c] != 0); + } + + FFTCache *getCacheAux(size_t c); + QMutex m_writeMutex; + QWaitCondition m_condition; + + fftsample *m_fftInput; + fftf_complex *m_fftOutput; + float *m_workbuffer; + fftf_plan m_fftPlan; + + class FillThread : public Thread + { + public: + FillThread(FFTDataServer &server, size_t fillFromColumn) : + m_server(server), m_extent(0), m_completion(0), + m_fillFrom(fillFromColumn) { } + + size_t getExtent() const { return m_extent; } + size_t getCompletion() const { return m_completion ? m_completion : 1; } + virtual void run(); + + protected: + FFTDataServer &m_server; + size_t m_extent; + size_t m_completion; + size_t m_fillFrom; + }; + + bool m_exiting; + bool m_suspended; + FillThread *m_fillThread; + + void deleteProcessingData(); + void fillColumn(size_t x); + + QString generateFileBasename() const; + static QString generateFileBasename(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar); + + typedef std::pair ServerCountPair; + typedef std::map ServerMap; + typedef std::deque ServerQueue; + + static ServerMap m_servers; + static ServerQueue m_releasedServers; // these are still in m_servers as well, with zero refcount + static QMutex m_serverMapMutex; + static FFTDataServer *findServer(QString); // call with serverMapMutex held + static void purgeLimbo(int maxSize = 3); // call with serverMapMutex held + + static void claimInstance(FFTDataServer *, bool needLock); + static void releaseInstance(FFTDataServer *, bool needLock); + +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fft/FFTFileCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFileCache.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,322 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTFileCache.h" + +#include "fileio/MatrixFile.h" + +#include "base/Profiler.h" + +#include + +#include + +// The underlying matrix has height (m_height * 2 + 1). In each +// column we store magnitude at [0], [2] etc and phase at [1], [3] +// etc, and then store the normalization factor (maximum magnitude) at +// [m_height * 2]. + +FFTFileCache::FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType) : + m_writebuf(0), + m_readbuf(0), + m_readbufCol(0), + m_readbufWidth(0), + m_mfc(new MatrixFile + (fileBase, mode, + storageType == Compact ? sizeof(uint16_t) : sizeof(float), + mode == MatrixFile::ReadOnly)), + m_storageType(storageType) +{ +// std::cerr << "FFTFileCache: storage type is " << (storageType == Compact ? "Compact" : storageType == Polar ? "Polar" : "Rectangular") << std::endl; +} + +FFTFileCache::~FFTFileCache() +{ + if (m_readbuf) delete[] m_readbuf; + if (m_writebuf) delete[] m_writebuf; + delete m_mfc; +} + +size_t +FFTFileCache::getWidth() const +{ + return m_mfc->getWidth(); +} + +size_t +FFTFileCache::getHeight() const +{ + size_t mh = m_mfc->getHeight(); + if (mh > 0) return (mh - 1) / 2; + else return 0; +} + +void +FFTFileCache::resize(size_t width, size_t height) +{ + QMutexLocker locker(&m_writeMutex); + + m_mfc->resize(width, height * 2 + 1); + if (m_readbuf) { + delete[] m_readbuf; + m_readbuf = 0; + } + if (m_writebuf) { + delete[] m_writebuf; + } + m_writebuf = new char[(height * 2 + 1) * m_mfc->getCellSize()]; +} + +void +FFTFileCache::reset() +{ + m_mfc->reset(); +} + +float +FFTFileCache::getMagnitudeAt(size_t x, size_t y) const +{ + Profiler profiler("FFTFileCache::getMagnitudeAt", false); + + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0) + * getNormalizationFactor(x); + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = sqrtf(real * real + imag * imag); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2); + break; + } + + return value; +} + +float +FFTFileCache::getNormalizedMagnitudeAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.0; + break; + + default: + { + float mag = getMagnitudeAt(x, y); + float factor = getNormalizationFactor(x); + if (factor != 0) value = mag / factor; + else value = 0.f; + break; + } + } + + return value; +} + +float +FFTFileCache::getMaximumMagnitudeAt(size_t x) const +{ + return getNormalizationFactor(x); +} + +float +FFTFileCache::getPhaseAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactSigned(x, y * 2 + 1) / 32767.0) * M_PI; + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = princargf(atan2f(imag, real)); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2 + 1); + break; + } + + return value; +} + +void +FFTFileCache::getValuesAt(size_t x, size_t y, float &real, float &imag) const +{ + switch (m_storageType) { + + case Rectangular: + real = getFromReadBufStandard(x, y * 2); + imag = getFromReadBufStandard(x, y * 2 + 1); + return; + + default: + float mag = getMagnitudeAt(x, y); + float phase = getPhaseAt(x, y); + real = mag * cosf(phase); + imag = mag * sinf(phase); + return; + } +} + +bool +FFTFileCache::haveSetColumnAt(size_t x) const +{ + return m_mfc->haveSetColumnAt(x); +} + +void +FFTFileCache::setColumnAt(size_t x, float *mags, float *phases, float factor) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + switch (m_storageType) { + + case Compact: + for (size_t y = 0; y < h; ++y) { + ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mags[y] / factor) * 65535.0); + ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phases[y] * 32767) / M_PI)); + } + break; + + case Rectangular: + for (size_t y = 0; y < h; ++y) { + ((float *)m_writebuf)[y * 2] = mags[y] * cosf(phases[y]); + ((float *)m_writebuf)[y * 2 + 1] = mags[y] * sinf(phases[y]); + } + break; + + case Polar: + for (size_t y = 0; y < h; ++y) { + ((float *)m_writebuf)[y * 2] = mags[y]; + ((float *)m_writebuf)[y * 2 + 1] = phases[y]; + } + break; + } + + static float maxFactor = 0; + if (factor > maxFactor) maxFactor = factor; +// std::cerr << "Normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << std::endl; + + if (m_storageType == Compact) { + if (factor < 0.f || factor > 1.f) { + std::cerr << "WARNING: FFTFileCache::setColumnAt: Normalization factor " << factor << " out of range" << std::endl; + if (factor < 0.f) factor = 0.f; + if (factor > 1.f) factor = 1.f; + } + ((uint16_t *)m_writebuf)[h * 2] = (uint16_t)(factor * 65535.0); + } else { + ((float *)m_writebuf)[h * 2] = factor; + } + m_mfc->setColumnAt(x, m_writebuf); +} + +void +FFTFileCache::setColumnAt(size_t x, float *real, float *imag) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + float max = 0.0f; + + switch (m_storageType) { + + case Compact: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + } + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + float phase = princargf(atan2f(imag[y], real[y])); + ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mag / max) * 65535.0); + ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phase * 32767) / M_PI)); + } + break; + + case Rectangular: + for (size_t y = 0; y < h; ++y) { + ((float *)m_writebuf)[y * 2] = real[y]; + ((float *)m_writebuf)[y * 2 + 1] = imag[y]; + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + } + break; + + case Polar: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + ((float *)m_writebuf)[y * 2] = mag; + ((float *)m_writebuf)[y * 2 + 1] = princargf(atan2f(imag[y], real[y])); + } + break; + } + + ((float *)m_writebuf)[h * 2] = max; + m_mfc->setColumnAt(x, m_writebuf); +} + +size_t +FFTFileCache::getCacheSize(size_t width, size_t height, StorageType type) +{ + return (height * 2 + 1) * width * + (type == Compact ? sizeof(uint16_t) : sizeof(float)) + + 2 * sizeof(size_t); // matrix file header size +} + +void +FFTFileCache::populateReadBuf(size_t x) const +{ + Profiler profiler("FFTFileCache::populateReadBuf", false); + + if (!m_readbuf) { + m_readbuf = new char[m_mfc->getHeight() * 2 * m_mfc->getCellSize()]; + } + m_mfc->getColumnAt(x, m_readbuf); + if (m_mfc->haveSetColumnAt(x + 1)) { + m_mfc->getColumnAt + (x + 1, m_readbuf + m_mfc->getCellSize() * m_mfc->getHeight()); + m_readbufWidth = 2; + } else { + m_readbufWidth = 1; + } + m_readbufCol = x; +} + diff -r 000000000000 -r fc9323a41f5a data/fft/FFTFileCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFileCache.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,114 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_FILE_CACHE_H_ +#define _FFT_FILE_CACHE_H_ + +#include "FFTCache.h" +#include "fileio/MatrixFile.h" + +#include + +class FFTFileCache : public FFTCache +{ +public: + enum StorageType { + Compact, // 16 bits normalized polar + Rectangular, // floating point real+imag + Polar, // floating point mag+phase + }; + + FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType); + virtual ~FFTFileCache(); + + MatrixFile::Mode getMode() const { return m_mfc->getMode(); } + + virtual size_t getWidth() const; + virtual size_t getHeight() const; + + virtual void resize(size_t width, size_t height); + virtual void reset(); // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const; + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const; + virtual float getMaximumMagnitudeAt(size_t x) const; + virtual float getPhaseAt(size_t x, size_t y) const; + + virtual void getValuesAt(size_t x, size_t y, float &real, float &imag) const; + + virtual bool haveSetColumnAt(size_t x) const; + + virtual void setColumnAt(size_t x, float *mags, float *phases, float factor); + virtual void setColumnAt(size_t x, float *reals, float *imags); + + virtual void suspend() { m_mfc->suspend(); } + + static size_t getCacheSize(size_t width, size_t height, StorageType type); + +protected: + char *m_writebuf; + mutable char *m_readbuf; + mutable size_t m_readbufCol; + mutable size_t m_readbufWidth; + + float getFromReadBufStandard(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((float *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufStandard(x, y); + } + } + + float getFromReadBufCompactUnsigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((uint16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactUnsigned(x, y); + } + } + + float getFromReadBufCompactSigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((int16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactSigned(x, y); + } + } + + void populateReadBuf(size_t x) const; + + float getNormalizationFactor(size_t col) const { + if (m_storageType != Compact) { + return getFromReadBufStandard(col, m_mfc->getHeight() - 1); + } else { + float factor; + factor = getFromReadBufCompactUnsigned(col, m_mfc->getHeight() - 1); + return factor / 65535.0; + } + } + + MatrixFile *m_mfc; + QMutex m_writeMutex; + StorageType m_storageType; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fft/FFTMemoryCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTMemoryCache.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,121 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTMemoryCache.h" +#include "system/System.h" + +#include + +FFTMemoryCache::FFTMemoryCache() : + m_width(0), + m_height(0), + m_magnitude(0), + m_phase(0), + m_factor(0) +{ +} + +FFTMemoryCache::~FFTMemoryCache() +{ +// std::cerr << "FFTMemoryCache[" << this << "]::~Cache" << std::endl; + + for (size_t i = 0; i < m_width; ++i) { + if (m_magnitude && m_magnitude[i]) free(m_magnitude[i]); + if (m_phase && m_phase[i]) free(m_phase[i]); + } + + if (m_magnitude) free(m_magnitude); + if (m_phase) free(m_phase); + if (m_factor) free(m_factor); +} + +void +FFTMemoryCache::resize(size_t width, size_t height) +{ +// std::cerr << "FFTMemoryCache[" << this << "]::resize(" << width << "x" << height << " = " << width*height << ")" << std::endl; + + if (m_width == width && m_height == height) return; + + resize(m_magnitude, width, height); + resize(m_phase, width, height); + m_colset.resize(width); + + m_factor = (float *)realloc(m_factor, width * sizeof(float)); + + m_width = width; + m_height = height; + +// std::cerr << "done, width = " << m_width << " height = " << m_height << std::endl; +} + +void +FFTMemoryCache::resize(uint16_t **&array, size_t width, size_t height) +{ + for (size_t i = width; i < m_width; ++i) { + free(array[i]); + } + + if (width != m_width) { + array = (uint16_t **)realloc(array, width * sizeof(uint16_t *)); + if (!array) throw std::bad_alloc(); + MUNLOCK(array, width * sizeof(uint16_t *)); + } + + for (size_t i = m_width; i < width; ++i) { + array[i] = 0; + } + + for (size_t i = 0; i < width; ++i) { + array[i] = (uint16_t *)realloc(array[i], height * sizeof(uint16_t)); + if (!array[i]) throw std::bad_alloc(); + MUNLOCK(array[i], height * sizeof(uint16_t)); + } +} + +void +FFTMemoryCache::reset() +{ + for (size_t x = 0; x < m_width; ++x) { + for (size_t y = 0; y < m_height; ++y) { + m_magnitude[x][y] = 0; + m_phase[x][y] = 0; + } + m_factor[x] = 1.0; + } +} + +void +FFTMemoryCache::setColumnAt(size_t x, float *reals, float *imags) +{ + float max = 0.0; + + for (size_t y = 0; y < m_height; ++y) { + float mag = sqrtf(reals[y] * reals[y] + imags[y] * imags[y]); + float phase = atan2f(imags[y], reals[y]); + phase = princargf(phase); + reals[y] = mag; + imags[y] = phase; + if (mag > max) max = mag; + } + + setColumnAt(x, reals, imags, max); +} + +size_t +FFTMemoryCache::getCacheSize(size_t width, size_t height) +{ + return (height * 2 + 1) * width * sizeof(uint16_t); +} + diff -r 000000000000 -r fc9323a41f5a data/fft/FFTMemoryCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTMemoryCache.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,130 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_MEMORY_CACHE_H_ +#define _FFT_MEMORY_CACHE_H_ + +#include "FFTCache.h" + +#include "base/ResizeableBitset.h" + +/** + * In-memory FFT cache. For this we want to cache magnitude with + * enough resolution to have gain applied afterwards and determine + * whether something is a peak or not, and also cache phase rather + * than only phase-adjusted frequency so that we don't have to + * recalculate if switching between phase and magnitude displays. At + * the same time, we don't want to take up too much memory. It's not + * expected to be accurate enough to be used as input for DSP or + * resynthesis code. + * + * This implies probably 16 bits for a normalized magnitude and at + * most 16 bits for phase. + * + * Each column's magnitudes are expected to be stored normalized + * to [0,1] with respect to the column, so the normalization + * factor should be calculated before all values in a column, and + * set appropriately. + */ + +class FFTMemoryCache : public FFTCache +{ +public: + FFTMemoryCache(); // of size zero, call resize() before using + virtual ~FFTMemoryCache(); + + virtual size_t getWidth() const { return m_width; } + virtual size_t getHeight() const { return m_height; } + + virtual void resize(size_t width, size_t height); + virtual void reset(); // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const { + return getNormalizedMagnitudeAt(x, y) * m_factor[x]; + } + + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const { + return float(m_magnitude[x][y]) / 65535.0; + } + + virtual float getMaximumMagnitudeAt(size_t x) const { + return m_factor[x]; + } + + virtual float getPhaseAt(size_t x, size_t y) const { + int16_t i = (int16_t)m_phase[x][y]; + return (float(i) / 32767.0) * M_PI; + } + + virtual void getValuesAt(size_t x, size_t y, float &real, float &imag) const { + float mag = getMagnitudeAt(x, y); + float phase = getPhaseAt(x, y); + real = mag * cosf(phase); + imag = mag * sinf(phase); + } + + virtual void setNormalizationFactor(size_t x, float factor) { + if (x < m_width) m_factor[x] = factor; + } + + virtual void setMagnitudeAt(size_t x, size_t y, float mag) { + // norm factor must already be set + setNormalizedMagnitudeAt(x, y, mag / m_factor[x]); + } + + virtual void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) { + if (x < m_width && y < m_height) { + m_magnitude[x][y] = uint16_t(norm * 65535.0); + } + } + + virtual void setPhaseAt(size_t x, size_t y, float phase) { + // phase in range -pi -> pi + if (x < m_width && y < m_height) { + m_phase[x][y] = uint16_t(int16_t((phase * 32767) / M_PI)); + } + } + + virtual bool haveSetColumnAt(size_t x) const { + return m_colset.get(x); + } + + virtual void setColumnAt(size_t x, float *mags, float *phases, float factor) { + setNormalizationFactor(x, factor); + for (size_t y = 0; y < m_height; ++y) { + setMagnitudeAt(x, y, mags[y]); + setPhaseAt(x, y, phases[y]); + } + m_colset.set(x); + } + + virtual void setColumnAt(size_t x, float *reals, float *imags); + + static size_t getCacheSize(size_t width, size_t height); + +private: + size_t m_width; + size_t m_height; + uint16_t **m_magnitude; + uint16_t **m_phase; + float *m_factor; + ResizeableBitset m_colset; + + void resize(uint16_t **&, size_t, size_t); +}; + + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fft/FFTapi.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTapi.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,224 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + FFT code from Don Cross's public domain FFT implementation. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTapi.h" + +#ifndef HAVE_FFTW3F + +#include +#include + +void +fft(unsigned int n, bool inverse, double *ri, double *ii, double *ro, double *io) +{ + if (!ri || !ro || !io) return; + + unsigned int bits; + unsigned int i, j, k, m; + unsigned int blockSize, blockEnd; + + double tr, ti; + + if (n < 2) return; + if (n & (n-1)) return; + + double angle = 2.0 * M_PI; + if (inverse) angle = -angle; + + for (i = 0; ; ++i) { + if (n & (1 << i)) { + bits = i; + break; + } + } + + int *table = new int[n]; + + for (i = 0; i < n; ++i) { + + m = i; + + for (j = k = 0; j < bits; ++j) { + k = (k << 1) | (m & 1); + m >>= 1; + } + + table[i] = k; + } + + if (ii) { + for (i = 0; i < n; ++i) { + ro[table[i]] = ri[i]; + io[table[i]] = ii[i]; + } + } else { + for (i = 0; i < n; ++i) { + ro[table[i]] = ri[i]; + io[table[i]] = 0.0; + } + } + + blockEnd = 1; + + for (blockSize = 2; blockSize <= n; blockSize <<= 1) { + + double delta = angle / (double)blockSize; + double sm2 = -sin(-2 * delta); + double sm1 = -sin(-delta); + double cm2 = cos(-2 * delta); + double cm1 = cos(-delta); + double w = 2 * cm1; + double ar[3], ai[3]; + + for (i = 0; i < n; i += blockSize) { + + ar[2] = cm2; + ar[1] = cm1; + + ai[2] = sm2; + ai[1] = sm1; + + for (j = i, m = 0; m < blockEnd; j++, m++) { + + ar[0] = w * ar[1] - ar[2]; + ar[2] = ar[1]; + ar[1] = ar[0]; + + ai[0] = w * ai[1] - ai[2]; + ai[2] = ai[1]; + ai[1] = ai[0]; + + k = j + blockEnd; + tr = ar[0] * ro[k] - ai[0] * io[k]; + ti = ar[0] * io[k] + ai[0] * ro[k]; + + ro[k] = ro[j] - tr; + io[k] = io[j] - ti; + + ro[j] += tr; + io[j] += ti; + } + } + + blockEnd = blockSize; + } + +/* fftw doesn't normalise, so nor will we + + if (inverse) { + + double denom = (double)n; + + for (i = 0; i < n; i++) { + ro[i] /= denom; + io[i] /= denom; + } + } +*/ + delete[] table; +} + +struct fftf_plan_ { + int size; + int inverse; + float *real; + fftf_complex *cplx; +}; + +fftf_plan +fftf_plan_dft_r2c_1d(int n, float *in, fftf_complex *out, unsigned) +{ + if (n < 2) return 0; + if (n & (n-1)) return 0; + + fftf_plan_ *plan = new fftf_plan_; + plan->size = n; + plan->inverse = 0; + plan->real = in; + plan->cplx = out; + return plan; +} + +fftf_plan +fftf_plan_dft_c2r_1d(int n, fftf_complex *in, float *out, unsigned) +{ + if (n < 2) return 0; + if (n & (n-1)) return 0; + + fftf_plan_ *plan = new fftf_plan_; + plan->size = n; + plan->inverse = 1; + plan->real = out; + plan->cplx = in; + return plan; +} + +void +fftf_destroy_plan(fftf_plan p) +{ + delete p; +} + +void +fftf_execute(const fftf_plan p) +{ + float *real = p->real; + fftf_complex *cplx = p->cplx; + int n = p->size; + int forward = !p->inverse; + + double *ri = new double[n]; + double *ro = new double[n]; + double *io = new double[n]; + + double *ii = 0; + if (!forward) ii = new double[n]; + + if (forward) { + for (int i = 0; i < n; ++i) { + ri[i] = real[i]; + } + } else { + for (int i = 0; i < n/2+1; ++i) { + ri[i] = cplx[i][0]; + ii[i] = cplx[i][1]; + if (i > 0) { + ri[n-i] = ri[i]; + ii[n-i] = -ii[i]; + } + } + } + + fft(n, !forward, ri, ii, ro, io); + + if (forward) { + for (int i = 0; i < n/2+1; ++i) { + cplx[i][0] = ro[i]; + cplx[i][1] = io[i]; + } + } else { + for (int i = 0; i < n; ++i) { + real[i] = ro[i]; + } + } + + delete[] ri; + delete[] ro; + delete[] io; + if (ii) delete[] ii; +} + +#endif diff -r 000000000000 -r fc9323a41f5a data/fft/FFTapi.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTapi.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_API_H_ +#define _FFT_API_H_ + +#ifdef HAVE_FFTW3F + +#include + +#define fftf_complex fftwf_complex +#define fftf_malloc fftwf_malloc +#define fftf_free fftwf_free +#define fftf_plan fftwf_plan +#define fftf_plan_dft_r2c_1d fftwf_plan_dft_r2c_1d +#define fftf_plan_dft_c2r_1d fftwf_plan_dft_c2r_1d +#define fftf_execute fftwf_execute +#define fftf_destroy_plan fftwf_destroy_plan + +#else + +// Provide a fallback FFT implementation if FFTW3f is not available. + +typedef float fftf_complex[2]; +#define fftf_malloc malloc +#define fftf_free free + +struct fftf_plan_; +typedef fftf_plan_ *fftf_plan; + +fftf_plan fftf_plan_dft_r2c_1d(int n, float *in, fftf_complex *out, unsigned); +fftf_plan fftf_plan_dft_c2r_1d(int n, fftf_complex *in, float *out, unsigned); +void fftf_execute(const fftf_plan p); +void fftf_destroy_plan(fftf_plan p); + +#define FFTW_ESTIMATE 0 + +#endif + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/AudioFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,17 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioFileReader.h" + diff -r 000000000000 -r fc9323a41f5a data/fileio/AudioFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_FILE_READER_H_ +#define _AUDIO_FILE_READER_H_ + +#include +#include "model/Model.h" // for SampleBlock + +class AudioFileReader : public QObject +{ + Q_OBJECT + +public: + virtual ~AudioFileReader() { } + + bool isOK() const { return (m_channelCount > 0); } + + virtual QString getError() const { return ""; } + + size_t getFrameCount() const { return m_frameCount; } + size_t getChannelCount() const { return m_channelCount; } + size_t getSampleRate() const { return m_sampleRate; } + + /** + * The subclass implementations of this function must be + * thread-safe -- that is, safe to call from multiple threads with + * different arguments on the same object at the same time. + */ + virtual void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const = 0; + + virtual bool isUpdating() const { return false; } + +signals: + void frameCountChanged(); + +protected: + size_t m_frameCount; + size_t m_channelCount; + size_t m_sampleRate; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/AudioFileReaderFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReaderFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,151 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioFileReaderFactory.h" + +#include "WavFileReader.h" +#include "OggVorbisFileReader.h" +#include "MP3FileReader.h" + +#include +#include +#include + +QString +AudioFileReaderFactory::getKnownExtensions() +{ + std::set extensions; + + WavFileReader::getSupportedExtensions(extensions); +#ifdef HAVE_MAD + MP3FileReader::getSupportedExtensions(extensions); +#endif +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + OggVorbisFileReader::getSupportedExtensions(extensions); +#endif +#endif + + QString rv; + for (std::set::const_iterator i = extensions.begin(); + i != extensions.end(); ++i) { + if (i != extensions.begin()) rv += " "; + rv += "*." + *i; + } + + return rv; +} + +AudioFileReader * +AudioFileReaderFactory::createReader(QString path) +{ + QString err; + + AudioFileReader *reader = 0; + + // First try to construct a preferred reader based on the + // extension. If we can't identify one or it fails to load the + // file, fall back to trying all readers in no particular order. + + QString ext = QFileInfo(path).suffix().toLower(); + std::set extensions; + + WavFileReader::getSupportedExtensions(extensions); + if (extensions.find(ext) != extensions.end()) { + reader = new WavFileReader(path); + } + +#ifdef HAVE_MAD + if (!reader) { + extensions.clear(); + MP3FileReader::getSupportedExtensions(extensions); + if (extensions.find(ext) != extensions.end()) { + reader = new MP3FileReader + (path, true, MP3FileReader::CacheInTemporaryFile); + } + } +#endif +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + if (!reader) { + extensions.clear(); + OggVorbisFileReader::getSupportedExtensions(extensions); + if (extensions.find(ext) != extensions.end()) { + reader = new OggVorbisFileReader + (path, true, OggVorbisFileReader::CacheInTemporaryFile); + } + } +#endif +#endif + + if (reader) { + if (reader->isOK()) return reader; + if (reader->getError() != "") { + std::cerr << "AudioFileReaderFactory: Preferred reader for " + << "extension \"" << ext.toStdString() << "\" failed: \"" + << reader->getError().toStdString() << "\"" << std::endl; + } else { + std::cerr << "AudioFileReaderFactory: Preferred reader for " + << "extension \"" << ext.toStdString() << "\" failed" + << std::endl; + } + delete reader; + reader = 0; + } + + reader = new WavFileReader(path); + if (reader->isOK()) return reader; + if (reader->getError() != "") { + std::cerr << "AudioFileReaderFactory: WAV file reader error: \"" + << reader->getError().toStdString() << "\"" << std::endl; + } else { + std::cerr << "AudioFileReaderFactory: WAV file reader failed" + << std::endl; + } + delete reader; + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + reader = new OggVorbisFileReader + (path, true, OggVorbisFileReader::CacheInTemporaryFile); + if (reader->isOK()) return reader; + if (reader->getError() != "") { + std::cerr << "AudioFileReaderFactory: Ogg file reader error: \"" + << reader->getError().toStdString() << "\"" << std::endl; + } else { + std::cerr << "AudioFileReaderFactory: Ogg file reader failed" + << std::endl; + } + delete reader; +#endif +#endif + +#ifdef HAVE_MAD + reader = new MP3FileReader + (path, true, MP3FileReader::CacheInTemporaryFile); + if (reader->isOK()) return reader; + if (reader->getError() != "") { + std::cerr << "AudioFileReaderFactory: MP3 file reader error: \"" + << reader->getError().toStdString() << "\"" << std::endl; + } else { + std::cerr << "AudioFileReaderFactory: MP3 file reader failed" + << std::endl; + } + delete reader; +#endif + + return 0; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/AudioFileReaderFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReaderFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,43 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_FILE_READER_FACTORY_H_ +#define _AUDIO_FILE_READER_FACTORY_H_ + +#include + +class AudioFileReader; + +class AudioFileReaderFactory +{ +public: + /** + * Return the file extensions that we have audio file readers for, + * in a format suitable for use with QFileDialog. For example, + * "*.wav *.aiff *.ogg". + */ + static QString getKnownExtensions(); + + /** + * Return an audio file reader initialised to the file at the + * given path, or NULL if no suitable reader for this path is + * available or the file cannot be opened. + * Caller owns the returned object and must delete it after use. + */ + static AudioFileReader *createReader(QString path); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/BZipFileDevice.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BZipFileDevice.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,217 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "BZipFileDevice.h" + +#include + +#include + +BZipFileDevice::BZipFileDevice(QString fileName) : + m_fileName(fileName), + m_file(0), + m_bzFile(0), + m_atEnd(true), + m_ok(true) +{ +} + +BZipFileDevice::~BZipFileDevice() +{ +// std::cerr << "BZipFileDevice::~BZipFileDevice(" << m_fileName.toStdString() << ")" << std::endl; + if (m_bzFile) close(); +} + +bool +BZipFileDevice::isOK() const +{ + return m_ok; +} + +bool +BZipFileDevice::open(OpenMode mode) +{ + setErrorString(""); + + if (m_bzFile) { + setErrorString(tr("File is already open")); + return false; + } + + if (mode & Append) { + setErrorString(tr("Append mode not supported")); + m_ok = false; + return false; + } + + if ((mode & (ReadOnly | WriteOnly)) == 0) { + setErrorString(tr("File access mode not specified")); + m_ok = false; + return false; + } + + if ((mode & ReadOnly) && (mode & WriteOnly)) { + setErrorString(tr("Read and write modes both specified")); + m_ok = false; + return false; + } + + if (mode & WriteOnly) { + + m_file = fopen(m_fileName.toLocal8Bit().data(), "wb"); + if (!m_file) { + setErrorString(tr("Failed to open file for writing")); + m_ok = false; + return false; + } + + int bzError = BZ_OK; + m_bzFile = BZ2_bzWriteOpen(&bzError, m_file, 9, 0, 0); + + if (!m_bzFile) { + fclose(m_file); + m_file = 0; + setErrorString(tr("Failed to open bzip2 stream for writing")); + m_ok = false; + return false; + } + +// std::cerr << "BZipFileDevice: opened \"" << m_fileName.toStdString() << "\" for writing" << std::endl; + + setErrorString(QString()); + setOpenMode(mode); + return true; + } + + if (mode & ReadOnly) { + + m_file = fopen(m_fileName.toLocal8Bit().data(), "rb"); + if (!m_file) { + setErrorString(tr("Failed to open file for reading")); + m_ok = false; + return false; + } + + int bzError = BZ_OK; + m_bzFile = BZ2_bzReadOpen(&bzError, m_file, 0, 0, NULL, 0); + + if (!m_bzFile) { + fclose(m_file); + m_file = 0; + setErrorString(tr("Failed to open bzip2 stream for reading")); + m_ok = false; + return false; + } + +// std::cerr << "BZipFileDevice: opened \"" << m_fileName.toStdString() << "\" for reading" << std::endl; + + m_atEnd = false; + + setErrorString(QString()); + setOpenMode(mode); + return true; + } + + setErrorString(tr("Internal error (open for neither read nor write)")); + m_ok = false; + return false; +} + +void +BZipFileDevice::close() +{ + if (!m_bzFile) { + setErrorString(tr("File not open")); + m_ok = false; + return; + } + + int bzError = BZ_OK; + + if (openMode() & WriteOnly) { + unsigned int in = 0, out = 0; + BZ2_bzWriteClose(&bzError, m_bzFile, 0, &in, &out); +// std::cerr << "Wrote bzip2 stream (in=" << in << ", out=" << out << ")" << std::endl; + if (bzError != BZ_OK) { + setErrorString(tr("bzip2 stream write close error")); + } + fclose(m_file); + m_bzFile = 0; + m_file = 0; + m_ok = false; + return; + } + + if (openMode() & ReadOnly) { + BZ2_bzReadClose(&bzError, m_bzFile); + if (bzError != BZ_OK) { + setErrorString(tr("bzip2 stream read close error")); + } + fclose(m_file); + m_bzFile = 0; + m_file = 0; + m_ok = false; + return; + } + + setErrorString(tr("Internal error (close for neither read nor write)")); + return; +} + +qint64 +BZipFileDevice::readData(char *data, qint64 maxSize) +{ + if (m_atEnd) return 0; + + int bzError = BZ_OK; + int read = BZ2_bzRead(&bzError, m_bzFile, data, maxSize); + +// std::cerr << "BZipFileDevice::readData: requested " << maxSize << ", read " << read << std::endl; + + if (bzError != BZ_OK) { + if (bzError != BZ_STREAM_END) { + std::cerr << "BZipFileDevice::readData: error condition" << std::endl; + setErrorString(tr("bzip2 stream read error")); + m_ok = false; + return -1; + } else { +// std::cerr << "BZipFileDevice::readData: reached end of file" << std::endl; + m_atEnd = true; + } + } + + return read; +} + +qint64 +BZipFileDevice::writeData(const char *data, qint64 maxSize) +{ + int bzError = BZ_OK; + BZ2_bzWrite(&bzError, m_bzFile, (void *)data, maxSize); + +// std::cerr << "BZipFileDevice::writeData: " << maxSize << " to write" << std::endl; + + if (bzError != BZ_OK) { + std::cerr << "BZipFileDevice::writeData: error condition" << std::endl; + setErrorString("bzip2 stream write error"); + m_ok = false; + return -1; + } + +// std::cerr << "BZipFileDevice::writeData: wrote " << maxSize << std::endl; + + return maxSize; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/BZipFileDevice.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BZipFileDevice.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _BZIP_FILE_DEVICE_H_ +#define _BZIP_FILE_DEVICE_H_ + +#include + +#include + +class BZipFileDevice : public QIODevice +{ + Q_OBJECT + +public: + BZipFileDevice(QString fileName); + virtual ~BZipFileDevice(); + + virtual bool open(OpenMode mode); + virtual void close(); + + virtual bool isOK() const; + + virtual bool isSequential() const { return true; } + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + + QString m_fileName; + + FILE *m_file; + BZFILE *m_bzFile; + bool m_atEnd; + bool m_ok; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/CSVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,644 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "CSVFileReader.h" + +#include "model/Model.h" +#include "base/RealTime.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/EditableDenseThreeDimensionalModel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +CSVFileReader::CSVFileReader(QString path, size_t mainModelSampleRate) : + m_file(0), + m_mainModelSampleRate(mainModelSampleRate) +{ + m_file = new QFile(path); + bool good = false; + + if (!m_file->exists()) { + m_error = QFile::tr("File \"%1\" does not exist").arg(path); + } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = QFile::tr("Failed to open file \"%1\"").arg(path); + } else { + good = true; + } + + if (!good) { + delete m_file; + m_file = 0; + } +} + +CSVFileReader::~CSVFileReader() +{ + std::cerr << "CSVFileReader::~CSVFileReader: file is " << m_file << std::endl; + + if (m_file) { + std::cerr << "CSVFileReader::CSVFileReader: Closing file" << std::endl; + m_file->close(); + } + delete m_file; +} + +bool +CSVFileReader::isOK() const +{ + return (m_file != 0); +} + +QString +CSVFileReader::getError() const +{ + return m_error; +} + +Model * +CSVFileReader::load() const +{ + if (!m_file) return 0; + + CSVFormatDialog *dialog = new CSVFormatDialog + (0, m_file, m_mainModelSampleRate); + + if (dialog->exec() == QDialog::Rejected) { + delete dialog; + return 0; + } + + CSVFormatDialog::ModelType modelType = dialog->getModelType(); + CSVFormatDialog::TimingType timingType = dialog->getTimingType(); + CSVFormatDialog::TimeUnits timeUnits = dialog->getTimeUnits(); + QString separator = dialog->getSeparator(); + size_t sampleRate = dialog->getSampleRate(); + size_t windowSize = dialog->getWindowSize(); + + delete dialog; + + if (timingType == CSVFormatDialog::ExplicitTiming) { + windowSize = 1; + if (timeUnits == CSVFormatDialog::TimeSeconds) { + sampleRate = m_mainModelSampleRate; + } + } + + SparseOneDimensionalModel *model1 = 0; + SparseTimeValueModel *model2 = 0; + EditableDenseThreeDimensionalModel *model3 = 0; + Model *model = 0; + + QTextStream in(m_file); + in.seek(0); + + unsigned int warnings = 0, warnLimit = 10; + unsigned int lineno = 0; + + float min = 0.0, max = 0.0; + + size_t frameNo = 0; + + while (!in.atEnd()) { + + QString line = in.readLine().trimmed(); + if (line.startsWith("#") || line.trimmed() == "") continue; + + QStringList list = line.split(separator); + + if (!model) { + + switch (modelType) { + + case CSVFormatDialog::OneDimensionalModel: + model1 = new SparseOneDimensionalModel(sampleRate, windowSize); + model = model1; + break; + + case CSVFormatDialog::TwoDimensionalModel: + model2 = new SparseTimeValueModel(sampleRate, windowSize, false); + model = model2; + break; + + case CSVFormatDialog::ThreeDimensionalModel: + model3 = new EditableDenseThreeDimensionalModel(sampleRate, + windowSize, + list.size()); + model = model3; + break; + } + } + + QStringList tidyList; + QRegExp nonNumericRx("[^0-9.,+-]"); + + for (int i = 0; i < list.size(); ++i) { + + QString s(list[i].trimmed()); + + if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) { + s = s.mid(1, s.length() - 2); + } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) { + s = s.mid(1, s.length() - 2); + } + + if (i == 0 && timingType == CSVFormatDialog::ExplicitTiming) { + + bool ok = false; + QString numeric = s; + numeric.remove(nonNumericRx); + + if (timeUnits == CSVFormatDialog::TimeSeconds) { + + double time = numeric.toDouble(&ok); + frameNo = int(time * sampleRate + 0.00001); + + } else { + + frameNo = numeric.toInt(&ok); + + if (timeUnits == CSVFormatDialog::TimeWindows) { + frameNo *= windowSize; + } + } + + if (!ok) { + if (warnings < warnLimit) { + std::cerr << "WARNING: CSVFileReader::load: " + << "Bad time format (\"" << s.toStdString() + << "\") in data line " + << lineno << ":" << std::endl; + std::cerr << line.toStdString() << std::endl; + } else if (warnings == warnLimit) { + std::cerr << "WARNING: Too many warnings" << std::endl; + } + ++warnings; + } + } else { + tidyList.push_back(s); + } + } + + if (modelType == CSVFormatDialog::OneDimensionalModel) { + + SparseOneDimensionalModel::Point point + (frameNo, + tidyList.size() > 0 ? tidyList[tidyList.size()-1] : + QString("%1").arg(lineno)); + + model1->addPoint(point); + + } else if (modelType == CSVFormatDialog::TwoDimensionalModel) { + + SparseTimeValueModel::Point point + (frameNo, + tidyList.size() > 0 ? tidyList[0].toFloat() : 0.0, + tidyList.size() > 1 ? tidyList[1] : QString("%1").arg(lineno)); + + model2->addPoint(point); + + } else if (modelType == CSVFormatDialog::ThreeDimensionalModel) { + + DenseThreeDimensionalModel::Column values; + + for (int i = 0; i < tidyList.size(); ++i) { + + bool ok = false; + float value = list[i].toFloat(&ok); + values.push_back(value); + + if ((lineno == 0 && i == 0) || value < min) min = value; + if ((lineno == 0 && i == 0) || value > max) max = value; + + if (!ok) { + if (warnings < warnLimit) { + std::cerr << "WARNING: CSVFileReader::load: " + << "Non-numeric value in data line " << lineno + << ":" << std::endl; + std::cerr << line.toStdString() << std::endl; + ++warnings; + } else if (warnings == warnLimit) { + std::cerr << "WARNING: Too many warnings" << std::endl; + } + } + } + + std::cerr << "Setting bin values for count " << lineno << ", frame " + << frameNo << ", time " << RealTime::frame2RealTime(frameNo, sampleRate) << std::endl; + + model3->setColumn(frameNo / model3->getResolution(), values); + } + + ++lineno; + if (timingType == CSVFormatDialog::ImplicitTiming || + list.size() == 0) { + frameNo += windowSize; + } + } + + if (modelType == CSVFormatDialog::ThreeDimensionalModel) { + model3->setMinimumLevel(min); + model3->setMaximumLevel(max); + } + + return model; +} + + +CSVFormatDialog::CSVFormatDialog(QWidget *parent, QFile *file, + size_t defaultSampleRate) : + QDialog(parent), + m_modelType(OneDimensionalModel), + m_timingType(ExplicitTiming), + m_timeUnits(TimeAudioFrames), + m_separator("") +{ + setModal(true); + setWindowTitle(tr("Select Data Format")); + + (void)guessFormat(file); + + QGridLayout *layout = new QGridLayout; + + layout->addWidget(new QLabel(tr("\nPlease select the correct data format for this file.\n")), + 0, 0, 1, 4); + + layout->addWidget(new QLabel(tr("Each row specifies:")), 1, 0); + + m_modelTypeCombo = new QComboBox; + m_modelTypeCombo->addItem(tr("A point in time")); + m_modelTypeCombo->addItem(tr("A value at a time")); + m_modelTypeCombo->addItem(tr("A set of values")); + layout->addWidget(m_modelTypeCombo, 1, 1, 1, 2); + connect(m_modelTypeCombo, SIGNAL(activated(int)), + this, SLOT(modelTypeChanged(int))); + m_modelTypeCombo->setCurrentIndex(int(m_modelType)); + + layout->addWidget(new QLabel(tr("The first column contains:")), 2, 0); + + m_timingTypeCombo = new QComboBox; + m_timingTypeCombo->addItem(tr("Time, in seconds")); + m_timingTypeCombo->addItem(tr("Time, in audio sample frames")); + m_timingTypeCombo->addItem(tr("Data (rows are consecutive in time)")); + layout->addWidget(m_timingTypeCombo, 2, 1, 1, 2); + connect(m_timingTypeCombo, SIGNAL(activated(int)), + this, SLOT(timingTypeChanged(int))); + m_timingTypeCombo->setCurrentIndex(m_timingType == ExplicitTiming ? + m_timeUnits == TimeSeconds ? 0 : 1 : 2); + + m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):")); + layout->addWidget(m_sampleRateLabel, 3, 0); + + size_t sampleRates[] = { + 8000, 11025, 12000, 22050, 24000, 32000, + 44100, 48000, 88200, 96000, 176400, 192000 + }; + + m_sampleRateCombo = new QComboBox; + m_sampleRate = defaultSampleRate; + for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) { + m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i])); + if (sampleRates[i] == m_sampleRate) m_sampleRateCombo->setCurrentIndex(i); + } + m_sampleRateCombo->setEditable(true); + + layout->addWidget(m_sampleRateCombo, 3, 1); + connect(m_sampleRateCombo, SIGNAL(activated(QString)), + this, SLOT(sampleRateChanged(QString))); + connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)), + this, SLOT(sampleRateChanged(QString))); + + m_windowSizeLabel = new QLabel(tr("Frame increment between rows:")); + layout->addWidget(m_windowSizeLabel, 4, 0); + + m_windowSizeCombo = new QComboBox; + m_windowSize = 1024; + for (int i = 0; i <= 16; ++i) { + int value = 1 << i; + m_windowSizeCombo->addItem(QString("%1").arg(value)); + if (value == int(m_windowSize)) m_windowSizeCombo->setCurrentIndex(i); + } + m_windowSizeCombo->setEditable(true); + + layout->addWidget(m_windowSizeCombo, 4, 1); + connect(m_windowSizeCombo, SIGNAL(activated(QString)), + this, SLOT(windowSizeChanged(QString))); + connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)), + this, SLOT(windowSizeChanged(QString))); + + layout->addWidget(new QLabel(tr("\nExample data from file:")), 5, 0, 1, 4); + + m_exampleWidget = new QTableWidget + (min(10, m_example.size()), m_maxExampleCols); + + layout->addWidget(m_exampleWidget, 6, 0, 1, 4); + layout->setColumnStretch(3, 10); + layout->setRowStretch(4, 10); + + QPushButton *ok = new QPushButton(tr("OK")); + connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + ok->setDefault(true); + + QPushButton *cancel = new QPushButton(tr("Cancel")); + connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(1); + buttonLayout->addWidget(ok); + buttonLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(layout); + mainLayout->addLayout(buttonLayout); + + setLayout(mainLayout); + + timingTypeChanged(m_timingTypeCombo->currentIndex()); +} + +CSVFormatDialog::~CSVFormatDialog() +{ +} + +void +CSVFormatDialog::populateExample() +{ + m_exampleWidget->setColumnCount + (m_timingType == ExplicitTiming ? + m_maxExampleCols - 1 : m_maxExampleCols); + + m_exampleWidget->setHorizontalHeaderLabels(QStringList()); + + for (int i = 0; i < m_example.size(); ++i) { + for (int j = 0; j < m_example[i].size(); ++j) { + + QTableWidgetItem *item = new QTableWidgetItem(m_example[i][j]); + + if (j == 0) { + if (m_timingType == ExplicitTiming) { + m_exampleWidget->setVerticalHeaderItem(i, item); + continue; + } else { + QTableWidgetItem *header = + new QTableWidgetItem(QString("%1").arg(i)); + header->setFlags(Qt::ItemIsEnabled); + m_exampleWidget->setVerticalHeaderItem(i, header); + } + } + int index = j; + if (m_timingType == ExplicitTiming) --index; + item->setFlags(Qt::ItemIsEnabled); + m_exampleWidget->setItem(i, index, item); + } + } +} + +void +CSVFormatDialog::modelTypeChanged(int type) +{ + m_modelType = (ModelType)type; + + if (m_modelType == ThreeDimensionalModel) { + // We can't load 3d models with explicit timing, because the 3d + // model is dense so we need a fixed sample increment + m_timingTypeCombo->setCurrentIndex(2); + timingTypeChanged(2); + } +} + +void +CSVFormatDialog::timingTypeChanged(int type) +{ + switch (type) { + + case 0: + m_timingType = ExplicitTiming; + m_timeUnits = TimeSeconds; + m_sampleRateCombo->setEnabled(false); + m_sampleRateLabel->setEnabled(false); + m_windowSizeCombo->setEnabled(false); + m_windowSizeLabel->setEnabled(false); + if (m_modelType == ThreeDimensionalModel) { + m_modelTypeCombo->setCurrentIndex(1); + modelTypeChanged(1); + } + break; + + case 1: + m_timingType = ExplicitTiming; + m_timeUnits = TimeAudioFrames; + m_sampleRateCombo->setEnabled(true); + m_sampleRateLabel->setEnabled(true); + m_windowSizeCombo->setEnabled(false); + m_windowSizeLabel->setEnabled(false); + if (m_modelType == ThreeDimensionalModel) { + m_modelTypeCombo->setCurrentIndex(1); + modelTypeChanged(1); + } + break; + + case 2: + m_timingType = ImplicitTiming; + m_timeUnits = TimeWindows; + m_sampleRateCombo->setEnabled(true); + m_sampleRateLabel->setEnabled(true); + m_windowSizeCombo->setEnabled(true); + m_windowSizeLabel->setEnabled(true); + break; + } + + populateExample(); +} + +void +CSVFormatDialog::sampleRateChanged(QString rateString) +{ + bool ok = false; + int sampleRate = rateString.toInt(&ok); + if (ok) m_sampleRate = sampleRate; +} + +void +CSVFormatDialog::windowSizeChanged(QString sizeString) +{ + bool ok = false; + int size = sizeString.toInt(&ok); + if (ok) m_windowSize = size; +} + +bool +CSVFormatDialog::guessFormat(QFile *file) +{ + QTextStream in(file); + in.seek(0); + + unsigned int lineno = 0; + + bool nonIncreasingPrimaries = false; + bool nonNumericPrimaries = false; + bool floatPrimaries = false; + bool variableItemCount = false; + int itemCount = 1; + int earliestNonNumericItem = -1; + + float prevPrimary = 0.0; + + m_maxExampleCols = 0; + + while (!in.atEnd()) { + + QString line = in.readLine().trimmed(); + if (line.startsWith("#")) continue; + + if (m_separator == "") { + //!!! to do: ask the user + if (line.split(",").size() >= 2) m_separator = ","; + else if (line.split("\t").size() >= 2) m_separator = "\t"; + else if (line.split("|").size() >= 2) m_separator = "|"; + else if (line.split("/").size() >= 2) m_separator = "/"; + else if (line.split(":").size() >= 2) m_separator = ":"; + else m_separator = " "; + } + + QStringList list = line.split(m_separator); + QStringList tidyList; + + for (int i = 0; i < list.size(); ++i) { + + QString s(list[i]); + bool numeric = false; + + if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) { + s = s.mid(1, s.length() - 2); + } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) { + s = s.mid(1, s.length() - 2); + } else { + (void)s.toFloat(&numeric); + } + + tidyList.push_back(s); + + if (lineno == 0 || (list.size() < itemCount)) { + itemCount = list.size(); + } else { + if (itemCount != list.size()) { + variableItemCount = true; + } + } + + if (i == 0) { // primary + + if (numeric) { + + float primary = s.toFloat(); + + if (lineno > 0 && primary <= prevPrimary) { + nonIncreasingPrimaries = true; + } + + if (s.contains(".") || s.contains(",")) { + floatPrimaries = true; + } + + prevPrimary = primary; + + } else { + nonNumericPrimaries = true; + } + } else { // secondary + + if (!numeric) { + if (earliestNonNumericItem < 0 || + i < earliestNonNumericItem) { + earliestNonNumericItem = i; + } + } + } + } + + if (lineno < 10) { + m_example.push_back(tidyList); + if (lineno == 0 || tidyList.size() > m_maxExampleCols) { + m_maxExampleCols = tidyList.size(); + } + } + + ++lineno; + + if (lineno == 50) break; + } + + if (nonNumericPrimaries || nonIncreasingPrimaries) { + + // Primaries are probably not a series of times + + m_timingType = ImplicitTiming; + m_timeUnits = TimeWindows; + + if (nonNumericPrimaries) { + m_modelType = OneDimensionalModel; + } else if (itemCount == 1 || variableItemCount || + (earliestNonNumericItem != -1)) { + m_modelType = TwoDimensionalModel; + } else { + m_modelType = ThreeDimensionalModel; + } + + } else { + + // Increasing numeric primaries -- likely to be time + + m_timingType = ExplicitTiming; + + if (floatPrimaries) { + m_timeUnits = TimeSeconds; + } else { + m_timeUnits = TimeAudioFrames; + } + + if (itemCount == 1) { + m_modelType = OneDimensionalModel; + } else if (variableItemCount || (earliestNonNumericItem != -1)) { + if (earliestNonNumericItem != -1 && earliestNonNumericItem < 2) { + m_modelType = OneDimensionalModel; + } else { + m_modelType = TwoDimensionalModel; + } + } else { + m_modelType = ThreeDimensionalModel; + } + } + + std::cerr << "Estimated model type: " << m_modelType << std::endl; + std::cerr << "Estimated timing type: " << m_timingType << std::endl; + std::cerr << "Estimated units: " << m_timeUnits << std::endl; + + in.seek(0); + return true; +} diff -r 000000000000 -r fc9323a41f5a data/fileio/CSVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,111 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _CSV_FILE_READER_H_ +#define _CSV_FILE_READER_H_ + +#include "DataFileReader.h" + +#include +#include +#include + +class QFile; +class QTableWidget; +class QComboBox; +class QLabel; + + +class CSVFileReader : public DataFileReader +{ +public: + CSVFileReader(QString path, size_t mainModelSampleRate); + virtual ~CSVFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Model *load() const; + +protected: + QFile *m_file; + QString m_error; + size_t m_mainModelSampleRate; +}; + + +class CSVFormatDialog : public QDialog +{ + Q_OBJECT + +public: + CSVFormatDialog(QWidget *parent, QFile *file, size_t defaultSampleRate); + + ~CSVFormatDialog(); + + enum ModelType { + OneDimensionalModel, + TwoDimensionalModel, + ThreeDimensionalModel + }; + + enum TimingType { + ExplicitTiming, + ImplicitTiming + }; + + enum TimeUnits { + TimeSeconds, + TimeAudioFrames, + TimeWindows + }; + + ModelType getModelType() const { return m_modelType; } + TimingType getTimingType() const { return m_timingType; } + TimeUnits getTimeUnits() const { return m_timeUnits; } + QString getSeparator() const { return m_separator; } + size_t getSampleRate() const { return m_sampleRate; } + size_t getWindowSize() const { return m_windowSize; } + +protected slots: + void modelTypeChanged(int type); + void timingTypeChanged(int type); + void sampleRateChanged(QString); + void windowSizeChanged(QString); + +protected: + ModelType m_modelType; + TimingType m_timingType; + TimeUnits m_timeUnits; + QString m_separator; + size_t m_sampleRate; + size_t m_windowSize; + + QList m_example; + int m_maxExampleCols; + QTableWidget *m_exampleWidget; + + QComboBox *m_modelTypeCombo; + QComboBox *m_timingTypeCombo; + QLabel *m_sampleRateLabel; + QComboBox *m_sampleRateCombo; + QLabel *m_windowSizeLabel; + QComboBox *m_windowSizeCombo; + + bool guessFormat(QFile *file); + void populateExample(); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/CSVFileWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileWriter.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "CSVFileWriter.h" + +#include "model/Model.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/NoteModel.h" +#include "model/TextModel.h" + +#include +#include + +CSVFileWriter::CSVFileWriter(QString path, Model *model, QString delimiter) : + m_path(path), + m_model(model), + m_error(""), + m_delimiter(delimiter) +{ +} + +CSVFileWriter::~CSVFileWriter() +{ +} + +bool +CSVFileWriter::isOK() const +{ + return m_error == ""; +} + +QString +CSVFileWriter::getError() const +{ + return m_error; +} + +void +CSVFileWriter::write() +{ + QFile file(m_path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + m_error = tr("Failed to open file %1 for writing").arg(m_path); + return; + } + + QTextStream out(&file); + out << m_model->toDelimitedDataString(m_delimiter); + + file.close(); +} + + diff -r 000000000000 -r fc9323a41f5a data/fileio/CSVFileWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileWriter.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,46 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _CSV_FILE_WRITER_H_ +#define _CSV_FILE_WRITER_H_ + +#include +#include + +class Model; + +class CSVFileWriter : public QObject +{ + Q_OBJECT + +public: + CSVFileWriter(QString path, Model *model, QString delimiter = ","); + virtual ~CSVFileWriter(); + + virtual bool isOK() const; + virtual QString getError() const; + + virtual void write(); + +protected: + QString m_path; + Model *m_model; + QString m_error; + QString m_delimiter; +}; + +#endif + + diff -r 000000000000 -r fc9323a41f5a data/fileio/CodedAudioFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CodedAudioFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,195 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "CodedAudioFileReader.h" + +#include "WavFileReader.h" +#include "base/TempDirectory.h" +#include "base/Exceptions.h" +#include "base/Profiler.h" + +#include +#include + +CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode) : + m_cacheMode(cacheMode), + m_initialised(false), + m_cacheFileWritePtr(0), + m_cacheFileReader(0), + m_cacheWriteBuffer(0), + m_cacheWriteBufferIndex(0), + m_cacheWriteBufferSize(16384) +{ +} + +CodedAudioFileReader::~CodedAudioFileReader() +{ + if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr); + if (m_cacheFileReader) delete m_cacheFileReader; + if (m_cacheWriteBuffer) delete[] m_cacheWriteBuffer; + + if (m_cacheFileName != "") { + if (!QFile(m_cacheFileName).remove()) { + std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::endl; + } + } +} + +void +CodedAudioFileReader::initialiseDecodeCache() +{ + if (m_cacheMode == CacheInTemporaryFile) { + + m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount]; + m_cacheWriteBufferIndex = 0; + + try { + QDir dir(TempDirectory::getInstance()->getPath()); + m_cacheFileName = dir.filePath(QString("decoded_%1.wav") + .arg((intptr_t)this)); + + SF_INFO fileInfo; + fileInfo.samplerate = m_sampleRate; + fileInfo.channels = m_channelCount; + fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(), + SFM_WRITE, &fileInfo); + + if (!m_cacheFileWritePtr) { + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName.toStdString() << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << std::endl; + m_cacheMode = CacheInMemory; + } + } catch (DirectoryCreationFailed f) { + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::endl; + m_cacheMode = CacheInMemory; + } + } + + if (m_cacheMode == CacheInMemory) { + m_data.clear(); + } + + m_initialised = true; +} + +void +CodedAudioFileReader::addSampleToDecodeCache(float sample) +{ + if (!m_initialised) return; + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + + m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; + + if (m_cacheWriteBufferIndex == + m_cacheWriteBufferSize * m_channelCount) { + + //!!! check for return value! out of disk space, etc! + sf_writef_float(m_cacheFileWritePtr, + m_cacheWriteBuffer, + m_cacheWriteBufferSize); + + m_cacheWriteBufferIndex = 0; + } + break; + + case CacheInMemory: + m_data.push_back(sample); + break; + } +} + +void +CodedAudioFileReader::finishDecodeCache() +{ + Profiler profiler("CodedAudioFileReader::finishDecodeCache", true); + + if (!m_initialised) { + std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl; + return; + } + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + + if (m_cacheWriteBufferIndex > 0) { + //!!! check for return value! out of disk space, etc! + sf_writef_float(m_cacheFileWritePtr, + m_cacheWriteBuffer, + m_cacheWriteBufferIndex / m_channelCount); + } + + if (m_cacheWriteBuffer) { + delete[] m_cacheWriteBuffer; + m_cacheWriteBuffer = 0; + } + + m_cacheWriteBufferIndex = 0; + + sf_close(m_cacheFileWritePtr); + m_cacheFileWritePtr = 0; + + m_cacheFileReader = new WavFileReader(m_cacheFileName); + + if (!m_cacheFileReader->isOK()) { + std::cerr << "ERROR: CodedAudioFileReader::finishDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::endl; + delete m_cacheFileReader; + m_cacheFileReader = 0; + } + break; + + case CacheInMemory: + // nothing to do + break; + } +} + +void +CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const +{ + if (!m_initialised) return; + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + if (m_cacheFileReader) { + m_cacheFileReader->getInterleavedFrames(start, count, frames); + } + break; + + case CacheInMemory: + { + frames.clear(); + if (!isOK()) return; + if (count == 0) return; + + // slownessabounds + + for (size_t i = start; i < start + count; ++i) { + for (size_t ch = 0; ch < m_channelCount; ++ch) { + size_t index = i * m_channelCount + ch; + if (index >= m_data.size()) return; + frames.push_back(m_data[index]); + } + } + } + } +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/CodedAudioFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CodedAudioFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _CODED_AUDIO_FILE_READER_H_ +#define _CODED_AUDIO_FILE_READER_H_ + +#include "AudioFileReader.h" + +#include + +class WavFileReader; + +class CodedAudioFileReader : public AudioFileReader +{ +public: + virtual ~CodedAudioFileReader(); + + enum CacheMode { + CacheInTemporaryFile, + CacheInMemory + }; + + virtual void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const; + +protected: + CodedAudioFileReader(CacheMode cacheMode); + + void initialiseDecodeCache(); // samplerate, channels must have been set + void addSampleToDecodeCache(float sample); + void finishDecodeCache(); + bool isDecodeCacheInitialised() const { return m_initialised; } + + CacheMode m_cacheMode; + SampleBlock m_data; + bool m_initialised; + + QString m_cacheFileName; + SNDFILE *m_cacheFileWritePtr; + WavFileReader *m_cacheFileReader; + float *m_cacheWriteBuffer; + size_t m_cacheWriteBufferIndex; + size_t m_cacheWriteBufferSize; // frames +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/DataFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DATA_FILE_READER_H_ +#define _DATA_FILE_READER_H_ + +#include +#include + +class Model; + +class DataFileReader : public QObject +{ +public: + /** + * Return true if the file appears to be of the correct type. + * + * The DataFileReader will be constructed by passing a file path + * to its constructor. If the file can at that time be determined + * to be not of a type that this reader can read, it should return + * false in response to any subsequent call to isOK(). + * + * If the file is apparently of the correct type, isOK() should + * return true; if it turns out that the file cannot after all be + * read (because it's corrupted or the detection misfired), then + * the read() function may return NULL. + */ + virtual bool isOK() const = 0; + + virtual QString getError() const { return ""; } + + /** + * Read the file and return the corresponding data model. This + * function is not expected to be thread-safe or reentrant. This + * function may be interactive (i.e. it's permitted to pop up + * dialogs and windows and ask the user to specify any details + * that can't be automatically extracted from the file). + * + * Return NULL if the file cannot be parsed at all (although it's + * preferable to return a partial model and warn the user). + * + * Caller owns the returned model and must delete it after use. + */ + virtual Model *load() const = 0; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/DataFileReaderFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReaderFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,61 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "DataFileReaderFactory.h" +#include "MIDIFileReader.h" +#include "CSVFileReader.h" + +#include "model/Model.h" + +#include + +QString +DataFileReaderFactory::getKnownExtensions() +{ + return "*.svl *.csv *.lab *.mid *.txt"; +} + +DataFileReader * +DataFileReaderFactory::createReader(QString path, size_t mainModelSampleRate) +{ + QString err; + + DataFileReader *reader = 0; + + reader = new MIDIFileReader(path, mainModelSampleRate); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; + + reader = new CSVFileReader(path, mainModelSampleRate); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; + + return 0; +} + +Model * +DataFileReaderFactory::load(QString path, size_t mainModelSampleRate) +{ + DataFileReader *reader = createReader(path, mainModelSampleRate); + if (!reader) return NULL; + + Model *model = reader->load(); + delete reader; + + return model; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/DataFileReaderFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReaderFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,51 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DATA_FILE_READER_FACTORY_H_ +#define _DATA_FILE_READER_FACTORY_H_ + +#include + +class DataFileReader; +class Model; + +class DataFileReaderFactory +{ +public: + /** + * Return the file extensions that we have data file readers for, + * in a format suitable for use with QFileDialog. For example, + * "*.csv *.xml". + */ + static QString getKnownExtensions(); + + /** + * Return a data file reader initialised to the file at the + * given path, or NULL if no suitable reader for this path is + * available or the file cannot be opened. + * Caller owns the returned object and must delete it after use. + */ + static DataFileReader *createReader(QString path, + size_t mainModelSampleRate); + + /** + * Read the given path, if a suitable reader is available. + * Return NULL if no reader succeeded in reading this file. + */ + static Model *load(QString path, size_t mainModelSampleRate); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/FFTFuzzyAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFuzzyAdapter.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTFuzzyAdapter.h" + +#include + +FFTFuzzyAdapter::FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + m_server(0), + m_xshift(0), + m_yshift(0) +{ + m_server = FFTDataServer::getFuzzyInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + + size_t xratio = windowIncrement / m_server->getWindowIncrement(); + size_t yratio = m_server->getFFTSize() / fftSize; + + while (xratio > 1) { + if (xratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: Window increment ratio " + << windowIncrement << " / " + << m_server->getWindowIncrement() + << " must be a power of two" << std::endl; + assert(!(xratio & 0x1)); + } + ++m_xshift; + xratio >>= 1; + } + + while (yratio > 1) { + if (yratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: FFT size ratio " + << m_server->getFFTSize() << " / " << fftSize + << " must be a power of two" << std::endl; + assert(!(yratio & 0x1)); + } + ++m_yshift; + yratio >>= 1; + } +} + +FFTFuzzyAdapter::~FFTFuzzyAdapter() +{ + FFTDataServer::releaseInstance(m_server); +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/FFTFuzzyAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFuzzyAdapter.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_FUZZY_ADAPTER_H_ +#define _FFT_FUZZY_ADAPTER_H_ + +#include "FFTDataServer.h" + +class FFTFuzzyAdapter +{ +public: + FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + ~FFTFuzzyAdapter(); + + size_t getWidth() const { + return m_server->getWidth() >> m_xshift; + } + size_t getHeight() const { + return m_server->getHeight() >> m_yshift; + } + float getMagnitudeAt(size_t x, size_t y) { + return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getNormalizedMagnitudeAt(size_t x, size_t y) { + return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getMaximumMagnitudeAt(size_t x) { + return m_server->getMaximumMagnitudeAt(x << m_xshift); + } + float getPhaseAt(size_t x, size_t y) { + return m_server->getPhaseAt(x << m_xshift, y << m_yshift); + } + void getValuesAt(size_t x, size_t y, float &real, float &imaginary) { + m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary); + } + bool isColumnReady(size_t x) { + return m_server->isColumnReady(x << m_xshift); + } + bool isLocalPeak(size_t x, size_t y) { + float mag = getMagnitudeAt(x, y); + if (y > 0 && mag < getMagnitudeAt(x, y - 1)) return false; + if (y < getHeight() - 1 && mag < getMagnitudeAt(x, y + 1)) return false; + return true; + } + bool isOverThreshold(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const { return m_server->getFillCompletion(); } + size_t getFillExtent() const { return m_server->getFillExtent(); } + +private: + FFTFuzzyAdapter(const FFTFuzzyAdapter &); // not implemented + FFTFuzzyAdapter &operator=(const FFTFuzzyAdapter &); // not implemented + + FFTDataServer *m_server; + int m_xshift; + int m_yshift; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/FileFinder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileFinder.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,478 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FileFinder.h" +#include "RemoteFile.h" +#include "AudioFileReaderFactory.h" +#include "DataFileReaderFactory.h" + +#include +#include +#include +#include +#include + +#include + +FileFinder * +FileFinder::m_instance = 0; + +FileFinder::FileFinder() : + m_lastLocatedLocation("") +{ +} + +FileFinder::~FileFinder() +{ +} + +FileFinder * +FileFinder::getInstance() +{ + if (m_instance == 0) { + m_instance = new FileFinder(); + } + return m_instance; +} + +QString +FileFinder::getOpenFileName(FileType type, QString fallbackLocation) +{ + QString settingsKey; + QString lastPath = fallbackLocation; + + QString title = tr("Select file"); + QString filter = tr("All files (*.*)"); + + switch (type) { + + case SessionFile: + settingsKey = "sessionpath"; + title = tr("Select a session file"); + filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); + break; + + case AudioFile: + settingsKey = "audiopath"; + title = "Select an audio file"; + filter = tr("Audio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions()); + break; + + case LayerFile: + settingsKey = "layerpath"; + filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()); + break; + + case SessionOrAudioFile: + settingsKey = "lastpath"; + filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions()); + break; + + case ImageFile: + settingsKey = "imagepath"; + filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); + break; + + case AnyFile: + settingsKey = "lastpath"; + filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions()) + .arg(DataFileReaderFactory::getKnownExtensions()); + break; + }; + + if (lastPath == "") { + char *home = getenv("HOME"); + if (home) lastPath = home; + else lastPath = "."; + } else if (QFileInfo(lastPath).isDir()) { + lastPath = QFileInfo(lastPath).canonicalPath(); + } else { + lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); + } + + QSettings settings; + settings.beginGroup("FileFinder"); + lastPath = settings.value(settingsKey, lastPath).toString(); + + QString path = ""; + + // Use our own QFileDialog just for symmetry with getSaveFileName below + + QFileDialog dialog; + dialog.setFilters(filter.split('\n')); + dialog.setWindowTitle(title); + dialog.setDirectory(lastPath); + + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::ExistingFile); + + if (dialog.exec()) { + QStringList files = dialog.selectedFiles(); + if (!files.empty()) path = *files.begin(); + + QFileInfo fi(path); + + if (!fi.exists()) { + + QMessageBox::critical(0, tr("File does not exist"), + tr("File \"%1\" does not exist").arg(path)); + path = ""; + + } else if (!fi.isReadable()) { + + QMessageBox::critical(0, tr("File is not readable"), + tr("File \"%1\" can not be read").arg(path)); + path = ""; + + } else if (fi.isDir()) { + + QMessageBox::critical(0, tr("Directory selected"), + tr("File \"%1\" is a directory").arg(path)); + path = ""; + + } else if (!fi.isFile()) { + + QMessageBox::critical(0, tr("Non-file selected"), + tr("Path \"%1\" is not a file").arg(path)); + path = ""; + + } else if (fi.size() == 0) { + + QMessageBox::critical(0, tr("File is empty"), + tr("File \"%1\" is empty").arg(path)); + path = ""; + } + } + + if (path != "") { + settings.setValue(settingsKey, + QFileInfo(path).absoluteDir().canonicalPath()); + } + + return path; +} + +QString +FileFinder::getSaveFileName(FileType type, QString fallbackLocation) +{ + QString settingsKey; + QString lastPath = fallbackLocation; + + QString title = tr("Select file"); + QString filter = tr("All files (*.*)"); + + switch (type) { + + case SessionFile: + settingsKey = "savesessionpath"; + title = tr("Select a session file"); + filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); + break; + + case AudioFile: + settingsKey = "saveaudiopath"; + title = "Select an audio file"; + title = tr("Select a file to export to"); + filter = tr("WAV audio files (*.wav)\nAll files (*.*)"); + break; + + case LayerFile: + settingsKey = "savelayerpath"; + title = tr("Select a file to export to"); + filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)"); + break; + + case SessionOrAudioFile: + std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl; + abort(); + + case ImageFile: + settingsKey = "saveimagepath"; + title = tr("Select a file to export to"); + filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); + break; + + case AnyFile: + std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl; + abort(); + }; + + if (lastPath == "") { + char *home = getenv("HOME"); + if (home) lastPath = home; + else lastPath = "."; + } else if (QFileInfo(lastPath).isDir()) { + lastPath = QFileInfo(lastPath).canonicalPath(); + } else { + lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); + } + + QSettings settings; + settings.beginGroup("FileFinder"); + lastPath = settings.value(settingsKey, lastPath).toString(); + + QString path = ""; + + // Use our own QFileDialog instead of static functions, as we may + // need to adjust the file extension based on the selected filter + + QFileDialog dialog; + dialog.setFilters(filter.split('\n')); + dialog.setWindowTitle(title); + dialog.setDirectory(lastPath); + + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setConfirmOverwrite(false); // we'll do that + + if (type == SessionFile) { + dialog.setDefaultSuffix("sv"); + } else if (type == AudioFile) { + dialog.setDefaultSuffix("wav"); + } else if (type == ImageFile) { + dialog.setDefaultSuffix("png"); + } + + bool good = false; + + while (!good) { + + path = ""; + + if (!dialog.exec()) break; + + QStringList files = dialog.selectedFiles(); + if (files.empty()) break; + path = *files.begin(); + + QFileInfo fi(path); + + if (type == LayerFile && fi.suffix() == "") { + QString expectedExtension; + QString selectedFilter = dialog.selectedFilter(); + if (selectedFilter.contains(".svl")) { + expectedExtension = "svl"; + } else if (selectedFilter.contains(".txt")) { + expectedExtension = "txt"; + } else if (selectedFilter.contains(".csv")) { + expectedExtension = "csv"; + } + if (expectedExtension != "") { + path = QString("%1.%2").arg(path).arg(expectedExtension); + fi = QFileInfo(path); + } + } + + if (fi.isDir()) { + QMessageBox::critical(0, tr("Directory selected"), + tr("File \"%1\" is a directory").arg(path)); + continue; + } + + if (fi.exists()) { + if (QMessageBox::question(0, tr("File exists"), + tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), + QMessageBox::Ok, + QMessageBox::Cancel) != QMessageBox::Ok) { + continue; + } + } + + good = true; + } + + if (path != "") { + settings.setValue(settingsKey, + QFileInfo(path).absoluteDir().canonicalPath()); + } + + return path; +} + +void +FileFinder::registerLastOpenedFilePath(FileType type, QString path) +{ + QString settingsKey; + + switch (type) { + case SessionFile: + settingsKey = "sessionpath"; + break; + + case AudioFile: + settingsKey = "audiopath"; + break; + + case LayerFile: + settingsKey = "layerpath"; + break; + + case SessionOrAudioFile: + settingsKey = "lastpath"; + break; + + case ImageFile: + settingsKey = "imagepath"; + break; + + case AnyFile: + settingsKey = "lastpath"; + break; + } + + if (path != "") { + QSettings settings; + settings.beginGroup("FileFinder"); + path = QFileInfo(path).absoluteDir().canonicalPath(); + settings.setValue(settingsKey, path); + settings.setValue("lastpath", path); + } +} + +QString +FileFinder::find(FileType type, QString location, QString lastKnownLocation) +{ + if (QFileInfo(location).exists()) return location; + + if (RemoteFile::canHandleScheme(QUrl(location))) { + RemoteFile rf(location); + bool available = rf.isAvailable(); + rf.deleteLocalFile(); + if (available) return location; + } + + QString foundAt = ""; + + if ((foundAt = findRelative(location, lastKnownLocation)) != "") { + return foundAt; + } + + if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") { + return foundAt; + } + + return locateInteractive(type, location); +} + +QString +FileFinder::findRelative(QString location, QString relativeTo) +{ + if (relativeTo == "") return ""; + + std::cerr << "Looking for \"" << location.toStdString() << "\" next to \"" + << relativeTo.toStdString() << "\"..." << std::endl; + + QString fileName; + QString resolved; + + if (RemoteFile::canHandleScheme(QUrl(location))) { + fileName = QUrl(location).path().section('/', -1, -1, + QString::SectionSkipEmpty); + } else { + fileName = QFileInfo(location).fileName(); + } + + if (RemoteFile::canHandleScheme(QUrl(relativeTo))) { + resolved = QUrl(relativeTo).resolved(fileName).toString(); + RemoteFile rf(resolved); + if (!rf.isAvailable()) resolved = ""; + std::cerr << "resolved: " << resolved.toStdString() << std::endl; + rf.deleteLocalFile(); + } else { + resolved = QFileInfo(relativeTo).dir().filePath(fileName); + if (!QFileInfo(resolved).exists() || + !QFileInfo(resolved).isFile() || + !QFileInfo(resolved).isReadable()) { + resolved = ""; + } + } + + return resolved; +} + +QString +FileFinder::locateInteractive(FileType type, QString thing) +{ + QString question; + if (type == AudioFile) { + question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?"); + } else { + question = tr("File \"%1\" could not be opened.\nDo you want to locate it?"); + } + + QString path = ""; + bool done = false; + + while (!done) { + + int rv = QMessageBox::question + (0, + tr("Failed to open file"), + question.arg(thing), + tr("Locate file..."), + tr("Use URL..."), + tr("Cancel"), + 0, 2); + + switch (rv) { + + case 0: // Locate file + + if (QFileInfo(thing).dir().exists()) { + path = QFileInfo(thing).dir().canonicalPath(); + } + + path = getOpenFileName(type, path); + done = (path != ""); + break; + + case 1: // Use URL + { + bool ok = false; + path = QInputDialog::getText + (0, tr("Use URL"), + tr("Please enter the URL to use for this file:"), + QLineEdit::Normal, "", &ok); + + if (ok && path != "") { + RemoteFile rf(path); + if (rf.isAvailable()) { + done = true; + } else { + QMessageBox::critical + (0, tr("Failed to open location"), + tr("URL \"%1\" could not be opened").arg(path)); + path = ""; + } + rf.deleteLocalFile(); + } + break; + } + + case 2: // Cancel + path = ""; + done = true; + break; + } + } + + if (path != "") m_lastLocatedLocation = path; + return path; +} + + diff -r 000000000000 -r fc9323a41f5a data/fileio/FileFinder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileFinder.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FILE_FINDER_H_ +#define _FILE_FINDER_H_ + +#include +#include + +class FileFinder : public QObject +{ + Q_OBJECT + +public: + virtual ~FileFinder(); + + enum FileType { + SessionFile, + AudioFile, + LayerFile, + SessionOrAudioFile, + ImageFile, + AnyFile + }; + + QString getOpenFileName(FileType type, QString fallbackLocation = ""); + QString getSaveFileName(FileType type, QString fallbackLocation = ""); + void registerLastOpenedFilePath(FileType type, QString path); + + QString find(FileType type, QString location, QString lastKnownLocation = ""); + + static FileFinder *getInstance(); + +protected: + FileFinder(); + static FileFinder *m_instance; + + QString findRelative(QString location, QString relativeTo); + QString locateInteractive(FileType type, QString thing); + + QString m_lastLocatedLocation; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/FileReadThread.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileReadThread.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,299 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FileReadThread.h" + +#include "base/Profiler.h" + +#include +//#include + +//#define DEBUG_FILE_READ_THREAD 1 + +FileReadThread::FileReadThread() : + m_nextToken(0), + m_exiting(false) +{ +} + +void +FileReadThread::run() +{ + m_mutex.lock(); + + while (!m_exiting) { + if (m_queue.empty()) { + m_condition.wait(&m_mutex, 1000); + } else { + process(); + } + notifyCancelled(); + } + + notifyCancelled(); + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::run() exiting" << std::endl; +#endif +} + +void +FileReadThread::finish() +{ +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::finish()" << std::endl; +#endif + + m_mutex.lock(); + while (!m_queue.empty()) { + m_cancelledRequests[m_queue.begin()->first] = m_queue.begin()->second; + m_newlyCancelled.insert(m_queue.begin()->first); + m_queue.erase(m_queue.begin()); + } + + m_exiting = true; + m_mutex.unlock(); + + m_condition.wakeAll(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::finish() exiting" << std::endl; +#endif +} + +int +FileReadThread::request(const Request &request) +{ + m_mutex.lock(); + + int token = m_nextToken++; + m_queue[token] = request; + + m_mutex.unlock(); + m_condition.wakeAll(); + + return token; +} + +void +FileReadThread::cancel(int token) +{ + m_mutex.lock(); + + if (m_queue.find(token) != m_queue.end()) { + m_cancelledRequests[token] = m_queue[token]; + m_queue.erase(token); + m_newlyCancelled.insert(token); + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + m_cancelledRequests[token] = m_readyRequests[token]; + m_readyRequests.erase(token); + } else { + std::cerr << "WARNING: FileReadThread::cancel: token " << token << " not found" << std::endl; + } + + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::cancel(" << token << ") waking condition" << std::endl; +#endif + + m_condition.wakeAll(); +} + +bool +FileReadThread::isReady(int token) +{ + m_mutex.lock(); + + bool ready = m_readyRequests.find(token) != m_readyRequests.end(); + + m_mutex.unlock(); + return ready; +} + +bool +FileReadThread::isCancelled(int token) +{ + m_mutex.lock(); + + bool cancelled = + m_cancelledRequests.find(token) != m_cancelledRequests.end() && + m_newlyCancelled.find(token) == m_newlyCancelled.end(); + + m_mutex.unlock(); + return cancelled; +} + +bool +FileReadThread::getRequest(int token, Request &request) +{ + m_mutex.lock(); + + bool found = false; + + if (m_queue.find(token) != m_queue.end()) { + request = m_queue[token]; + found = true; + } else if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) { + request = m_cancelledRequests[token]; + found = true; + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + request = m_readyRequests[token]; + found = true; + } + + m_mutex.unlock(); + + return found; +} + +void +FileReadThread::done(int token) +{ + m_mutex.lock(); + + bool found = false; + + if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) { + m_cancelledRequests.erase(token); + m_newlyCancelled.erase(token); + found = true; + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + m_readyRequests.erase(token); + found = true; + } else if (m_queue.find(token) != m_queue.end()) { + std::cerr << "WARNING: FileReadThread::done(" << token << "): request is still in queue (wait or cancel it)" << std::endl; + } + + m_mutex.unlock(); + + if (!found) { + std::cerr << "WARNING: FileReadThread::done(" << token << "): request not found" << std::endl; + } +} + +void +FileReadThread::process() +{ + // entered with m_mutex locked and m_queue non-empty + +#ifdef DEBUG_FILE_READ_THREAD + Profiler profiler("FileReadThread::process()", true); +#endif + + int token = m_queue.begin()->first; + Request request = m_queue.begin()->second; + + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: reading " << request.start << ", " << request.size << " on " << request.fd << std::endl; +#endif + + bool successful = false; + bool seekFailed = false; + ssize_t r = 0; + + if (request.mutex) request.mutex->lock(); + + if (::lseek(request.fd, request.start, SEEK_SET) == (off_t)-1) { + seekFailed = true; + } else { + + // if request.size is large, we want to avoid making a single + // system call to read it all as it may block too much + + static const size_t blockSize = 256 * 1024; + + size_t size = request.size; + char *destination = request.data; + + while (size > 0) { + size_t readSize = size; + if (readSize > blockSize) readSize = blockSize; + ssize_t br = ::read(request.fd, destination, readSize); + if (br < 0) { + r = br; + break; + } else { + r += br; + if (br < ssize_t(readSize)) break; + } + destination += readSize; + size -= readSize; + } + } + + if (request.mutex) request.mutex->unlock(); + + if (seekFailed) { + ::perror("Seek failed"); + std::cerr << "ERROR: FileReadThread::process: seek to " + << request.start << " failed" << std::endl; + request.size = 0; + } else { + if (r < 0) { + ::perror("ERROR: FileReadThread::process: Read failed"); + request.size = 0; + } else if (r < ssize_t(request.size)) { + std::cerr << "WARNING: FileReadThread::process: read " + << request.size << " returned only " << r << " bytes" + << std::endl; + request.size = r; + usleep(100000); + } else { + successful = true; + } + } + + // Check that the token hasn't been cancelled and the thread + // hasn't been asked to finish + + m_mutex.lock(); + + request.successful = successful; + + if (m_queue.find(token) != m_queue.end() && !m_exiting) { + m_queue.erase(token); + m_readyRequests[token] = request; +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: done, marking as ready" << std::endl; +#endif + } else { +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: request disappeared or exiting" << std::endl; +#endif + } +} + +void +FileReadThread::notifyCancelled() +{ + // entered with m_mutex locked + + while (!m_newlyCancelled.empty()) { + + int token = *m_newlyCancelled.begin(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::notifyCancelled: token " << token << std::endl; +#endif + + m_newlyCancelled.erase(token); + } +} + + diff -r 000000000000 -r fc9323a41f5a data/fileio/FileReadThread.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileReadThread.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FILE_READ_THREAD_H_ +#define _FILE_READ_THREAD_H_ + +#include "base/Thread.h" + +#include +#include + +#include +#include + +//#include + +class FileReadThread : public Thread +{ + Q_OBJECT + +public: + FileReadThread(); + + virtual void run(); + virtual void finish(); + + struct Request { + int fd; + QMutex *mutex; // used to synchronise access to fd; may be null + off_t start; + size_t size; + char *data; // caller is responsible for allocating and deallocating + bool successful; // set by FileReadThread after processing request + }; + + virtual int request(const Request &request); + virtual void cancel(int token); + + virtual bool isReady(int token); + virtual bool isCancelled(int token); // and safe to delete + virtual bool getRequest(int token, Request &request); + virtual void done(int token); + +protected: + int m_nextToken; + bool m_exiting; + + typedef std::map RequestQueue; + RequestQueue m_queue; + RequestQueue m_cancelledRequests; + RequestQueue m_readyRequests; + std::set m_newlyCancelled; + + QMutex m_mutex; + QWaitCondition m_condition; + + void process(); + void notifyCancelled(); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/MIDIFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MIDIFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1248 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Richard Bown and Chris Cannam. +*/ + + +#include +#include +#include +#include +#include + +#include "MIDIFileReader.h" + +#include "model/Model.h" +#include "base/Pitch.h" +#include "base/RealTime.h" +#include "model/NoteModel.h" + +#include +#include +#include + +#include + +using std::string; +using std::ifstream; +using std::stringstream; +using std::cerr; +using std::endl; +using std::ends; +using std::ios; +using std::vector; +using std::map; +using std::set; + +//#define MIDI_DEBUG 1 + +static const char *const MIDI_FILE_HEADER = "MThd"; +static const char *const MIDI_TRACK_HEADER = "MTrk"; + +static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK = 0x80; +static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F; +static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF = 0x80; +static const MIDIFileReader::MIDIByte MIDI_NOTE_ON = 0x90; +static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0; +static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE = 0xB0; +static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE = 0xC0; +static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0; +static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND = 0xE0; +static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0; +static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0; +static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1; +static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR = 0xF2; +static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT = 0xF3; +static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST = 0xF6; +static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7; +static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK = 0xF8; +static const MIDIFileReader::MIDIByte MIDI_START = 0xFA; +static const MIDIFileReader::MIDIByte MIDI_CONTINUE = 0xFB; +static const MIDIFileReader::MIDIByte MIDI_STOP = 0xFC; +static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING = 0xFE; +static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET = 0xFF; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT = 0x7E; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT = 0x7F; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07; +static const MIDIFileReader::MIDIByte MIDI_MMC_STOP = 0x01; +static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY = 0x02; +static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03; +static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD = 0x04; +static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND = 0x05; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE = 0x06; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT = 0x07; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08; +static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE = 0x08; +static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT = 0x0A; +static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE = 0x44; +static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT = 0xFF; +static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER = 0x00; +static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT = 0x01; +static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02; +static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME = 0x03; +static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME = 0x04; +static const MIDIFileReader::MIDIByte MIDI_LYRIC = 0x05; +static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER = 0x06; +static const MIDIFileReader::MIDIByte MIDI_CUE_POINT = 0x07; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX = 0x20; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21; +static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK = 0x2F; +static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO = 0x51; +static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET = 0x54; +static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE = 0x58; +static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE = 0x59; +static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME = 0x07; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION = 0x01; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN = 0x0A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE = 0x48; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK = 0x49; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER = 0x4A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB = 0x5B; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET = 0x79; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; +static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL = 9; + +class MIDIEvent +{ +public: + typedef MIDIFileReader::MIDIByte MIDIByte; + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + MIDIByte data1 = 0, + MIDIByte data2 = 0) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(data1), + m_data2(data2), + m_metaEventCode(0) + { } + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + MIDIByte metaEventCode, + const string &metaMessage) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(0), + m_data2(0), + m_metaEventCode(metaEventCode), + m_metaMessage(metaMessage) + { } + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + const string &sysEx) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(0), + m_data2(0), + m_metaEventCode(0), + m_metaMessage(sysEx) + { } + + ~MIDIEvent() { } + + void setTime(const unsigned long &time) { m_deltaTime = time; } + void setDuration(const unsigned long& duration) { m_duration = duration;} + unsigned long addTime(const unsigned long &time) { + m_deltaTime += time; + return m_deltaTime; + } + + MIDIByte getMessageType() const + { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); } + + MIDIByte getChannelNumber() const + { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); } + + unsigned long getTime() const { return m_deltaTime; } + unsigned long getDuration() const { return m_duration; } + + MIDIByte getPitch() const { return m_data1; } + MIDIByte getVelocity() const { return m_data2; } + MIDIByte getData1() const { return m_data1; } + MIDIByte getData2() const { return m_data2; } + MIDIByte getEventCode() const { return m_eventCode; } + + bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); } + + MIDIByte getMetaEventCode() const { return m_metaEventCode; } + string getMetaMessage() const { return m_metaMessage; } + void setMetaMessage(const string &meta) { m_metaMessage = meta; } + + friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); + +private: + MIDIEvent& operator=(const MIDIEvent); + + unsigned long m_deltaTime; + unsigned long m_duration; + MIDIByte m_eventCode; + MIDIByte m_data1; // or Note + MIDIByte m_data2; // or Velocity + MIDIByte m_metaEventCode; + string m_metaMessage; +}; + +// Comparator for sorting +// +struct MIDIEventCmp +{ + bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const + { return mE1.getTime() < mE2.getTime(); } + + bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const + { return mE1->getTime() < mE2->getTime(); } +}; + +class MIDIException : virtual public std::exception +{ +public: + MIDIException(QString message) throw() : m_message(message) { + cerr << "WARNING: MIDI exception: " + << message.toLocal8Bit().data() << endl; + } + virtual ~MIDIException() throw() { } + + virtual const char *what() const throw() { + return m_message.toLocal8Bit().data(); + } + +protected: + QString m_message; +}; + + +MIDIFileReader::MIDIFileReader(QString path, + size_t mainModelSampleRate) : + m_timingDivision(0), + m_format(MIDI_FILE_BAD_FORMAT), + m_numberOfTracks(0), + m_trackByteCount(0), + m_decrementCount(false), + m_path(path), + m_midiFile(0), + m_fileSize(0), + m_mainModelSampleRate(mainModelSampleRate) +{ + if (parseFile()) { + m_error = ""; + } +} + +MIDIFileReader::~MIDIFileReader() +{ + for (MIDIComposition::iterator i = m_midiComposition.begin(); + i != m_midiComposition.end(); ++i) { + + for (MIDITrack::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + delete *j; + } + + i->second.clear(); + } + + m_midiComposition.clear(); +} + +bool +MIDIFileReader::isOK() const +{ + return (m_error == ""); +} + +QString +MIDIFileReader::getError() const +{ + return m_error; +} + +long +MIDIFileReader::midiBytesToLong(const string& bytes) +{ + if (bytes.length() != 4) { + throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4)); + } + + long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) | + ((long)(((MIDIByte)bytes[1]) << 16)) | + ((long)(((MIDIByte)bytes[2]) << 8)) | + ((long)((MIDIByte)(bytes[3]))); + + return longRet; +} + +int +MIDIFileReader::midiBytesToInt(const string& bytes) +{ + if (bytes.length() != 2) { + throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2)); + } + + int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) | + ((int)(((MIDIByte)bytes[1]))); + return(intRet); +} + + +// Gets a single byte from the MIDI byte stream. For each track +// section we can read only a specified number of bytes held in +// m_trackByteCount. +// +MIDIFileReader::MIDIByte +MIDIFileReader::getMIDIByte() +{ + if (!m_midiFile) { + throw MIDIException(tr("getMIDIByte called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && m_trackByteCount <= 0) { + throw MIDIException(tr("Attempt to get more bytes than expected on Track")); + } + + char byte; + if (m_midiFile->read(&byte, 1)) { + --m_trackByteCount; + return (MIDIByte)byte; + } + + throw MIDIException(tr("Attempt to read past MIDI file end")); +} + + +// Gets a specified number of bytes from the MIDI byte stream. For +// each track section we can read only a specified number of bytes +// held in m_trackByteCount. +// +string +MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes) +{ + if (!m_midiFile) { + throw MIDIException(tr("getMIDIBytes called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { + throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount)); + } + + string stringRet; + char fileMIDIByte; + + while (stringRet.length() < numberOfBytes && + m_midiFile->read(&fileMIDIByte, 1)) { + stringRet += fileMIDIByte; + } + + // if we've reached the end of file without fulfilling the + // quota then panic as our parsing has performed incorrectly + // + if (stringRet.length() < numberOfBytes) { + stringRet = ""; + throw MIDIException(tr("Attempt to read past MIDI file end")); + } + + // decrement the byte count + if (m_decrementCount) + m_trackByteCount -= stringRet.length(); + + return stringRet; +} + + +// Get a long number of variable length from the MIDI byte stream. +// +long +MIDIFileReader::getNumberFromMIDIBytes(int firstByte) +{ + if (!m_midiFile) { + throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open")); + } + + long longRet = 0; + MIDIByte midiByte; + + if (firstByte >= 0) { + midiByte = (MIDIByte)firstByte; + } else if (m_midiFile->eof()) { + return longRet; + } else { + midiByte = getMIDIByte(); + } + + longRet = midiByte; + if (midiByte & 0x80) { + longRet &= 0x7F; + do { + midiByte = getMIDIByte(); + longRet = (longRet << 7) + (midiByte & 0x7F); + } while (!m_midiFile->eof() && (midiByte & 0x80)); + } + + return longRet; +} + + +// Seek to the next track in the midi file and set the number +// of bytes to be read in the counter m_trackByteCount. +// +bool +MIDIFileReader::skipToNextTrack() +{ + if (!m_midiFile) { + throw MIDIException(tr("skipToNextTrack called but no MIDI file open")); + } + + string buffer, buffer2; + m_trackByteCount = -1; + m_decrementCount = false; + + while (!m_midiFile->eof() && (m_decrementCount == false)) { + buffer = getMIDIBytes(4); + if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) { + m_trackByteCount = midiBytesToLong(getMIDIBytes(4)); + m_decrementCount = true; + } + } + + if (m_trackByteCount == -1) { // we haven't found a track + return false; + } else { + return true; + } +} + + +// Read in a MIDI file. The parsing process throws exceptions back up +// here if we run into trouble which we can then pass back out to +// whoever called us using a nice bool. +// +bool +MIDIFileReader::parseFile() +{ + m_error = ""; + +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl; +#endif + + // Open the file + m_midiFile = new ifstream(m_path.toLocal8Bit().data(), + ios::in | ios::binary); + + if (!*m_midiFile) { + m_error = "File not found or not readable."; + m_format = MIDI_FILE_BAD_FORMAT; + delete m_midiFile; + return false; + } + + bool retval = false; + + try { + + // Set file size so we can count it off + // + m_midiFile->seekg(0, ios::end); + m_fileSize = m_midiFile->tellg(); + m_midiFile->seekg(0, ios::beg); + + // Parse the MIDI header first. The first 14 bytes of the file. + if (!parseHeader(getMIDIBytes(14))) { + m_format = MIDI_FILE_BAD_FORMAT; + m_error = "Not a MIDI file."; + goto done; + } + + unsigned int i = 0; + + for (unsigned int j = 0; j < m_numberOfTracks; ++j) { + +#ifdef MIDI_DEBUG + cerr << "Parsing Track " << j << endl; +#endif + + if (!skipToNextTrack()) { +#ifdef MIDI_DEBUG + cerr << "Couldn't find Track " << j << endl; +#endif + m_error = "File corrupted or in non-standard format?"; + m_format = MIDI_FILE_BAD_FORMAT; + goto done; + } + +#ifdef MIDI_DEBUG + cerr << "Track has " << m_trackByteCount << " bytes" << endl; +#endif + + // Run through the events taking them into our internal + // representation. + if (!parseTrack(i)) { +#ifdef MIDI_DEBUG + cerr << "Track " << j << " parsing failed" << endl; +#endif + m_error = "File corrupted or in non-standard format?"; + m_format = MIDI_FILE_BAD_FORMAT; + goto done; + } + + ++i; // j is the source track number, i the destination + } + + m_numberOfTracks = i; + retval = true; + + } catch (MIDIException e) { + + cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl; + m_error = e.what(); + } + +done: + m_midiFile->close(); + delete m_midiFile; + + for (unsigned int track = 0; track < m_numberOfTracks; ++track) { + + // Convert the deltaTime to an absolute time since the track + // start. The addTime method returns the sum of the current + // MIDI Event delta time plus the argument. + + unsigned long acc = 0; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); ++i) { + acc = (*i)->addTime(acc); + } + + if (consolidateNoteOffEvents(track)) { // returns true if some notes exist + m_loadableTracks.insert(track); + } + } + + for (unsigned int track = 0; track < m_numberOfTracks; ++track) { + updateTempoMap(track); + } + + calculateTempoTimestamps(); + + return retval; +} + +// Parse and ensure the MIDI Header is legitimate +// +bool +MIDIFileReader::parseHeader(const string &midiHeader) +{ + if (midiHeader.size() < 14) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; +#endif + return false; + } + + if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << "- file header not found or malformed" + << endl; +#endif + return false; + } + + if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << " - header length incorrect" + << endl; +#endif + return false; + } + + m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2)); + m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2)); + m_timingDivision = midiBytesToInt(midiHeader.substr(12,2)); + + if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << "- can't load sequential track file" + << endl; +#endif + return false; + } + +#ifdef MIDI_DEBUG + if (m_timingDivision < 0) { + cerr << "MIDIFileReader::parseHeader()" + << " - file uses SMPTE timing" + << endl; + } +#endif + + return true; +} + +// Extract the contents from a MIDI file track and places it into +// our local map of MIDI events. +// +bool +MIDIFileReader::parseTrack(unsigned int &lastTrackNum) +{ + MIDIByte midiByte, metaEventCode, data1, data2; + MIDIByte eventCode = 0x80; + string metaMessage; + unsigned int messageLength; + unsigned long deltaTime; + unsigned long accumulatedTime = 0; + + // The trackNum passed in to this method is the default track for + // all events provided they're all on the same channel. If we find + // events on more than one channel, we increment trackNum and record + // the mapping from channel to trackNum in this channelTrackMap. + // We then return the new trackNum by reference so the calling + // method knows we've got more tracks than expected. + + // This would be a vector but we need -1 to indicate + // "not yet used" + vector channelTrackMap(16, -1); + + // This is used to store the last absolute time found on each track, + // allowing us to modify delta-times correctly when separating events + // out from one to multiple tracks + // + map trackTimeMap; + + // Meta-events don't have a channel, so we place them in a fixed + // track number instead + unsigned int metaTrack = lastTrackNum; + + // Remember the last non-meta status byte (-1 if we haven't seen one) + int runningStatus = -1; + + bool firstTrack = true; + + while (!m_midiFile->eof() && (m_trackByteCount > 0)) { + + if (eventCode < 0x80) { +#ifdef MIDI_DEBUG + cerr << "WARNING: Invalid event code " << eventCode + << " in MIDI file" << endl; +#endif + throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); + } + + deltaTime = getNumberFromMIDIBytes(); + +#ifdef MIDI_DEBUG + cerr << "read delta time " << deltaTime << endl; +#endif + + // Get a single byte + midiByte = getMIDIByte(); + + if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { + + if (runningStatus < 0) { + throw MIDIException(tr("Running status used for first event in track")); + } + + eventCode = (MIDIByte)runningStatus; + data1 = midiByte; + +#ifdef MIDI_DEBUG + cerr << "using running status (byte " << int(midiByte) << " found)" << endl; +#endif + } else { +#ifdef MIDI_DEBUG + cerr << "have new event code " << int(midiByte) << endl; +#endif + eventCode = midiByte; + data1 = getMIDIByte(); + } + + if (eventCode == MIDI_FILE_META_EVENT) { + + metaEventCode = data1; + messageLength = getNumberFromMIDIBytes(); + +//#ifdef MIDI_DEBUG + cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; +//#endif + metaMessage = getMIDIBytes(messageLength); + + long gap = accumulatedTime - trackTimeMap[metaTrack]; + accumulatedTime += deltaTime; + deltaTime += gap; + trackTimeMap[metaTrack] = accumulatedTime; + + MIDIEvent *e = new MIDIEvent(deltaTime, + MIDI_FILE_META_EVENT, + metaEventCode, + metaMessage); + + m_midiComposition[metaTrack].push_back(e); + + if (metaEventCode == MIDI_TRACK_NAME) { + m_trackNames[metaTrack] = metaMessage.c_str(); + } + + } else { // non-meta events + + runningStatus = eventCode; + + MIDIEvent *midiEvent; + + int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); + if (channelTrackMap[channel] == -1) { + if (!firstTrack) ++lastTrackNum; + else firstTrack = false; + channelTrackMap[channel] = lastTrackNum; + } + + unsigned int trackNum = channelTrackMap[channel]; + + // accumulatedTime is abs time of last event on any track; + // trackTimeMap[trackNum] is that of last event on this track + + long gap = accumulatedTime - trackTimeMap[trackNum]; + accumulatedTime += deltaTime; + deltaTime += gap; + trackTimeMap[trackNum] = accumulatedTime; + + switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { + + case MIDI_NOTE_ON: + case MIDI_NOTE_OFF: + case MIDI_POLY_AFTERTOUCH: + case MIDI_CTRL_CHANGE: + data2 = getMIDIByte(); + + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); + + /* + cerr << "MIDI event for channel " << channel << " (track " + << trackNum << ")" << endl; + midiEvent->print(); + */ + + + m_midiComposition[trackNum].push_back(midiEvent); + + if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) { + m_percussionTracks.insert(trackNum); + } + + break; + + case MIDI_PITCH_BEND: + data2 = getMIDIByte(); + + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_PROG_CHANGE: + case MIDI_CHNL_AFTERTOUCH: + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_SYSTEM_EXCLUSIVE: + messageLength = getNumberFromMIDIBytes(data1); + +#ifdef MIDI_DEBUG + cerr << "SysEx of " << messageLength << " bytes found" << endl; +#endif + + metaMessage= getMIDIBytes(messageLength); + + if (MIDIByte(metaMessage[metaMessage.length() - 1]) != + MIDI_END_OF_EXCLUSIVE) + { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack() - " + << "malformed or unsupported SysEx type" + << endl; +#endif + continue; + } + + // chop off the EOX + // length fixed by Pedro Lopez-Cabanillas (20030523) + // + metaMessage = metaMessage.substr(0, metaMessage.length()-1); + + midiEvent = new MIDIEvent(deltaTime, + MIDI_SYSTEM_EXCLUSIVE, + metaMessage); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_END_OF_EXCLUSIVE: +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack() - " + << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; +#endif + break; + + default: +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack()" + << " - Unsupported MIDI Event Code: " + << (int)eventCode << endl; +#endif + break; + } + } + } + + if (lastTrackNum > metaTrack) { + for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) { + m_trackNames[track] = QString("%1 <%2>") + .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1); + } + } + + return true; +} + +// Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after +// reading them and modifying their relevant NOTE ONs. Return true +// if there are some notes in this track. +// +bool +MIDIFileReader::consolidateNoteOffEvents(unsigned int track) +{ + bool notesOnTrack = false; + bool noteOffFound; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); i++) { + + if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) { + + notesOnTrack = true; + noteOffFound = false; + + for (MIDITrack::iterator j = i; + j != m_midiComposition[track].end(); j++) { + + if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) && + ((*j)->getPitch() == (*i)->getPitch()) && + ((*j)->getMessageType() == MIDI_NOTE_OFF || + ((*j)->getMessageType() == MIDI_NOTE_ON && + (*j)->getVelocity() == 0x00))) { + + (*i)->setDuration((*j)->getTime() - (*i)->getTime()); + + delete *j; + m_midiComposition[track].erase(j); + + noteOffFound = true; + break; + } + } + + // If no matching NOTE OFF has been found then set + // Event duration to length of track + // + if (!noteOffFound) { + MIDITrack::iterator j = m_midiComposition[track].end(); + --j; + (*i)->setDuration((*j)->getTime() - (*i)->getTime()); + } + } + } + + return notesOnTrack; +} + +// Add any tempo events found in the given track to the global tempo map. +// +void +MIDIFileReader::updateTempoMap(unsigned int track) +{ + std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); ++i) { + + if ((*i)->isMeta() && + (*i)->getMetaEventCode() == MIDI_SET_TEMPO) { + + MIDIByte m0 = (*i)->getMetaMessage()[0]; + MIDIByte m1 = (*i)->getMetaMessage()[1]; + MIDIByte m2 = (*i)->getMetaMessage()[2]; + + long tempo = (((m0 << 8) + m1) << 8) + m2; + + std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl; + + if (tempo != 0) { + double qpm = 60000000.0 / double(tempo); + m_tempoMap[(*i)->getTime()] = + TempoChange(RealTime::zeroTime, qpm); + } + } + } +} + +void +MIDIFileReader::calculateTempoTimestamps() +{ + unsigned long lastMIDITime = 0; + RealTime lastRealTime = RealTime::zeroTime; + double tempo = 120.0; + int td = m_timingDivision; + if (td == 0) td = 96; + + for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) { + + unsigned long mtime = i->first; + unsigned long melapsed = mtime - lastMIDITime; + double quarters = double(melapsed) / double(td); + double seconds = (60.0 * quarters) / tempo; + + RealTime t = lastRealTime + RealTime::fromSeconds(seconds); + + i->second.first = t; + + lastRealTime = t; + lastMIDITime = mtime; + tempo = i->second.second; + } +} + +RealTime +MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const +{ + unsigned long tempoMIDITime = 0; + RealTime tempoRealTime = RealTime::zeroTime; + double tempo = 120.0; + + TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime); + if (i != m_tempoMap.begin()) { + --i; + tempoMIDITime = i->first; + tempoRealTime = i->second.first; + tempo = i->second.second; + } + + int td = m_timingDivision; + if (td == 0) td = 96; + + unsigned long melapsed = midiTime - tempoMIDITime; + double quarters = double(melapsed) / double(td); + double seconds = (60.0 * quarters) / tempo; + +/* + std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" + << std::endl; + std::cerr << "timing division = " << td << std::endl; + std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" + << tempoRealTime << ")" << std::endl; + std::cerr << "quarters since then = " << quarters << std::endl; + std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl; + std::cerr << "seconds since then = " << seconds << std::endl; + std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl; +*/ + + return tempoRealTime + RealTime::fromSeconds(seconds); +} + +Model * +MIDIFileReader::load() const +{ + if (!isOK()) return 0; + + if (m_loadableTracks.empty()) { + QMessageBox::critical(0, tr("No notes in MIDI file"), + tr("MIDI file \"%1\" has no notes in any track") + .arg(m_path)); + return 0; + } + + std::set tracksToLoad; + + if (m_loadableTracks.size() == 1) { + + tracksToLoad.insert(*m_loadableTracks.begin()); + + } else { + + QStringList available; + QString allTracks = tr("Merge all tracks"); + QString allNonPercussion = tr("Merge all non-percussion tracks"); + + int nonTrackItems = 1; + + available << allTracks; + + if (!m_percussionTracks.empty() && + (m_percussionTracks.size() < m_loadableTracks.size())) { + available << allNonPercussion; + ++nonTrackItems; + } + + for (set::const_iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + unsigned int trackNo = *i; + QString label; + + QString perc; + if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) { + perc = tr(" - uses GM percussion channel"); + } + + if (m_trackNames.find(trackNo) != m_trackNames.end()) { + label = tr("Track %1 (%2)%3") + .arg(trackNo).arg(m_trackNames.find(trackNo)->second) + .arg(perc); + } else { + label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc); + } + available << label; + } + + bool ok = false; + QString selected = QInputDialog::getItem + (0, tr("Select track or tracks to import"), + tr("You can only import this file as a single annotation layer,\nbut the file contains more than one track,\nor notes on more than one channel.\n\nPlease select the track or merged tracks you wish to import:"), + available, 0, false, &ok); + + if (!ok || selected.isEmpty()) return 0; + + if (selected == allTracks || selected == allNonPercussion) { + + for (set::const_iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + if (selected == allTracks || + m_percussionTracks.find(*i) == m_percussionTracks.end()) { + + tracksToLoad.insert(*i); + } + } + + } else { + + int j = nonTrackItems; + + for (set::const_iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + if (selected == available[j]) { + tracksToLoad.insert(*i); + break; + } + + ++j; + } + } + } + + if (tracksToLoad.empty()) return 0; + + size_t n = tracksToLoad.size(), count = 0; + Model *model = 0; + + for (std::set::iterator i = tracksToLoad.begin(); + i != tracksToLoad.end(); ++i) { + + int minProgress = (100 * count) / n; + int progressAmount = 100 / n; + + model = loadTrack(*i, model, minProgress, progressAmount); + + ++count; + } + + if (dynamic_cast(model)) { + dynamic_cast(model)->setCompletion(100); + } + + return model; +} + +Model * +MIDIFileReader::loadTrack(unsigned int trackToLoad, + Model *existingModel, + int minProgress, + int progressAmount) const +{ + if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) { + return 0; + } + + NoteModel *model = 0; + + if (existingModel) { + model = dynamic_cast(existingModel); + if (!model) { + std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl; + } + } + + if (!model) { + model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); + model->setValueQuantization(1.0); + } + + const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; + + size_t totalEvents = track.size(); + size_t count = 0; + + bool minorKey = false; + bool sharpKey = true; + + for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) { + + RealTime rt = getTimeForMIDITime((*i)->getTime()); + + // We ignore most of these event types for now, though in + // theory some of the text ones could usefully be incorporated + + if ((*i)->isMeta()) { + + switch((*i)->getMetaEventCode()) { + + case MIDI_KEY_SIGNATURE: + minorKey = (int((*i)->getMetaMessage()[1]) != 0); + sharpKey = (int((*i)->getMetaMessage()[0]) >= 0); + break; + + case MIDI_TEXT_EVENT: + case MIDI_LYRIC: + case MIDI_TEXT_MARKER: + case MIDI_COPYRIGHT_NOTICE: + case MIDI_TRACK_NAME: + // The text events that we could potentially use + break; + + case MIDI_SET_TEMPO: + // Already dealt with in a separate pass previously + break; + + case MIDI_TIME_SIGNATURE: + // Not yet! + break; + + case MIDI_SEQUENCE_NUMBER: + case MIDI_CHANNEL_PREFIX_OR_PORT: + case MIDI_INSTRUMENT_NAME: + case MIDI_CUE_POINT: + case MIDI_CHANNEL_PREFIX: + case MIDI_SEQUENCER_SPECIFIC: + case MIDI_SMPTE_OFFSET: + default: + break; + } + + } else { + + switch ((*i)->getMessageType()) { + + case MIDI_NOTE_ON: + + if ((*i)->getVelocity() == 0) break; // effective note-off + else { + RealTime endRT = getTimeForMIDITime((*i)->getTime() + + (*i)->getDuration()); + + long startFrame = RealTime::realTime2Frame + (rt, model->getSampleRate()); + + long endFrame = RealTime::realTime2Frame + (endRT, model->getSampleRate()); + + QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(), + 0, + !sharpKey); + + QString noteLabel = tr("%1 - vel %2") + .arg(pitchLabel).arg(int((*i)->getVelocity())); + + Note note(startFrame, (*i)->getPitch(), + endFrame - startFrame, noteLabel); + +// std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl; + + model->addPoint(note); + break; + } + + case MIDI_PITCH_BEND: + // I guess we could make some use of this... + break; + + case MIDI_NOTE_OFF: + case MIDI_PROG_CHANGE: + case MIDI_CTRL_CHANGE: + case MIDI_SYSTEM_EXCLUSIVE: + case MIDI_POLY_AFTERTOUCH: + case MIDI_CHNL_AFTERTOUCH: + break; + + default: + break; + } + } + + model->setCompletion(minProgress + + (count * progressAmount) / totalEvents); + ++count; + } + + return model; +} + + diff -r 000000000000 -r fc9323a41f5a data/fileio/MIDIFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MIDIFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,110 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Richard Bown and Chris Cannam. +*/ + +#ifndef _MIDI_FILE_READER_H_ +#define _MIDI_FILE_READER_H_ + +#include "DataFileReader.h" +#include "base/RealTime.h" + +#include +#include +#include + +#include + +class MIDIEvent; + +class MIDIFileReader : public DataFileReader +{ + Q_OBJECT + +public: + MIDIFileReader(QString path, size_t mainModelSampleRate); + virtual ~MIDIFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Model *load() const; + + typedef unsigned char MIDIByte; + +protected: + typedef std::vector MIDITrack; + typedef std::map MIDIComposition; + typedef std::pair TempoChange; // time, qpm + typedef std::map TempoMap; // key is MIDI time + + typedef enum { + MIDI_SINGLE_TRACK_FILE = 0x00, + MIDI_SIMULTANEOUS_TRACK_FILE = 0x01, + MIDI_SEQUENTIAL_TRACK_FILE = 0x02, + MIDI_FILE_BAD_FORMAT = 0xFF + } MIDIFileFormatType; + + bool parseFile(); + bool parseHeader(const std::string &midiHeader); + bool parseTrack(unsigned int &trackNum); + + Model *loadTrack(unsigned int trackNum, + Model *existingModel = 0, + int minProgress = 0, + int progressAmount = 100) const; + + bool consolidateNoteOffEvents(unsigned int track); + void updateTempoMap(unsigned int track); + void calculateTempoTimestamps(); + RealTime getTimeForMIDITime(unsigned long midiTime) const; + + // Internal convenience functions + // + int midiBytesToInt(const std::string &bytes); + long midiBytesToLong(const std::string &bytes); + + long getNumberFromMIDIBytes(int firstByte = -1); + + MIDIByte getMIDIByte(); + std::string getMIDIBytes(unsigned long bytes); + + bool skipToNextTrack(); + + int m_timingDivision; // pulses per quarter note + MIDIFileFormatType m_format; + unsigned int m_numberOfTracks; + + long m_trackByteCount; + bool m_decrementCount; + + std::map m_trackNames; + std::set m_loadableTracks; + std::set m_percussionTracks; + MIDIComposition m_midiComposition; + TempoMap m_tempoMap; + + QString m_path; + std::ifstream *m_midiFile; + size_t m_fileSize; + QString m_error; + size_t m_mainModelSampleRate; +}; + + +#endif // _MIDI_FILE_READER_H_ diff -r 000000000000 -r fc9323a41f5a data/fileio/MP3FileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MP3FileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,250 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_MAD + +#include "MP3FileReader.h" +#include "system/System.h" + +#include +#include +#include + +#include + +#include +#include +#include + +MP3FileReader::MP3FileReader(QString path, bool showProgress, CacheMode mode) : + CodedAudioFileReader(mode), + m_path(path) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + m_fileSize = 0; + m_bitrateNum = 0; + m_bitrateDenom = 0; + m_frameCount = 0; + m_cancelled = false; + + struct stat stat; + if (::stat(path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) { + m_error = QString("File %1 does not exist.").arg(path); + return; + } + + m_fileSize = stat.st_size; + + int fd = -1; + if ((fd = ::open(path.toLocal8Bit().data(), O_RDONLY +#ifdef _WIN32 + | O_BINARY +#endif + , 0)) < 0) { + m_error = QString("Failed to open file %1 for reading.").arg(path); + return; + } + + unsigned char *filebuffer = 0; + + try { + filebuffer = new unsigned char[m_fileSize]; + } catch (...) { + m_error = QString("Out of memory"); + ::close(fd); + return; + } + + ssize_t sz = 0; + size_t offset = 0; + while (offset < m_fileSize) { + sz = ::read(fd, filebuffer + offset, m_fileSize - offset); + if (sz < 0) { + m_error = QString("Read error for file %1 (after %2 bytes)") + .arg(path).arg(offset); + delete[] filebuffer; + ::close(fd); + return; + } else if (sz == 0) { + std::cerr << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes") + .arg(offset).arg(m_fileSize).toStdString() << std::endl; + m_fileSize = offset; + break; + } + offset += sz; + } + + ::close(fd); + + if (showProgress) { + m_progress = new QProgressDialog + (QObject::tr("Decoding %1...").arg(QFileInfo(path).fileName()), + QObject::tr("Stop"), 0, 100); + m_progress->hide(); + } + + if (!decode(filebuffer, m_fileSize)) { + m_error = QString("Failed to decode file %1.").arg(path); + delete[] filebuffer; + return; + } + + if (isDecodeCacheInitialised()) finishDecodeCache(); + + if (showProgress) { + delete m_progress; + m_progress = 0; + } + + delete[] filebuffer; +} + +MP3FileReader::~MP3FileReader() +{ +} + +bool +MP3FileReader::decode(void *mm, size_t sz) +{ + DecoderData data; + struct mad_decoder decoder; + + data.start = (unsigned char const *)mm; + data.length = (unsigned long)sz; + data.reader = this; + + mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0); + mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); + mad_decoder_finish(&decoder); + + return true; +} + +enum mad_flow +MP3FileReader::input(void *dp, struct mad_stream *stream) +{ + DecoderData *data = (DecoderData *)dp; + + if (!data->length) return MAD_FLOW_STOP; + mad_stream_buffer(stream, data->start, data->length); + data->length = 0; + + return MAD_FLOW_CONTINUE; +} + +enum mad_flow +MP3FileReader::output(void *dp, + struct mad_header const *header, + struct mad_pcm *pcm) +{ + DecoderData *data = (DecoderData *)dp; + return data->reader->accept(header, pcm); +} + +enum mad_flow +MP3FileReader::accept(struct mad_header const *header, + struct mad_pcm *pcm) +{ + int channels = pcm->channels; + int frames = pcm->length; + + if (header) { + m_bitrateNum += header->bitrate; + m_bitrateDenom ++; + } + + if (frames < 1) return MAD_FLOW_CONTINUE; + + if (m_channelCount == 0) { + m_channelCount = channels; + m_sampleRate = pcm->samplerate; + } + + if (m_bitrateDenom > 0) { + double bitrate = m_bitrateNum / m_bitrateDenom; + double duration = double(m_fileSize * 8) / bitrate; + double elapsed = double(m_frameCount) / m_sampleRate; + double percent = ((elapsed * 100.0) / duration); + int progress = int(percent); + if (progress < 1) progress = 1; + if (progress > 99) progress = 99; + if (progress > m_progress->value()) { + m_progress->setValue(progress); + m_progress->show(); + m_progress->raise(); + qApp->processEvents(); + if (m_progress->wasCanceled()) { + m_cancelled = true; + } + } + } + + if (m_cancelled) return MAD_FLOW_STOP; + + m_frameCount += frames; + + if (!isDecodeCacheInitialised()) { + initialiseDecodeCache(); + } + + for (int i = 0; i < frames; ++i) { + + for (int ch = 0; ch < channels; ++ch) { + mad_fixed_t sample = 0; + if (ch < int(sizeof(pcm->samples) / sizeof(pcm->samples[0]))) { + sample = pcm->samples[ch][i]; + } + float fsample = float(sample) / float(MAD_F_ONE); + addSampleToDecodeCache(fsample); + } + + if (! (i & 0xffff)) { + // periodically munlock to ensure we don't exhaust real memory + // if running with memory locked down + MUNLOCK_SAMPLEBLOCK(m_data); + } + } + + if (frames > 0) { + MUNLOCK_SAMPLEBLOCK(m_data); + } + + return MAD_FLOW_CONTINUE; +} + +enum mad_flow +MP3FileReader::error(void *dp, + struct mad_stream *stream, + struct mad_frame *) +{ + DecoderData *data = (DecoderData *)dp; + + fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n", + stream->error, mad_stream_errorstr(stream), + stream->this_frame - data->start); + + return MAD_FLOW_CONTINUE; +} + +void +MP3FileReader::getSupportedExtensions(std::set &extensions) +{ + extensions.insert("mp3"); +} + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/MP3FileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MP3FileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _MP3_FILE_READER_H_ +#define _MP3_FILE_READER_H_ + +#ifdef HAVE_MAD + +#include "CodedAudioFileReader.h" + +#include + +#include + +class QProgressDialog; + +class MP3FileReader : public CodedAudioFileReader +{ +public: + MP3FileReader(QString path, bool showProgress, CacheMode cacheMode); + virtual ~MP3FileReader(); + + virtual QString getError() const { return m_error; } + + static void getSupportedExtensions(std::set &extensions); + +protected: + QString m_path; + QString m_error; + size_t m_fileSize; + double m_bitrateNum; + size_t m_bitrateDenom; + + QProgressDialog *m_progress; + bool m_cancelled; + + struct DecoderData + { + unsigned char const *start; + unsigned long length; + MP3FileReader *reader; + }; + + bool decode(void *mm, size_t sz); + enum mad_flow accept(struct mad_header const *, struct mad_pcm *); + + static enum mad_flow input(void *, struct mad_stream *); + static enum mad_flow output(void *, struct mad_header const *, struct mad_pcm *); + static enum mad_flow error(void *, struct mad_stream *, struct mad_frame *); +}; + +#endif + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/MatrixFile.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatrixFile.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,642 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "MatrixFile.h" +#include "base/TempDirectory.h" +#include "system/System.h" +#include "base/Profiler.h" +#include "base/Exceptions.h" + +#include +#include +#include +//#include + +#include + +#include +#include + +#include +#include + +//#define DEBUG_MATRIX_FILE 1 +//#define DEBUG_MATRIX_FILE_READ_SET 1 + +#ifdef DEBUG_MATRIX_FILE_READ_SET +#ifndef DEBUG_MATRIX_FILE +#define DEBUG_MATRIX_FILE 1 +#endif +#endif + +std::map MatrixFile::m_refcount; +QMutex MatrixFile::m_refcountMutex; + +MatrixFile::ResizeableBitsetMap MatrixFile::m_columnBitsets; +QMutex MatrixFile::m_columnBitsetWriteMutex; + +FileReadThread *MatrixFile::m_readThread = 0; + +static size_t totalStorage = 0; +static size_t totalMemory = 0; +static size_t totalCount = 0; + +MatrixFile::MatrixFile(QString fileBase, Mode mode, + size_t cellSize, bool eagerCache) : + m_fd(-1), + m_mode(mode), + m_flags(0), + m_fmode(0), + m_cellSize(cellSize), + m_width(0), + m_height(0), + m_headerSize(2 * sizeof(size_t)), + m_defaultCacheWidth(1024), + m_prevX(0), + m_eagerCache(eagerCache), + m_requestToken(-1), + m_spareData(0), + m_columnBitset(0) +{ + Profiler profiler("MatrixFile::MatrixFile", true); + + if (!m_readThread) { + m_readThread = new FileReadThread; + m_readThread->start(); + } + + m_cache.data = 0; + + QDir tempDir(TempDirectory::getInstance()->getPath()); + QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); + bool newFile = !QFileInfo(fileName).exists(); + + if (newFile && m_mode == ReadOnly) { + std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode " + << "specified, but cache file does not exist" << std::endl; + throw FileNotFound(fileName); + } + + if (!newFile && m_mode == ReadWrite) { + std::cerr << "Note: MatrixFile::MatrixFile: Read/write mode " + << "specified, but file already exists; falling back to " + << "read-only mode" << std::endl; + m_mode = ReadOnly; + } + + if (!eagerCache && m_mode == ReadOnly) { + std::cerr << "WARNING: MatrixFile::MatrixFile: Eager cacheing not " + << "specified, but file is open in read-only mode -- cache " + << "will not be used" << std::endl; + } + + m_flags = 0; + m_fmode = S_IRUSR | S_IWUSR; + + if (m_mode == ReadWrite) { + m_flags = O_RDWR | O_CREAT; + } else { + m_flags = O_RDONLY; + } + +#ifdef _WIN32 + m_flags |= O_BINARY; +#endif + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl; +#endif + + if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { + ::perror("Open failed"); + std::cerr << "ERROR: MatrixFile::MatrixFile: " + << "Failed to open cache file \"" + << fileName.toStdString() << "\""; + if (m_mode == ReadWrite) std::cerr << " for writing"; + std::cerr << std::endl; + throw FailedToOpenFile(fileName); + } + + if (newFile) { + resize(0, 0); // write header + } else { + size_t header[2]; + if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) { + ::perror("MatrixFile::MatrixFile: read failed"); + std::cerr << "ERROR: MatrixFile::MatrixFile: " + << "Failed to read header (fd " << m_fd << ", file \"" + << fileName.toStdString() << "\")" << std::endl; + throw FileReadFailed(fileName); + } + m_width = header[0]; + m_height = header[1]; + seekTo(0, 0); + } + + m_fileName = fileName; + + m_columnBitsetWriteMutex.lock(); + + if (m_columnBitsets.find(m_fileName) == m_columnBitsets.end()) { + m_columnBitsets[m_fileName] = new ResizeableBitset; + } + m_columnBitset = m_columnBitsets[m_fileName]; + + m_columnBitsetWriteMutex.unlock(); + + QMutexLocker locker(&m_refcountMutex); + ++m_refcount[fileName]; + +// std::cerr << "MatrixFile(" << this << "): fd " << m_fd << ", file " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl; + +// std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl; + + ++totalCount; + +} + +MatrixFile::~MatrixFile() +{ + char *requestData = 0; + + if (m_requestToken >= 0) { + FileReadThread::Request request; + if (m_readThread->getRequest(m_requestToken, request)) { + requestData = request.data; + } + m_readThread->cancel(m_requestToken); + } + + if (requestData) free(requestData); + if (m_cache.data) free(m_cache.data); + if (m_spareData) free(m_spareData); + + if (m_fd >= 0) { + if (::close(m_fd) < 0) { + ::perror("MatrixFile::~MatrixFile: close failed"); + } + } + + if (m_fileName != "") { + + QMutexLocker locker(&m_refcountMutex); + + if (--m_refcount[m_fileName] == 0) { + + if (::unlink(m_fileName.toLocal8Bit())) { +// ::perror("Unlink failed"); +// std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl; + } else { +// std::cerr << "deleted " << m_fileName.toStdString() << std::endl; + } + + QMutexLocker locker2(&m_columnBitsetWriteMutex); + m_columnBitsets.erase(m_fileName); + delete m_columnBitset; + } + } + + totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize); + totalCount --; + +// std::cerr << "MatrixFile::~MatrixFile: " << std::endl; +// std::cerr << "Total storage now " << totalStorage/1024 << "K, theoretical max memory " +// << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl; + +} + +void +MatrixFile::resize(size_t w, size_t h) +{ + Profiler profiler("MatrixFile::resize", true); + + assert(m_mode == ReadWrite); + + QMutexLocker locker(&m_fdMutex); + + totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize); + + off_t off = m_headerSize + (w * h * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::resize(" << w << ", " << h << "): resizing file" << std::endl; +#endif + + if (w * h < m_width * m_height) { + if (::ftruncate(m_fd, off) < 0) { + ::perror("WARNING: MatrixFile::resize: ftruncate failed"); + throw FileOperationFailed(m_fileName, "ftruncate"); + } + } + + m_width = 0; + m_height = 0; + + if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) { + ::perror("ERROR: MatrixFile::resize: Seek to write header failed"); + throw FileOperationFailed(m_fileName, "lseek"); + } + + size_t header[2]; + header[0] = w; + header[1] = h; + if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) { + ::perror("ERROR: MatrixFile::resize: Failed to write header"); + throw FileOperationFailed(m_fileName, "write"); + } + + if (w > 0 && m_defaultCacheWidth > w) { + m_defaultCacheWidth = w; + } + + static size_t maxCacheMB = 16; + if (2 * m_defaultCacheWidth * h * m_cellSize > maxCacheMB * 1024 * 1024) { //!!! + m_defaultCacheWidth = (maxCacheMB * 1024 * 1024) / (2 * h * m_cellSize); + if (m_defaultCacheWidth < 16) m_defaultCacheWidth = 16; + } + + if (m_columnBitset) { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->resize(w); + } + + if (m_cache.data) { + free(m_cache.data); + m_cache.data = 0; + } + + if (m_spareData) { + free(m_spareData); + m_spareData = 0; + } + + m_width = w; + m_height = h; + + totalStorage += (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory += (2 * m_defaultCacheWidth * m_height * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::resize(" << w << ", " << h << "): cache width " + << m_defaultCacheWidth << ", storage " + << (m_headerSize + w * h * m_cellSize) << ", mem " + << (2 * h * m_defaultCacheWidth * m_cellSize) << std::endl; + + std::cerr << "Total storage " << totalStorage/1024 << "K, theoretical max memory " + << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl; +#endif + + seekTo(0, 0); +} + +void +MatrixFile::reset() +{ + Profiler profiler("MatrixFile::reset", true); + + assert (m_mode == ReadWrite); + + if (m_eagerCache) { + void *emptyCol = calloc(m_height, m_cellSize); + for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol); + free(emptyCol); + } + + if (m_columnBitset) { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->resize(m_width); + } +} + +void +MatrixFile::getColumnAt(size_t x, void *data) +{ +// Profiler profiler("MatrixFile::getColumnAt"); + +// assert(haveSetColumnAt(x)); + + if (getFromCache(x, 0, m_height, data)) return; + +// Profiler profiler2("MatrixFile::getColumnAt (uncached)"); + + ssize_t r = 0; + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::getColumnAt(" << x << ")" + << ": reading the slow way"; + + if (m_requestToken >= 0 && + x >= m_requestingX && + x < m_requestingX + m_requestingWidth) { + + std::cerr << " (awaiting " << m_requestingX << ", " << m_requestingWidth << " from disk)"; + } + + std::cerr << std::endl; +#endif + + m_fdMutex.lock(); + + if (seekTo(x, 0)) { + r = ::read(m_fd, data, m_height * m_cellSize); + } + + m_fdMutex.unlock(); + + if (r < 0) { + ::perror("MatrixFile::getColumnAt: read failed"); + std::cerr << "ERROR: MatrixFile::getColumnAt: " + << "Failed to read column " << x << " (height " << m_height << ", cell size " << m_cellSize << ", fd " << m_fd << ", file \"" + << m_fileName.toStdString() << "\")" << std::endl; + throw FileReadFailed(m_fileName); + } + + return; +} + +bool +MatrixFile::getFromCache(size_t x, size_t ystart, size_t ycount, void *data) +{ + m_cacheMutex.lock(); + + if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) { + bool left = (m_cache.data && x < m_cache.x); + m_cacheMutex.unlock(); + primeCache(x, left); // this doesn't take effect until a later callback + m_prevX = x; + return false; + } + + memcpy(data, + m_cache.data + m_cellSize * ((x - m_cache.x) * m_height + ystart), + ycount * m_cellSize); + + m_cacheMutex.unlock(); + + if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) { + primeCache(x, true); + } + + if (m_cache.x + m_cache.width < m_width && + x > m_prevX && + x > m_cache.x + (m_cache.width * 3) / 4) { + primeCache(x, false); + } + + m_prevX = x; + return true; +} + +void +MatrixFile::setColumnAt(size_t x, const void *data) +{ + assert(m_mode == ReadWrite); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::setColumnAt(" << x << ")" << std::endl; +#endif + + ssize_t w = 0; + bool seekFailed = false; + + m_fdMutex.lock(); + + if (seekTo(x, 0)) { + w = ::write(m_fd, data, m_height * m_cellSize); + } else { + seekFailed = true; + } + + m_fdMutex.unlock(); + + if (!seekFailed && w != ssize_t(m_height * m_cellSize)) { + ::perror("WARNING: MatrixFile::setColumnAt: write failed"); + throw FileOperationFailed(m_fileName, "write"); + } else if (seekFailed) { + throw FileOperationFailed(m_fileName, "seek"); + } else { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->set(x); + } +} + +void +MatrixFile::suspend() +{ + QMutexLocker locker(&m_fdMutex); + QMutexLocker locker2(&m_cacheMutex); + + if (m_fd < 0) return; // already suspended + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::suspend(): fd was " << m_fd << std::endl; +#endif + + if (m_requestToken >= 0) { + void *data = 0; + FileReadThread::Request request; + if (m_readThread->getRequest(m_requestToken, request)) { + data = request.data; + } + m_readThread->cancel(m_requestToken); + if (data) free(data); + m_requestToken = -1; + } + + if (m_cache.data) { + free(m_cache.data); + m_cache.data = 0; + } + + if (m_spareData) { + free(m_spareData); + m_spareData = 0; + } + + if (::close(m_fd) < 0) { + ::perror("WARNING: MatrixFile::suspend: close failed"); + throw FileOperationFailed(m_fileName, "close"); + } + + m_fd = -1; +} + +void +MatrixFile::resume() +{ + if (m_fd >= 0) return; + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile(" << this << ")::resume()" << std::endl; +#endif + + if ((m_fd = ::open(m_fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { + ::perror("Open failed"); + std::cerr << "ERROR: MatrixFile::resume: " + << "Failed to open cache file \"" + << m_fileName.toStdString() << "\""; + if (m_mode == ReadWrite) std::cerr << " for writing"; + std::cerr << std::endl; + throw FailedToOpenFile(m_fileName); + } + + std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::resume(): fd is " << m_fd << std::endl; +} + +void +MatrixFile::primeCache(size_t x, bool goingLeft) +{ +// Profiler profiler("MatrixFile::primeCache"); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl; +#endif + + size_t rx = x; + size_t rw = m_defaultCacheWidth; + + size_t left = rw / 3; + if (goingLeft) left = (rw * 2) / 3; + + if (rx > left) rx -= left; + else rx = 0; + + if (rx + rw > m_width) rw = m_width - rx; + + if (!m_eagerCache) { + + size_t ti = 0; + + for (ti = 0; ti < rw; ++ti) { + if (!m_columnBitset->get(rx + ti)) break; + } + +#ifdef DEBUG_MATRIX_FILE + if (ti < rw) { + std::cerr << "eagerCache is false and there's a hole at " + << rx + ti << ", reducing rw from " << rw << " to " + << ti << std::endl; + } +#endif + + rw = min(rw, ti); + if (rw < 10 || rx + rw <= x) return; + } + + QMutexLocker locker(&m_cacheMutex); + + FileReadThread::Request request; + + if (m_requestToken >= 0 && + m_readThread->getRequest(m_requestToken, request)) { + + if (x >= m_requestingX && + x < m_requestingX + m_requestingWidth) { + + if (m_readThread->isReady(m_requestToken)) { + + if (!request.successful) { + std::cerr << "ERROR: MatrixFile::primeCache: Last request was unsuccessful" << std::endl; + throw FileReadFailed(m_fileName); + } + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl; +#endif + + m_cache.x = (request.start - m_headerSize) / (m_height * m_cellSize); + m_cache.width = request.size / (m_height * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "received last request: actual size is: " << m_cache.x << ", " << m_cache.width << std::endl; +#endif + + if (m_cache.data) { + if (m_spareData) free(m_spareData); + m_spareData = m_cache.data; + } + m_cache.data = request.data; + + m_readThread->done(m_requestToken); + m_requestToken = -1; + } + + // already requested something covering this area; wait for it + return; + } + + // the current request is no longer of any use + m_readThread->cancel(m_requestToken); + + // crude way to avoid leaking the data + while (!m_readThread->isCancelled(m_requestToken)) { + usleep(10000); + } + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "cancelled " << m_requestToken << std::endl; +#endif + + if (m_spareData) free(m_spareData); + m_spareData = request.data; + m_readThread->done(m_requestToken); + + m_requestToken = -1; + } + + if (m_fd < 0) { + m_fdMutex.lock(); + if (m_fd < 0) resume(); + m_fdMutex.unlock(); + } + + request.fd = m_fd; + request.mutex = &m_fdMutex; + request.start = m_headerSize + rx * m_height * m_cellSize; + request.size = rw * m_height * m_cellSize; + request.data = (char *)realloc(m_spareData, rw * m_height * m_cellSize); + MUNLOCK(request.data, rw * m_height * m_cellSize); + m_spareData = 0; + + m_requestingX = rx; + m_requestingWidth = rw; + + int token = m_readThread->request(request); +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::primeCache: request token is " + << token << " (x = [" << rx << "], w = [" << rw << "], left = [" << goingLeft << "])" << std::endl; +#endif + m_requestToken = token; +} + +bool +MatrixFile::seekTo(size_t x, size_t y) +{ + if (m_fd < 0) resume(); + + off_t off = m_headerSize + (x * m_height + y) * m_cellSize; + + if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { + ::perror("Seek failed"); + std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y + << ") failed" << std::endl; + return false; + } + + return true; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/MatrixFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatrixFile.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,124 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _MATRIX_FILE_CACHE_H_ +#define _MATRIX_FILE_CACHE_H_ + +#include "base/ResizeableBitset.h" + +#include "FileReadThread.h" + +#include +#include +#include +#include + +class MatrixFile : public QObject +{ + Q_OBJECT + +public: + enum Mode { ReadOnly, ReadWrite }; + + /** + * Construct a MatrixFile object reading from and/or writing to + * the matrix file with the given base name in the application's + * temporary directory. + * + * If mode is ReadOnly, the file must exist and be readable. + * + * If mode is ReadWrite and the file does not exist, it will be + * created. If mode is ReadWrite and the file does exist, the + * existing file will be used and the mode will be reset to + * ReadOnly. Call getMode() to check whether this has occurred + * after construction. + * + * cellSize specifies the size in bytes of the object type stored + * in the matrix. For example, use cellSize = sizeof(float) for a + * matrix of floats. The MatrixFile object doesn't care about the + * objects themselves, it just deals with raw data of a given size. + * + * If eagerCache is true, blocks from the file will be cached for + * read. If eagerCache is false, only columns that have been set + * by calling setColumnAt on this MatrixFile (i.e. columns for + * which haveSetColumnAt returns true) will be cached. + */ + MatrixFile(QString fileBase, Mode mode, size_t cellSize, bool eagerCache); + virtual ~MatrixFile(); + + Mode getMode() const { return m_mode; } + + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + size_t getCellSize() const { return m_cellSize; } + + void resize(size_t width, size_t height); + void reset(); + + bool haveSetColumnAt(size_t x) const { return m_columnBitset->get(x); } + void getColumnAt(size_t x, void *data); + void setColumnAt(size_t x, const void *data); + + void suspend(); + +protected: + int m_fd; + Mode m_mode; + int m_flags; + mode_t m_fmode; + size_t m_cellSize; + size_t m_width; + size_t m_height; + size_t m_headerSize; + QString m_fileName; + size_t m_defaultCacheWidth; + size_t m_prevX; + + struct Cache { + size_t x; + size_t width; + char *data; + }; + + Cache m_cache; + bool m_eagerCache; + + bool getFromCache(size_t x, size_t ystart, size_t ycount, void *data); + void primeCache(size_t x, bool left); + + void resume(); + + bool seekTo(size_t x, size_t y); + + static FileReadThread *m_readThread; + int m_requestToken; + + size_t m_requestingX; + size_t m_requestingWidth; + char *m_spareData; + + static std::map m_refcount; + static QMutex m_refcountMutex; + QMutex m_fdMutex; + QMutex m_cacheMutex; + + typedef std::map ResizeableBitsetMap; + static ResizeableBitsetMap m_columnBitsets; + static QMutex m_columnBitsetWriteMutex; + ResizeableBitset *m_columnBitset; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/fileio/OggVorbisFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/OggVorbisFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,165 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +#include "OggVorbisFileReader.h" +#include "base/Profiler.h" +#include "system/System.h" + +#include +#include +//#include +#include +#include + +#include +#include +#include + +static int instances = 0; + +OggVorbisFileReader::OggVorbisFileReader(QString path, bool showProgress, + CacheMode mode) : + CodedAudioFileReader(mode), + m_path(path), + m_progress(0), + m_fileSize(0), + m_bytesRead(0), + m_cancelled(false) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + + std::cerr << "OggVorbisFileReader::OggVorbisFileReader(" << path.toLocal8Bit().data() << "): now have " << (++instances) << " instances" << std::endl; + + Profiler profiler("OggVorbisFileReader::OggVorbisFileReader", true); + + QFileInfo info(path); + m_fileSize = info.size(); + + OGGZ *oggz; + if (!(oggz = oggz_open(path.toLocal8Bit().data(), OGGZ_READ))) { + m_error = QString("File %1 is not an OGG file.").arg(path); + return; + } + + FishSoundInfo fsinfo; + m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo); + + fish_sound_set_decoded_callback(m_fishSound, acceptFrames, this); + oggz_set_read_callback(oggz, -1, readPacket, this); + + if (showProgress) { + m_progress = new QProgressDialog + (QObject::tr("Decoding %1...").arg(QFileInfo(path).fileName()), + QObject::tr("Stop"), 0, 100); + m_progress->hide(); + } + + while (oggz_read(oggz, 1024) > 0); + + fish_sound_delete(m_fishSound); + m_fishSound = 0; + oggz_close(oggz); + + if (isDecodeCacheInitialised()) finishDecodeCache(); + + if (showProgress) { + delete m_progress; + m_progress = 0; + } +} + +OggVorbisFileReader::~OggVorbisFileReader() +{ + std::cerr << "OggVorbisFileReader::~OggVorbisFileReader(" << m_path.toLocal8Bit().data() << "): now have " << (--instances) << " instances" << std::endl; +} + +int +OggVorbisFileReader::readPacket(OGGZ *, ogg_packet *packet, long, void *data) +{ + OggVorbisFileReader *reader = (OggVorbisFileReader *)data; + FishSound *fs = reader->m_fishSound; + + fish_sound_prepare_truncation(fs, packet->granulepos, packet->e_o_s); + fish_sound_decode(fs, packet->packet, packet->bytes); + + reader->m_bytesRead += packet->bytes; + + if (reader->m_fileSize > 0 && reader->m_progress) { + // The number of bytes read by this function is smaller than + // the file size because of the packet headers + int progress = lrint(double(reader->m_bytesRead) * 114 / + double(reader->m_fileSize)); + if (progress > 99) progress = 99; + if (progress > reader->m_progress->value()) { + reader->m_progress->setValue(progress); + reader->m_progress->show(); + reader->m_progress->raise(); + qApp->processEvents(); + if (reader->m_progress->wasCanceled()) { + reader->m_cancelled = true; + } + } + } + + if (reader->m_cancelled) return 1; + return 0; +} + +int +OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes, + void *data) +{ + OggVorbisFileReader *reader = (OggVorbisFileReader *)data; + + if (reader->m_channelCount == 0) { + FishSoundInfo fsinfo; + fish_sound_command(fs, FISH_SOUND_GET_INFO, + &fsinfo, sizeof(FishSoundInfo)); + reader->m_channelCount = fsinfo.channels; + reader->m_sampleRate = fsinfo.samplerate; + reader->initialiseDecodeCache(); + } + + if (nframes > 0) { + + reader->m_frameCount += nframes; + + for (long i = 0; i < nframes; ++i) { + for (size_t c = 0; c < reader->m_channelCount; ++c) { + reader->addSampleToDecodeCache(frames[c][i]); +// reader->m_data.push_back(frames[c][i]); + } + } + + MUNLOCK_SAMPLEBLOCK(reader->m_data); + } + + if (reader->m_cancelled) return 1; + return 0; +} + +void +OggVorbisFileReader::getSupportedExtensions(std::set &extensions) +{ + extensions.insert("ogg"); +} + +#endif +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/OggVorbisFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/OggVorbisFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _OGG_VORBIS_FILE_READER_H_ +#define _OGG_VORBIS_FILE_READER_H_ + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +#include "CodedAudioFileReader.h" + +#include +#include + +#include + +class QProgressDialog; + +class OggVorbisFileReader : public CodedAudioFileReader +{ +public: + OggVorbisFileReader(QString path, bool showProgress, CacheMode cacheMode); + virtual ~OggVorbisFileReader(); + + virtual QString getError() const { return m_error; } + + static void getSupportedExtensions(std::set &extensions); + +protected: + QString m_path; + QString m_error; + + FishSound *m_fishSound; + QProgressDialog *m_progress; + size_t m_fileSize; + size_t m_bytesRead; + bool m_cancelled; + + static int readPacket(OGGZ *, ogg_packet *, long, void *); + static int acceptFrames(FishSound *, float **, long, void *); +}; + +#endif +#endif + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/RemoteFile.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/RemoteFile.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,401 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RemoteFile.h" +#include "base/TempDirectory.h" +#include "base/Exceptions.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +int +RemoteFile::m_count = 0; + +QMutex +RemoteFile::m_fileCreationMutex; + +RemoteFile::RemoteFile(QUrl url) : + m_ftp(0), + m_http(0), + m_localFile(0), + m_ok(false), + m_lastStatus(0), + m_done(false), + m_progressDialog(0), + m_progressShowTimer(this) +{ + if (!canHandleScheme(url)) { + std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl; + return; + } + + m_localFilename = createLocalFile(url); + if (m_localFilename == "") return; + m_localFile = new QFile(m_localFilename); + m_localFile->open(QFile::WriteOnly); + + QString scheme = url.scheme().toLower(); + + if (scheme == "http") { + + m_ok = true; + m_http = new QHttp(url.host(), url.port(80)); + connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool))); + connect(m_http, SIGNAL(dataReadProgress(int, int)), + this, SLOT(dataReadProgress(int, int))); + connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), + this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &))); + m_http->get(url.path(), m_localFile); + + } else if (scheme == "ftp") { + + m_ok = true; + m_ftp = new QFtp; + connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool))); + connect(m_ftp, SIGNAL(commandFinished(int, bool)), + this, SLOT(ftpCommandFinished(int, bool))); + connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)), + this, SLOT(dataTransferProgress(qint64, qint64))); + m_ftp->connectToHost(url.host(), url.port(21)); + + QString username = url.userName(); + if (username == "") { + username = "anonymous"; + } + + QString password = url.password(); + if (password == "") { + password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST")); + } + + m_ftp->login(username, password); + + QString dirpath = url.path().section('/', 0, -2); + QString filename = url.path().section('/', -1); + + if (dirpath == "") dirpath = "/"; + m_ftp->cd(dirpath); + m_ftp->get(filename, m_localFile); + } + + if (m_ok) { + m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100); + m_progressDialog->hide(); + connect(&m_progressShowTimer, SIGNAL(timeout()), + this, SLOT(showProgressDialog())); + connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled())); + m_progressShowTimer.setSingleShot(true); + m_progressShowTimer.start(2000); + } +} + +RemoteFile::~RemoteFile() +{ + cleanup(); +} + +void +RemoteFile::cleanup() +{ +// std::cerr << "RemoteFile::cleanup" << std::endl; + m_done = true; + if (m_http) { + delete m_http; + m_http = 0; + } + if (m_ftp) { + m_ftp->abort(); + m_ftp->deleteLater(); + m_ftp = 0; + } + delete m_progressDialog; + m_progressDialog = 0; + delete m_localFile; + m_localFile = 0; +} + +bool +RemoteFile::canHandleScheme(QUrl url) +{ + QString scheme = url.scheme().toLower(); + return (scheme == "http" || scheme == "ftp"); +} + +bool +RemoteFile::isAvailable() +{ + while (m_ok && (!m_done && m_lastStatus == 0)) { + QApplication::processEvents(); + } + bool available = true; + if (!m_ok) available = false; + else available = (m_lastStatus / 100 == 2); + std::cerr << "RemoteFile::isAvailable: " << (available ? "yes" : "no") + << std::endl; + return available; +} + +void +RemoteFile::wait() +{ + while (m_ok && !m_done) { + QApplication::processEvents(); + } +} + +bool +RemoteFile::isOK() const +{ + return m_ok; +} + +bool +RemoteFile::isDone() const +{ + return m_done; +} + +QString +RemoteFile::getLocalFilename() const +{ + return m_localFilename; +} + +QString +RemoteFile::getErrorString() const +{ + return m_errorString; +} + +void +RemoteFile::dataReadProgress(int done, int total) +{ + dataTransferProgress(done, total); +} + +void +RemoteFile::httpResponseHeaderReceived(const QHttpResponseHeader &resp) +{ + m_lastStatus = resp.statusCode(); + if (m_lastStatus / 100 >= 4) { + m_errorString = QString("%1 %2") + .arg(resp.statusCode()).arg(resp.reasonPhrase()); + std::cerr << "RemoteFile::responseHeaderReceived: " + << m_errorString.toStdString() << std::endl; + } else { + std::cerr << "RemoteFile::responseHeaderReceived: " + << m_lastStatus << std::endl; + } +} + +void +RemoteFile::ftpCommandFinished(int id, bool error) +{ + std::cerr << "RemoteFile::ftpCommandFinished(" << id << ", " << error << ")" << std::endl; + + if (!m_ftp) return; + + QFtp::Command command = m_ftp->currentCommand(); + + if (!error) { + std::cerr << "RemoteFile::ftpCommandFinished: success for command " + << command << std::endl; + return; + } + + if (command == QFtp::ConnectToHost) { + m_errorString = tr("Failed to connect to FTP server"); + } else if (command == QFtp::Login) { + m_errorString = tr("Login failed"); + } else if (command == QFtp::Cd) { + m_errorString = tr("Failed to change to correct directory"); + } else if (command == QFtp::Get) { + m_errorString = tr("FTP download aborted"); + } + + m_lastStatus = 400; // for done() +} + +void +RemoteFile::dataTransferProgress(qint64 done, qint64 total) +{ + if (!m_progressDialog) return; + + int percent = int((double(done) / double(total)) * 100.0 - 0.1); + emit progress(percent); + + if (percent > 0) { + m_progressDialog->setValue(percent); + m_progressDialog->show(); + } +} + +void +RemoteFile::cancelled() +{ + deleteLocalFile(); + m_done = true; + m_ok = false; + m_errorString = tr("Download cancelled"); +} + +void +RemoteFile::done(bool error) +{ + std::cerr << "RemoteFile::done(" << error << ")" << std::endl; + + if (m_done) return; + + emit progress(100); + + if (error) { + if (m_http) { + m_errorString = m_http->errorString(); + } else if (m_ftp) { + m_errorString = m_ftp->errorString(); + } + } + + if (m_lastStatus / 100 >= 4) { + error = true; + } + + cleanup(); + + if (!error) { + QFileInfo fi(m_localFilename); + if (!fi.exists()) { + m_errorString = tr("Failed to create local file %1").arg(m_localFilename); + error = true; + } else if (fi.size() == 0) { + m_errorString = tr("File contains no data!"); + error = true; + } + } + + if (error) { + deleteLocalFile(); + } + + m_ok = !error; + m_done = true; +} + +void +RemoteFile::deleteLocalFile() +{ +// std::cerr << "RemoteFile::deleteLocalFile" << std::endl; + + cleanup(); + + if (m_localFilename == "") return; + + m_fileCreationMutex.lock(); + + if (!QFile(m_localFilename).remove()) { + std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl; + } else { + m_localFilename = ""; + } + + m_fileCreationMutex.unlock(); + + m_done = true; +} + +void +RemoteFile::showProgressDialog() +{ + if (m_progressDialog) m_progressDialog->show(); +} + +QString +RemoteFile::createLocalFile(QUrl url) +{ + QDir dir; + try { + dir = TempDirectory::getInstance()->getSubDirectoryPath("download"); + } catch (DirectoryCreationFailed f) { + std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl; + return ""; + } + + QString filepart = url.path().section('/', -1, -1, + QString::SectionSkipEmpty); + + QString extension = filepart.section('.', -1); + QString base = filepart; + if (extension != "") { + base = base.left(base.length() - extension.length() - 1); + } + if (base == "") base = "remote"; + + QString filename; + + if (extension == "") { + filename = base; + } else { + filename = QString("%1.%2").arg(base).arg(extension); + } + + QString filepath(dir.filePath(filename)); + + std::cerr << "RemoteFile::createLocalFile: URL is \"" << url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::endl; + + m_fileCreationMutex.lock(); + ++m_count; + + if (QFileInfo(filepath).exists() || + !QFile(filepath).open(QFile::WriteOnly)) { + + std::cerr << "RemoteFile::createLocalFile: Failed to create local file \"" + << filepath.toStdString() << "\" for URL \"" + << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl; + + + if (extension == "") { + filename = QString("%1_%2").arg(base).arg(m_count); + } else { + filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension); + } + filepath = dir.filePath(filename); + + if (QFileInfo(filepath).exists() || + !QFile(filepath).open(QFile::WriteOnly)) { + + std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \"" + << filepath.toStdString() << "\" for URL \"" + << url.toString().toStdString() << "\" (or file already exists)" << std::endl; + + m_fileCreationMutex.unlock(); + return ""; + } + } + + m_fileCreationMutex.unlock(); + + std::cerr << "RemoteFile::createLocalFile: url " + << url.toString().toStdString() << " -> local filename " + << filepath.toStdString() << std::endl; + + return filepath; +} diff -r 000000000000 -r fc9323a41f5a data/fileio/RemoteFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/RemoteFile.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,85 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _REMOTE_FILE_H_ +#define _REMOTE_FILE_H_ + +#include +#include +#include +#include + +class QFtp; +class QHttp; +class QFile; +class QProgressDialog; +class QHttpResponseHeader; + +class RemoteFile : public QObject +{ + Q_OBJECT + +public: + RemoteFile(QUrl url); + virtual ~RemoteFile(); + + bool isAvailable(); + + void wait(); + + bool isOK() const; + bool isDone() const; + + QString getLocalFilename() const; + QString getErrorString() const; + + void deleteLocalFile(); + + static bool canHandleScheme(QUrl url); + +signals: + void progress(int percent); + void ready(); + +protected slots: + void dataReadProgress(int done, int total); + void httpResponseHeaderReceived(const QHttpResponseHeader &resp); + void ftpCommandFinished(int, bool); + void dataTransferProgress(qint64 done, qint64 total); + void done(bool error); + void showProgressDialog(); + void cancelled(); + +protected: + QFtp *m_ftp; + QHttp *m_http; + QFile *m_localFile; + QString m_localFilename; + QString m_errorString; + bool m_ok; + int m_lastStatus; + bool m_done; + QProgressDialog *m_progressDialog; + QTimer m_progressShowTimer; + + void cleanup(); + + QString createLocalFile(QUrl url); + + static QMutex m_fileCreationMutex; + static int m_count; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/SVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/SVFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1005 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SVFileReader.h" + +#include "base/Layer.h" +#include "base/View.h" +#include "base/PlayParameters.h" +#include "base/PlayParameterRepository.h" + +#include "AudioFileReaderFactory.h" + +#include "model/WaveFileModel.h" +#include "model/DenseThreeDimensionalModel.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/NoteModel.h" +#include "model/TextModel.h" + +#include "widgets/Pane.h" + +#include "main/Document.h" + +#include +#include +#include + +#include + +SVFileReader::SVFileReader(Document *document, + SVFileReaderPaneCallback &callback) : + m_document(document), + m_paneCallback(callback), + m_currentPane(0), + m_currentDataset(0), + m_currentDerivedModel(0), + m_currentPlayParameters(0), + m_datasetSeparator(" "), + m_inRow(false), + m_rowNumber(0), + m_ok(false) +{ +} + +void +SVFileReader::parse(const QString &xmlData) +{ + QXmlInputSource inputSource; + inputSource.setData(xmlData); + parse(inputSource); +} + +void +SVFileReader::parse(QXmlInputSource &inputSource) +{ + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + m_ok = reader.parse(inputSource); +} + +bool +SVFileReader::isOK() +{ + return m_ok; +} + +SVFileReader::~SVFileReader() +{ + if (!m_awaitingDatasets.empty()) { + std::cerr << "WARNING: SV-XML: File ended with " + << m_awaitingDatasets.size() << " unfilled model dataset(s)" + << std::endl; + } + + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + if (!unaddedModels.empty()) { + std::cerr << "WARNING: SV-XML: File contained " + << unaddedModels.size() << " unused models" + << std::endl; + while (!unaddedModels.empty()) { + delete *unaddedModels.begin(); + unaddedModels.erase(unaddedModels.begin()); + } + } +} + +bool +SVFileReader::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString name = qName.toLower(); + + bool ok = false; + + // Valid element names: + // + // sv + // data + // dataset + // display + // derivation + // playparameters + // layer + // model + // point + // row + // view + // window + + if (name == "sv") { + + // nothing needed + ok = true; + + } else if (name == "data") { + + // nothing needed + m_inData = true; + ok = true; + + } else if (name == "display") { + + // nothing needed + ok = true; + + } else if (name == "window") { + + ok = readWindow(attributes); + + } else if (name == "model") { + + ok = readModel(attributes); + + } else if (name == "dataset") { + + ok = readDatasetStart(attributes); + + } else if (name == "bin") { + + ok = addBinToDataset(attributes); + + } else if (name == "point") { + + ok = addPointToDataset(attributes); + + } else if (name == "row") { + + ok = addRowToDataset(attributes); + + } else if (name == "layer") { + + addUnaddedModels(); // all models must be specified before first layer + ok = readLayer(attributes); + + } else if (name == "view") { + + m_inView = true; + ok = readView(attributes); + + } else if (name == "derivation") { + + ok = readDerivation(attributes); + + } else if (name == "playparameters") { + + ok = readPlayParameters(attributes); + + } else if (name == "plugin") { + + ok = readPlugin(attributes); + + } else if (name == "selections") { + + m_inSelections = true; + ok = true; + + } else if (name == "selection") { + + ok = readSelection(attributes); + } + + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to completely process element \"" + << name.toLocal8Bit().data() << "\"" << std::endl; + } + + return true; +} + +bool +SVFileReader::characters(const QString &text) +{ + bool ok = false; + + if (m_inRow) { + ok = readRowData(text); + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to read row data content for row " << m_rowNumber << std::endl; + } + } + + return true; +} + +bool +SVFileReader::endElement(const QString &, const QString &, + const QString &qName) +{ + QString name = qName.toLower(); + + if (name == "dataset") { + + if (m_currentDataset) { + + bool foundInAwaiting = false; + + for (std::map::iterator i = m_awaitingDatasets.begin(); + i != m_awaitingDatasets.end(); ++i) { + if (m_models[i->second] == m_currentDataset) { + m_awaitingDatasets.erase(i); + foundInAwaiting = true; + break; + } + } + + if (!foundInAwaiting) { + std::cerr << "WARNING: SV-XML: Dataset precedes model, or no model uses dataset" << std::endl; + } + } + + m_currentDataset = 0; + + } else if (name == "data") { + + addUnaddedModels(); + m_inData = false; + + } else if (name == "derivation") { + + if (m_currentDerivedModel) { + m_document->addDerivedModel(m_currentTransform, + m_document->getMainModel(), //!!! + m_currentTransformChannel, + m_currentDerivedModel, + m_currentTransformConfiguration); + m_addedModels.insert(m_currentDerivedModel); + m_currentDerivedModel = 0; + m_currentTransform = ""; + m_currentTransformConfiguration = ""; + } + + } else if (name == "row") { + m_inRow = false; + } else if (name == "view") { + m_inView = false; + } else if (name == "selections") { + m_inSelections = false; + } else if (name == "playparameters") { + m_currentPlayParameters = 0; + } + + return true; +} + +bool +SVFileReader::error(const QXmlParseException &exception) +{ + m_errorString = + QString("ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::error(exception); +} + +bool +SVFileReader::fatalError(const QXmlParseException &exception) +{ + m_errorString = + QString("FATAL ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::fatalError(exception); +} + + +#define READ_MANDATORY(TYPE, NAME, CONVERSION) \ + TYPE NAME = attributes.value(#NAME).trimmed().CONVERSION(&ok); \ + if (!ok) { \ + std::cerr << "WARNING: SV-XML: Missing or invalid mandatory " #TYPE " attribute \"" #NAME "\"" << std::endl; \ + return false; \ + } + +bool +SVFileReader::readWindow(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, width, toInt); + READ_MANDATORY(int, height, toInt); + + m_paneCallback.setWindowSize(width, height); + return true; +} + +void +SVFileReader::addUnaddedModels() +{ + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + for (std::set::iterator i = unaddedModels.begin(); + i != unaddedModels.end(); ++i) { + m_document->addImportedModel(*i); + m_addedModels.insert(*i); + } +} + +bool +SVFileReader::readModel(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + + if (m_models.find(id) != m_models.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate model id " << id + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + READ_MANDATORY(int, sampleRate, toInt); + + QString type = attributes.value("type").trimmed(); + bool mainModel = (attributes.value("mainModel").trimmed() == "true"); + + if (type == "wavefile") { + + QString file = attributes.value("file"); + WaveFileModel *model = new WaveFileModel(file); + + while (!model->isOK()) { + + delete model; + model = 0; + + if (QMessageBox::question(0, + QMessageBox::tr("Failed to open file"), + QMessageBox::tr("Audio file \"%1\" could not be opened.\nLocate it?").arg(file), + QMessageBox::Ok, + QMessageBox::Cancel) == QMessageBox::Ok) { + + QString path = QFileDialog::getOpenFileName + (0, QFileDialog::tr("Locate file \"%1\"").arg(QFileInfo(file).fileName()), file, + QFileDialog::tr("Audio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions())); + + if (path != "") { + model = new WaveFileModel(path); + } else { + return false; + } + } else { + return false; + } + } + + m_models[id] = model; + if (mainModel) { + m_document->setMainModel(model); + m_addedModels.insert(model); + } + // Derived models will be added when their derivation + // is found. + + return true; + + } else if (type == "dense") { + + READ_MANDATORY(int, dimensions, toInt); + + // Currently the only dense model we support here + // is the dense 3d model. Dense time-value models + // are always file-backed waveform data, at this + // point, and they come in as the wavefile model + // type above. + + if (dimensions == 3) { + + READ_MANDATORY(int, windowSize, toInt); + READ_MANDATORY(int, yBinCount, toInt); + + DenseThreeDimensionalModel *model = + new DenseThreeDimensionalModel(sampleRate, windowSize, yBinCount); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + if (ok) model->setMinimumLevel(minimum); + + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + if (ok) model->setMaximumLevel(maximum); + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + m_models[id] = model; + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected dense model dimension (" + << dimensions << ")" << std::endl; + } + } else if (type == "sparse") { + + READ_MANDATORY(int, dimensions, toInt); + + if (dimensions == 1) { + + READ_MANDATORY(int, resolution, toInt); + + SparseOneDimensionalModel *model = new SparseOneDimensionalModel + (sampleRate, resolution); + m_models[id] = model; + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else if (dimensions == 2 || dimensions == 3) { + + READ_MANDATORY(int, resolution, toInt); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + float valueQuantization = + attributes.value("valueQuantization").trimmed().toFloat(&ok); + + bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true"); + + QString units = attributes.value("units"); + + if (dimensions == 2) { + if (attributes.value("subtype") == "text") { + TextModel *model = new TextModel + (sampleRate, resolution, notifyOnAdd); + m_models[id] = model; + } else { + SparseTimeValueModel *model = new SparseTimeValueModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setScaleUnits(units); + m_models[id] = model; + } + } else { + NoteModel *model = new NoteModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setValueQuantization(valueQuantization); + model->setScaleUnits(units); + m_models[id] = model; + } + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected sparse model dimension (" + << dimensions << ")" << std::endl; + } + } else { + + std::cerr << "WARNING: SV-XML: Unexpected model type \"" + << type.toLocal8Bit().data() << "\" for model id" << id << std::endl; + } + + return false; +} + +bool +SVFileReader::readView(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + m_currentPane = 0; + + if (type != "pane") { + std::cerr << "WARNING: SV-XML: Unexpected view type \"" + << type.toLocal8Bit().data() << "\"" << std::endl; + return false; + } + + m_currentPane = m_paneCallback.addPane(); + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: Internal error: Failed to add pane!" + << std::endl; + return false; + } + + bool ok = false; + + View *view = m_currentPane; + + // The view properties first + + READ_MANDATORY(size_t, centre, toUInt); + READ_MANDATORY(size_t, zoom, toUInt); + READ_MANDATORY(int, followPan, toInt); + READ_MANDATORY(int, followZoom, toInt); + READ_MANDATORY(int, light, toInt); + QString tracking = attributes.value("tracking"); + + // Specify the follow modes before we set the actual values + view->setFollowGlobalPan(followPan); + view->setFollowGlobalZoom(followZoom); + view->setPlaybackFollow(tracking == "scroll" ? View::PlaybackScrollContinuous : + tracking == "page" ? View::PlaybackScrollPage + : View::PlaybackIgnore); + + // Then set these values + view->setCentreFrame(centre); + view->setZoomLevel(zoom); + view->setLightBackground(light); + + // And pane properties + READ_MANDATORY(int, centreLineVisible, toInt); + m_currentPane->setCentreLineVisible(centreLineVisible); + + int height = attributes.value("height").toInt(&ok); + if (ok) { + m_currentPane->resize(m_currentPane->width(), height); + } + + return true; +} + +bool +SVFileReader::readLayer(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + + int id; + bool ok = false; + id = attributes.value("id").trimmed().toInt(&ok); + + if (!ok) { + std::cerr << "WARNING: SV-XML: No layer id for layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + Layer *layer = 0; + bool isNewLayer = false; + + // Layers are expected to be defined in layer elements in the data + // section, and referred to in layer elements in the view + // sections. So if we're in the data section, we expect this + // layer not to exist already; if we're in the view section, we + // expect it to exist. + + if (m_inData) { + + if (m_layers.find(id) != m_layers.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate layer id " << id + << " in data section" << std::endl; + return false; + } + + layer = m_layers[id] = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + + } else { + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: No current pane for layer " << id + << " in view section" << std::endl; + return false; + } + + if (m_layers.find(id) != m_layers.end()) { + + layer = m_layers[id]; + + } else { + std::cerr << "WARNING: SV-XML: Layer id " << id + << " in view section has not been defined -- defining it here" + << std::endl; + + layer = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + } + } + + if (!layer) { + std::cerr << "WARNING: SV-XML: Failed to add layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + if (isNewLayer) { + + QString name = attributes.value("name"); + layer->setObjectName(name); + + int modelId; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (modelOk) { + if (m_models.find(modelId) != m_models.end()) { + Model *model = m_models[modelId]; + m_document->setModel(layer, model); + } else { + std::cerr << "WARNING: SV-XML: Unknown model id " << modelId + << " in layer definition" << std::endl; + } + } + + layer->setProperties(attributes); + } + + if (!m_inData && m_currentPane) { + m_document->addLayerToView(m_currentPane, layer); + } + + return true; +} + +bool +SVFileReader::readDatasetStart(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + READ_MANDATORY(int, dimensions, toInt); + + if (m_awaitingDatasets.find(id) == m_awaitingDatasets.end()) { + std::cerr << "WARNING: SV-XML: Unwanted dataset " << id << std::endl; + return false; + } + + int modelId = m_awaitingDatasets[id]; + + Model *model = 0; + if (m_models.find(modelId) != m_models.end()) { + model = m_models[modelId]; + } else { + std::cerr << "WARNING: SV-XML: Internal error: Unknown model " << modelId + << " expecting dataset " << id << std::endl; + return false; + } + + bool good = false; + + switch (dimensions) { + case 1: + if (dynamic_cast(model)) good = true; + break; + + case 2: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; + break; + + case 3: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) { + m_datasetSeparator = attributes.value("separator"); + good = true; + } + break; + } + + if (!good) { + std::cerr << "WARNING: SV-XML: Model id " << modelId << " has wrong number of dimensions for " << dimensions << "-D dataset " << id << std::endl; + m_currentDataset = 0; + return false; + } + + m_currentDataset = model; + return true; +} + +bool +SVFileReader::addPointToDataset(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, frame, toInt); + + SparseOneDimensionalModel *sodm = dynamic_cast + (m_currentDataset); + + if (sodm) { + QString label = attributes.value("label"); + sodm->addPoint(SparseOneDimensionalModel::Point(frame, label)); + return true; + } + + SparseTimeValueModel *stvm = dynamic_cast + (m_currentDataset); + + if (stvm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + stvm->addPoint(SparseTimeValueModel::Point(frame, value, label)); + return ok; + } + + NoteModel *nm = dynamic_cast(m_currentDataset); + + if (nm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + float duration = 0.0; + duration = attributes.value("duration").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + nm->addPoint(NoteModel::Point(frame, value, duration, label)); + return ok; + } + + TextModel *tm = dynamic_cast(m_currentDataset); + + if (tm) { + float height = 0.0; + height = attributes.value("height").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + tm->addPoint(TextModel::Point(frame, height, label)); + return ok; + } + + std::cerr << "WARNING: SV-XML: Point element found in non-point dataset" << std::endl; + + return false; +} + +bool +SVFileReader::addBinToDataset(const QXmlAttributes &attributes) +{ + DenseThreeDimensionalModel *dtdm = dynamic_cast + (m_currentDataset); + + if (dtdm) { + + bool ok = false; + int n = attributes.value("number").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid bin number" + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + dtdm->setBinName(n, name); + return true; + } + + std::cerr << "WARNING: SV-XML: Bin definition found in incompatible dataset" << std::endl; + + return false; +} + + +bool +SVFileReader::addRowToDataset(const QXmlAttributes &attributes) +{ + m_inRow = false; + + bool ok = false; + m_rowNumber = attributes.value("n").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid row number" + << std::endl; + return false; + } + + m_inRow = true; + +// std::cerr << "SV-XML: In row " << m_rowNumber << std::endl; + + return true; +} + +bool +SVFileReader::readRowData(const QString &text) +{ + DenseThreeDimensionalModel *dtdm = dynamic_cast + (m_currentDataset); + + bool warned = false; + + if (dtdm) { + QStringList data = text.split(m_datasetSeparator); + + DenseThreeDimensionalModel::BinValueSet values; + + for (QStringList::iterator i = data.begin(); i != data.end(); ++i) { + + if (values.size() == dtdm->getYBinCount()) { + if (!warned) { + std::cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row " + << m_rowNumber << std::endl; + warned = true; + } + } + + bool ok; + float value = i->toFloat(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Bad floating-point value " + << i->toLocal8Bit().data() + << " in row data" << std::endl; + } else { + values.push_back(value); + } + } + + size_t windowStartFrame = m_rowNumber * dtdm->getWindowSize(); + + dtdm->setBinValues(windowStartFrame, values); + return true; + } + + std::cerr << "WARNING: SV-XML: Row data found in non-row dataset" << std::endl; + + return false; +} + +bool +SVFileReader::readDerivation(const QXmlAttributes &attributes) +{ + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for derivation" << std::endl; + return false; + } + + QString transform = attributes.value("transform"); + + if (m_models.find(modelId) != m_models.end()) { + + m_currentDerivedModel = m_models[modelId]; + m_currentTransform = transform; + m_currentTransformConfiguration = ""; + + bool ok = false; + int channel = attributes.value("channel").trimmed().toInt(&ok); + if (ok) m_currentTransformChannel = channel; + else m_currentTransformChannel = -1; + + } else { + std::cerr << "WARNING: SV-XML: Unknown derived model " << modelId + << " for transform \"" << transform.toLocal8Bit().data() << "\"" + << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlayParameters(const QXmlAttributes &attributes) +{ + m_currentPlayParameters = 0; + + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for play parameters" << std::endl; + return false; + } + + if (m_models.find(modelId) != m_models.end()) { + + bool ok = false; + + PlayParameters *parameters = PlayParameterRepository::getInstance()-> + getPlayParameters(m_models[modelId]); + + if (!parameters) { + std::cerr << "WARNING: SV-XML: Play parameters for model " + << modelId + << " not found - has model been added to document?" + << std::endl; + return false; + } + + bool muted = (attributes.value("mute").trimmed() == "true"); + if (ok) parameters->setPlayMuted(muted); + + float pan = attributes.value("pan").toFloat(&ok); + if (ok) parameters->setPlayPan(pan); + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) parameters->setPlayGain(gain); + + QString pluginId = attributes.value("pluginId"); + if (pluginId != "") parameters->setPlayPluginId(pluginId); + + m_currentPlayParameters = parameters; + +// std::cerr << "Current play parameters for model: " << m_models[modelId] << ": " << m_currentPlayParameters << std::endl; + + } else { + + std::cerr << "WARNING: SV-XML: Unknown model " << modelId + << " for play parameters" << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlugin(const QXmlAttributes &attributes) +{ + if (!m_currentDerivedModel && !m_currentPlayParameters) { + std::cerr << "WARNING: SV-XML: Plugin found outside derivation or play parameters" << std::endl; + return false; + } + + QString configurationXml = "setPlayPluginConfiguration(configurationXml); + } else { + m_currentTransformConfiguration += configurationXml; + } + + return true; +} + +bool +SVFileReader::readSelection(const QXmlAttributes &attributes) +{ + bool ok; + + READ_MANDATORY(int, start, toInt); + READ_MANDATORY(int, end, toInt); + + m_paneCallback.addSelection(start, end); + + return true; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/SVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/SVFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,108 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SV_FILE_READER_H_ +#define _SV_FILE_READER_H_ + +#include "layer/LayerFactory.h" +#include "transform/Transform.h" + +#include + +#include + +class Pane; +class Model; +class Document; +class PlayParameters; + +class SVFileReaderPaneCallback +{ +public: + virtual Pane *addPane() = 0; + virtual void setWindowSize(int width, int height) = 0; + virtual void addSelection(int start, int end) = 0; +}; + +class SVFileReader : public QXmlDefaultHandler +{ +public: + SVFileReader(Document *document, + SVFileReaderPaneCallback &callback); + virtual ~SVFileReader(); + + void parse(const QString &xmlData); + void parse(QXmlInputSource &source); + + bool isOK(); + QString getErrorString() const { return m_errorString; } + + // For loading a single layer onto an existing pane + void setCurrentPane(Pane *pane) { m_currentPane = pane; } + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, + const QString &qName, + const QXmlAttributes& atts); + + virtual bool characters(const QString &); + + virtual bool endElement(const QString &namespaceURI, + const QString &localName, + const QString &qName); + + bool error(const QXmlParseException &exception); + bool fatalError(const QXmlParseException &exception); + +protected: + bool readWindow(const QXmlAttributes &); + bool readModel(const QXmlAttributes &); + bool readView(const QXmlAttributes &); + bool readLayer(const QXmlAttributes &); + bool readDatasetStart(const QXmlAttributes &); + bool addBinToDataset(const QXmlAttributes &); + bool addPointToDataset(const QXmlAttributes &); + bool addRowToDataset(const QXmlAttributes &); + bool readRowData(const QString &); + bool readDerivation(const QXmlAttributes &); + bool readPlayParameters(const QXmlAttributes &); + bool readPlugin(const QXmlAttributes &); + bool readSelection(const QXmlAttributes &); + void addUnaddedModels(); + + Document *m_document; + SVFileReaderPaneCallback &m_paneCallback; + Pane *m_currentPane; + std::map m_layers; + std::map m_models; + std::set m_addedModels; + std::map m_awaitingDatasets; // map dataset id -> model id + Model *m_currentDataset; + Model *m_currentDerivedModel; + PlayParameters *m_currentPlayParameters; + QString m_currentTransform; + int m_currentTransformChannel; + QString m_currentTransformConfiguration; + QString m_datasetSeparator; + bool m_inRow; + bool m_inView; + bool m_inData; + bool m_inSelections; + int m_rowNumber; + QString m_errorString; + bool m_ok; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/WavFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,185 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WavFileReader.h" + +#include + +#include + +WavFileReader::WavFileReader(QString path, bool fileUpdating) : + m_file(0), + m_path(path), + m_buffer(0), + m_bufsiz(0), + m_lastStart(0), + m_lastCount(0), + m_updating(fileUpdating) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + + m_fileInfo.format = 0; + m_fileInfo.frames = 0; + m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo); + + if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) { + std::cerr << "WavFileReader::initialize: Failed to open file (" + << sf_strerror(m_file) << ")" << std::endl; + + if (m_file) { + m_error = QString("Couldn't load audio file '%1':\n%2") + .arg(m_path).arg(sf_strerror(m_file)); + } else { + m_error = QString("Failed to open audio file '%1'") + .arg(m_path); + } + return; + } + + if (m_fileInfo.channels > 0) { + m_frameCount = m_fileInfo.frames; + m_channelCount = m_fileInfo.channels; + m_sampleRate = m_fileInfo.samplerate; + } + +// std::cerr << "WavFileReader: Frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << std::endl; + +} + +WavFileReader::~WavFileReader() +{ + if (m_file) sf_close(m_file); +} + +void +WavFileReader::updateFrameCount() +{ + QMutexLocker locker(&m_mutex); + + size_t prevCount = m_fileInfo.frames; + + if (m_file) { + sf_close(m_file); + m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo); + if (!m_file || m_fileInfo.channels <= 0) { + std::cerr << "WavFileReader::updateFrameCount: Failed to open file (" + << sf_strerror(m_file) << ")" << std::endl; + } + } + +// std::cerr << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << std::endl; + + m_frameCount = m_fileInfo.frames; + + if (m_channelCount == 0) { + m_channelCount = m_fileInfo.channels; + m_sampleRate = m_fileInfo.samplerate; + } + + if (m_frameCount != prevCount) { +// std::cerr << "frameCountChanged" << std::endl; + emit frameCountChanged(); + } +} + +void +WavFileReader::updateDone() +{ + updateFrameCount(); + m_updating = false; +} + +void +WavFileReader::getInterleavedFrames(size_t start, size_t count, + SampleBlock &results) const +{ + if (count == 0) return; + results.clear(); + + QMutexLocker locker(&m_mutex); + + if (!m_file || !m_channelCount) { + return; + } + + if ((long)start >= m_fileInfo.frames) { +// std::cerr << "WavFileReader::getInterleavedFrames: " << start +// << " > " << m_fileInfo.frames << std::endl; + return; + } + + if (long(start + count) > m_fileInfo.frames) { + count = m_fileInfo.frames - start; + } + + sf_count_t readCount = 0; + + if (start != m_lastStart || count != m_lastCount) { + + if (sf_seek(m_file, start, SEEK_SET) < 0) { +// std::cerr << "sf_seek failed" << std::endl; + return; + } + + if (count * m_fileInfo.channels > m_bufsiz) { +// std::cerr << "WavFileReader: Reallocating buffer for " << count +// << " frames, " << m_fileInfo.channels << " channels: " +// << m_bufsiz << " floats" << std::endl; + m_bufsiz = count * m_fileInfo.channels; + delete[] m_buffer; + m_buffer = new float[m_bufsiz]; + } + + if ((readCount = sf_readf_float(m_file, m_buffer, count)) < 0) { +// std::cerr << "sf_readf_float failed" << std::endl; + return; + } + + m_lastStart = start; + m_lastCount = readCount; + } + + for (size_t i = 0; i < count * m_fileInfo.channels; ++i) { + if (i >= m_bufsiz) { + std::cerr << "INTERNAL ERROR: WavFileReader::getInterleavedFrames: " << i << " >= " << m_bufsiz << std::endl; + } + results.push_back(m_buffer[i]); + } + + return; +} + +void +WavFileReader::getSupportedExtensions(std::set &extensions) +{ + int count; + + if (sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) { + extensions.insert("wav"); + extensions.insert("aiff"); + extensions.insert("aif"); + return; + } + + SF_FORMAT_INFO info; + for (int i = 0; i < count; ++i) { + info.format = i; + if (!sf_command(0, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) { + extensions.insert(info.extension); + } + } +} diff -r 000000000000 -r fc9323a41f5a data/fileio/WavFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,64 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WAV_FILE_READER_H_ +#define _WAV_FILE_READER_H_ + +#include "AudioFileReader.h" + +#include +#include + +#include + +class WavFileReader : public AudioFileReader +{ +public: + WavFileReader(QString path, bool fileUpdating = false); + virtual ~WavFileReader(); + + virtual QString getError() const { return m_error; } + + /** + * Must be safe to call from multiple threads with different + * arguments on the same object at the same time. + */ + virtual void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const; + + static void getSupportedExtensions(std::set &extensions); + + bool isUpdating() const { return m_updating; } + + void updateFrameCount(); + void updateDone(); + +protected: + SF_INFO m_fileInfo; + SNDFILE *m_file; + + QString m_path; + QString m_error; + + mutable QMutex m_mutex; + mutable float *m_buffer; + mutable size_t m_bufsiz; + mutable size_t m_lastStart; + mutable size_t m_lastCount; + + bool m_updating; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/fileio/WavFileWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileWriter.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,167 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WavFileWriter.h" + +#include "model/DenseTimeValueModel.h" +#include "base/Selection.h" +#include "system/System.h" + +#include + +#include + +WavFileWriter::WavFileWriter(QString path, + size_t sampleRate, + size_t channels) : + m_path(path), + m_sampleRate(sampleRate), + m_channels(channels), + m_file(0) +{ + SF_INFO fileInfo; + fileInfo.samplerate = m_sampleRate; + fileInfo.channels = m_channels; + fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + m_file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo); + if (!m_file) { + std::cerr << "WavFileWriter: Failed to open file (" + << sf_strerror(m_file) << ")" << std::endl; + m_error = QString("Failed to open audio file '%1' for writing") + .arg(m_path); + } +} + +WavFileWriter::~WavFileWriter() +{ + if (m_file) close(); +} + +bool +WavFileWriter::isOK() const +{ + return (m_error.isEmpty()); +} + +QString +WavFileWriter::getError() const +{ + return m_error; +} + +bool +WavFileWriter::writeModel(DenseTimeValueModel *source, + MultiSelection *selection) +{ + if (source->getChannelCount() != m_channels) { + std::cerr << "WavFileWriter::writeModel: Wrong number of channels (" + << source->getChannelCount() << " != " << m_channels << ")" + << std::endl; + m_error = QString("Failed to write model to audio file '%1'") + .arg(m_path); + return false; + } + + if (!m_file) { + m_error = QString("Failed to write model to audio file '%1': File not open") + .arg(m_path); + return false; + } + + bool ownSelection = false; + if (!selection) { + selection = new MultiSelection; + selection->setSelection(Selection(source->getStartFrame(), + source->getEndFrame())); + ownSelection = true; + } + + size_t bs = 2048; + float *ub = new float[bs]; // uninterleaved buffer (one channel) + float *ib = new float[bs * m_channels]; // interleaved buffer + + for (MultiSelection::SelectionList::const_iterator i = + selection->getSelections().begin(); + i != selection->getSelections().end(); ++i) { + + size_t f0(i->getStartFrame()), f1(i->getEndFrame()); + + for (size_t f = f0; f < f1; f += bs) { + + size_t n = min(bs, f1 - f); + + for (int c = 0; c < int(m_channels); ++c) { + source->getValues(c, f, f + n, ub); + for (size_t i = 0; i < n; ++i) { + ib[i * m_channels + c] = ub[i]; + } + } + + sf_count_t written = sf_writef_float(m_file, ib, n); + + if (written < n) { + m_error = QString("Only wrote %1 of %2 frames at file frame %3") + .arg(written).arg(n).arg(f); + break; + } + } + } + + delete[] ub; + delete[] ib; + if (ownSelection) delete selection; + + return isOK(); +} + +bool +WavFileWriter::writeSamples(float **samples, size_t count) +{ + if (!m_file) { + m_error = QString("Failed to write model to audio file '%1': File not open") + .arg(m_path); + return false; + } + + float *b = new float[count * m_channels]; + for (size_t i = 0; i < count; ++i) { + for (size_t c = 0; c < m_channels; ++c) { + b[i * m_channels + c] = samples[c][i]; + } + } + + sf_count_t written = sf_writef_float(m_file, b, count); + + delete[] b; + + if (written < count) { + m_error = QString("Only wrote %1 of %2 frames") + .arg(written).arg(count); + } + + return isOK(); +} + +bool +WavFileWriter::close() +{ + if (m_file) { + sf_close(m_file); + m_file = 0; + } + return true; +} + diff -r 000000000000 -r fc9323a41f5a data/fileio/WavFileWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileWriter.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WAV_FILE_WRITER_H_ +#define _WAV_FILE_WRITER_H_ + +#include + +#include + +class DenseTimeValueModel; +class MultiSelection; + +class WavFileWriter +{ +public: + WavFileWriter(QString path, size_t sampleRate, size_t channels); + virtual ~WavFileWriter(); + + bool isOK() const; + + virtual QString getError() const; + + QString getPath() const { return m_path; } + + bool writeModel(DenseTimeValueModel *source, + MultiSelection *selection = 0); + + bool writeSamples(float **samples, size_t count); // count per channel + + bool close(); + +protected: + QString m_path; + size_t m_sampleRate; + size_t m_channels; + SNDFILE *m_file; + QString m_error; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/DenseThreeDimensionalModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/DenseThreeDimensionalModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,107 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DENSE_THREE_DIMENSIONAL_MODEL_H_ +#define _DENSE_THREE_DIMENSIONAL_MODEL_H_ + +#include "Model.h" +#include "base/ZoomConstraint.h" + +#include +#include + +class DenseThreeDimensionalModel : public Model +{ + Q_OBJECT + +public: + /** + * Return the number of sample frames covered by each column of bins. + */ + virtual size_t getResolution() const = 0; + + /** + * Return the number of columns of bins in the model. + */ + virtual size_t getWidth() const = 0; + + /** + * Return the number of bins in each column. + */ + virtual size_t getHeight() const = 0; + + /** + * Return the minimum permissible value in each bin. + */ + virtual float getMinimumLevel() const = 0; + + /** + * Return the maximum permissible value in each bin. + */ + virtual float getMaximumLevel() const = 0; + + /** + * Return true if there are data available for the given column. + * This should return true only if getColumn(column) would not + * have to do any substantial work to calculate its return values. + * If this function returns false, it may still be possible to + * retrieve the column, but its values may have to be calculated. + */ + virtual bool isColumnAvailable(size_t column) const = 0; + + typedef std::vector Column; + + /** + * Get data from the given column of bin values. + */ + virtual void getColumn(size_t column, Column &result) const = 0; + + /** + * Get the single data point from the n'th bin of the given column. + */ + virtual float getValueAt(size_t column, size_t n) const = 0; + + /** + * Get the name of a given bin (i.e. a label to associate with + * that bin across all columns). + */ + virtual QString getBinName(size_t n) const = 0; + + /** + * Utility function to query whether a given bin is greater than + * its (vertical) neighbours. + */ + bool isLocalPeak(size_t x, size_t y) { + float value = getValueAt(x, y); + if (y > 0 && value < getValueAt(x, y - 1)) return false; + if (y < getHeight() - 1 && value < getValueAt(x, y + 1)) return false; + return true; + } + + /** + * Utility function to query whether a given bin is greater than a + * certain threshold. + */ + bool isOverThreshold(size_t x, size_t y, float threshold) { + return getValueAt(x, y) > threshold; + } + + virtual int getCompletion() const = 0; + +protected: + DenseThreeDimensionalModel() { } +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/DenseTimeValueModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/DenseTimeValueModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,23 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "DenseTimeValueModel.h" +#include "base/PlayParameterRepository.h" + +DenseTimeValueModel::DenseTimeValueModel() +{ + PlayParameterRepository::getInstance()->addModel(this); +} + diff -r 000000000000 -r fc9323a41f5a data/model/DenseTimeValueModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/DenseTimeValueModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DENSE_TIME_VALUE_MODEL_H_ +#define _DENSE_TIME_VALUE_MODEL_H_ + +#include + +#include "Model.h" + +/** + * Base class for models containing dense two-dimensional data (value + * against time). For example, audio waveform data. + */ + +class DenseTimeValueModel : public Model +{ + Q_OBJECT + +public: + DenseTimeValueModel(); + + /** + * Return the minimum possible value found in this model type. + * (That is, the minimum that would be valid, not the minimum + * actually found in a particular model). + */ + virtual float getValueMinimum() const = 0; + + /** + * Return the minimum possible value found in this model type. + * (That is, the minimum that would be valid, not the minimum + * actually found in a particular model). + */ + virtual float getValueMaximum() const = 0; + + /** + * Return the number of distinct channels for this model. + */ + virtual size_t getChannelCount() const = 0; + + /** + * Get the specified set of samples from the given channel of the + * model in single-precision floating-point format. Return the + * number of samples actually retrieved. + * If the channel is given as -1, mix all available channels and + * return the result. + */ + virtual size_t getValues(int channel, size_t start, size_t end, + float *buffer) const = 0; + + /** + * Get the specified set of samples from the given channel of the + * model in double-precision floating-point format. Return the + * number of samples actually retrieved. + * If the channel is given as -1, mix all available channels and + * return the result. + */ + virtual size_t getValues(int channel, size_t start, size_t end, + double *buffer) const = 0; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/EditableDenseThreeDimensionalModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,328 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "EditableDenseThreeDimensionalModel.h" +#include "system/System.h" + +#include + +#include + +#include + +EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(size_t sampleRate, + size_t resolution, + size_t yBinCount, + bool notifyOnAdd) : + m_sampleRate(sampleRate), + m_resolution(resolution), + m_yBinCount(yBinCount), + m_minimum(0.0), + m_maximum(0.0), + m_haveExtents(false), + m_notifyOnAdd(notifyOnAdd), + m_sinceLastNotifyMin(-1), + m_sinceLastNotifyMax(-1), + m_completion(100) +{ +} + +bool +EditableDenseThreeDimensionalModel::isOK() const +{ + return true; +} + +size_t +EditableDenseThreeDimensionalModel::getSampleRate() const +{ + return m_sampleRate; +} + +size_t +EditableDenseThreeDimensionalModel::getStartFrame() const +{ + return 0; +} + +size_t +EditableDenseThreeDimensionalModel::getEndFrame() const +{ + return m_resolution * m_data.size() + (m_resolution - 1); +} + +Model * +EditableDenseThreeDimensionalModel::clone() const +{ + EditableDenseThreeDimensionalModel *model = + new EditableDenseThreeDimensionalModel + (m_sampleRate, m_resolution, m_yBinCount); + + model->m_minimum = m_minimum; + model->m_maximum = m_maximum; + model->m_haveExtents = m_haveExtents; + + for (size_t i = 0; i < m_data.size(); ++i) { + model->setColumn(i, m_data[i]); + } + + return model; +} + +size_t +EditableDenseThreeDimensionalModel::getResolution() const +{ + return m_resolution; +} + +void +EditableDenseThreeDimensionalModel::setResolution(size_t sz) +{ + m_resolution = sz; +} + +size_t +EditableDenseThreeDimensionalModel::getWidth() const +{ + return m_data.size(); +} + +size_t +EditableDenseThreeDimensionalModel::getHeight() const +{ + return m_yBinCount; +} + +void +EditableDenseThreeDimensionalModel::setHeight(size_t sz) +{ + m_yBinCount = sz; +} + +float +EditableDenseThreeDimensionalModel::getMinimumLevel() const +{ + return m_minimum; +} + +void +EditableDenseThreeDimensionalModel::setMinimumLevel(float level) +{ + m_minimum = level; +} + +float +EditableDenseThreeDimensionalModel::getMaximumLevel() const +{ + return m_maximum; +} + +void +EditableDenseThreeDimensionalModel::setMaximumLevel(float level) +{ + m_maximum = level; +} + +void +EditableDenseThreeDimensionalModel::getColumn(size_t index, + Column &result) const +{ + QMutexLocker locker(&m_mutex); + + if (index < m_data.size()) { + result = m_data[index]; + } else { + result.clear(); + } + + while (result.size() < m_yBinCount) result.push_back(m_minimum); +} + +float +EditableDenseThreeDimensionalModel::getValueAt(size_t index, size_t n) const +{ + QMutexLocker locker(&m_mutex); + + if (index < m_data.size()) { + const Column &s = m_data[index]; +// std::cerr << "index " << index << ", n " << n << ", res " << m_resolution << ", size " << s.size() +// << std::endl; + if (n < s.size()) return s[n]; + } + + return m_minimum; +} + +void +EditableDenseThreeDimensionalModel::setColumn(size_t index, + const Column &values) +{ + QMutexLocker locker(&m_mutex); + + while (index >= m_data.size()) { + m_data.push_back(Column()); + } + + bool allChange = false; + + for (size_t i = 0; i < values.size(); ++i) { + float value = values[i]; + if (isnan(value) || isinf(value)) { + continue; + } + if (!m_haveExtents || value < m_minimum) { + m_minimum = value; + allChange = true; + } + if (!m_haveExtents || value > m_maximum) { + m_maximum = value; + allChange = true; + } + m_haveExtents = true; + } + + m_data[index] = values; + + long windowStart = index; + windowStart *= m_resolution; + + if (m_notifyOnAdd) { + if (allChange) { + emit modelChanged(); + } else { + emit modelChanged(windowStart, windowStart + m_resolution); + } + } else { + if (allChange) { + m_sinceLastNotifyMin = -1; + m_sinceLastNotifyMax = -1; + emit modelChanged(); + } else { + if (m_sinceLastNotifyMin == -1 || + windowStart < m_sinceLastNotifyMin) { + m_sinceLastNotifyMin = windowStart; + } + if (m_sinceLastNotifyMax == -1 || + windowStart > m_sinceLastNotifyMax) { + m_sinceLastNotifyMax = windowStart; + } + } + } +} + +QString +EditableDenseThreeDimensionalModel::getBinName(size_t n) const +{ + if (m_binNames.size() > n) return m_binNames[n]; + else return ""; +} + +void +EditableDenseThreeDimensionalModel::setBinName(size_t n, QString name) +{ + while (m_binNames.size() <= n) m_binNames.push_back(""); + m_binNames[n] = name; + emit modelChanged(); +} + +void +EditableDenseThreeDimensionalModel::setBinNames(std::vector names) +{ + m_binNames = names; + emit modelChanged(); +} + +void +EditableDenseThreeDimensionalModel::setCompletion(int completion) +{ + if (m_completion != completion) { + m_completion = completion; + + if (completion == 100) { + + m_notifyOnAdd = true; // henceforth + emit modelChanged(); + + } else if (!m_notifyOnAdd) { + + if (m_sinceLastNotifyMin >= 0 && + m_sinceLastNotifyMax >= 0) { + emit modelChanged(m_sinceLastNotifyMin, + m_sinceLastNotifyMax + m_resolution); + m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; + } else { + emit completionChanged(); + } + } else { + emit completionChanged(); + } + } +} + +void +EditableDenseThreeDimensionalModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + // For historical reasons we read and write "resolution" as "windowSize" + + out << Model::toXmlString + (indent, QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" %6") + .arg(m_resolution) + .arg(m_yBinCount) + .arg(m_minimum) + .arg(m_maximum) + .arg(getObjectExportId(&m_data)) + .arg(extraAttributes)); + + out << indent; + out << QString("\n") + .arg(getObjectExportId(&m_data)); + + for (size_t i = 0; i < m_binNames.size(); ++i) { + if (m_binNames[i] != "") { + out << indent + " "; + out << QString("\n") + .arg(i).arg(m_binNames[i]); + } + } + + for (size_t i = 0; i < m_data.size(); ++i) { + out << indent + " "; + out << QString("").arg(i); + for (size_t j = 0; j < m_data[i].size(); ++j) { + if (j > 0) out << " "; + out << m_data[i][j]; + } + out << QString("\n"); + } + + out << indent + "\n"; +} + +QString +EditableDenseThreeDimensionalModel::toXmlString(QString indent, + QString extraAttributes) const +{ + QString s; + + { + QTextStream out(&s); + toXml(out, indent, extraAttributes); + } + + return s; +} + diff -r 000000000000 -r fc9323a41f5a data/model/EditableDenseThreeDimensionalModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,139 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _EDITABLE_DENSE_THREE_DIMENSIONAL_MODEL_H_ +#define _EDITABLE_DENSE_THREE_DIMENSIONAL_MODEL_H_ + +#include "DenseThreeDimensionalModel.h" + +class EditableDenseThreeDimensionalModel : public DenseThreeDimensionalModel +{ + Q_OBJECT + +public: + EditableDenseThreeDimensionalModel(size_t sampleRate, + size_t resolution, + size_t yBinCount, + bool notifyOnAdd = true); + + virtual bool isOK() const; + + virtual size_t getSampleRate() const; + virtual size_t getStartFrame() const; + virtual size_t getEndFrame() const; + + virtual Model *clone() const; + + + /** + * Return the number of sample frames covered by each set of bins. + */ + virtual size_t getResolution() const; + + /** + * Set the number of sample frames covered by each set of bins. + */ + virtual void setResolution(size_t sz); + + /** + * Return the number of columns. + */ + virtual size_t getWidth() const; + + /** + * Return the number of bins in each set of bins. + */ + virtual size_t getHeight() const; + + /** + * Set the number of bins in each set of bins. + */ + virtual void setHeight(size_t sz); + + /** + * Return the minimum value of the value in each bin. + */ + virtual float getMinimumLevel() const; + + /** + * Set the minimum value of the value in a bin. + */ + virtual void setMinimumLevel(float sz); + + /** + * Return the maximum value of the value in each bin. + */ + virtual float getMaximumLevel() const; + + /** + * Set the maximum value of the value in a bin. + */ + virtual void setMaximumLevel(float sz); + + /** + * Return true if there are data available for the given column. + */ + virtual bool isColumnAvailable(size_t x) const { return x < getWidth(); } + + /** + * Get the set of bin values at the given column. + */ + virtual void getColumn(size_t x, Column &) const; + + /** + * Get a single value, from the n'th bin of the given column. + */ + virtual float getValueAt(size_t x, size_t n) const; + + /** + * Set the entire set of bin values at the given column. + */ + virtual void setColumn(size_t x, const Column &values); + + virtual QString getBinName(size_t n) const; + virtual void setBinName(size_t n, QString); + virtual void setBinNames(std::vector names); + + virtual void setCompletion(int completion); + virtual int getCompletion() const { return m_completion; } + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +protected: + typedef std::vector ValueMatrix; + ValueMatrix m_data; + + std::vector m_binNames; + + size_t m_sampleRate; + size_t m_resolution; + size_t m_yBinCount; + float m_minimum; + float m_maximum; + bool m_haveExtents; + bool m_notifyOnAdd; + long m_sinceLastNotifyMin; + long m_sinceLastNotifyMax; + int m_completion; + + mutable QMutex m_mutex; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/FFTModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/FFTModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,120 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTModel.h" +#include "DenseTimeValueModel.h" + +#include "base/Profiler.h" + +#include + +FFTModel::FFTModel(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + //!!! ZoomConstraint! + m_server(0), + m_xshift(0), + m_yshift(0) +{ + m_server = FFTDataServer::getFuzzyInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + + if (!m_server) return; // caller should check isOK() + + size_t xratio = windowIncrement / m_server->getWindowIncrement(); + size_t yratio = m_server->getFFTSize() / fftSize; + + while (xratio > 1) { + if (xratio & 0x1) { + std::cerr << "ERROR: FFTModel: Window increment ratio " + << windowIncrement << " / " + << m_server->getWindowIncrement() + << " must be a power of two" << std::endl; + assert(!(xratio & 0x1)); + } + ++m_xshift; + xratio >>= 1; + } + + while (yratio > 1) { + if (yratio & 0x1) { + std::cerr << "ERROR: FFTModel: FFT size ratio " + << m_server->getFFTSize() << " / " << fftSize + << " must be a power of two" << std::endl; + assert(!(yratio & 0x1)); + } + ++m_yshift; + yratio >>= 1; + } +} + +FFTModel::~FFTModel() +{ + if (m_server) FFTDataServer::releaseInstance(m_server); +} + +size_t +FFTModel::getSampleRate() const +{ + return isOK() ? m_server->getModel()->getSampleRate() : 0; +} + +void +FFTModel::getColumn(size_t x, Column &result) const +{ + Profiler profiler("FFTModel::getColumn", false); + + result.clear(); + size_t height(getHeight()); + for (size_t y = 0; y < height; ++y) { + result.push_back(const_cast(this)->getMagnitudeAt(x, y)); + } +} + +QString +FFTModel::getBinName(size_t n) const +{ + size_t sr = getSampleRate(); + if (!sr) return ""; + QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2)); + return name; +} + +Model * +FFTModel::clone() const +{ + return new FFTModel(*this); +} + +FFTModel::FFTModel(const FFTModel &model) : + DenseThreeDimensionalModel(), + m_server(model.m_server), + m_xshift(model.m_xshift), + m_yshift(model.m_yshift) +{ + FFTDataServer::claimInstance(m_server); +} + diff -r 000000000000 -r fc9323a41f5a data/model/FFTModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/FFTModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,143 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_MODEL_H_ +#define _FFT_MODEL_H_ + +#include "data/fft/FFTDataServer.h" +#include "DenseThreeDimensionalModel.h" + +/** + * An implementation of DenseThreeDimensionalModel that makes FFT data + * derived from a DenseTimeValueModel available as a generic data grid. + * The FFT data is acquired using FFTDataServer. + */ + +class FFTModel : public DenseThreeDimensionalModel +{ + Q_OBJECT + +public: + /** + * Construct an FFT model derived from the given + * DenseTimeValueModel, with the given window parameters and FFT + * size (which may exceed the window size, for zero-padded FFTs). + * + * If the model has multiple channels use only the given channel, + * unless the channel is -1 in which case merge all available + * channels. + * + * If polar is true, the data will normally be retrieved from the + * FFT model in magnitude/phase form; otherwise it will normally + * be retrieved in "cartesian" real/imaginary form. The results + * should be the same either way, but a "polar" model addressed in + * "cartesian" form or vice versa may suffer a performance + * penalty. + * + * The fillFromColumn argument gives a hint that the FFT data + * server should aim to start calculating FFT data at that column + * number if possible, as that is likely to be requested first. + */ + FFTModel(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + ~FFTModel(); + + float getMagnitudeAt(size_t x, size_t y) { + return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getNormalizedMagnitudeAt(size_t x, size_t y) { + return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getMaximumMagnitudeAt(size_t x) { + return m_server->getMaximumMagnitudeAt(x << m_xshift); + } + float getPhaseAt(size_t x, size_t y) { + return m_server->getPhaseAt(x << m_xshift, y << m_yshift); + } + void getValuesAt(size_t x, size_t y, float &real, float &imaginary) { + m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary); + } + bool isColumnAvailable(size_t x) const { + return m_server->isColumnReady(x << m_xshift); + } + + size_t getFillExtent() const { return m_server->getFillExtent(); } + + // DenseThreeDimensionalModel and Model methods: + // + virtual size_t getWidth() const { + return m_server->getWidth() >> m_xshift; + } + virtual size_t getHeight() const { + // If there is no y-shift, the server's height (based on its + // fftsize/2 + 1) is correct. If there is a shift, then the + // server is using a larger fft size than we want, so we shift + // it right as many times as necessary, but then we need to + // re-add the "+1" part (because ((fftsize*2)/2 + 1) / 2 != + // fftsize/2 + 1). + return (m_server->getHeight() >> m_yshift) + (m_yshift > 0 ? 1 : 0); + } + virtual float getValueAt(size_t x, size_t y) const { + return const_cast(this)->getMagnitudeAt(x, y); + } + virtual bool isOK() const { + return m_server && m_server->getModel(); + } + virtual size_t getStartFrame() const { + return 0; + } + virtual size_t getEndFrame() const { + return getWidth() * getResolution() + getResolution(); + } + virtual size_t getSampleRate() const; + virtual size_t getResolution() const { + return m_server->getWindowIncrement() << m_xshift; + } + virtual size_t getYBinCount() const { + return getHeight(); + } + virtual float getMinimumLevel() const { + return 0.f; // Can't provide + } + virtual float getMaximumLevel() const { + return 1.f; // Can't provide + } + virtual void getColumn(size_t x, Column &result) const; + virtual QString getBinName(size_t n) const; + + virtual int getCompletion() const { return m_server->getFillCompletion(); } + + virtual Model *clone() const; + + virtual void suspend() { m_server->suspend(); } + virtual void suspendWrites() { m_server->suspendWrites(); } + virtual void resume() { m_server->resume(); } + +private: + FFTModel(const FFTModel &); + FFTModel &operator=(const FFTModel &); // not implemented + + FFTDataServer *m_server; + int m_xshift; + int m_yshift; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/Model.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/Model.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Model.h" +#include "base/PlayParameterRepository.h" + +#include + +#include + +const int Model::COMPLETION_UNKNOWN = -1; + +Model::~Model() +{ +// std::cerr << "Model::~Model(" << this << ")" << std::endl; + + // Subclasses have to handle adding themselves to the repository, + // if they want to be played. We can't do it from here because + // the repository would be unable to tell whether we were playable + // or not (because dynamic_cast won't work from the base class ctor) + PlayParameterRepository::getInstance()->removeModel(this); +} + +void +Model::toXml(QTextStream &stream, QString indent, + QString extraAttributes) const +{ + stream << indent; + stream << QString("\n") + .arg(getObjectExportId(this)) + .arg(encodeEntities(objectName())) + .arg(getSampleRate()) + .arg(getStartFrame()) + .arg(getEndFrame()) + .arg(extraAttributes); +} + +QString +Model::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += indent; + + s += QString("\n") + .arg(getObjectExportId(this)) + .arg(encodeEntities(objectName())) + .arg(getSampleRate()) + .arg(getStartFrame()) + .arg(getEndFrame()) + .arg(extraAttributes); + + return s; +} + diff -r 000000000000 -r fc9323a41f5a data/model/Model.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/Model.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,145 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include +#include + +#include "base/XmlExportable.h" + +typedef std::vector SampleBlock; + +class ZoomConstraint; + +/** + * Model is the base class for all data models that represent any sort + * of data on a time scale based on an audio frame rate. + */ + +class Model : public QObject, + public XmlExportable +{ + Q_OBJECT + +public: + virtual ~Model(); + + /** + * Return true if the model was constructed successfully. Classes + * that refer to the model should always test this before use. + */ + virtual bool isOK() const = 0; + + /** + * Return the first audio frame spanned by the model. + */ + virtual size_t getStartFrame() const = 0; + + /** + * Return the last audio frame spanned by the model. + */ + virtual size_t getEndFrame() const = 0; + + /** + * Return the frame rate in frames per second. + */ + virtual size_t getSampleRate() const = 0; + + /** + * Return a copy of this model. + * + * If the model is not editable, this may be effectively a shallow + * copy. If the model is editable, however, this operation must + * properly copy all of the model's editable data. + * + * In general this operation is not useful for non-editable dense + * models such as waveforms, because there may be no efficient + * copy operation implemented -- for such models it is better not + * to copy at all. + * + * Caller owns the returned value. + */ + virtual Model *clone() const = 0; + + /** + * Return true if the model has finished loading or calculating + * all its data, for a model that is capable of calculating in a + * background thread. The default implementation is appropriate + * for a thread that does not background any work but carries out + * all its calculation from the constructor or accessors. + * + * If "completion" is non-NULL, this function should return + * through it an estimated percentage value showing how far + * through the background operation it thinks it is (for progress + * reporting). If it has no way to calculate progress, it may + * return the special value COMPLETION_UNKNOWN. + */ + virtual bool isReady(int *completion = 0) const { + bool ok = isOK(); + if (completion) *completion = (ok ? 100 : 0); + return ok; + } + static const int COMPLETION_UNKNOWN; + + /** + * If this model imposes a zoom constraint, i.e. some limit to the + * set of resolutions at which its data can meaningfully be + * displayed, then return it. + */ + virtual const ZoomConstraint *getZoomConstraint() const { + return 0; + } + + virtual void toXml(QTextStream &stream, + QString indent = "", + QString extraAttributes = "") const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + virtual QString toDelimitedDataString(QString) const { return ""; } + +signals: + /** + * Emitted when a model has been edited (or more data retrieved + * from cache, in the case of a cached model that generates slowly) + */ + void modelChanged(); + + /** + * Emitted when a model has been edited (or more data retrieved + * from cache, in the case of a cached model that generates slowly) + */ + void modelChanged(size_t startFrame, size_t endFrame); + + /** + * Emitted when some internal processing has advanced a stage, but + * the model has not changed externally. Views should respond by + * updating any progress meters or other monitoring, but not + * refreshing the actual view. + */ + void completionChanged(); + +protected: + Model() { } + + // Not provided. + Model(const Model &); + Model &operator=(const Model &); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/NoteModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/NoteModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NoteModel.h" + +NoteModel::PointList +NoteModel::getPoints(long start, long end) const +{ + if (start > end) return PointList(); + QMutexLocker locker(&m_mutex); + + Note endPoint(end); + + PointListConstIterator endItr = m_points.upper_bound(endPoint); + + if (endItr != m_points.end()) ++endItr; + if (endItr != m_points.end()) ++endItr; + + PointList rv; + + for (PointListConstIterator i = endItr; i != m_points.begin(); ) { + --i; + if (i->frame < start) { + if (i->frame + long(i->duration) >= start) { + rv.insert(*i); + } + } else if (i->frame <= end) { + rv.insert(*i); + } + } + + return rv; +} + +NoteModel::PointList +NoteModel::getPoints(long frame) const +{ + QMutexLocker locker(&m_mutex); + + if (m_resolution == 0) return PointList(); + + long start = (frame / m_resolution) * m_resolution; + long end = start + m_resolution; + + Note endPoint(end); + + PointListConstIterator endItr = m_points.upper_bound(endPoint); + + PointList rv; + + for (PointListConstIterator i = endItr; i != m_points.begin(); ) { + --i; + if (i->frame < start) { + if (i->frame + long(i->duration) >= start) { + rv.insert(*i); + } + } else if (i->frame <= end) { + rv.insert(*i); + } + } + + return rv; +} diff -r 000000000000 -r fc9323a41f5a data/model/NoteModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/NoteModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,136 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTE_MODEL_H_ +#define _NOTE_MODEL_H_ + +#include "SparseValueModel.h" +#include "base/PlayParameterRepository.h" +#include "base/RealTime.h" + +/** + * Note type for use in a SparseModel or SparseValueModel. All we + * mean by a "note" is something that has an onset time, a single + * value, and a duration. Like other points, it can also have a + * label. With this point type, the model can be thought of as + * representing a simple MIDI-type piano roll, except that the y + * coordinates (values) do not have to be discrete integers. + */ + +struct Note +{ +public: + Note(long _frame) : frame(_frame), value(0.0f), duration(0) { } + Note(long _frame, float _value, size_t _duration, QString _label) : + frame(_frame), value(_value), duration(_duration), label(_label) { } + + int getDimensions() const { return 3; } + + long frame; + float value; + size_t duration; + QString label; + + QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return QString("%1\n") + .arg(indent).arg(frame).arg(value).arg(duration).arg(label).arg(extraAttributes); + } + + QString toDelimitedDataString(QString delimiter, size_t sampleRate) const + { + QStringList list; + list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); + list << QString("%1").arg(value); + list << QString("%1").arg(duration); + list << label; + return list.join(delimiter); + } + + struct Comparator { + bool operator()(const Note &p1, + const Note &p2) const { + if (p1.frame != p2.frame) return p1.frame < p2.frame; + if (p1.value != p2.value) return p1.value < p2.value; + if (p1.duration != p2.duration) return p1.duration < p2.duration; + return p1.label < p2.label; + } + }; + + struct OrderComparator { + bool operator()(const Note &p1, + const Note &p2) const { + return p1.frame < p2.frame; + } + }; +}; + + +class NoteModel : public SparseValueModel +{ +public: + NoteModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true) : + SparseValueModel(sampleRate, resolution, + notifyOnAdd), + m_valueQuantization(0) + { + PlayParameterRepository::getInstance()->addModel(this); + } + + NoteModel(size_t sampleRate, size_t resolution, + float valueMinimum, float valueMaximum, + bool notifyOnAdd = true) : + SparseValueModel(sampleRate, resolution, + valueMinimum, valueMaximum, + notifyOnAdd), + m_valueQuantization(0) + { + PlayParameterRepository::getInstance()->addModel(this); + } + + float getValueQuantization() const { return m_valueQuantization; } + void setValueQuantization(float q) { m_valueQuantization = q; } + + /** + * Notes have a duration, so this returns all points that span any + * of the given range (as well as the usual additional few before + * and after). Consequently this can be very slow (optimised data + * structures still to be done!). + */ + virtual PointList getPoints(long start, long end) const; + + /** + * Notes have a duration, so this returns all points that span the + * given frame. Consequently this can be very slow (optimised + * data structures still to be done!). + */ + virtual PointList getPoints(long frame) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return SparseValueModel::toXmlString + (indent, + QString("%1 valueQuantization=\"%2\"") + .arg(extraAttributes).arg(m_valueQuantization)); + } + +protected: + float m_valueQuantization; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/PowerOfSqrtTwoZoomConstraint.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,104 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PowerOfSqrtTwoZoomConstraint.h" + +#include +#include + + +size_t +PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize, + RoundingDirection dir) const +{ + int type, power; + size_t rv = getNearestBlockSize(blockSize, type, power, dir); + return rv; +} + +size_t +PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize, + int &type, + int &power, + RoundingDirection dir) const +{ +// std::cerr << "given " << blockSize << std::endl; + + size_t minCachePower = getMinCachePower(); + + if (blockSize < (1U << minCachePower)) { + type = -1; + power = 0; + float val = 1.0, prevVal = 1.0; + while (val + 0.01 < blockSize) { + prevVal = val; + val *= sqrt(2.0); + } + size_t rval; + if (dir == RoundUp) rval = size_t(val + 0.01); + else if (dir == RoundDown) rval = size_t(prevVal + 0.01); + else if (val - blockSize < blockSize - prevVal) rval = size_t(val + 0.01); + else rval = size_t(prevVal + 0.01); +// std::cerr << "returning " << rval << std::endl; + return rval; + } + + unsigned int prevBase = (1 << minCachePower); + unsigned int prevPower = minCachePower; + unsigned int prevType = 0; + + size_t result = 0; + + for (unsigned int i = 0; ; ++i) { + + power = minCachePower + i/2; + type = i % 2; + + unsigned int base; + if (type == 0) { + base = (1 << power); + } else { + base = (((unsigned int)((1 << minCachePower) * sqrt(2.0) + 0.01)) + << (power - minCachePower)); + } + +// std::cerr << "Testing base " << base << std::endl; + if (base >= blockSize) { + if (dir == RoundNearest) { + if (base - blockSize < blockSize - prevBase) { + dir = RoundUp; + } else { + dir = RoundDown; + } + } + if (dir == RoundUp) { + result = base; + break; + } else { + type = prevType; + power = prevPower; + result = prevBase; + break; + } + } + + prevType = type; + prevPower = power; + prevBase = base; + } + + if (result > getMaxZoomLevel()) result = getMaxZoomLevel(); + return result; +} diff -r 000000000000 -r fc9323a41f5a data/model/PowerOfSqrtTwoZoomConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/PowerOfSqrtTwoZoomConstraint.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,38 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _POWER_OF_SQRT_TWO_ZOOM_CONSTRAINT_H_ +#define _POWER_OF_SQRT_TWO_ZOOM_CONSTRAINT_H_ + +#include "base/ZoomConstraint.h" + +class PowerOfSqrtTwoZoomConstraint : virtual public ZoomConstraint +{ +public: + virtual size_t getNearestBlockSize(size_t requestedBlockSize, + RoundingDirection dir = RoundNearest) + const; + + virtual size_t getNearestBlockSize(size_t requestedBlockSize, + int &type, + int &power, + RoundingDirection dir = RoundNearest) + const; + + virtual size_t getMinCachePower() const { return 6; } +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/model/PowerOfTwoZoomConstraint.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/PowerOfTwoZoomConstraint.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,47 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PowerOfTwoZoomConstraint.h" + +size_t +PowerOfTwoZoomConstraint::getNearestBlockSize(size_t req, + RoundingDirection dir) const +{ + size_t result = 0; + + for (size_t bs = 1; ; bs *= 2) { + if (bs >= req) { + if (dir == RoundNearest) { + if (bs - req < req - bs/2) { + result = bs; + break; + } else { + result = bs/2; + break; + } + } else if (dir == RoundDown) { + result = bs/2; + break; + } else { + result = bs; + break; + } + } + } + + if (result > getMaxZoomLevel()) result = getMaxZoomLevel(); + return result; +} + diff -r 000000000000 -r fc9323a41f5a data/model/PowerOfTwoZoomConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/PowerOfTwoZoomConstraint.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,30 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _POWER_OF_TWO_ZOOM_CONSTRAINT_H_ +#define _POWER_OF_TWO_ZOOM_CONSTRAINT_H_ + +#include "base/ZoomConstraint.h" + +class PowerOfTwoZoomConstraint : virtual public ZoomConstraint +{ +public: + virtual size_t getNearestBlockSize(size_t requestedBlockSize, + RoundingDirection dir = RoundNearest) + const; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/model/RangeSummarisableTimeValueModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/RangeSummarisableTimeValueModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_ +#define _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_ + +#include + +#include "DenseTimeValueModel.h" +#include "base/ZoomConstraint.h" + +/** + * Base class for models containing dense two-dimensional data (value + * against time) that may be meaningfully represented in a zoomed view + * using min/max range summaries. Audio waveform data is an obvious + * example: think "peaks and minima" for "ranges". + */ + +class RangeSummarisableTimeValueModel : public DenseTimeValueModel +{ + Q_OBJECT + +public: + struct Range + { + float min; + float max; + float absmean; + Range() { min = 0.f, max = 0.f , absmean = 0.f;} + Range(const Range &r) + { min = r.min, max = r.max, absmean = r.absmean; } + Range(float min_, float max_, float absmean_) + { min = min_, max = max_, absmean = absmean_; } + }; + + typedef std::vector RangeBlock; + + /** + * Return ranges between the given start and end frames, + * summarised at the given block size. ((end - start + 1) / + * blockSize) ranges should ideally be returned. + * + * If the given block size is not supported by this model + * (according to its zoom constraint), also modify the blockSize + * parameter so as to return the block size that was actually + * obtained. + */ + virtual void getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, + size_t &blockSize) const = 0; + + /** + * Return the range between the given start and end frames, + * summarised at a block size equal to the distance between start + * and end frames. + */ + virtual Range getRange(size_t channel, size_t start, size_t end) const = 0; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/model/SparseModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/SparseModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,644 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPARSE_MODEL_H_ +#define _SPARSE_MODEL_H_ + +#include "Model.h" +#include "base/Command.h" +#include "base/CommandHistory.h" + +#include + +#include +#include +#include + + +/** + * Model containing sparse data (points with some properties). The + * properties depend on the point type. + */ + +template +class SparseModel : public Model +{ +public: + SparseModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true); + virtual ~SparseModel() { } + + virtual bool isOK() const { return true; } + virtual size_t getStartFrame() const; + virtual size_t getEndFrame() const; + virtual size_t getSampleRate() const { return m_sampleRate; } + + virtual Model *clone() const; + + // Number of frames of the underlying sample rate that this model + // is capable of resolving to. For example, if m_resolution == 10 + // then every point in this model will be at a multiple of 10 + // sample frames and should be considered to cover a window ending + // 10 sample frames later. + virtual size_t getResolution() const { + return m_resolution ? m_resolution : 1; + } + virtual void setResolution(size_t resolution); + + typedef PointType Point; + typedef std::multiset PointList; + typedef typename PointList::iterator PointListIterator; + typedef typename PointList::const_iterator PointListConstIterator; + + /** + * Return whether the model is empty or not. + */ + virtual bool isEmpty() const; + + /** + * Get the total number of points in the model. + */ + virtual size_t getPointCount() const; + + /** + * Get all of the points in this model between the given + * boundaries (in frames), as well as up to two points before and + * after the boundaries. If you need exact boundaries, check the + * point coordinates in the returned list. + */ + virtual PointList getPoints(long start, long end) const; + + /** + * Get all points that cover the given frame number, taking the + * resolution of the model into account. + */ + virtual PointList getPoints(long frame) const; + + /** + * Return all points that share the nearest frame number prior to + * the given one at which there are any points. + */ + virtual PointList getPreviousPoints(long frame) const; + + /** + * Return all points that share the nearest frame number + * subsequent to the given one at which there are any points. + */ + virtual PointList getNextPoints(long frame) const; + + /** + * Remove all points. + */ + virtual void clear(); + + /** + * Add a point. + */ + virtual void addPoint(const PointType &point); + + /** + * Remove a point. Points are not necessarily unique, so this + * function will remove the first point that compares equal to the + * supplied one using Point::Comparator. Other identical points + * may remain in the model. + */ + virtual void deletePoint(const PointType &point); + + virtual void setCompletion(int completion); + virtual int getCompletion() const { return m_completion; } + + virtual bool hasTextLabels() const { return m_hasTextLabels; } + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + virtual QString toDelimitedDataString(QString delimiter) const + { + QString s; + for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) { + s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n"; + } + return s; + } + + /** + * Command to add a point, with undo. + */ + class AddPointCommand : public Command + { + public: + AddPointCommand(SparseModel *model, + const PointType &point, + QString name = "") : + m_model(model), m_point(point), m_name(name) { } + + virtual QString getName() const { + return (m_name == "" ? tr("Add Point") : m_name); + } + + virtual void execute() { m_model->addPoint(m_point); } + virtual void unexecute() { m_model->deletePoint(m_point); } + + const PointType &getPoint() const { return m_point; } + + private: + SparseModel *m_model; + PointType m_point; + QString m_name; + }; + + + /** + * Command to remove a point, with undo. + */ + class DeletePointCommand : public Command + { + public: + DeletePointCommand(SparseModel *model, + const PointType &point) : + m_model(model), m_point(point) { } + + virtual QString getName() const { return tr("Delete Point"); } + + virtual void execute() { m_model->deletePoint(m_point); } + virtual void unexecute() { m_model->addPoint(m_point); } + + const PointType &getPoint() const { return m_point; } + + private: + SparseModel *m_model; + PointType m_point; + }; + + + /** + * Command to add or remove a series of points, with undo. + * Consecutive add/remove pairs for the same point are collapsed. + */ + class EditCommand : public MacroCommand + { + public: + EditCommand(SparseModel *model, QString commandName); + + virtual void addPoint(const PointType &point); + virtual void deletePoint(const PointType &point); + + /** + * Stack an arbitrary other command in the same sequence. + */ + virtual void addCommand(Command *command) { addCommand(command, true); } + + /** + * If any points have been added or deleted, add this command + * to the command history. Otherwise delete the command. + */ + virtual void finish(); + + protected: + virtual void addCommand(Command *command, bool executeFirst); + + SparseModel *m_model; + }; + + + /** + * Command to relabel a point. + */ + class RelabelCommand : public Command + { + public: + RelabelCommand(SparseModel *model, + const PointType &point, + QString newLabel) : + m_model(model), m_oldPoint(point), m_newPoint(point) { + m_newPoint.label = newLabel; + } + + virtual QString getName() const { return tr("Re-Label Point"); } + + virtual void execute() { + m_model->deletePoint(m_oldPoint); + m_model->addPoint(m_newPoint); + std::swap(m_oldPoint, m_newPoint); + } + + virtual void unexecute() { execute(); } + + private: + SparseModel *m_model; + PointType m_oldPoint; + PointType m_newPoint; + }; + + + +protected: + size_t m_sampleRate; + size_t m_resolution; + bool m_notifyOnAdd; + long m_sinceLastNotifyMin; + long m_sinceLastNotifyMax; + bool m_hasTextLabels; + + PointList m_points; + size_t m_pointCount; + mutable QMutex m_mutex; + int m_completion; +}; + + +template +SparseModel::SparseModel(size_t sampleRate, + size_t resolution, + bool notifyOnAdd) : + m_sampleRate(sampleRate), + m_resolution(resolution), + m_notifyOnAdd(notifyOnAdd), + m_sinceLastNotifyMin(-1), + m_sinceLastNotifyMax(-1), + m_hasTextLabels(false), + m_pointCount(0), + m_completion(100) +{ +} + +template +size_t +SparseModel::getStartFrame() const +{ + QMutexLocker locker(&m_mutex); + size_t f = 0; + if (!m_points.empty()) { + f = m_points.begin()->frame; + } + return f; +} + +template +size_t +SparseModel::getEndFrame() const +{ + QMutexLocker locker(&m_mutex); + size_t f = 0; + if (!m_points.empty()) { + PointListConstIterator i(m_points.end()); + f = (--i)->frame; + } + return f; +} + +template +Model * +SparseModel::clone() const +{ + SparseModel *model = + new SparseModel(m_sampleRate, m_resolution, m_notifyOnAdd); + model->m_points = m_points; + model->m_pointCount = m_pointCount; + return model; +} + +template +bool +SparseModel::isEmpty() const +{ + return m_pointCount == 0; +} + +template +size_t +SparseModel::getPointCount() const +{ + return m_pointCount; +} + +template +typename SparseModel::PointList +SparseModel::getPoints(long start, long end) const +{ + if (start > end) return PointList(); + QMutexLocker locker(&m_mutex); + + PointType startPoint(start), endPoint(end); + + PointListConstIterator startItr = m_points.lower_bound(startPoint); + PointListConstIterator endItr = m_points.upper_bound(endPoint); + + if (startItr != m_points.begin()) --startItr; + if (startItr != m_points.begin()) --startItr; + if (endItr != m_points.end()) ++endItr; + if (endItr != m_points.end()) ++endItr; + + PointList rv; + + for (PointListConstIterator i = startItr; i != endItr; ++i) { + rv.insert(*i); + } + + return rv; +} + +template +typename SparseModel::PointList +SparseModel::getPoints(long frame) const +{ + QMutexLocker locker(&m_mutex); + + if (m_resolution == 0) return PointList(); + + long start = (frame / m_resolution) * m_resolution; + long end = start + m_resolution; + + PointType startPoint(start), endPoint(end); + + PointListConstIterator startItr = m_points.lower_bound(startPoint); + PointListConstIterator endItr = m_points.upper_bound(endPoint); + + PointList rv; + + for (PointListConstIterator i = startItr; i != endItr; ++i) { + rv.insert(*i); + } + + return rv; +} + +template +typename SparseModel::PointList +SparseModel::getPreviousPoints(long originFrame) const +{ + QMutexLocker locker(&m_mutex); + + PointType lookupPoint(originFrame); + PointList rv; + + PointListConstIterator i = m_points.lower_bound(lookupPoint); + if (i == m_points.begin()) return rv; + + --i; + long frame = i->frame; + while (i->frame == frame) { + rv.insert(*i); + if (i == m_points.begin()) break; + --i; + } + + return rv; +} + +template +typename SparseModel::PointList +SparseModel::getNextPoints(long originFrame) const +{ + QMutexLocker locker(&m_mutex); + + PointType lookupPoint(originFrame); + PointList rv; + + PointListConstIterator i = m_points.upper_bound(lookupPoint); + if (i == m_points.end()) return rv; + + long frame = i->frame; + while (i != m_points.end() && i->frame == frame) { + rv.insert(*i); + ++i; + } + + return rv; +} + +template +void +SparseModel::setResolution(size_t resolution) +{ + { + QMutexLocker locker(&m_mutex); + m_resolution = resolution; + } + emit modelChanged(); +} + +template +void +SparseModel::clear() +{ + { + QMutexLocker locker(&m_mutex); + m_points.clear(); + m_pointCount = 0; + } + emit modelChanged(); +} + +template +void +SparseModel::addPoint(const PointType &point) +{ +// std::cout << "SparseModel::addPoint(" << point.frame << ", " +// << point.value << ")" << std::endl; + + { + QMutexLocker locker(&m_mutex); + m_points.insert(point); + m_pointCount++; + if (point.label != "") m_hasTextLabels = true; + } + + // Even though this model is nominally sparse, there may still be + // too many signals going on here (especially as they'll probably + // be queued from one thread to another), which is why we need the + // notifyOnAdd as an option rather than a necessity (the + // alternative is to notify on setCompletion). + + if (m_notifyOnAdd) { + emit modelChanged(point.frame, point.frame + m_resolution); + } else { + if (m_sinceLastNotifyMin == -1 || + point.frame < m_sinceLastNotifyMin) { + m_sinceLastNotifyMin = point.frame; + } + if (m_sinceLastNotifyMax == -1 || + point.frame > m_sinceLastNotifyMax) { + m_sinceLastNotifyMax = point.frame; + } + } +} + +template +void +SparseModel::deletePoint(const PointType &point) +{ + { + QMutexLocker locker(&m_mutex); + + PointListIterator i = m_points.lower_bound(point); + typename PointType::Comparator comparator; + while (i != m_points.end()) { + if (i->frame > point.frame) break; + if (!comparator(*i, point) && !comparator(point, *i)) { + m_points.erase(i); + m_pointCount--; + break; + } + ++i; + } + } +// std::cout << "SparseOneDimensionalModel: emit modelChanged(" +// << point.frame << ")" << std::endl; + emit modelChanged(point.frame, point.frame + m_resolution); +} + +template +void +SparseModel::setCompletion(int completion) +{ +// std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; + + if (m_completion != completion) { + m_completion = completion; + + if (completion == 100) { + + m_notifyOnAdd = true; // henceforth + emit modelChanged(); + + } else if (!m_notifyOnAdd) { + + if (m_sinceLastNotifyMin >= 0 && + m_sinceLastNotifyMax >= 0) { + emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax); + m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; + } else { + emit completionChanged(); + } + } else { + emit completionChanged(); + } + } +} + +template +void +SparseModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + Model::toXml + (out, + indent, + QString("type=\"sparse\" dimensions=\"%1\" resolution=\"%2\" notifyOnAdd=\"%3\" dataset=\"%4\" %5") + .arg(PointType(0).getDimensions()) + .arg(m_resolution) + .arg(m_notifyOnAdd ? "true" : "false") + .arg(getObjectExportId(&m_points)) + .arg(extraAttributes)); + + out << indent; + out << QString("\n") + .arg(getObjectExportId(&m_points)) + .arg(PointType(0).getDimensions()); + + for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) { + out << i->toXmlString(indent + " "); + } + + out << indent; + out << "\n"; +} + +template +QString +SparseModel::toXmlString(QString indent, + QString extraAttributes) const +{ + QString s; + + { + QTextStream out(&s); + toXml(out, indent, extraAttributes); + } + + return s; +} + +template +SparseModel::EditCommand::EditCommand(SparseModel *model, + QString commandName) : + MacroCommand(commandName), + m_model(model) +{ +} + +template +void +SparseModel::EditCommand::addPoint(const PointType &point) +{ + addCommand(new AddPointCommand(m_model, point), true); +} + +template +void +SparseModel::EditCommand::deletePoint(const PointType &point) +{ + addCommand(new DeletePointCommand(m_model, point), true); +} + +template +void +SparseModel::EditCommand::finish() +{ + if (!m_commands.empty()) { + CommandHistory::getInstance()->addCommand(this, false); + } else { + delete this; + } +} + +template +void +SparseModel::EditCommand::addCommand(Command *command, + bool executeFirst) +{ + if (executeFirst) command->execute(); + + if (!m_commands.empty()) { + DeletePointCommand *dpc = dynamic_cast(command); + if (dpc) { + AddPointCommand *apc = dynamic_cast + (m_commands[m_commands.size() - 1]); + typename PointType::Comparator comparator; + if (apc) { + if (!comparator(apc->getPoint(), dpc->getPoint()) && + !comparator(dpc->getPoint(), apc->getPoint())) { + deleteCommand(apc); + return; + } + } + } + } + + MacroCommand::addCommand(command); +} + + +#endif + + + diff -r 000000000000 -r fc9323a41f5a data/model/SparseOneDimensionalModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/SparseOneDimensionalModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPARSE_ONE_DIMENSIONAL_MODEL_H_ +#define _SPARSE_ONE_DIMENSIONAL_MODEL_H_ + +#include "SparseModel.h" +#include "base/PlayParameterRepository.h" +#include "base/RealTime.h" + +struct OneDimensionalPoint +{ +public: + OneDimensionalPoint(long _frame) : frame(_frame) { } + OneDimensionalPoint(long _frame, QString _label) : frame(_frame), label(_label) { } + + int getDimensions() const { return 1; } + + long frame; + QString label; + + QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return QString("%1\n") + .arg(indent).arg(frame).arg(label).arg(extraAttributes); + } + + QString toDelimitedDataString(QString delimiter, size_t sampleRate) const + { + QStringList list; + list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); + list << label; + return list.join(delimiter); + } + + struct Comparator { + bool operator()(const OneDimensionalPoint &p1, + const OneDimensionalPoint &p2) const { + if (p1.frame != p2.frame) return p1.frame < p2.frame; + return p1.label < p2.label; + } + }; + + struct OrderComparator { + bool operator()(const OneDimensionalPoint &p1, + const OneDimensionalPoint &p2) const { + return p1.frame < p2.frame; + } + }; +}; + + +class SparseOneDimensionalModel : public SparseModel +{ +public: + SparseOneDimensionalModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true) : + SparseModel(sampleRate, resolution, notifyOnAdd) + { + PlayParameterRepository::getInstance()->addModel(this); + } + + int getIndexOf(const Point &point) { + // slow + int i = 0; + Point::Comparator comparator; + for (PointList::const_iterator j = m_points.begin(); + j != m_points.end(); ++j, ++i) { + if (!comparator(*j, point) && !comparator(point, *j)) return i; + } + return -1; + } +}; + +#endif + + + diff -r 000000000000 -r fc9323a41f5a data/model/SparseTimeValueModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/SparseTimeValueModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,102 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPARSE_TIME_VALUE_MODEL_H_ +#define _SPARSE_TIME_VALUE_MODEL_H_ + +#include "SparseValueModel.h" +#include "base/PlayParameterRepository.h" +#include "base/RealTime.h" + +/** + * Time/value point type for use in a SparseModel or SparseValueModel. + * With this point type, the model basically represents a wiggly-line + * plot with points at arbitrary intervals of the model resolution. + */ + +struct TimeValuePoint +{ +public: + TimeValuePoint(long _frame) : frame(_frame), value(0.0f) { } + TimeValuePoint(long _frame, float _value, QString _label) : + frame(_frame), value(_value), label(_label) { } + + int getDimensions() const { return 2; } + + long frame; + float value; + QString label; + + QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return QString("%1\n") + .arg(indent).arg(frame).arg(value).arg(label).arg(extraAttributes); + } + + QString toDelimitedDataString(QString delimiter, size_t sampleRate) const + { + QStringList list; + list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); + list << QString("%1").arg(value); + list << label; + return list.join(delimiter); + } + + struct Comparator { + bool operator()(const TimeValuePoint &p1, + const TimeValuePoint &p2) const { + if (p1.frame != p2.frame) return p1.frame < p2.frame; + if (p1.value != p2.value) return p1.value < p2.value; + return p1.label < p2.label; + } + }; + + struct OrderComparator { + bool operator()(const TimeValuePoint &p1, + const TimeValuePoint &p2) const { + return p1.frame < p2.frame; + } + }; +}; + + +class SparseTimeValueModel : public SparseValueModel +{ +public: + SparseTimeValueModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true) : + SparseValueModel(sampleRate, resolution, + notifyOnAdd) + { + PlayParameterRepository::getInstance()->addModel(this); + } + + SparseTimeValueModel(size_t sampleRate, size_t resolution, + float valueMinimum, float valueMaximum, + bool notifyOnAdd = true) : + SparseValueModel(sampleRate, resolution, + valueMinimum, valueMaximum, + notifyOnAdd) + { + PlayParameterRepository::getInstance()->addModel(this); + } +}; + + +#endif + + + diff -r 000000000000 -r fc9323a41f5a data/model/SparseValueModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/SparseValueModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,128 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPARSE_VALUE_MODEL_H_ +#define _SPARSE_VALUE_MODEL_H_ + +#include "system/System.h" +#include "SparseModel.h" +#include "base/UnitDatabase.h" + +#include + +/** + * Model containing sparse data (points with some properties) of which + * one of the properties is an arbitrary float value. The other + * properties depend on the point type. + */ + +template +class SparseValueModel : public SparseModel +{ +public: + SparseValueModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true) : + SparseModel(sampleRate, resolution, notifyOnAdd), + m_valueMinimum(0.f), + m_valueMaximum(0.f), + m_haveExtents(false) + { } + + SparseValueModel(size_t sampleRate, size_t resolution, + float valueMinimum, float valueMaximum, + bool notifyOnAdd = true) : + SparseModel(sampleRate, resolution, notifyOnAdd), + m_valueMinimum(valueMinimum), + m_valueMaximum(valueMaximum), + m_haveExtents(true) + { } + + using SparseModel::m_points; + using SparseModel::modelChanged; + + virtual float getValueMinimum() const { return m_valueMinimum; } + virtual float getValueMaximum() const { return m_valueMaximum; } + + virtual QString getScaleUnits() const { return m_units; } + virtual void setScaleUnits(QString units) { + m_units = units; + UnitDatabase::getInstance()->registerUnit(units); + } + + virtual void addPoint(const PointType &point) + { + bool allChange = false; + + if (!isnan(point.value) && !isinf(point.value)) { + if (!m_haveExtents || point.value < m_valueMinimum) { + m_valueMinimum = point.value; allChange = true; + } + if (!m_haveExtents || point.value > m_valueMaximum) { + m_valueMaximum = point.value; allChange = true; + } + m_haveExtents = true; + } + + SparseModel::addPoint(point); + if (allChange) emit modelChanged(); + } + + virtual void deletePoint(const PointType &point) + { + SparseModel::deletePoint(point); + + if (point.value == m_valueMinimum || + point.value == m_valueMaximum) { + + float formerMin = m_valueMinimum, formerMax = m_valueMaximum; + + for (typename SparseModel::PointList::const_iterator i + = m_points.begin(); + i != m_points.end(); ++i) { + + if (i == m_points.begin() || i->value < m_valueMinimum) { + m_valueMinimum = i->value; + } + if (i == m_points.begin() || i->value > m_valueMaximum) { + m_valueMaximum = i->value; + } + } + + if (formerMin != m_valueMinimum || formerMax != m_valueMaximum) { + emit modelChanged(); + } + } + } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return SparseModel::toXmlString + (indent, + QString("%1 minimum=\"%2\" maximum=\"%3\" units=\"%4\"") + .arg(extraAttributes).arg(m_valueMinimum).arg(m_valueMaximum) + .arg(this->encodeEntities(m_units))); + } + +protected: + float m_valueMinimum; + float m_valueMaximum; + bool m_haveExtents; + QString m_units; +}; + + +#endif + diff -r 000000000000 -r fc9323a41f5a data/model/TextModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/TextModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TEXT_MODEL_H_ +#define _TEXT_MODEL_H_ + +#include "SparseModel.h" +#include "base/RealTime.h" + +/** + * Text point type for use in a SparseModel. This represents a piece + * of text at a given time and y-value in the [0,1) range (indicative + * of height on the window). Intended for casual textual annotations. + */ + +struct TextPoint +{ +public: + TextPoint(long _frame) : frame(_frame), height(0.0f) { } + TextPoint(long _frame, float _height, QString _label) : + frame(_frame), height(_height), label(_label) { } + + int getDimensions() const { return 2; } + + long frame; + float height; + QString label; + + QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return QString("%1\n") + .arg(indent).arg(frame).arg(height).arg(label).arg(extraAttributes); + } + + QString toDelimitedDataString(QString delimiter, size_t sampleRate) const + { + QStringList list; + list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); + list << QString("%1").arg(height); + list << label; + return list.join(delimiter); + } + + struct Comparator { + bool operator()(const TextPoint &p1, + const TextPoint &p2) const { + if (p1.frame != p2.frame) return p1.frame < p2.frame; + if (p1.height != p2.height) return p1.height < p2.height; + return p1.label < p2.label; + } + }; + + struct OrderComparator { + bool operator()(const TextPoint &p1, + const TextPoint &p2) const { + return p1.frame < p2.frame; + } + }; +}; + + +// Make this a class rather than a typedef so it can be predeclared. + +class TextModel : public SparseModel +{ +public: + TextModel(size_t sampleRate, size_t resolution, bool notifyOnAdd = true) : + SparseModel(sampleRate, resolution, notifyOnAdd) + { } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const + { + return SparseModel::toXmlString + (indent, + QString("%1 subtype=\"text\"") + .arg(extraAttributes)); + } +}; + + +#endif + + + diff -r 000000000000 -r fc9323a41f5a data/model/WaveFileModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WaveFileModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,600 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WaveFileModel.h" + +#include "fileio/AudioFileReader.h" +#include "fileio/AudioFileReaderFactory.h" + +#include "system/System.h" + +#include +#include + +#include +//#include +#include +#include + +#include + +//#define DEBUG_WAVE_FILE_MODEL 1 + +using std::cerr; +using std::endl; + +PowerOfSqrtTwoZoomConstraint +WaveFileModel::m_zoomConstraint; + +WaveFileModel::WaveFileModel(QString path) : + m_path(path), + m_myReader(true), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + m_reader = AudioFileReaderFactory::createReader(path); + setObjectName(QFileInfo(path).fileName()); + if (isOK()) fillCache(); +} + +WaveFileModel::WaveFileModel(QString path, QString originalLocation) : + m_path(originalLocation), + m_myReader(true), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + m_reader = AudioFileReaderFactory::createReader(path); + setObjectName(QFileInfo(originalLocation).fileName()); + if (isOK()) fillCache(); +} + +WaveFileModel::WaveFileModel(QString path, AudioFileReader *reader) : + m_path(path), + m_myReader(false), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + m_reader = reader; + setObjectName(QFileInfo(path).fileName()); + fillCache(); +} + +WaveFileModel::~WaveFileModel() +{ + m_exiting = true; + if (m_fillThread) m_fillThread->wait(); + if (m_myReader) delete m_reader; + m_reader = 0; +} + +bool +WaveFileModel::isOK() const +{ + return m_reader && m_reader->isOK(); +} + +bool +WaveFileModel::isReady(int *completion) const +{ + bool ready = (isOK() && (m_fillThread == 0)); + double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); + if (completion) *completion = int(c * 100.0 + 0.01); +#ifdef DEBUG_WAVE_FILE_MODEL + std::cerr << "WaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << std::endl; +#endif + return ready; +} + +Model * +WaveFileModel::clone() const +{ + WaveFileModel *model = new WaveFileModel(m_path); + return model; +} + +size_t +WaveFileModel::getFrameCount() const +{ + if (!m_reader) return 0; + return m_reader->getFrameCount(); +} + +size_t +WaveFileModel::getChannelCount() const +{ + if (!m_reader) return 0; + return m_reader->getChannelCount(); +} + +size_t +WaveFileModel::getSampleRate() const +{ + if (!m_reader) return 0; + return m_reader->getSampleRate(); +} + +size_t +WaveFileModel::getValues(int channel, size_t start, size_t end, + float *buffer) const +{ + // Always read these directly from the file. + // This is used for e.g. audio playback. + // Could be much more efficient (although compiler optimisation will help) + + if (end < start) { + std::cerr << "ERROR: WaveFileModel::getValues[float]: end < start (" + << end << " < " << start << ")" << std::endl; + assert(end >= start); + } + + if (!m_reader || !m_reader->isOK()) return 0; + +#ifdef DEBUG_WAVE_FILE_MODEL +// std::cerr << "WaveFileModel::getValues(" << channel << ", " +// << start << ", " << end << "): calling reader" << std::endl; +#endif + + SampleBlock frames; + m_reader->getInterleavedFrames(start, end - start, frames); + + size_t i = 0; + + int ch0 = channel, ch1 = channel, channels = getChannelCount(); + if (channel == -1) { + ch0 = 0; + ch1 = channels - 1; + } + + while (i < end - start) { + + buffer[i] = 0.0; + + for (int ch = ch0; ch <= ch1; ++ch) { + + size_t index = i * channels + ch; + if (index >= frames.size()) break; + + float sample = frames[index]; + buffer[i] += sample; + } + + ++i; + } + + return i; +} + +size_t +WaveFileModel::getValues(int channel, size_t start, size_t end, + double *buffer) const +{ + if (end < start) { + std::cerr << "ERROR: WaveFileModel::getValues[double]: end < start (" + << end << " < " << start << ")" << std::endl; + assert(end >= start); + } + + if (!m_reader || !m_reader->isOK()) return 0; + + SampleBlock frames; + m_reader->getInterleavedFrames(start, end - start, frames); + + size_t i = 0; + + int ch0 = channel, ch1 = channel, channels = getChannelCount(); + if (channel == -1) { + ch0 = 0; + ch1 = channels - 1; + } + + while (i < end - start) { + + buffer[i] = 0.0; + + for (int ch = ch0; ch <= ch1; ++ch) { + + size_t index = i * channels + ch; + if (index >= frames.size()) break; + + float sample = frames[index]; + buffer[i] += sample; + } + + ++i; + } + + return i; +} + +void +WaveFileModel::getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, size_t &blockSize) const +{ + ranges.clear(); + if (!isOK()) return; + + if (end <= start) { + std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRanges (end = " << end << ", start = " << start << ", blocksize = " << blockSize << ")" << std::endl; + return; + } + + int cacheType = 0; + int power = m_zoomConstraint.getMinCachePower(); + blockSize = m_zoomConstraint.getNearestBlockSize + (blockSize, cacheType, power, ZoomConstraint::RoundUp); + + size_t channels = getChannelCount(); + + if (cacheType != 0 && cacheType != 1) { + + // We need to read directly from the file. We haven't got + // this cached. Hope the requested area is small. This is + // not optimal -- we'll end up reading the same frames twice + // for stereo files, in two separate calls to this method. + // We could fairly trivially handle this for most cases that + // matter by putting a single cache in getInterleavedFrames + // for short queries. + + SampleBlock frames; + m_reader->getInterleavedFrames(start, end - start, frames); + float max = 0.0, min = 0.0, total = 0.0; + size_t i = 0, count = 0; + + while (i < end - start) { + + size_t index = i * channels + channel; + if (index >= frames.size()) break; + + float sample = frames[index]; + if (sample > max || count == 0) max = sample; + if (sample < min || count == 0) min = sample; + total += fabsf(sample); + + ++i; + ++count; + + if (count == blockSize) { + ranges.push_back(Range(min, max, total / count)); + min = max = total = 0.0f; + count = 0; + } + } + + if (count > 0) { + ranges.push_back(Range(min, max, total / count)); + } + + return; + + } else { + + QMutexLocker locker(&m_mutex); + + const RangeBlock &cache = m_cache[cacheType]; + + size_t cacheBlock, div; + + if (cacheType == 0) { + cacheBlock = (1 << m_zoomConstraint.getMinCachePower()); + div = (1 << power) / cacheBlock; + } else { + cacheBlock = ((unsigned int)((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.0) + 0.01)); + div = ((unsigned int)((1 << power) * sqrt(2.0) + 0.01)) / cacheBlock; + } + + size_t startIndex = start / cacheBlock; + size_t endIndex = end / cacheBlock; + + float max = 0.0, min = 0.0, total = 0.0; + size_t i = 0, count = 0; + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", end " << end << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; +#endif + + for (i = 0; i < endIndex - startIndex; ) { + + size_t index = (i + startIndex) * channels + channel; + if (index >= cache.size()) break; + + const Range &range = cache[index]; + if (range.max > max || count == 0) max = range.max; + if (range.min < min || count == 0) min = range.min; + total += range.absmean; + + ++i; + ++count; + + if (count == div) { + ranges.push_back(Range(min, max, total / count)); + min = max = total = 0.0f; + count = 0; + } + } + + if (count > 0) { + ranges.push_back(Range(min, max, total / count)); + } + } + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "returning " << ranges.size() << " ranges" << endl; +#endif + return; +} + +WaveFileModel::Range +WaveFileModel::getRange(size_t channel, size_t start, size_t end) const +{ + Range range; + if (!isOK()) return range; + + if (end <= start) { + std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRange (end = " << end << ", start = " << start << ")" << std::endl; + return range; + } + + size_t blockSize; + for (blockSize = 1; blockSize <= end - start; blockSize *= 2); + blockSize /= 2; + + bool first = false; + + size_t blockStart = (start / blockSize) * blockSize; + size_t blockEnd = (end / blockSize) * blockSize; + + if (blockStart < start) blockStart += blockSize; + + if (blockEnd > blockStart) { + RangeBlock ranges; + getRanges(channel, blockStart, blockEnd, ranges, blockSize); + for (size_t i = 0; i < ranges.size(); ++i) { + if (first || ranges[i].min < range.min) range.min = ranges[i].min; + if (first || ranges[i].max > range.max) range.max = ranges[i].max; + if (first || ranges[i].absmean < range.absmean) range.absmean = ranges[i].absmean; + first = false; + } + } + + if (blockStart > start) { + Range startRange = getRange(channel, start, blockStart); + range.min = min(range.min, startRange.min); + range.max = max(range.max, startRange.max); + range.absmean = min(range.absmean, startRange.absmean); + } + + if (blockEnd < end) { + Range endRange = getRange(channel, blockEnd, end); + range.min = min(range.min, endRange.min); + range.max = max(range.max, endRange.max); + range.absmean = min(range.absmean, endRange.absmean); + } + + return range; +} + +void +WaveFileModel::fillCache() +{ + m_mutex.lock(); + + m_updateTimer = new QTimer(this); + connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); + m_updateTimer->start(100); + + m_fillThread = new RangeCacheFillThread(*this); + connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); + + m_mutex.unlock(); + m_fillThread->start(); + +#ifdef DEBUG_WAVE_FILE_MODEL + std::cerr << "WaveFileModel::fillCache: started fill thread" << std::endl; +#endif +} + +void +WaveFileModel::fillTimerTimedOut() +{ + if (m_fillThread) { + size_t fillExtent = m_fillThread->getFillExtent(); +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; +#endif + if (fillExtent > m_lastFillExtent) { + emit modelChanged(m_lastFillExtent, fillExtent); + m_lastFillExtent = fillExtent; + } + } else { +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "WaveFileModel::fillTimerTimedOut: no thread" << std::endl; +#endif + emit modelChanged(); + } +} + +void +WaveFileModel::cacheFilled() +{ + m_mutex.lock(); + delete m_fillThread; + m_fillThread = 0; + delete m_updateTimer; + m_updateTimer = 0; + m_mutex.unlock(); + emit modelChanged(); +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "WaveFileModel::cacheFilled" << endl; +#endif +} + +void +WaveFileModel::RangeCacheFillThread::run() +{ + size_t cacheBlockSize[2]; + cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); + cacheBlockSize[1] = ((unsigned int)((1 << m_model.m_zoomConstraint.getMinCachePower()) * + sqrt(2.0) + 0.01)); + + size_t frame = 0; + size_t readBlockSize = 16384; + SampleBlock block; + + if (!m_model.isOK()) return; + + size_t channels = m_model.getChannelCount(); + bool updating = m_model.m_reader->isUpdating(); + + if (updating) { + while (channels == 0 && !m_model.m_exiting) { +// std::cerr << "WaveFileModel::fill: Waiting for channels..." << std::endl; + sleep(1); + channels = m_model.getChannelCount(); + } + } + + Range *range = new Range[2 * channels]; + size_t count[2]; + count[0] = count[1] = 0; + + bool first = true; + + while (first || updating) { + + updating = m_model.m_reader->isUpdating(); + m_frameCount = m_model.getFrameCount(); + +// std::cerr << "WaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << std::endl; + + while (frame < m_frameCount) { + + if (updating && (frame + readBlockSize > m_frameCount)) break; + + m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block); + + for (size_t i = 0; i < readBlockSize; ++i) { + + if (channels * i + channels > block.size()) break; + + for (size_t ch = 0; ch < size_t(channels); ++ch) { + + size_t index = channels * i + ch; + float sample = block[index]; + + for (size_t ct = 0; ct < 2; ++ct) { // cache type + + size_t rangeIndex = ch * 2 + ct; + + if (sample > range[rangeIndex].max || count[ct] == 0) { + range[rangeIndex].max = sample; + } + if (sample < range[rangeIndex].min || count[ct] == 0) { + range[rangeIndex].min = sample; + } + range[rangeIndex].absmean += fabsf(sample); + } + } + + QMutexLocker locker(&m_model.m_mutex); + + for (size_t ct = 0; ct < 2; ++ct) { + + if (++count[ct] == cacheBlockSize[ct]) { + + for (size_t ch = 0; ch < size_t(channels); ++ch) { + size_t rangeIndex = ch * 2 + ct; + range[rangeIndex].absmean /= count[ct]; + m_model.m_cache[ct].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + } + + count[ct] = 0; + } + } + + ++frame; + } + + if (m_model.m_exiting) break; + + m_fillExtent = frame; + } + + first = false; + if (m_model.m_exiting) break; + if (updating) { + sleep(1); + } + } + + if (!m_model.m_exiting) { + + QMutexLocker locker(&m_model.m_mutex); + + for (size_t ct = 0; ct < 2; ++ct) { + + if (count[ct] > 0) { + + for (size_t ch = 0; ch < size_t(channels); ++ch) { + size_t rangeIndex = ch * 2 + ct; + range[rangeIndex].absmean /= count[ct]; + m_model.m_cache[ct].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + } + + count[ct] = 0; + } + + const Range &rr = *m_model.m_cache[ct].begin(); + MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range)); + } + } + + delete[] range; + + m_fillExtent = m_frameCount; + +#ifdef DEBUG_WAVE_FILE_MODEL + for (size_t ct = 0; ct < 2; ++ct) { + cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl; + } +#endif +} + +void +WaveFileModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + Model::toXml(out, indent, + QString("type=\"wavefile\" file=\"%1\" %2") + .arg(m_path).arg(extraAttributes)); +} + +QString +WaveFileModel::toXmlString(QString indent, + QString extraAttributes) const +{ + return Model::toXmlString(indent, + QString("type=\"wavefile\" file=\"%1\" %2") + .arg(m_path).arg(extraAttributes)); +} + diff -r 000000000000 -r fc9323a41f5a data/model/WaveFileModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WaveFileModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,119 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WAVE_FILE_MODEL_H_ +#define _WAVE_FILE_MODEL_H_ + +#include "base/Thread.h" +#include +#include + +#include "RangeSummarisableTimeValueModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" + +#include + +class AudioFileReader; + +class WaveFileModel : public RangeSummarisableTimeValueModel +{ + Q_OBJECT + +public: + WaveFileModel(QString path); + WaveFileModel(QString path, QString originalLocation); + WaveFileModel(QString originalLocation, AudioFileReader *reader); + ~WaveFileModel(); + + bool isOK() const; + bool isReady(int *) const; + + const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + + size_t getFrameCount() const; + size_t getChannelCount() const; + size_t getSampleRate() const; + + virtual Model *clone() const; + + float getValueMinimum() const { return -1.0f; } + float getValueMaximum() const { return 1.0f; } + + virtual size_t getStartFrame() const { return 0; } + virtual size_t getEndFrame() const { return getFrameCount(); } + + virtual size_t getValues(int channel, size_t start, size_t end, + float *buffer) const; + + virtual size_t getValues(int channel, size_t start, size_t end, + double *buffer) const; + + virtual void getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, + size_t &blockSize) const; + + virtual Range getRange(size_t channel, size_t start, size_t end) const; + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +signals: + void modelChanged(); + void modelChanged(size_t, size_t); + void completionChanged(); + +protected slots: + void fillTimerTimedOut(); + void cacheFilled(); + +protected: + void initialize(); + + class RangeCacheFillThread : public Thread + { + public: + RangeCacheFillThread(WaveFileModel &model) : + m_model(model), m_fillExtent(0), + m_frameCount(model.getFrameCount()) { } + + size_t getFillExtent() const { return m_fillExtent; } + virtual void run(); + + protected: + WaveFileModel &m_model; + size_t m_fillExtent; + size_t m_frameCount; + }; + + void fillCache(); + + QString m_path; + AudioFileReader *m_reader; + bool m_myReader; + + RangeBlock m_cache[2]; // interleaved at two base resolutions + mutable QMutex m_mutex; + RangeCacheFillThread *m_fillThread; + QTimer *m_updateTimer; + size_t m_lastFillExtent; + bool m_exiting; + static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a data/model/WritableWaveFileModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WritableWaveFileModel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,225 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WritableWaveFileModel.h" + +#include "base/TempDirectory.h" +#include "base/Exceptions.h" + +#include "fileio/WavFileWriter.h" +#include "fileio/WavFileReader.h" + +#include + +#include +#include + +//#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1 + +WritableWaveFileModel::WritableWaveFileModel(size_t sampleRate, + size_t channels, + QString path) : + m_model(0), + m_writer(0), + m_reader(0), + m_sampleRate(sampleRate), + m_channels(channels), + m_frameCount(0), + m_completion(0) +{ + if (path.isEmpty()) { + try { + QDir dir(TempDirectory::getInstance()->getPath()); + path = dir.filePath(QString("written_%1.wav") + .arg((intptr_t)this)); + } catch (DirectoryCreationFailed f) { + std::cerr << "WritableWaveFileModel: Failed to create temporary directory" << std::endl; + return; + } + } + + m_writer = new WavFileWriter(path, sampleRate, channels); + if (!m_writer->isOK()) { + std::cerr << "WritableWaveFileModel: Error in creating WAV file writer: " << m_writer->getError().toStdString() << std::endl; + delete m_writer; + m_writer = 0; + return; + } + + m_reader = new WavFileReader(m_writer->getPath(), true); + if (!m_reader->getError().isEmpty()) { + std::cerr << "WritableWaveFileModel: Error in creating wave file reader" << std::endl; + delete m_reader; + m_reader = 0; + return; + } + + m_model = new WaveFileModel(m_writer->getPath(), m_reader); + if (!m_model->isOK()) { + std::cerr << "WritableWaveFileModel: Error in creating wave file model" << std::endl; + delete m_model; + m_model = 0; + delete m_reader; + m_reader = 0; + return; + } + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); +} + +WritableWaveFileModel::~WritableWaveFileModel() +{ + delete m_model; + delete m_writer; + delete m_reader; +} + +bool +WritableWaveFileModel::addSamples(float **samples, size_t count) +{ + if (!m_writer) return false; + +#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL +// std::cerr << "WritableWaveFileModel::addSamples(" << count << ")" << std::endl; +#endif + + if (!m_writer->writeSamples(samples, count)) { + std::cerr << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << m_writer->getError().toStdString() << std::endl; + return false; + } + + m_frameCount += count; + + static int updateCounter = 0; + + if (m_reader && m_reader->getChannelCount() == 0) { +#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL + std::cerr << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (initial)" << std::endl; +#endif + m_reader->updateFrameCount(); + } else if (++updateCounter == 100) { +#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL + std::cerr << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (periodic)" << std::endl; +#endif + if (m_reader) m_reader->updateFrameCount(); + updateCounter = 0; + } + + return true; +} + +bool +WritableWaveFileModel::isOK() const +{ + bool ok = (m_writer && m_writer->isOK()); +// std::cerr << "WritableWaveFileModel::isOK(): ok = " << ok << std::endl; + return ok; +} + +bool +WritableWaveFileModel::isReady(int *completion) const +{ + if (completion) *completion = m_completion; + return (m_completion == 100); +} + +void +WritableWaveFileModel::setCompletion(int completion) +{ + m_completion = completion; + if (completion == 100) { + if (m_reader) m_reader->updateDone(); + } +} + +size_t +WritableWaveFileModel::getFrameCount() const +{ +// std::cerr << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << std::endl; + return m_frameCount; +} + +Model * +WritableWaveFileModel::clone() const +{ + assert(0); //!!! + return 0; +} + +size_t +WritableWaveFileModel::getValues(int channel, size_t start, size_t end, + float *buffer) const +{ + if (!m_model || m_model->getChannelCount() == 0) return 0; + return m_model->getValues(channel, start, end, buffer); +} + +size_t +WritableWaveFileModel::getValues(int channel, size_t start, size_t end, + double *buffer) const +{ + if (!m_model || m_model->getChannelCount() == 0) return 0; +// std::cerr << "WritableWaveFileModel::getValues(" << channel << ", " +// << start << ", " << end << "): calling model" << std::endl; + return m_model->getValues(channel, start, end, buffer); +} + +void +WritableWaveFileModel::getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, + size_t &blockSize) const +{ + ranges.clear(); + if (!m_model || m_model->getChannelCount() == 0) return; + m_model->getRanges(channel, start, end, ranges, blockSize); +} + +WritableWaveFileModel::Range +WritableWaveFileModel::getRange(size_t channel, size_t start, size_t end) const +{ + if (!m_model || m_model->getChannelCount() == 0) return Range(); + return m_model->getRange(channel, start, end); +} + +void +WritableWaveFileModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + // We don't actually write the data to XML. We just write a brief + // description of the model. Any code that uses this class is + // going to need to be aware that it will have to make separate + // arrangements for the audio file itself. + + Model::toXml + (out, indent, + QString("type=\"writablewavefile\" file=\"%1\" channels=\"%2\" %3") + .arg(m_writer->getPath()).arg(m_model->getChannelCount()).arg(extraAttributes)); +} + +QString +WritableWaveFileModel::toXmlString(QString indent, + QString extraAttributes) const +{ + // As above. + + return Model::toXmlString + (indent, + QString("type=\"writablewavefile\" file=\"%1\" channels=\"%2\" %3") + .arg(m_writer->getPath()).arg(m_model->getChannelCount()).arg(extraAttributes)); +} + diff -r 000000000000 -r fc9323a41f5a data/model/WritableWaveFileModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WritableWaveFileModel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,92 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WRITABLE_WAVE_FILE_MODEL_H_ +#define _WRITABLE_WAVE_FILE_MODEL_H_ + +#include "WaveFileModel.h" + +class WavFileWriter; +class WavFileReader; + +class WritableWaveFileModel : public RangeSummarisableTimeValueModel +{ + Q_OBJECT + +public: + WritableWaveFileModel(size_t sampleRate, size_t channels, QString path = ""); + ~WritableWaveFileModel(); + + /** + * Call addSamples to append a block of samples to the end of the + * file. Caller should also call setCompletion to update the + * progress of this file, if it has a known end point, and should + * call setCompletion(100) when the file has been written. + */ + virtual bool addSamples(float **samples, size_t count); + + bool isOK() const; + bool isReady(int *) const; + + virtual void setCompletion(int completion); // percentage + virtual int getCompletion() const { return m_completion; } + + const ZoomConstraint *getZoomConstraint() const { + static PowerOfSqrtTwoZoomConstraint zc; + return &zc; + } + + size_t getFrameCount() const; + size_t getChannelCount() const { return m_channels; } + size_t getSampleRate() const { return m_sampleRate; } + + virtual Model *clone() const; + + float getValueMinimum() const { return -1.0f; } + float getValueMaximum() const { return 1.0f; } + + virtual size_t getStartFrame() const { return 0; } + virtual size_t getEndFrame() const { return getFrameCount(); } + + virtual size_t getValues(int channel, size_t start, size_t end, + float *buffer) const; + + virtual size_t getValues(int channel, size_t start, size_t end, + double *buffer) const; + + virtual void getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, size_t &blockSize) const; + + virtual Range getRange(size_t channel, size_t start, size_t end) const; + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +protected: + WaveFileModel *m_model; + WavFileWriter *m_writer; + WavFileReader *m_reader; + size_t m_sampleRate; + size_t m_channels; + size_t m_frameCount; + int m_completion; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a data/svdata.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/svdata.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,904 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a deploy_mac.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deploy_mac.sh Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,72 @@ +#!/bin/bash + +# this script should be executed from the directory that contains the sonic-visualiser.app directory (application bundle) +# it copies the required 3rd party libraries into the application bundle and corrects the library install names and references + +LIBPREFIX=/usr/local/lib/ +#QTPREFIX=/usr/local/Trolltech/Qt-4.1.0/lib/ +TARGETPATH=sonic-visualiser.app/Contents/Frameworks/ + +mkdir $TARGETPATH + +# add other required libs (e.g. oggz) +# use "otool -L " to get a list of required libraries + +#LIBS="libmad.0.2.1.dylib libsndfile.1.dylib libsamplerate.0.dylib libportaudio.dylib.0.0.18 liboggz.1.dylib libfishsound.1.dylib libvorbisenc.2.dylib libvorbis.0.dylib libogg.0.dylib" + +# libjack.0.dylib liblrdf.2.dylib libraptor.1.dylib + +LIBS= + +for LIB in $LIBS; +do + cp $LIBPREFIX$LIB $TARGETPATH +done + +for LIB in $LIBS; +do + install_name_tool -id @executable_path/../Frameworks/${LIB} sonic-visualiser.app/Contents/Frameworks/${LIB} +done + +for LIB in $LIBS; +do + install_name_tool -change ${LIBPREFIX}${LIB} @executable_path/../Frameworks/${LIB} sonic-visualiser.app/Contents/MacOS/sonic-visualiser +done + +pushd $TARGETPATH + +for LIB in $LIBS; +do +for TARGETLIB in $LIBS; +do + install_name_tool -change ${LIBPREFIX}${TARGETLIB} @executable_path/../Frameworks/${TARGETLIB} $LIB +done +done + +popd + +QTPREFIX=/Library/Frameworks/ +QTFWKS="QtXml QtCore QtGui QtNetwork" + +# copy the dynamic libraries into the app bundle + +for FWK in $QTFWKS; do + cp ${QTPREFIX}${FWK}.framework/Versions/4/${FWK} ${TARGETPATH} +done + +# change the id's of the dylibs +for FWK in $QTFWKS; do + install_name_tool -id @executable_path/../Frameworks/${FWK} sonic-visualiser.app/Contents/Frameworks/$FWK +done + +# tell the linker to look for dylibs in the app bundle +for FWK in $QTFWKS; do + install_name_tool -change ${FWK}.framework/Versions/4/${FWK} @executable_path/../Frameworks/${FWK} sonic-visualiser.app/Contents/MacOS/sonic-visualiser +done + +# correct dependencies between QT dylibs +for FWK in $QTFWKS; do + case $FWK in QtCore) continue;; esac + install_name_tool -change QtCore.framework/Versions/4/QtCore @executable_path/../Frameworks/QtCore sonic-visualiser.app/Contents/Frameworks/${FWK} +done + diff -r 000000000000 -r fc9323a41f5a find-todo.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/find-todo.pl Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,69 @@ +#!/usr/bin/perl -w +require 5; +use strict; + +# Command line should contain a list of files to look in. +# +# A likely invocation would be +# find . \( -name \*.h -o -name \*.cpp \) -print | xargs perl ./find-todo.pl +# +# Unusual or binary files will be ignored. + +my $todo_count = 0; +my $file_count = 0; +my $unfinished_count = 0; + +sub print_file_maybe +{ + my ($pf, $file) = @_; + if (! $$pf) { + print "\n$file:\n"; + $$pf = 1; + $file_count++; + } +} + +foreach (@ARGV) { + + my $file = $_; + next if ($file =~ /~$/); + open FILE, $file or next; + my $in_todo = 0; + my $printed_file = 0; + my $line = 0; + + while () { + + $line++; + last if (m.^[\200-\377].); # probably a binary file + + if ($in_todo) { + + if (m, /[/\*](!!!)?\s* (.*) $ ,x) { + print " $2\n"; + } else { + $in_todo = 0; + } + + } else { + + if (m, /[/\*] !!!\s* (.*) $ ,x) { + print_file_maybe(\$printed_file, $file); + print sprintf("%8d", $line), ": $1\n"; + $in_todo = 1; + $todo_count++; + + } elsif (m, /[/\*] \Q...\E \s* (.*) $ ,x) { + print_file_maybe(\$printed_file, $file); + print sprintf("%8d", $line), ": [U] $1\n"; + $unfinished_count++; + } + } + } + + close FILE; +} + +print "\nTotal: $todo_count problem items, $unfinished_count unfinished " . + "markers in $file_count files\n\n"; + diff -r 000000000000 -r fc9323a41f5a ladspa.cat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ladspa.cat Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,304 @@ +ladspa:adsr_1653:adsr::Amplitude > Dynamics +ladspa:adsr_1680:adsr_g+t::Amplitude > Dynamics +ladspa:alias_1407:alias::Amplitude > Distortions +ladspa:allpass_1895:allpass_c::Time > Delays +ladspa:allpass_1895:allpass_l::Time > Delays +ladspa:allpass_1895:allpass_n::Time > Delays +ladspa:amp_1181:amp::Amplitude > Amplifiers +ladspa:amp_1654:amp_gaia_oa::Amplitude > Amplifiers +ladspa:amp_1654:amp_gcia_oa::Amplitude > Amplifiers +ladspa:amp:amp_mono::Amplitude > Amplifiers +ladspa:amp:amp_stereo::Amplitude > Amplifiers +ladspa:am_pitchshift_1433:amPitchshift::Frequency > Pitch shifters +ladspa:analogue_osc_1416:analogueOsc::Generators > Oscillators +ladspa:bandpass_a_iir_1893:bandpass_a_iir::Frequency > Filters > Bandpass +ladspa:bandpass_iir_1892:bandpass_iir::Frequency > Filters > Bandpass +ladspa:blvco:Pulse-VCO::Generators > Oscillators +ladspa:blvco:Rec-VCO::Generators > Oscillators +ladspa:blvco:Saw-VCO::Generators > Oscillators +ladspa:bode_shifter_1431:bodeShifter::Spectral +ladspa:bode_shifter_cv_1432:bodeShifterCV::Spectral +ladspa:branch_1673:branch_ia_oaoa::Utilities +ladspa:branch_1673:branch_ic_ococ::Utilities +ladspa:butterworth_1902:butthigh_iir::Frequency > Filters > Highpass +ladspa:butterworth_1902:buttlow_iir::Frequency > Filters > Lowpass +ladspa:butterworth_1902:bwxover_iir::Frequency > Filters > Bandpass +ladspa:caps:AmpIII::Amplitude > Distortions +ladspa:caps:AmpIV::Amplitude > Distortions +ladspa:caps:Cabinet::Amplitude > Distortions +ladspa:caps:CEO::Generators > Oscillators +ladspa:caps:Chorus::Time > Chorus +ladspa:caps:Click::Utilities +ladspa:caps:Clip::Amplitude > Distortions +ladspa:caps:Compress::Amplitude > Dynamics > Compressors +ladspa:caps:Eq::Frequency > EQs +ladspa:caps:HRTF::Utilities +ladspa:caps:JVRev::Time > Reverbs +ladspa:caps:Lorenz::Generators +ladspa:caps:Pan::Utilities +ladspa:caps:Phaser::Time > Phasers +ladspa:caps:Plate2x2::Time > Reverbs +ladspa:caps:Plate::Time > Reverbs +ladspa:caps:PreampIII::Amplitude > Distortions +ladspa:caps:PreampIV::Amplitude > Distortions +ladspa:caps:Roessler::Generators +ladspa:caps:Sin::Generators +ladspa:caps:StereoChorus::Time > Chorus +ladspa:caps:SweepVF::Frequency > Filters +ladspa:caps:VCOd::Generators > Oscillators +ladspa:caps:VCOs::Generators > Oscillators +ladspa:caps:White::Generators +ladspa:chebstortion_1430:chebstortion::Amplitude > Distortions +ladspa:cmt:am::Amplitude > Modulators +ladspa:cmt:amp_mono::Amplitude > Amplifiers +ladspa:cmt:amp_stereo::Amplitude > Amplifiers +ladspa:cmt:analogue::Generators +ladspa:cmt:bf2cube::Utilities > Ambisonic +ladspa:cmt:bf2quad::Utilities > Ambisonic +ladspa:cmt:bf2stereo::Utilities > Ambisonic +ladspa:cmt:bf_rotate_z::Utilities > Ambisonic +ladspa:cmt:canyon_delay::Time > Delays +ladspa:cmt:compress_peak::Amplitude > Dynamics > Compressors +ladspa:cmt:compress_rms::Amplitude > Dynamics > Compressors +ladspa:cmt:delay_0.01s::Time > Delays +ladspa:cmt:delay_0.1s::Time > Delays +ladspa:cmt:delay_1s::Time > Delays +ladspa:cmt:delay_5s::Time > Delays +ladspa:cmt:delay_60s::Time > Delays +ladspa:cmt:disintegrator::Amplitude > Distortions +ladspa:cmt:encode_bformat::Utilities > Ambisonic +ladspa:cmt:encode_fmh::Utilities > Ambisonic +ladspa:cmt:expand_peak::Amplitude > Dynamics > Expanders +ladspa:cmt:expand_rms::Amplitude > Dynamics > Expanders +ladspa:cmt:fbdelay_0.01s::Time > Delays +ladspa:cmt:fbdelay_0.1s::Time > Delays +ladspa:cmt:fbdelay_1s::Time > Delays +ladspa:cmt:fbdelay_5s::Time > Delays +ladspa:cmt:fbdelay_60s::Time > Delays +ladspa:cmt:fmh2bf::Utilities > Ambisonic +ladspa:cmt:fmh2oct::Utilities > Ambisonic +ladspa:cmt:fmh_rotate_z::Utilities > Ambisonic +ladspa:cmt:freeverb3::Time > Reverbs +ladspa:cmt:grain_scatter::Generators +ladspa:cmt:hard_gate::Amplitude > Dynamics > Gates +ladspa:cmt:hpf::Frequency > Filters > Highpass +ladspa:cmt:identity_audio::Utilities +ladspa:cmt:identity_control::Utilities +ladspa:cmt:limit_peak::Amplitude > Dynamics > Limiters +ladspa:cmt:limit_rms::Amplitude > Dynamics > Limiters +ladspa:cmt:lofi::Amplitude > Distortions +ladspa:cmt:logistic::Utilities +ladspa:cmt:lpf::Frequency > Filters > Lowpass +ladspa:cmt:mixer::Utilities +ladspa:cmt:noise_source_white::Generators +ladspa:cmt:null_ai::Utilities +ladspa:cmt:null_ao::Utilities +ladspa:cmt:null_ci::Utilities +ladspa:cmt:null_co::Utilities +ladspa:cmt:organ::Generators +ladspa:cmt:peak::Utilities +ladspa:cmt:phasemod::Generators +ladspa:cmt:pink_full_frequency::Generators +ladspa:cmt:pink_interpolated_audio::Generators +ladspa:cmt:pink_sh::Generators +ladspa:cmt:sine_faaa::Generators +ladspa:cmt:sine_faac::Generators +ladspa:cmt:sine_fcaa::Generators +ladspa:cmt:sine_fcac::Generators +ladspa:cmt:sledgehammer::Amplitude > Dynamics > Limiters +ladspa:cmt:syndrum::Generators +ladspa:cmt:track_max_peak::Utilities +ladspa:cmt:track_max_rms::Utilities +ladspa:cmt:track_peak::Utilities +ladspa:cmt:track_rms::Utilities +ladspa:cmt:vcf303::Frequency > Filters > Lowpass +ladspa:cmt:wshape_sine::Amplitude > Waveshapers +ladspa:comb_1190:comb::Frequency > Filters > Combs +ladspa:comb_1887:comb_c::Time > Delays +ladspa:comb_1887:comb_l::Time > Delays +ladspa:comb_1887:comb_n::Time > Delays +ladspa:comb_splitter_1411:combSplitter::Frequency > Filters > Combs +ladspa:const_1909:const::Utilities +ladspa:crossover_dist_1404:crossoverDist::Amplitude > Distortions +ladspa:cs_chorus:Chorus1::Time > Chorus +ladspa:cs_chorus:Chorus2::Time > Chorus +ladspa:cs_phaser:Phaser1+LFO::Time > Phasers +ladspa:cs_phaser:Phaser1::Time > Phasers +ladspa:dahdsr_2021:dahdsr_cg+t_control::Amplitude > Dynamics +ladspa:dahdsr_2021:dahdsr_g+t_audio::Amplitude > Dynamics +ladspa:dahdsr_2021:dahdsr_g+t_control::Amplitude > Dynamics +ladspa:dc_remove_1207:dcRemove::Frequency > Filters > Highpass +ladspa:decay_1886:decay::Utilities +ladspa:decimator_1202:decimator::Amplitude > Distortions +ladspa:declip_1195:declip::Amplitude > Waveshapers +ladspa:delay_1898:delay_c::Time > Delays +ladspa:delay_1898:delay_l::Time > Delays +ladspa:delay_1898:delay_n::Time > Delays +ladspa:delay:delay_5s::Time > Delays +ladspa:delayorama_1402:delayorama::Time > Delays +ladspa:difference_2030:difference_iama_oa::Utilities +ladspa:difference_2030:difference_iamc_oa::Utilities +ladspa:difference_2030:difference_icma_oa::Utilities +ladspa:difference_2030:difference_icmc_oc::Utilities +ladspa:diode_1185:diode::Amplitude > Distortions +ladspa:divider_1186:divider::Generators +ladspa:dj_eq_1901:dj_eq::Frequency > EQs +ladspa:dj_eq_1901:dj_eq_mono::Frequency > EQs +ladspa:dj_flanger_1438:djFlanger::Time > Flangers +ladspa:dyson_compress_1403:dysonCompress::Amplitude > Dynamics > Compressors +ladspa:fad_delay_1192:fadDelay::Time > Delays +ladspa:fast_lookahead_limiter_1913:fastLookaheadLimiter::Amplitude > Dynamics > Limiters +ladspa:filter:hpf::Frequency > Filters > Highpass +ladspa:filter:lpf::Frequency > Filters > Lowpass +ladspa:filters:Parametric1::Frequency > Filters +ladspa:flanger_1191:flanger::Time > Flangers +ladspa:fmod_1656:fmod_fama_oa::Frequency +ladspa:fmod_1656:fmod_famc_oa::Frequency +ladspa:fmod_1656:fmod_fcma_oa::Frequency +ladspa:fmod_1656:fmod_fcmc_oc::Frequency +ladspa:fm_osc_1415:fmOsc::Generators > Oscillators +ladspa:foldover_1213:foldover::Amplitude > Distortions +ladspa:foverdrive_1196:foverdrive::Amplitude > Distortions +ladspa:freq_tracker_1418:freqTracker::Frequency > Measurement +ladspa:g2reverb:G2reverb::Time > Reverbs +ladspa:gate_1410:gate::Amplitude > Dynamics > Gates +ladspa:giant_flange_1437:giantFlange::Time > Flangers +ladspa:gong_1424:gong::Simulators +ladspa:gong_beater_1439:gongBeater::Generators +ladspa:gsm_1215:gsm::Amplitude > Distortions +ladspa:gverb_1216:gverb::Time > Reverbs +ladspa:hard_limiter_1413:hardLimiter::Amplitude > Dynamics > Limiters +ladspa:harmonic_gen_1220:harmonicGen::Generators +ladspa:hermes_filter_1200:hermesFilter::Frequency > Filters +ladspa:highpass_iir_1890:highpass_iir::Frequency > Filters > Highpass +ladspa:hilbert_1440:hilbert::Utilities +ladspa:imp_1199:imp::Spectral +ladspa:impulse_1885:impulse_fc::Utilities +ladspa:interpolator_1660:interpolator::Utilities +ladspa:inv_1429:inv::Utilities +ladspa:jamincont_1912:jaminController::Utilities +ladspa:karaoke_1409:karaoke::Utilities +ladspa:latency_1914:artificialLatency::Utilities +ladspa:lcr_delay_1436:lcrDelay::Time > Delays +ladspa:lowpass_iir_1891:lowpass_iir::Frequency > Filters > Lowpass +ladspa:lp4pole_1671:lp4pole_faraia_oa::Frequency > Filters > Lowpass +ladspa:lp4pole_1671:lp4pole_fcrcia_oa::Frequency > Filters > Lowpass +ladspa:ls_filter_1908:lsFilter::Frequency > Filters +ladspa:matrix_ms_st_1421:matrixMSSt::Utilities +ladspa:matrix_spatialiser_1422:matrixSpatialiser::Utilities +ladspa:matrix_st_ms_1420:matrixStMS::Utilities +ladspa:mbeq_1197:mbeq::Frequency > EQs > Multiband +ladspa:mod_delay_1419:modDelay::Time > Delays +ladspa:multivoice_chorus_1201:multivoiceChorus::Time > Chorus +ladspa:mvchpf24:Mvchpf-1::Frequency > Filters +ladspa:mvclpf24:Mvclpf-1::Frequency > Filters +ladspa:mvclpf24:Mvclpf-2::Frequency > Filters +ladspa:mvclpf24:Mvclpf-3::Frequency > Filters +ladspa:mvclpf24:Mvclpf-4::Frequency > Filters +ladspa:noise:noise_white::Generators +ladspa:notch_iir_1894:notch_iir::Frequency > Filters > Notch +ladspa:phasers_1217:autoPhaser::Time > Phasers +ladspa:phasers_1217:fourByFourPole::Frequency > Filters > Allpass +ladspa:phasers_1217:lfoPhaser::Time > Phasers +ladspa:pitch_scale_1193:pitchScale::Frequency > Pitch shifters +ladspa:pitch_scale_1194:pitchScaleHQ::Frequency > Pitch shifters +ladspa:plate_1423:plate::Time > Reverbs +ladspa:pointer_cast_1910:pointerCastDistortion::Amplitude > Distortions +ladspa:product_1668:product_iaia_oa::Utilities +ladspa:product_1668:product_iaic_oa::Utilities +ladspa:product_1668:product_icic_oc::Utilities +ladspa:pulse_1645:pulse_fapa_oa::Generators > Oscillators +ladspa:pulse_1645:pulse_fapc_oa::Generators > Oscillators +ladspa:pulse_1645:pulse_fcpa_oa::Generators > Oscillators +ladspa:pulse_1645:pulse_fcpc_oa::Generators > Oscillators +ladspa:pvoc:Accumulate::Spectral +ladspa:pvoc:Exaggerate::Spectral +ladspa:pvoc:Transpose::Frequency > Pitch shifters +ladspa:quantiser100_2029:quantiser100::Utilities +ladspa:quantiser20_2027:quantiser20::Utilities +ladspa:quantiser50_2028:quantiser50::Utilities +ladspa:random_1661:random_fasa_oa::Generators +ladspa:random_1661:random_fasc_oa::Generators +ladspa:random_1661:random_fcsa_oa::Generators +ladspa:random_1661:random_fcsc_oa::Generators +ladspa:rate_shifter_1417:rateShifter::Frequency > Pitch shifters +ladspa:ratio_2034:ratio_nada_oa::Utilities +ladspa:ratio_2034:ratio_nadc_oa::Utilities +ladspa:ratio_2034:ratio_ncda_oa::Utilities +ladspa:ratio_2034:ratio_ncdc_oc::Utilities +ladspa:retro_flange_1208:retroFlange::Time > Flangers +ladspa:revdelay_1605:revdelay::Time > Delays +ladspa:ringmod_1188:ringmod_1i1o1l::Amplitude > Modulators +ladspa:ringmod_1188:ringmod_2i1o::Amplitude > Modulators +ladspa:satan_maximiser_1408:satanMaximiser::Amplitude > Dynamics +ladspa:sawtooth_1641:sawtooth_fa_oa::Generators > Oscillators +ladspa:sawtooth_1641:sawtooth_fc_oa::Generators > Oscillators +ladspa:sc1_1425:sc1::Amplitude > Dynamics > Compressors +ladspa:sc2_1426:sc2::Amplitude > Dynamics > Compressors +ladspa:sc3_1427:sc3::Amplitude > Dynamics > Compressors +ladspa:sc4_1882:sc4::Amplitude > Dynamics > Compressors +ladspa:sc4m_1916:sc4m::Amplitude > Dynamics > Compressors +ladspa:se4_1883:se4::Amplitude > Dynamics > Compressors +ladspa:sequencer16_1677:sequencer16::Simulators +ladspa:sequencer32_1676:sequencer32::Simulators +ladspa:sequencer64_1675:sequencer64::Simulators +ladspa:shaper_1187:shaper::Amplitude > Waveshapers +ladspa:sifter_1210:sifter::Amplitude > Distortions +ladspa:sin_cos_1881:sinCos::Generators > Oscillators +ladspa:sine:sine_faaa::Generators > Oscillators +ladspa:sine:sine_faac::Generators > Oscillators +ladspa:sine:sine_fcaa::Generators > Oscillators +ladspa:sine:sine_fcac::Generators > Oscillators +ladspa:single_para_1203:singlePara::Frequency > EQs > Parametric +ladspa:sinus_wavewrapper_1198:sinusWavewrapper::Amplitude > Waveshapers +ladspa:smooth_decimate_1414:smoothDecimate::Amplitude > Distortions +ladspa:split_1406:split::Utilities +ladspa:square_1643:square_fa_oa::Generators > Oscillators +ladspa:square_1643:square_fc_oa::Generators > Oscillators +ladspa:step_muxer_1212:stepMuxer::Utilities +ladspa:sum_1665:sum_iaia_oa::Utilities +ladspa:sum_1665:sum_iaic_oa::Utilities +ladspa:sum_1665:sum_icic_oc::Utilities +ladspa:surround_encoder_1401:surroundEncoder::Utilities +ladspa:svf_1214:svf::Frequency > Filters +ladspa:sync_pulse_2023:syncpulse_fapaga_oa::Generators > Oscillators +ladspa:sync_pulse_2023:syncpulse_fcpcga_oa::Generators > Oscillators +ladspa:sync_square_1678:syncsquare_faga_oa::Generators > Oscillators +ladspa:sync_square_1678:syncsquare_fcga_oa::Generators > Oscillators +ladspa:tap_autopan:tap_autopan::Amplitude > Modulators +ladspa:tap_chorusflanger:tap_chorusflanger::Time > Flangers +ladspa:tap_deesser:tap_deesser::Amplitude > Dynamics +ladspa:tap_doubler:tap_doubler::Simulators +ladspa:tap_dynamics_m:tap_dynamics_m::Amplitude > Dynamics +ladspa:tap_dynamics_st:tap_dynamics_st::Amplitude > Dynamics +ladspa:tap_echo:tap_stereo_echo::Time > Delays +ladspa:tape_delay_1211:tapeDelay::Time > Delays +ladspa:tap_eqbw:tap_equalizer_bw::Frequency > EQs +ladspa:tap_eq:tap_equalizer::Frequency > EQs +ladspa:tap_limiter:tap_limiter::Amplitude > Dynamics > Limiters +ladspa:tap_pinknoise:tap_pinknoise::Utilities +ladspa:tap_pitch:tap_pitch::Frequency > Pitch shifters +ladspa:tap_reflector:tap_reflector::Time +ladspa:tap_reverb:tap_reverb::Time > Reverbs +ladspa:tap_rotspeak:tap_rotspeak::Simulators +ladspa:tap_sigmoid:tap_sigmoid::Amplitude > Distortions +ladspa:tap_tremolo:tap_tremolo::Amplitude > Modulators +ladspa:tap_tubewarmth:tap_tubewarmth::Simulators +ladspa:tap_vibrato:tap_vibrato::Amplitude > Modulators +ladspa:tracker_2025:tracker_gaaadaia_oa::Amplitude > Waveshapers +ladspa:tracker_2025:tracker_gaacdcia_oa::Amplitude > Waveshapers +ladspa:transient_1206:transient::Amplitude > Dynamics +ladspa:triangle_1649:triangle_fasa_oa::Generators > Oscillators +ladspa:triangle_1649:triangle_fasc_oa::Generators > Oscillators +ladspa:triangle_1649:triangle_fcsa_oa::Generators > Oscillators +ladspa:triangle_1649:triangle_fcsc_oa::Generators > Oscillators +ladspa:triple_para_1204:triplePara::Frequency > EQs > Parametric +ladspa:valve_1209:valve::Amplitude > Distortions +ladspa:valve_rect_1405:valveRect::Amplitude > Distortions +ladspa:vco_sawpulse:Pulse-VCO::Generators > Oscillators +ladspa:vco_sawpulse:Rec-VCO::Generators > Oscillators +ladspa:vco_sawpulse:Saw-VCO::Generators > Oscillators +ladspa:vynil_1905:vynil::Amplitude > Distortions +ladspa:wave_terrain_1412:waveTerrain::Generators > Oscillators +ladspa:xfade_1915:xfade4::Amplitude > Dynamics > Compressors +ladspa:zm1_1428:zm1::Utilities diff -r 000000000000 -r fc9323a41f5a layer/Colour3DPlotLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,818 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Colour3DPlotLayer.h" + +#include "view/View.h" +#include "base/Profiler.h" +#include "base/LogRange.h" +#include "ColourMapper.h" + +#include +#include +#include + +#include + +#include + +//#define DEBUG_COLOUR_3D_PLOT_LAYER_PAINT 1 + + +Colour3DPlotLayer::Colour3DPlotLayer() : + m_model(0), + m_cache(0), + m_cacheStart(0), + m_colourScale(LinearScale), + m_colourMap(0), + m_normalizeColumns(false), + m_normalizeVisibleArea(false) +{ + +} + +Colour3DPlotLayer::~Colour3DPlotLayer() +{ +} + +void +Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model) +{ + if (m_model == model) return; + const DenseThreeDimensionalModel *oldModel = m_model; + m_model = model; + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(cacheInvalid(size_t, size_t))); + + emit modelReplaced(); + emit sliceableModelReplaced(oldModel, model); +} + +void +Colour3DPlotLayer::cacheInvalid() +{ + delete m_cache; + m_cache = 0; +} + +void +Colour3DPlotLayer::cacheInvalid(size_t, size_t) +{ + cacheInvalid(); +} + +Layer::PropertyList +Colour3DPlotLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Colour Scale"); + list.push_back("Normalize Columns"); + list.push_back("Normalize Visible Area"); + return list; +} + +QString +Colour3DPlotLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Colour Scale") return tr("Scale"); + if (name == "Normalize Columns") return tr("Normalize Columns"); + if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + return ""; +} + +Layer::PropertyType +Colour3DPlotLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Normalize Columns") return ToggleProperty; + if (name == "Normalize Visible Area") return ToggleProperty; + return ValueProperty; +} + +QString +Colour3DPlotLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Normalize Columns" || + name == "Normalize Visible Area" || + name == "Colour Scale") return tr("Scale"); + return QString(); +} + +int +Colour3DPlotLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + int garbage0, garbage1, garbage2; + if (!min) min = &garbage0; + if (!max) max = &garbage1; + if (!deflt) deflt = &garbage2; + + if (name == "Colour Scale") { + + *min = 0; + *max = 2; + *deflt = (int)LinearScale; + + val = (int)m_colourScale; + + } else if (name == "Colour") { + + *min = 0; + *max = ColourMapper::getColourMapCount() - 1; + *deflt = 0; + + val = m_colourMap; + + } else if (name == "Normalize Columns") { + + *deflt = 0; + val = (m_normalizeColumns ? 1 : 0); + + } else if (name == "Normalize Visible Area") { + + *deflt = 0; + val = (m_normalizeVisibleArea ? 1 : 0); + + } else { + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +Colour3DPlotLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + return ColourMapper::getColourMapName(value); + } + if (name == "Colour Scale") { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Log"); + case 2: return tr("+/-1"); + } + } + return tr(""); +} + +void +Colour3DPlotLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour Scale") { + switch (value) { + default: + case 0: setColourScale(LinearScale); break; + case 1: setColourScale(LogScale); break; + case 2: setColourScale(PlusMinusOneScale); break; + } + } else if (name == "Colour") { + setColourMap(value); + } else if (name == "Normalize Columns") { + setNormalizeColumns(value ? true : false); + } else if (name == "Normalize Visible Area") { + setNormalizeVisibleArea(value ? true : false); + } +} + +void +Colour3DPlotLayer::setColourScale(ColourScale scale) +{ + if (m_colourScale == scale) return; + m_colourScale = scale; + cacheInvalid(); + emit layerParametersChanged(); +} + +void +Colour3DPlotLayer::setColourMap(int map) +{ + if (m_colourMap == map) return; + m_colourMap = map; + cacheInvalid(); + emit layerParametersChanged(); +} + +void +Colour3DPlotLayer::setNormalizeColumns(bool n) +{ + if (m_normalizeColumns == n) return; + m_normalizeColumns = n; + cacheInvalid(); + emit layerParametersChanged(); +} + +bool +Colour3DPlotLayer::getNormalizeColumns() const +{ + return m_normalizeColumns; +} + +void +Colour3DPlotLayer::setNormalizeVisibleArea(bool n) +{ + if (m_normalizeVisibleArea == n) return; + m_normalizeVisibleArea = n; + cacheInvalid(); + emit layerParametersChanged(); +} + +bool +Colour3DPlotLayer::getNormalizeVisibleArea() const +{ + return m_normalizeVisibleArea; +} + +bool +Colour3DPlotLayer::isLayerScrollable(const View *v) const +{ + if (m_normalizeVisibleArea) return false; + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +QString +Colour3DPlotLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + if (!m_model) return ""; + + int x = pos.x(); + int y = pos.y(); + + size_t modelStart = m_model->getStartFrame(); + size_t modelResolution = m_model->getResolution(); + + float srRatio = + float(v->getViewManager()->getMainModelSampleRate()) / + float(m_model->getSampleRate()); + + int sx0 = int((v->getFrameForX(x) / srRatio - long(modelStart)) / + long(modelResolution)); + + int f0 = sx0 * modelResolution; + int f1 = f0 + modelResolution; + + float binHeight = float(v->height()) / m_model->getHeight(); + int sy = int((v->height() - y) / binHeight); + + float value = m_model->getValueAt(sx0, sy); + +// std::cerr << "bin value (" << sx0 << "," << sy << ") is " << value << std::endl; + + QString binName = m_model->getBinName(sy); + if (binName == "") binName = QString("[%1]").arg(sy + 1); + else binName = QString("%1 [%2]").arg(binName).arg(sy + 1); + + QString text = tr("Time:\t%1 - %2\nBin:\t%3\nValue:\t%4") + .arg(RealTime::frame2RealTime(f0, m_model->getSampleRate()) + .toText(true).c_str()) + .arg(RealTime::frame2RealTime(f1, m_model->getSampleRate()) + .toText(true).c_str()) + .arg(binName) + .arg(value); + + return text; +} + +int +Colour3DPlotLayer::getColourScaleWidth(QPainter &) const +{ + int cw = 20; + return cw; +} + +int +Colour3DPlotLayer::getVerticalScaleWidth(View *, QPainter &paint) const +{ + if (!m_model) return 0; + + QString sampleText = QString("[%1]").arg(m_model->getHeight()); + int tw = paint.fontMetrics().width(sampleText); + bool another = false; + + for (size_t i = 0; i < m_model->getHeight(); ++i) { + if (m_model->getBinName(i).length() > sampleText.length()) { + sampleText = m_model->getBinName(i); + another = true; + } + } + if (another) { + tw = max(tw, paint.fontMetrics().width(sampleText)); + } + + return tw + 13 + getColourScaleWidth(paint); +} + +void +Colour3DPlotLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model) return; + + int h = rect.height(), w = rect.width(); + float binHeight = float(v->height()) / m_model->getHeight(); + + int cw = getColourScaleWidth(paint); + + int ch = h - 20; + if (ch > 20 && m_cache) { + + paint.setPen(Qt::black); + paint.drawRect(4, 10, cw - 8, ch - 19); + + for (int y = 0; y < ch - 20; ++y) { + QRgb c = m_cache->color(((ch - 20 - y) * 255) / (ch - 20)); +// std::cerr << "y = " << y << ": rgb " << qRed(c) << "," << qGreen(c) << "," << qBlue(c) << std::endl; + paint.setPen(QColor(qRed(c), qGreen(c), qBlue(c))); + paint.drawLine(5, 11 + y, cw - 5, 11 + y); + } + } + + paint.setPen(Qt::black); + + int count = v->height() / paint.fontMetrics().height(); + int step = m_model->getHeight() / count; + if (step == 0) step = 1; + + for (size_t i = 0; i < m_model->getHeight(); ++i) { + + if ((i % step) != 0) continue; + + int y0 = int(v->height() - (i * binHeight) - 1); + + QString text = m_model->getBinName(i); + if (text == "") text = QString("[%1]").arg(i + 1); + + paint.drawLine(cw, y0, w, y0); + + int cy = int(y0 - (step * binHeight)/2); + int ty = cy + paint.fontMetrics().ascent()/2; + + paint.drawText(cw + 5, ty, text); + } +} + +void +Colour3DPlotLayer::getColumn(size_t col, + DenseThreeDimensionalModel::Column &values) const +{ + m_model->getColumn(col, values); + + float colMax = 0.f, colMin = 0.f; + + float min = 0.f, max = 0.f; + if (m_normalizeColumns) { + min = m_model->getMinimumLevel(); + max = m_model->getMaximumLevel(); + } + + if (m_normalizeColumns) { + for (size_t y = 0; y < values.size(); ++y) { + if (y == 0 || values[y] > colMax) colMax = values[y]; + if (y == 0 || values[y] < colMin) colMin = values[y]; + } + if (colMin == colMax) colMax = colMin + 1; + } + + for (size_t y = 0; y < values.size(); ++y) { + + float value = min; + + value = values[y]; + + if (m_normalizeColumns) { + float norm = (value - colMin) / (colMax - colMin); + value = min + (max - min) * norm; + } + + values[y] = value; + } +} + +void +Colour3DPlotLayer::fillCache(size_t firstBin, size_t lastBin) const +{ + size_t modelStart = m_model->getStartFrame(); + size_t modelEnd = m_model->getEndFrame(); + size_t modelResolution = m_model->getResolution(); + +// std::cerr << "Colour3DPlotLayer::fillCache: " << firstBin << " -> " << lastBin << std::endl; + + if (!m_normalizeVisibleArea || m_normalizeColumns) { + firstBin = modelStart / modelResolution; + lastBin = modelEnd / modelResolution; + } + + size_t cacheWidth = lastBin - firstBin + 1; + size_t cacheHeight = m_model->getHeight(); + + if (m_cache && + (m_cacheStart != firstBin || + m_cache->width() != int(cacheWidth) || + m_cache->height() != int(cacheHeight))) { + + delete m_cache; + m_cache = 0; + } + + if (m_cache) return; + + m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8); + m_cacheStart = firstBin; + +// std::cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " starting " << m_cacheStart << std::endl; + + m_cache->setNumColors(256); + DenseThreeDimensionalModel::Column values; + + float min = m_model->getMinimumLevel(); + float max = m_model->getMaximumLevel(); + + if (m_colourScale == LogScale) { + LogRange::mapRange(min, max); + } else if (m_colourScale == PlusMinusOneScale) { + min = -1.f; + max = 1.f; + } + + if (max == min) max = min + 1.0; + + ColourMapper mapper(m_colourMap, 0.f, 255.f); + + for (int index = 0; index < 256; ++index) { + + QColor colour = mapper.map(index); + m_cache->setColor(index, qRgb(colour.red(), colour.green(), colour.blue())); + } + + m_cache->fill(0); + + float visibleMax = 0.f, visibleMin = 0.f; + + if (m_normalizeVisibleArea && !m_normalizeColumns) { + + for (size_t c = firstBin; c <= lastBin; ++c) { + + values.clear(); + getColumn(c, values); + + float colMax = 0.f, colMin = 0.f; + + for (size_t y = 0; y < m_model->getHeight(); ++y) { + if (y >= values.size()) break; + if (y == 0 || values[y] > colMax) colMax = values[y]; + if (y == 0 || values[y] < colMin) colMin = values[y]; + } + + if (c == firstBin || colMax > visibleMax) visibleMax = colMax; + if (c == firstBin || colMin < visibleMin) visibleMin = colMin; + } + } + + if (visibleMin == visibleMax) visibleMax = visibleMin + 1; + + for (size_t c = firstBin; c <= lastBin; ++c) { + + values.clear(); + getColumn(c, values); + + for (size_t y = 0; y < m_model->getHeight(); ++y) { + + float value = min; + if (y < values.size()) { + value = values[y]; + } + + if (m_normalizeVisibleArea && !m_normalizeColumns) { + float norm = (value - visibleMin) / (visibleMax - visibleMin); + value = min + (max - min) * norm; + } + + if (m_colourScale == LogScale) { + value = LogRange::map(value); + } + + int pixel = int(((value - min) * 256) / (max - min)); + if (pixel < 0) pixel = 0; + if (pixel > 255) pixel = 255; + + m_cache->setPixel(c - firstBin, y, pixel); + } + } +} + +void +Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const +{ +// Profiler profiler("Colour3DPlotLayer::paint"); +#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT + std::cerr << "Colour3DPlotLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << std::endl; +#endif + + int completion = 0; + if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) { + if (completion > 0) { + paint.fillRect(0, 10, v->width() * completion / 100, + 10, QColor(120, 120, 120)); + } + return; + } + + if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->rect(); + + size_t modelStart = m_model->getStartFrame(); + size_t modelEnd = m_model->getEndFrame(); + size_t modelResolution = m_model->getResolution(); + + // The cache is from the model's start frame to the model's end + // frame at the model's window increment frames per pixel. We + // want to draw from our start frame + x0 * zoomLevel to our start + // frame + x1 * zoomLevel at zoomLevel frames per pixel. + + // We have quite different paint mechanisms for rendering "large" + // bins (more than one bin per pixel in both directions) and + // "small". This is "large"; see paintDense below for "small". + + int x0 = rect.left(); + int x1 = rect.right() + 1; + + int h = v->height(); + + float srRatio = + float(v->getViewManager()->getMainModelSampleRate()) / + float(m_model->getSampleRate()); + + int sx0 = int((v->getFrameForX(x0) / srRatio - long(modelStart)) + / long(modelResolution)); + int sx1 = int((v->getFrameForX(x1) / srRatio - long(modelStart)) + / long(modelResolution)); + int sh = m_model->getHeight(); + + if (sx0 > 0) --sx0; + fillCache(sx0 < 0 ? 0 : sx0, + sx1 < 0 ? 0 : sx1); + + if (int(m_model->getHeight()) >= v->height() || + int(modelResolution) < v->getZoomLevel() / 2) { + paintDense(v, paint, rect); + return; + } + +#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT + std::cerr << "Colour3DPlotLayer::paint: w " << w << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sw << ", sh " << sh << std::endl; + std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << std::endl; +#endif + + QPoint illuminatePos; + bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos); + char labelbuf[10]; + + for (int sx = sx0; sx <= sx1; ++sx) { + + int scx = 0; + if (sx > int(m_cacheStart)) scx = sx - m_cacheStart; + + int fx = sx * int(modelResolution); + + if (fx + int(modelResolution) < int(modelStart) || + fx > int(modelEnd)) continue; + + int rx0 = v->getXForFrame(int((fx + int(modelStart)) * srRatio)); + int rx1 = v->getXForFrame(int((fx + int(modelStart) + int(modelResolution) + 1) * srRatio)); + + int rw = rx1 - rx0; + if (rw < 1) rw = 1; + + bool showLabel = (rw > 10 && + paint.fontMetrics().width("0.000000") < rw - 3 && + paint.fontMetrics().height() < (h / sh)); + + for (int sy = 0; sy < sh; ++sy) { + + int ry0 = h - (sy * h) / sh - 1; + QRgb pixel = qRgb(255, 255, 255); + if (scx >= 0 && scx < m_cache->width() && + sy >= 0 && sy < m_cache->height()) { + pixel = m_cache->pixel(scx, sy); + } + + QRect r(rx0, ry0 - h / sh - 1, rw, h / sh + 1); + + if (rw == 1) { + paint.setPen(pixel); + paint.setBrush(Qt::NoBrush); + paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); + continue; + } + + QColor pen(255, 255, 255, 80); + QColor brush(pixel); + + if (rw > 3 && r.height() > 3) { + brush.setAlpha(160); + } + + paint.setPen(Qt::NoPen); + paint.setBrush(brush); + + if (illuminate) { + if (r.contains(illuminatePos)) { + paint.setPen(Qt::black);//!!! + } + } + +#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT + std::cerr << "rect " << r.x() << "," << r.y() << " " + << r.width() << "x" << r.height() << std::endl; +#endif + + paint.drawRect(r); + + if (showLabel) { + if (scx >= 0 && scx < m_cache->width() && + sy >= 0 && sy < m_cache->height()) { + float value = m_model->getValueAt(scx, sy); + sprintf(labelbuf, "%06f", value); + QString text(labelbuf); + paint.setPen(Qt::white); + paint.drawText(rx0 + 2, + ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), + text); + } + } + } + } +} + +void +Colour3DPlotLayer::paintDense(View *v, QPainter &paint, QRect rect) const +{ + size_t modelStart = m_model->getStartFrame(); + size_t modelResolution = m_model->getResolution(); + + float srRatio = + float(v->getViewManager()->getMainModelSampleRate()) / + float(m_model->getSampleRate()); + + int x0 = rect.left(); + int x1 = rect.right() + 1; + + int w = x1 - x0; + int h = v->height(); + int sh = m_model->getHeight(); + + QImage img(w, h, QImage::Format_RGB32); + + for (int x = x0; x < x1; ++x) { + + long xf = long(v->getFrameForX(x) / srRatio); + if (xf < 0) { + for (int y = 0; y < h; ++y) { + img.setPixel(x - x0, y, m_cache->color(0)); + } + continue; + } + + float sx0 = (float(xf) - modelStart) / modelResolution; + float sx1 = (float(v->getFrameForX(x+1) / srRatio) - modelStart) / modelResolution; + + int sx0i = int(sx0 + 0.001); + int sx1i = int(sx1); + + for (int y = 0; y < h; ++y) { + + float sy0 = (float(h - y - 1) * sh) / h; + float sy1 = (float(h - y) * sh) / h; + + int sy0i = int(sy0 + 0.001); + int sy1i = int(sy1); + + float mag = 0.0, div = 0.0; + int max = 0; + + for (int sx = sx0i; sx <= sx1i; ++sx) { + + int scx = 0; + if (sx > int(m_cacheStart)) scx = sx - int(m_cacheStart); + + if (scx < 0 || scx >= m_cache->width()) continue; + + for (int sy = sy0i; sy <= sy1i; ++sy) { + + if (sy < 0 || sy >= m_cache->height()) continue; + + float prop = 1.0; + if (sx == sx0i) prop *= (sx + 1) - sx0; + if (sx == sx1i) prop *= sx1 - sx; + if (sy == sy0i) prop *= (sy + 1) - sy0; + if (sy == sy1i) prop *= sy1 - sy; + + mag += prop * m_cache->pixelIndex(scx, sy); + max = max(max, m_cache->pixelIndex(scx, sy)); + div += prop; + } + } + + if (div != 0) mag /= div; + if (mag < 0) mag = 0; + if (mag > 255) mag = 255; + if (max < 0) max = 0; + if (max > 255) max = 255; + + img.setPixel(x - x0, y, m_cache->color(int(mag + 0.001))); +// img.setPixel(x - x0, y, m_cache->color(max)); + } + } + + paint.drawImage(x0, 0, img); +} + +bool +Colour3DPlotLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + int left = (frame / resolution) * resolution; + int right = left + resolution; + + switch (snap) { + case SnapLeft: frame = left; break; + case SnapRight: frame = right; break; + case SnapNearest: + case SnapNeighbouring: + if (frame - left > right - frame) frame = right; + else frame = left; + break; + } + + return true; +} + +QString +Colour3DPlotLayer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("scale=\"%1\" " + "colourScheme=\"%2\" " + "normalizeColumns=\"%3\" " + "normalizeVisibleArea=\"%4\"") + .arg((int)m_colourScale) + .arg(m_colourMap) + .arg(m_normalizeColumns ? "true" : "false") + .arg(m_normalizeVisibleArea ? "true" : "false"); + + return Layer::toXmlString(indent, extraAttributes + " " + s); +} + +void +Colour3DPlotLayer::setProperties(const QXmlAttributes &attributes) +{ + bool ok = false; + + ColourScale scale = (ColourScale)attributes.value("scale").toInt(&ok); + if (ok) setColourScale(scale); + + int colourMap = attributes.value("colourScheme").toInt(&ok); + if (ok) setColourMap(colourMap); + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + setNormalizeColumns(normalizeColumns); + + bool normalizeVisibleArea = + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); +} + diff -r 000000000000 -r fc9323a41f5a layer/Colour3DPlotLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,125 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _COLOUR_3D_PLOT_H_ +#define _COLOUR_3D_PLOT_H_ + +#include "SliceableLayer.h" + +#include "data/model/DenseThreeDimensionalModel.h" + +class View; +class QPainter; +class QImage; + +/** + * This is a view that displays dense 3-D data (time, some sort of + * binned y-axis range, value) as a colour plot with value mapped to + * colour range. Its source is a DenseThreeDimensionalModel. + * + * This was the original implementation for the spectrogram view, but + * it was replaced with a more efficient implementation that derived + * the spectrogram itself from a DenseTimeValueModel instead of using + * a three-dimensional model. This class is retained in case it + * becomes useful, but it will probably need some cleaning up if it's + * ever actually used. + */ + +class Colour3DPlotLayer : public SliceableLayer +{ + Q_OBJECT + +public: + Colour3DPlotLayer(); + ~Colour3DPlotLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { + return m_model ? m_model->getZoomConstraint() : 0; + } + virtual const Model *getModel() const { return m_model; } + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(View *v, QPainter &) const; + virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerColourSignificant() const { return true; } + + void setModel(const DenseThreeDimensionalModel *model); + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool getValueExtents(float &, float &, bool &, QString &) const { return false; } + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + virtual void setProperties(const QXmlAttributes &); + + enum ColourScale { LinearScale, LogScale, PlusMinusOneScale }; + + void setColourScale(ColourScale); + ColourScale getColourScale() const { return m_colourScale; } + + void setColourMap(int map); + int getColourMap() const; + + void setNormalizeColumns(bool n); + bool getNormalizeColumns() const; + + void setNormalizeVisibleArea(bool n); + bool getNormalizeVisibleArea() const; + + virtual const Model *getSliceableModel() const { return m_model; } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +protected slots: + void cacheInvalid(); + void cacheInvalid(size_t startFrame, size_t endFrame); + +protected: + const DenseThreeDimensionalModel *m_model; // I do not own this + + mutable QImage *m_cache; + mutable size_t m_cacheStart; + + ColourScale m_colourScale; + int m_colourMap; + bool m_normalizeColumns; + bool m_normalizeVisibleArea; + + void getColumn(size_t col, DenseThreeDimensionalModel::Column &) const; + + virtual int getColourScaleWidth(QPainter &) const; + virtual void fillCache(size_t firstBin, size_t lastBin) const; + virtual void paintDense(View *v, QPainter &paint, QRect rect) const; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/ColourMapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourMapper.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,211 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ColourMapper.h" + +#include + +#include + +ColourMapper::ColourMapper(int map, float min, float max) : + QObject(), + m_map(map), + m_min(min), + m_max(max) +{ + if (m_min == m_max) { + std::cerr << "WARNING: ColourMapper: min == max (== " << m_min + << "), adjusting" << std::endl; + m_max = m_min + 1; + } +} + +ColourMapper::~ColourMapper() +{ +} + +int +ColourMapper::getColourMapCount() +{ + return 10; +} + +QString +ColourMapper::getColourMapName(int n) +{ + if (n >= getColourMapCount()) return tr(""); + StandardMap map = (StandardMap)n; + + switch (map) { + case DefaultColours: return tr("Default"); + case WhiteOnBlack: return tr("White on Black"); + case BlackOnWhite: return tr("Black on White"); + case RedOnBlue: return tr("Red on Blue"); + case YellowOnBlack: return tr("Yellow on Black"); + case BlueOnBlack: return tr("Blue on Black"); + case Sunset: return tr("Sunset"); + case FruitSalad: return tr("Fruit Salad"); + case Banded: return tr("Banded"); + case Highlight: return tr("Highlight"); + } + + return tr(""); +} + +QColor +ColourMapper::map(float value) const +{ + float norm = (value - m_min) / (m_max - m_min); + if (norm < 0.f) norm = 0.f; + if (norm > 1.f) norm = 1.f; + + float h = 0.f, s = 0.f, v = 0.f, r = 0.f, g = 0.f, b = 0.f; + bool hsv = true; + +// float red = 0.f, green = 0.3333f; + float blue = 0.6666f, pieslice = 0.3333f; + + if (m_map >= getColourMapCount()) return Qt::black; + StandardMap map = (StandardMap)m_map; + + switch (map) { + + case DefaultColours: + h = blue - norm * 2.f * pieslice; + s = 0.5f + norm/2.f; + v = norm; + break; + + case WhiteOnBlack: + r = g = b = norm; + hsv = false; + break; + + case BlackOnWhite: + r = g = b = 1.f - norm; + hsv = false; + break; + + case RedOnBlue: + h = blue - pieslice/4.f + norm * (pieslice + pieslice/4.f); + s = 1.f; + v = norm; + break; + + case YellowOnBlack: + h = 0.15f; + s = 1.f; + v = norm; + break; + + case BlueOnBlack: + h = blue; + s = 1.f; + v = norm * 2.f; + if (v > 1.f) { + v = 1.f; + s = 1.f - (sqrtf(norm) - 0.707f) * 3.413f; + if (s < 0.f) s = 0.f; + if (s > 1.f) s = 1.f; + } + break; + + case Sunset: + r = (norm - 0.24f) * 2.38f; + if (r > 1.f) r = 1.f; + if (r < 0.f) r = 0.f; + g = (norm - 0.64f) * 2.777f; + if (g > 1.f) g = 1.f; + if (g < 0.f) g = 0.f; + b = (3.6f * norm); + if (norm > 0.277f) b = 2.f - b; + if (b > 1.f) b = 1.f; + if (b < 0.f) b = 0.f; + hsv = false; + break; + + case FruitSalad: + h = blue + (pieslice/6.f) - norm; + if (h < 0.f) h += 1.f; + s = 1.f; + v = 1.f; + break; + + case Banded: + if (norm < 0.125) return Qt::darkGreen; + else if (norm < 0.25) return Qt::green; + else if (norm < 0.375) return Qt::darkBlue; + else if (norm < 0.5) return Qt::blue; + else if (norm < 0.625) return Qt::darkYellow; + else if (norm < 0.75) return Qt::yellow; + else if (norm < 0.875) return Qt::darkRed; + else return Qt::red; + break; + + case Highlight: + if (norm > 0.99) return Qt::white; + else return Qt::darkBlue; + } + + if (hsv) { + return QColor::fromHsvF(h, s, v); + } else { + return QColor::fromRgbF(r, g, b); + } +} + +QColor +ColourMapper::getContrastingColour() const +{ + if (m_map >= getColourMapCount()) return Qt::white; + StandardMap map = (StandardMap)m_map; + + switch (map) { + + case DefaultColours: + return QColor(255, 150, 50); + + case WhiteOnBlack: + return Qt::red; + + case BlackOnWhite: + return Qt::darkGreen; + + case RedOnBlue: + return Qt::green; + + case YellowOnBlack: + return QColor::fromHsv(240, 255, 255); + + case BlueOnBlack: + return Qt::red; + + case Sunset: + return Qt::white; + + case FruitSalad: + return Qt::white; + + case Banded: + return Qt::cyan; + + case Highlight: + return Qt::red; + } + + return Qt::white; +} + + diff -r 000000000000 -r fc9323a41f5a layer/ColourMapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourMapper.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _COLOUR_MAPPER_H_ +#define _COLOUR_MAPPER_H_ + +#include +#include +#include + +/** + * A class for mapping intensity values onto various colour maps. + */ + +class ColourMapper : public QObject +{ + Q_OBJECT + +public: + ColourMapper(int map, float minValue, float maxValue); + virtual ~ColourMapper(); + + enum StandardMap { + DefaultColours, + Sunset, + WhiteOnBlack, + BlackOnWhite, + RedOnBlue, + YellowOnBlack, + BlueOnBlack, + FruitSalad, + Banded, + Highlight + }; + + int getMap() const { return m_map; } + float getMinValue() const { return m_min; } + float getMaxValue() const { return m_max; } + + static int getColourMapCount(); + static QString getColourMapName(int n); + + QColor map(float value) const; + + QColor getContrastingColour() const; // for cursors etc + +protected: + int m_map; + float m_min; + float m_max; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/Layer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Layer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,124 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Layer.h" +#include "view/View.h" +#include "data/model/Model.h" + +#include + +#include + +#include "LayerFactory.h" +#include "base/PlayParameterRepository.h" + +Layer::Layer() +{ +} + +Layer::~Layer() +{ +// std::cerr << "Layer::~Layer(" << this << ")" << std::endl; +} + +QString +Layer::getPropertyContainerIconName() const +{ + return LayerFactory::getInstance()->getLayerIconName + (LayerFactory::getInstance()->getLayerType(this)); +} + +QString +Layer::getLayerPresentationName() const +{ +// QString layerName = objectName(); + + LayerFactory *factory = LayerFactory::getInstance(); + QString layerName = factory->getLayerPresentationName + (factory->getLayerType(this)); + + QString modelName; + if (getModel()) modelName = getModel()->objectName(); + + QString text; + if (modelName != "") { + text = QString("%1: %2").arg(modelName).arg(layerName); + } else { + text = layerName; + } + + return text; +} + +void +Layer::setObjectName(const QString &name) +{ + QObject::setObjectName(name); + emit layerNameChanged(); +} + +QString +Layer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += indent; + + s += QString("\n") + .arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName + (LayerFactory::getInstance()->getLayerType(this)))) + .arg(getObjectExportId(this)) + .arg(encodeEntities(objectName())) + .arg(getObjectExportId(getModel())) + .arg(extraAttributes); + + return s; +} + +PlayParameters * +Layer::getPlayParameters() +{ +// std::cerr << "Layer (" << this << ", " << objectName().toStdString() << ")::getPlayParameters: model is "<< getModel() << std::endl; + const Model *model = getModel(); + if (model) { + return PlayParameterRepository::getInstance()->getPlayParameters(model); + } + return 0; +} + +void +Layer::setLayerDormant(const View *v, bool dormant) +{ + const void *vv = (const void *)v; + QMutexLocker locker(&m_dormancyMutex); + m_dormancy[vv] = dormant; +} + +bool +Layer::isLayerDormant(const View *v) const +{ + const void *vv = (const void *)v; + QMutexLocker locker(&m_dormancyMutex); + if (m_dormancy.find(vv) == m_dormancy.end()) return false; + return m_dormancy.find(vv)->second; +} + +void +Layer::showLayer(View *view, bool show) +{ + setLayerDormant(view, !show); + emit layerParametersChanged(); +} + diff -r 000000000000 -r fc9323a41f5a layer/Layer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Layer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,376 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LAYER_H_ +#define _LAYER_H_ + +#include "base/PropertyContainer.h" +#include "base/XmlExportable.h" +#include "base/Selection.h" + +#include +#include +#include +#include + +#include + +class ZoomConstraint; +class Model; +class QPainter; +class View; +class QMouseEvent; +class Clipboard; +class RangeMapper; + +/** + * The base class for visual representations of the data found in a + * Model. Layers are expected to be able to draw themselves onto a + * View, and may also be editable. + */ + +class Layer : public PropertyContainer, + public XmlExportable +{ + Q_OBJECT + +public: + Layer(); + virtual ~Layer(); + + virtual const Model *getModel() const = 0; + virtual Model *getModel() { + return const_cast(const_cast(this)->getModel()); + } + + /** + * Return a zoom constraint object defining the supported zoom + * levels for this layer. If this returns zero, the layer will + * support any integer zoom level. + */ + virtual const ZoomConstraint *getZoomConstraint() const { return 0; } + + /** + * Return true if this layer can handle zoom levels other than + * those supported by its zoom constraint (presumably less + * efficiently or accurately than the officially supported zoom + * levels). If true, the layer will unenthusistically accept any + * integer zoom level from 1 to the maximum returned by its zoom + * constraint. + */ + virtual bool supportsOtherZoomLevels() const { return true; } + + virtual void paint(View *, QPainter &, QRect) const = 0; + + enum VerticalPosition { + PositionTop, PositionMiddle, PositionBottom + }; + virtual VerticalPosition getPreferredTimeRulerPosition() const { + return PositionMiddle; + } + virtual VerticalPosition getPreferredFrameCountPosition() const { + return PositionBottom; + } + virtual bool hasLightBackground() const { + return true; + } + + virtual QString getPropertyContainerIconName() const; + + virtual QString getPropertyContainerName() const { + return objectName(); + } + + virtual QString getLayerPresentationName() const; + + virtual int getVerticalScaleWidth(View *, QPainter &) const { return 0; } + virtual void paintVerticalScale(View *, QPainter &, QRect) const { } + + virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */, + std::vector &) const { + return false; + } + virtual void paintCrosshairs(View *, QPainter &, QPoint) const { } + + virtual QString getFeatureDescription(View *, QPoint &) const { + return ""; + } + + enum SnapType { + SnapLeft, + SnapRight, + SnapNearest, + SnapNeighbouring + }; + + /** + * Adjust the given frame to snap to the nearest feature, if + * possible. + * + * If snap is SnapLeft or SnapRight, adjust the frame to match + * that of the nearest feature in the given direction regardless + * of how far away it is. If snap is SnapNearest, adjust the + * frame to that of the nearest feature in either direction. If + * snap is SnapNeighbouring, adjust the frame to that of the + * nearest feature if it is close, and leave it alone (returning + * false) otherwise. SnapNeighbouring should always choose the + * same feature that would be used in an editing operation through + * calls to editStart etc. + * + * Return true if a suitable feature was found and frame adjusted + * accordingly. Return false if no suitable feature was available + * (and leave frame unmodified). Also return the resolution of + * the model in this layer in sample frames. + */ + virtual bool snapToFeatureFrame(View * /* v */, + int & /* frame */, + size_t &resolution, + SnapType /* snap */) const { + resolution = 1; + return false; + } + + // Draw and edit modes: + // + // Layer needs to get actual mouse events, I guess. Draw mode is + // probably the easier. + + virtual void drawStart(View *, QMouseEvent *) { } + virtual void drawDrag(View *, QMouseEvent *) { } + virtual void drawEnd(View *, QMouseEvent *) { } + + virtual void editStart(View *, QMouseEvent *) { } + virtual void editDrag(View *, QMouseEvent *) { } + virtual void editEnd(View *, QMouseEvent *) { } + + virtual void editOpen(View *, QMouseEvent *) { } // on double-click + + virtual void moveSelection(Selection, size_t /* newStartFrame */) { } + virtual void resizeSelection(Selection, Selection /* newSize */) { } + virtual void deleteSelection(Selection) { } + + virtual void copy(Selection, Clipboard & /* to */) { } + + /** + * Paste from the given clipboard onto the layer at the given + * frame offset. If interactive is true, the layer may ask the + * user about paste options through a dialog if desired, and may + * return false if the user cancelled the paste operation. This + * function should return true if a paste actually occurred. + */ + virtual bool paste(const Clipboard & /* from */, + int /* frameOffset */, + bool /* interactive */) { return false; } + + // Text mode: + // + // Label nearest feature. We need to get the feature coordinates + // and current label from the layer, and then the pane can pop up + // a little text entry dialog at the right location. Or we edit + // in place? Probably the dialog is easier. + + /** + * This should return true if the layer can safely be scrolled + * automatically by a given view (simply copying the existing data + * and then refreshing the exposed area) without altering its + * meaning. For the view widget as a whole this is usually not + * possible because of invariant (non-scrolling) material + * displayed over the top, but the widget may be able to optimise + * scrolling better if it is known that individual views can be + * scrolled safely in this way. + */ + virtual bool isLayerScrollable(const View *) const { return true; } + + /** + * This should return true if the layer completely obscures any + * underlying layers. It's used to determine whether the view can + * safely draw any selection rectangles under the layer instead of + * over it, in the case where the layer is not scrollable and + * therefore needs to be redrawn each time (so that the selection + * rectangle can be cached). + */ + virtual bool isLayerOpaque() const { return false; } + + /** + * This should return true if the layer uses colours to indicate + * meaningful information (as opposed to just using a single + * colour of the user's choice). If this is the case, the view + * will show selections using unfilled rectangles instead of + * translucent filled rectangles, so as not to disturb the colours + * underneath. + */ + virtual bool isLayerColourSignificant() const { return false; } + + /** + * This should return true if the layer can be edited by the user. + * If this is the case, the appropriate edit tools may be made + * available by the application and the layer's drawStart/Drag/End + * and editStart/Drag/End methods should be implemented. + */ + virtual bool isLayerEditable() const { return false; } + + /** + * Return the proportion of background work complete in drawing + * this view, as a percentage -- in most cases this will be the + * value returned by pointer from a call to the underlying model's + * isReady(int *) call. The view may choose to show a progress + * meter if it finds that this returns < 100 at any given moment. + */ + virtual int getCompletion(View *) const { return 100; } + + virtual void setObjectName(const QString &name); + + /** + * Convert the layer's data (though not those of the model it + * refers to) into an XML string for file output. This class + * implements the basic name/type/model-id output; subclasses will + * typically call this superclass implementation with extra + * attributes describing their particular properties. + */ + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + /** + * Set the particular properties of a layer (those specific to the + * subclass) from a set of XML attributes. This is the effective + * inverse of the toXmlString method. + */ + virtual void setProperties(const QXmlAttributes &) = 0; + + /** + * Indicate that a layer is not currently visible in the given + * view and is not expected to become visible in the near future + * (for example because the user has explicitly removed or hidden + * it). The layer may respond by (for example) freeing any cache + * memory it is using, until next time its paint method is called, + * when it should set itself un-dormant again. + * + * A layer class that overrides this function must also call this + * class's implementation. + */ + virtual void setLayerDormant(const View *v, bool dormant); + + /** + * Return whether the layer is dormant (i.e. hidden) in the given + * view. + */ + virtual bool isLayerDormant(const View *v) const; + + virtual PlayParameters *getPlayParameters(); + + virtual bool needsTextLabelHeight() const { return false; } + + virtual bool hasTimeXAxis() const { return true; } + + /** + * Return the minimum and maximum values for the y axis of the + * model in this layer, as well as whether the layer is configured + * to use a logarithmic y axis display. Also return the unit for + * these values if known. + * + * This function returns the "normal" extents for the layer, not + * necessarily the extents actually in use in the display. + */ + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const = 0; + + /** + * Return the minimum and maximum values within the displayed + * range for the y axis, if only a subset of the whole range of + * the model (returned by getValueExtents) is being displayed. + * Return false if the layer is not imposing a particular display + * extent (using the normal layer extents or deferring to whatever + * is in use for the same units elsewhere in the view). + */ + virtual bool getDisplayExtents(float & /* min */, + float & /* max */) const { + return false; + } + + /** + * Set the displayed minimum and maximum values for the y axis to + * the given range, if supported. Return false if not supported + * on this layer (and set nothing). In most cases, layers that + * return false for getDisplayExtents should also return false for + * this function. + */ + virtual bool setDisplayExtents(float /* min */, + float /* max */) { + return false; + } + + /** + * Get the number of vertical zoom steps available for this layer. + * If vertical zooming is not available, return 0. The meaning of + * "zooming" is entirely up to the layer -- changing the zoom + * level may cause the layer to reset its display extents or + * change another property such as display gain. However, layers + * are advised for consistency to treat smaller zoom steps as + * "more distant" or "zoomed out" and larger ones as "closer" or + * "zoomed in". + * + * Layers that provide this facility should also emit the + * verticalZoomChanged signal if their vertical zoom changes + * due to factors other than setVerticalZoomStep being called. + */ + virtual int getVerticalZoomSteps(int & /* defaultStep */) const { return 0; } + + /** + * Get the current vertical zoom step. A layer may support finer + * control over ranges etc than is available through the integer + * zoom step mechanism; if this one does, it should just return + * the nearest of the available zoom steps to the current settings. + */ + virtual int getCurrentVerticalZoomStep() const { return 0; } + + /** + * Set the vertical zoom step. The meaning of "zooming" is + * entirely up to the layer -- changing the zoom level may cause + * the layer to reset its display extents or change another + * property such as display gain. + */ + virtual void setVerticalZoomStep(int) { } + + /** + * Create and return a range mapper for vertical zoom step values. + * See the RangeMapper documentation for more details. The + * returned value is allocated on the heap and will be deleted by + * the caller. + */ + virtual RangeMapper *getNewVerticalZoomRangeMapper() const { return 0; } + +public slots: + void showLayer(View *, bool show); + +signals: + void modelChanged(); + void modelCompletionChanged(); + void modelChanged(size_t startFrame, size_t endFrame); + void modelReplaced(); + + void layerParametersChanged(); + void layerParameterRangesChanged(); + void layerNameChanged(); + + void verticalZoomChanged(); + +private: + mutable QMutex m_dormancyMutex; + mutable std::map m_dormancy; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/LayerFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,396 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LayerFactory.h" + +#include "WaveformLayer.h" +#include "SpectrogramLayer.h" +#include "TimeRulerLayer.h" +#include "TimeInstantLayer.h" +#include "TimeValueLayer.h" +#include "NoteLayer.h" +#include "TextLayer.h" +#include "Colour3DPlotLayer.h" +#include "SpectrumLayer.h" +#include "SliceLayer.h" +#include "SliceableLayer.h" + +#include "data/model/RangeSummarisableTimeValueModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/NoteModel.h" +#include "data/model/TextModel.h" +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/WaveFileModel.h" +#include "data/model/WritableWaveFileModel.h" + +LayerFactory * +LayerFactory::m_instance = new LayerFactory; + +LayerFactory * +LayerFactory::getInstance() +{ + return m_instance; +} + +LayerFactory::~LayerFactory() +{ +} + +QString +LayerFactory::getLayerPresentationName(LayerType type) +{ + switch (type) { + case Waveform: return Layer::tr("Waveform"); + case Spectrogram: return Layer::tr("Spectrogram"); + case TimeRuler: return Layer::tr("Ruler"); + case TimeInstants: return Layer::tr("Time Instants"); + case TimeValues: return Layer::tr("Time Values"); + case Notes: return Layer::tr("Notes"); + case Text: return Layer::tr("Text"); + case Colour3DPlot: return Layer::tr("Colour 3D Plot"); + case Spectrum: return Layer::tr("Spectrum"); + case Slice: return Layer::tr("Time Slice"); + + case MelodicRangeSpectrogram: + // The user can change all the parameters of this after the + // fact -- there's nothing permanently melodic-range about it + // that should be encoded in its name + return Layer::tr("Spectrogram"); + + case PeakFrequencySpectrogram: + // likewise + return Layer::tr("Spectrogram"); + + default: break; + } + + return Layer::tr("Layer"); +} + +bool +LayerFactory::isLayerSliceable(const Layer *layer) +{ + if (dynamic_cast(layer)) { + if (dynamic_cast(layer)) { + + //!!! We can create slices of spectrograms, but there's a + // problem managing the models. The source model for the + // slice layer has to be one of the spectrogram's FFT + // models -- that's fine, except that we can't store & + // recall the slice layer with a reference to that model + // because the model is internal to the spectrogram layer + // and the document has no record of it. We would need + // some other way of managing models that are used in this + // way. For the moment we just don't allow slices of + // spectrograms -- and provide a spectrum layer for this + // instead. + // + // This business needs a bit more thought -- either come + // up with a sensible way to deal with that stuff, or + // simplify the existing slice layer logic so that it + // doesn't have to deal with models disappearing on it at + // all (and use the normal Document setModel mechanism to + // set its sliceable model instead of the fancy pants + // nonsense it's doing at the moment). + + return false; + } + return true; + } + return false; +} + +LayerFactory::LayerTypeSet +LayerFactory::getValidLayerTypes(Model *model) +{ + LayerTypeSet types; + + if (dynamic_cast(model)) { + types.insert(Colour3DPlot); + types.insert(Slice); + } + + if (dynamic_cast(model)) { + types.insert(Waveform); + } + + if (dynamic_cast(model)) { + types.insert(Spectrogram); + types.insert(MelodicRangeSpectrogram); + types.insert(PeakFrequencySpectrogram); + } + + if (dynamic_cast(model)) { + types.insert(TimeInstants); + } + + if (dynamic_cast(model)) { + types.insert(TimeValues); + +} + if (dynamic_cast(model)) { + types.insert(Notes); + } + + if (dynamic_cast(model)) { + types.insert(Text); + } + + if (dynamic_cast(model)) { + types.insert(Spectrum); + } + + // We don't count TimeRuler here as it doesn't actually display + // the data, although it can be backed by any model + + return types; +} + +LayerFactory::LayerTypeSet +LayerFactory::getValidEmptyLayerTypes() +{ + LayerTypeSet types; + types.insert(TimeInstants); + types.insert(TimeValues); + types.insert(Notes); + types.insert(Text); + //!!! and in principle Colour3DPlot -- now that's a challenge + return types; +} + +LayerFactory::LayerType +LayerFactory::getLayerType(const Layer *layer) +{ + if (dynamic_cast(layer)) return Waveform; + if (dynamic_cast(layer)) return Spectrogram; + if (dynamic_cast(layer)) return TimeRuler; + if (dynamic_cast(layer)) return TimeInstants; + if (dynamic_cast(layer)) return TimeValues; + if (dynamic_cast(layer)) return Notes; + if (dynamic_cast(layer)) return Text; + if (dynamic_cast(layer)) return Colour3DPlot; + if (dynamic_cast(layer)) return Spectrum; + if (dynamic_cast(layer)) return Slice; + return UnknownLayer; +} + +QString +LayerFactory::getLayerIconName(LayerType type) +{ + switch (type) { + case Waveform: return "waveform"; + case Spectrogram: return "spectrogram"; + case TimeRuler: return "timeruler"; + case TimeInstants: return "instants"; + case TimeValues: return "values"; + case Notes: return "notes"; + case Text: return "text"; + case Colour3DPlot: return "colour3d"; + case Spectrum: return "spectrum"; + case Slice: return "spectrum"; + default: return "unknown"; + } +} + +QString +LayerFactory::getLayerTypeName(LayerType type) +{ + switch (type) { + case Waveform: return "waveform"; + case Spectrogram: return "spectrogram"; + case TimeRuler: return "timeruler"; + case TimeInstants: return "timeinstants"; + case TimeValues: return "timevalues"; + case Notes: return "notes"; + case Text: return "text"; + case Colour3DPlot: return "colour3dplot"; + case Spectrum: return "spectrum"; + case Slice: return "slice"; + default: return "unknown"; + } +} + +LayerFactory::LayerType +LayerFactory::getLayerTypeForName(QString name) +{ + if (name == "waveform") return Waveform; + if (name == "spectrogram") return Spectrogram; + if (name == "timeruler") return TimeRuler; + if (name == "timeinstants") return TimeInstants; + if (name == "timevalues") return TimeValues; + if (name == "notes") return Notes; + if (name == "text") return Text; + if (name == "colour3dplot") return Colour3DPlot; + if (name == "spectrum") return Spectrum; + if (name == "slice") return Slice; + return UnknownLayer; +} + +void +LayerFactory::setModel(Layer *layer, Model *model) +{ +// if (trySetModel(layer, model)) +// return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + +// if (trySetModel(layer, model)) +// return; +} + +Model * +LayerFactory::createEmptyModel(LayerType layerType, Model *baseModel) +{ + if (layerType == TimeInstants) { + return new SparseOneDimensionalModel(baseModel->getSampleRate(), 1); + } else if (layerType == TimeValues) { + return new SparseTimeValueModel(baseModel->getSampleRate(), 1, true); + } else if (layerType == Notes) { + return new NoteModel(baseModel->getSampleRate(), 1, true); + } else if (layerType == Text) { + return new TextModel(baseModel->getSampleRate(), 1, true); + } else { + return 0; + } +} + +int +LayerFactory::getChannel(Layer *layer) +{ + if (dynamic_cast(layer)) { + return dynamic_cast(layer)->getChannel(); + } + if (dynamic_cast(layer)) { + return dynamic_cast(layer)->getChannel(); + } + return -1; +} + +void +LayerFactory::setChannel(Layer *layer, int channel) +{ + if (dynamic_cast(layer)) { + dynamic_cast(layer)->setChannel(channel); + return; + } + if (dynamic_cast(layer)) { + dynamic_cast(layer)->setChannel(channel); + return; + } +} + +Layer * +LayerFactory::createLayer(LayerType type) +{ + Layer *layer = 0; + + switch (type) { + + case Waveform: + layer = new WaveformLayer; + break; + + case Spectrogram: + layer = new SpectrogramLayer; + break; + + case TimeRuler: + layer = new TimeRulerLayer; + break; + + case TimeInstants: + layer = new TimeInstantLayer; + break; + + case TimeValues: + layer = new TimeValueLayer; + break; + + case Notes: + layer = new NoteLayer; + break; + + case Text: + layer = new TextLayer; + break; + + case Colour3DPlot: + layer = new Colour3DPlotLayer; + break; + + case Spectrum: + layer = new SpectrumLayer; + break; + + case Slice: + layer = new SliceLayer; + break; + + case MelodicRangeSpectrogram: + layer = new SpectrogramLayer(SpectrogramLayer::MelodicRange); + break; + + case PeakFrequencySpectrogram: + layer = new SpectrogramLayer(SpectrogramLayer::MelodicPeaks); + break; + + default: break; + } + + if (!layer) { + std::cerr << "LayerFactory::createLayer: Unknown layer type " + << type << std::endl; + } else { +// std::cerr << "LayerFactory::createLayer: Setting object name " +// << getLayerPresentationName(type).toStdString() << " on " << layer << std::endl; + layer->setObjectName(getLayerPresentationName(type)); + } + + return layer; +} + diff -r 000000000000 -r fc9323a41f5a layer/LayerFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LAYER_FACTORY_H_ +#define _LAYER_FACTORY_H_ + +#include +#include + +class Layer; +class Model; + +class LayerFactory +{ +public: + enum LayerType { + + // Standard layers + Waveform, + Spectrogram, + TimeRuler, + TimeInstants, + TimeValues, + Notes, + Text, + Colour3DPlot, + Spectrum, + Slice, + + // Layers with different initial parameters + MelodicRangeSpectrogram, + PeakFrequencySpectrogram, + + // Not-a-layer-type + UnknownLayer = 255 + }; + + static LayerFactory *getInstance(); + + virtual ~LayerFactory(); + + typedef std::set LayerTypeSet; + LayerTypeSet getValidLayerTypes(Model *model); + LayerTypeSet getValidEmptyLayerTypes(); + + LayerType getLayerType(const Layer *); + + Layer *createLayer(LayerType type); + + QString getLayerPresentationName(LayerType type); + + bool isLayerSliceable(const Layer *); + + void setModel(Layer *layer, Model *model); + Model *createEmptyModel(LayerType type, Model *baseModel); + + int getChannel(Layer *layer); + void setChannel(Layer *layer, int channel); + + QString getLayerIconName(LayerType); + QString getLayerTypeName(LayerType); + LayerType getLayerTypeForName(QString); + +protected: + template + bool trySetModel(Layer *layerBase, Model *modelBase) { + LayerClass *layer = dynamic_cast(layerBase); + if (!layer) return false; + ModelClass *model = dynamic_cast(modelBase); + if (!model) return false; + layer->setModel(model); + return true; + } + + static LayerFactory *m_instance; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/NoteLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/NoteLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1009 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NoteLayer.h" + +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "base/Pitch.h" +#include "base/LogRange.h" +#include "view/View.h" + +#include "data/model/NoteModel.h" + +#include "widgets/ItemEditDialog.h" + +#include "SpectrogramLayer.h" // for optional frequency alignment + +#include +#include +#include + +#include +#include + +NoteLayer::NoteLayer() : + Layer(), + m_model(0), + m_editing(false), + m_originalPoint(0, 0.0, 0, tr("New Point")), + m_editingPoint(0, 0.0, 0, tr("New Point")), + m_editingCommand(0), + m_colour(Qt::black), + m_verticalScale(AutoAlignScale) +{ + +} + +void +NoteLayer::setModel(NoteModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + +// std::cerr << "NoteLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +NoteLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Vertical Scale"); + list.push_back("Scale Units"); + return list; +} + +QString +NoteLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Vertical Scale") return tr("Vertical Scale"); + if (name == "Scale Units") return tr("Scale Units"); + return ""; +} + +Layer::PropertyType +NoteLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Scale Units") return UnitsProperty; + return ValueProperty; +} + +QString +NoteLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Vertical Scale" || name == "Scale Units") { + return tr("Scale"); + } + return QString(); +} + +int +NoteLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + //!!! factor this colour handling stuff out into a colour manager class + + int val = 0; + + if (name == "Colour") { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + + } else if (name == "Vertical Scale") { + + if (min) *min = 0; + if (max) *max = 3; + if (deflt) *deflt = int(AutoAlignScale); + + val = int(m_verticalScale); + + } else if (name == "Scale Units") { + + if (deflt) *deflt = 0; + if (m_model) { + val = UnitDatabase::getInstance()->getUnitId + (m_model->getScaleUnits()); + } + + } else { + + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +NoteLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } else if (name == "Vertical Scale") { + switch (value) { + default: + case 0: return tr("Auto-Align"); + case 1: return tr("Linear"); + case 2: return tr("Log"); + case 3: return tr("MIDI Notes"); + } + } + return tr(""); +} + +void +NoteLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour") { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } else if (name == "Vertical Scale") { + setVerticalScale(VerticalScale(value)); + } else if (name == "Scale Units") { + if (m_model) { + m_model->setScaleUnits + (UnitDatabase::getInstance()->getUnitById(value)); + emit modelChanged(); + } + } +} + +void +NoteLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +void +NoteLayer::setVerticalScale(VerticalScale scale) +{ + if (m_verticalScale == scale) return; + m_verticalScale = scale; + emit layerParametersChanged(); +} + +bool +NoteLayer::isLayerScrollable(const View *v) const +{ + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +bool +NoteLayer::shouldConvertMIDIToHz() const +{ + QString unit = m_model->getScaleUnits(); + return (unit != "Hz"); +// if (unit == "" || +// unit.startsWith("MIDI") || +// unit.startsWith("midi")) return true; +// return false; +} + +bool +NoteLayer::getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const +{ + if (!m_model) return false; + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (shouldConvertMIDIToHz()) { + unit = "Hz"; + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } else unit = m_model->getScaleUnits(); + + if (m_verticalScale == MIDIRangeScale || + m_verticalScale == LogScale) logarithmic = true; + + return true; +} + +bool +NoteLayer::getDisplayExtents(float &min, float &max) const +{ + if (!m_model || m_verticalScale == AutoAlignScale) return false; + + if (m_verticalScale == MIDIRangeScale) { + min = Pitch::getFrequencyForPitch(0); + max = Pitch::getFrequencyForPitch(127); + return true; + } + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + + return true; +} + +NoteModel::PointList +NoteLayer::getLocalPoints(View *v, int x) const +{ + if (!m_model) return NoteModel::PointList(); + + long frame = v->getFrameForX(x); + + NoteModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + NoteModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + NoteModel::PointList nextPoints = + m_model->getNextPoints(frame); + + NoteModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (long(prevPoints.begin()->frame) < v->getStartFrame() && + !(nextPoints.begin()->frame > v->getEndFrame())) { + usePoints = nextPoints; + } else if (long(nextPoints.begin()->frame) - frame < + frame - long(prevPoints.begin()->frame)) { + usePoints = nextPoints; + } + + if (!usePoints.empty()) { + int fuzz = 2; + int px = v->getXForFrame(usePoints.begin()->frame); + if ((px > x && px - x > fuzz) || + (px < x && x - px > fuzz + 1)) { + usePoints.clear(); + } + } + + return usePoints; +} + +QString +NoteLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return ""; + + NoteModel::PointList points = getLocalPoints(v, x); + + if (points.empty()) { + if (!m_model->isReady()) { + return tr("In progress"); + } else { + return tr("No local points"); + } + } + + Note note(0); + NoteModel::PointList::iterator i; + + for (i = points.begin(); i != points.end(); ++i) { + + int y = getYForValue(v, i->value); + int h = 3; + + if (m_model->getValueQuantization() != 0.0) { + h = y - getYForValue(v, i->value + m_model->getValueQuantization()); + if (h < 3) h = 3; + } + + if (pos.y() >= y - h && pos.y() <= y) { + note = *i; + break; + } + } + + if (i == points.end()) return tr("No local points"); + + RealTime rt = RealTime::frame2RealTime(note.frame, + m_model->getSampleRate()); + RealTime rd = RealTime::frame2RealTime(note.duration, + m_model->getSampleRate()); + + QString pitchText; + + if (shouldConvertMIDIToHz()) { + + int mnote = lrintf(note.value); + int cents = lrintf((note.value - mnote) * 100); + float freq = Pitch::getFrequencyForPitch(mnote, cents); + pitchText = tr("%1 (%2 Hz)") + .arg(Pitch::getPitchLabel(mnote, cents)).arg(freq); + + } else if (m_model->getScaleUnits() == "Hz") { + + pitchText = tr("%1 Hz (%2)") + .arg(note.value) + .arg(Pitch::getPitchLabelForFrequency(note.value)); + + } else { + pitchText = tr("%1 %2") + .arg(note.value).arg(m_model->getScaleUnits()); + } + + QString text; + + if (note.label == "") { + text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label")) + .arg(rt.toText(true).c_str()) + .arg(pitchText) + .arg(rd.toText(true).c_str()); + } else { + text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4")) + .arg(rt.toText(true).c_str()) + .arg(pitchText) + .arg(rd.toText(true).c_str()) + .arg(note.label); + } + + pos = QPoint(v->getXForFrame(note.frame), + getYForValue(v, note.value)); + return text; +} + +bool +NoteLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + NoteModel::PointList points; + + if (snap == SnapNeighbouring) { + + points = getLocalPoints(v, v->getXForFrame(frame)); + if (points.empty()) return false; + frame = points.begin()->frame; + return true; + } + + points = m_model->getPoints(frame, frame); + int snapped = frame; + bool found = false; + + for (NoteModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (snap == SnapRight) { + + if (i->frame > frame) { + snapped = i->frame; + found = true; + break; + } + + } else if (snap == SnapLeft) { + + if (i->frame <= frame) { + snapped = i->frame; + found = true; // don't break, as the next may be better + } else { + break; + } + + } else { // nearest + + NoteModel::PointList::const_iterator j = i; + ++j; + + if (j == points.end()) { + + snapped = i->frame; + found = true; + break; + + } else if (j->frame >= frame) { + + if (j->frame - frame < frame - i->frame) { + snapped = j->frame; + } else { + snapped = i->frame; + } + found = true; + break; + } + } + } + + frame = snapped; + return found; +} + +void +NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const +{ + min = 0.0; + max = 0.0; + log = false; + + QString queryUnits; + if (shouldConvertMIDIToHz()) queryUnits = "Hz"; + else queryUnits = m_model->getScaleUnits(); + + if (m_verticalScale == AutoAlignScale) { + + if (!v->getValueExtents(queryUnits, min, max, log)) { + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + + std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl; + + } else if (log) { + + LogRange::mapRange(min, max); + + std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl; + + } + + } else { + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (m_verticalScale == MIDIRangeScale) { + min = Pitch::getFrequencyForPitch(0); + max = Pitch::getFrequencyForPitch(127); + } else if (shouldConvertMIDIToHz()) { + min = Pitch::getFrequencyForPitch(lrintf(min)); + max = Pitch::getFrequencyForPitch(lrintf(max + 1)); + } + + if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + LogRange::mapRange(min, max); + log = true; + } + } + + if (max == min) max = min + 1.0; +} + +int +NoteLayer::getYForValue(View *v, float val) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + +// std::cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << std::endl; + + if (shouldConvertMIDIToHz()) { + val = Pitch::getFrequencyForPitch(lrintf(val), + lrintf((val - lrintf(val)) * 100)); +// std::cerr << "shouldConvertMIDIToHz true, val now = " << val << std::endl; + } + + if (logarithmic) { + val = LogRange::map(val); +// std::cerr << "logarithmic true, val now = " << val << std::endl; + } + + int y = int(h - ((val - min) * h) / (max - min)) - 1; +// std::cerr << "y = " << y << std::endl; + return y; +} + +float +NoteLayer::getValueForY(View *v, int y) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + + float val = min + (float(h - y) * float(max - min)) / h; + + if (logarithmic) { + val = powf(10.f, val); + } + + if (shouldConvertMIDIToHz()) { + val = Pitch::getPitchForFrequency(val); + } + + return val; +} + +void +NoteLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("NoteLayer::paint", true); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1); + + NoteModel::PointList points(m_model->getPoints(frame0, frame1)); + if (points.empty()) return; + + paint.setPen(m_colour); + + QColor brushColour(m_colour); + brushColour.setAlpha(80); + +// std::cerr << "NoteLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + float min = m_model->getValueMinimum(); + float max = m_model->getValueMaximum(); + if (max == min) max = min + 1.0; + + QPoint localPos; + long illuminateFrame = -1; + + if (v->shouldIlluminateLocalFeatures(this, localPos)) { + NoteModel::PointList localPoints = + getLocalPoints(v, localPos.x()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + paint.save(); + paint.setRenderHint(QPainter::Antialiasing, false); + + for (NoteModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const NoteModel::Point &p(*i); + + int x = v->getXForFrame(p.frame); + int y = getYForValue(v, p.value); + int w = v->getXForFrame(p.frame + p.duration) - x; + int h = 3; + + if (m_model->getValueQuantization() != 0.0) { + h = y - getYForValue(v, p.value + m_model->getValueQuantization()); + if (h < 3) h = 3; + } + + if (w < 1) w = 1; + paint.setPen(m_colour); + paint.setBrush(brushColour); + + if (illuminateFrame == p.frame) { + if (localPos.y() >= y - h && localPos.y() < y) { + paint.setPen(Qt::black);//!!! + paint.setBrush(Qt::black);//!!! + } + } + + paint.drawRect(x, y - h/2, w, h); + +/// if (p.label != "") { +/// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label); +/// } + } + + paint.restore(); +} + +void +NoteLayer::drawStart(View *v, QMouseEvent *e) +{ +// std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float value = getValueForY(v, e->y()); + + m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point")); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) m_editingCommand->finish(); + m_editingCommand = new NoteModel::EditCommand(m_model, + tr("Draw Point")); + m_editingCommand->addPoint(m_editingPoint); + + m_editing = true; +} + +void +NoteLayer::drawDrag(View *v, QMouseEvent *e) +{ +// std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float newValue = getValueForY(v, e->y()); + + long newFrame = m_editingPoint.frame; + long newDuration = frame - newFrame; + if (newDuration < 0) { + newFrame = frame; + newDuration = -newDuration; + } else if (newDuration == 0) { + newDuration = 1; + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = newFrame; + m_editingPoint.value = newValue; + m_editingPoint.duration = newDuration; + m_editingCommand->addPoint(m_editingPoint); +} + +void +NoteLayer::drawEnd(View *, QMouseEvent *) +{ +// std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + m_editingCommand->finish(); + m_editingCommand = 0; + m_editing = false; +} + +void +NoteLayer::editStart(View *v, QMouseEvent *e) +{ +// std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + NoteModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + m_editingPoint = *points.begin(); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) { + m_editingCommand->finish(); + m_editingCommand = 0; + } + + m_editing = true; +} + +void +NoteLayer::editDrag(View *v, QMouseEvent *e) +{ +// std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float value = getValueForY(v, e->y()); + + if (!m_editingCommand) { + m_editingCommand = new NoteModel::EditCommand(m_model, + tr("Drag Point")); + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingPoint.value = value; + m_editingCommand->addPoint(m_editingPoint); +} + +void +NoteLayer::editEnd(View *, QMouseEvent *) +{ +// std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + + if (m_editingCommand) { + + QString newName = m_editingCommand->getName(); + + if (m_editingPoint.frame != m_originalPoint.frame) { + if (m_editingPoint.value != m_originalPoint.value) { + newName = tr("Edit Point"); + } else { + newName = tr("Relocate Point"); + } + } else { + newName = tr("Change Point Value"); + } + + m_editingCommand->setName(newName); + m_editingCommand->finish(); + } + + m_editingCommand = 0; + m_editing = false; +} + +void +NoteLayer::editOpen(View *v, QMouseEvent *e) +{ + if (!m_model) return; + + NoteModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + NoteModel::Point note = *points.begin(); + + ItemEditDialog *dialog = new ItemEditDialog + (m_model->getSampleRate(), + ItemEditDialog::ShowTime | + ItemEditDialog::ShowDuration | + ItemEditDialog::ShowValue | + ItemEditDialog::ShowText, + m_model->getScaleUnits()); + + dialog->setFrameTime(note.frame); + dialog->setValue(note.value); + dialog->setFrameDuration(note.duration); + dialog->setText(note.label); + + if (dialog->exec() == QDialog::Accepted) { + + NoteModel::Point newNote = note; + newNote.frame = dialog->getFrameTime(); + newNote.value = dialog->getValue(); + newNote.duration = dialog->getFrameDuration(); + newNote.label = dialog->getText(); + + NoteModel::EditCommand *command = new NoteModel::EditCommand + (m_model, tr("Edit Point")); + command->deletePoint(note); + command->addPoint(newNote); + command->finish(); + } + + delete dialog; +} + +void +NoteLayer::moveSelection(Selection s, size_t newStartFrame) +{ + if (!m_model) return; + + NoteModel::EditCommand *command = + new NoteModel::EditCommand(m_model, tr("Drag Selection")); + + NoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (NoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + NoteModel::Point newPoint(*i); + newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +NoteLayer::resizeSelection(Selection s, Selection newSize) +{ + if (!m_model) return; + + NoteModel::EditCommand *command = + new NoteModel::EditCommand(m_model, tr("Resize Selection")); + + NoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + double ratio = + double(newSize.getEndFrame() - newSize.getStartFrame()) / + double(s.getEndFrame() - s.getStartFrame()); + + for (NoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + + double targetStart = i->frame; + targetStart = newSize.getStartFrame() + + double(targetStart - s.getStartFrame()) * ratio; + + double targetEnd = i->frame + i->duration; + targetEnd = newSize.getStartFrame() + + double(targetEnd - s.getStartFrame()) * ratio; + + NoteModel::Point newPoint(*i); + newPoint.frame = lrint(targetStart); + newPoint.duration = lrint(targetEnd - targetStart); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +NoteLayer::deleteSelection(Selection s) +{ + if (!m_model) return; + + NoteModel::EditCommand *command = + new NoteModel::EditCommand(m_model, tr("Delete Selected Points")); + + NoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (NoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + command->deletePoint(*i); + } + } + + command->finish(); +} + +void +NoteLayer::copy(Selection s, Clipboard &to) +{ + if (!m_model) return; + + NoteModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (NoteModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) { + Clipboard::Point point(i->frame, i->value, i->duration, i->label); + to.addPoint(point); + } + } +} + +bool +NoteLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */) +{ + if (!m_model) return false; + + const Clipboard::PointList &points = from.getPoints(); + + NoteModel::EditCommand *command = + new NoteModel::EditCommand(m_model, tr("Paste")); + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + size_t frame = 0; + if (frameOffset > 0 || -frameOffset < i->getFrame()) { + frame = i->getFrame() + frameOffset; + } + NoteModel::Point newPoint(frame); + + if (i->haveLabel()) newPoint.label = i->getLabel(); + if (i->haveValue()) newPoint.value = i->getValue(); + else newPoint.value = (m_model->getValueMinimum() + + m_model->getValueMaximum()) / 2; + if (i->haveDuration()) newPoint.duration = i->getDuration(); + else { + size_t nextFrame = frame; + Clipboard::PointList::const_iterator j = i; + for (; j != points.end(); ++j) { + if (!j->haveFrame()) continue; + if (j != i) break; + } + if (j != points.end()) { + nextFrame = j->getFrame(); + } + if (nextFrame == frame) { + newPoint.duration = m_model->getResolution(); + } else { + newPoint.duration = nextFrame - frame; + } + } + + command->addPoint(newPoint); + } + + command->finish(); + return true; +} + +QString +NoteLayer::toXmlString(QString indent, QString extraAttributes) const +{ + return Layer::toXmlString(indent, extraAttributes + + QString(" colour=\"%1\" verticalScale=\"%2\"") + .arg(encodeColour(m_colour)).arg(m_verticalScale)); +} + +void +NoteLayer::setProperties(const QXmlAttributes &attributes) +{ + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } + + bool ok; + VerticalScale scale = (VerticalScale) + attributes.value("verticalScale").toInt(&ok); + if (ok) setVerticalScale(scale); +} + + diff -r 000000000000 -r fc9323a41f5a layer/NoteLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/NoteLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,121 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTE_LAYER_H_ +#define _NOTE_LAYER_H_ + +#include "Layer.h" +#include "data/model/NoteModel.h" + +#include +#include + +class View; +class QPainter; + +class NoteLayer : public Layer +{ + Q_OBJECT + +public: + NoteLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual void drawStart(View *v, QMouseEvent *); + virtual void drawDrag(View *v, QMouseEvent *); + virtual void drawEnd(View *v, QMouseEvent *); + + virtual void editStart(View *v, QMouseEvent *); + virtual void editDrag(View *v, QMouseEvent *); + virtual void editEnd(View *v, QMouseEvent *); + + virtual void editOpen(View *v, QMouseEvent *); + + virtual void moveSelection(Selection s, size_t newStartFrame); + virtual void resizeSelection(Selection s, Selection newSize); + virtual void deleteSelection(Selection s); + + virtual void copy(Selection s, Clipboard &to); + virtual bool paste(const Clipboard &from, int frameOffset, + bool interactive); + + virtual const Model *getModel() const { return m_model; } + void setModel(NoteModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + enum VerticalScale { + AutoAlignScale, + LinearScale, + LogScale, + MIDIRangeScale + }; + + void setVerticalScale(VerticalScale scale); + VerticalScale getVerticalScale() const { return m_verticalScale; } + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerEditable() const { return true; } + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool getValueExtents(float &min, float &max, + bool &log, QString &unit) const; + + virtual bool getDisplayExtents(float &min, float &max) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + +protected: + void getScaleExtents(View *, float &min, float &max, bool &log) const; + int getYForValue(View *v, float value) const; + float getValueForY(View *v, int y) const; + bool shouldConvertMIDIToHz() const; + + NoteModel::PointList getLocalPoints(View *v, int) const; + + NoteModel *m_model; + bool m_editing; + NoteModel::Point m_originalPoint; + NoteModel::Point m_editingPoint; + NoteModel::EditCommand *m_editingCommand; + QColor m_colour; + VerticalScale m_verticalScale; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/PaintAssistant.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/PaintAssistant.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,210 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PaintAssistant.h" + +#include "base/AudioLevel.h" +#include "system/System.h" + +#include +#include + +#include +#include + +void +PaintAssistant::paintVerticalLevelScale(QPainter &paint, QRect rect, + float minVal, float maxVal, + Scale scale, int &mult, + std::vector *vy) +{ + static float meterdbs[] = { -40, -30, -20, -15, -10, + -5, -3, -2, -1, -0.5, 0 }; + + int h = rect.height(), w = rect.width(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1; + + int lastLabelledY = -1; + + int n = 10; + if (vy) vy->clear(); + + float step = 0; + mult = 1; + if (scale == LinearScale) { + step = (maxVal - minVal) / n; + int round = 0, limit = 10000000; + do { + round = int(minVal + step * mult); + mult *= 10; + } while (!round && mult < limit); + if (round) { + mult /= 10; +// std::cerr << "\n\nstep goes from " << step; + step = float(round) / mult; + n = lrintf((maxVal - minVal) / step); + if (mult > 1) { + mult /= 10; + } +// std::cerr << " to " << step << " (n = " << n << ")" << std::endl; + } + } + + for (int i = 0; i <= n; ++i) { + + float val = 0.0, nval = 0.0; + QString text = ""; + + switch (scale) { + + case LinearScale: + val = (minVal + (i * step)); + text = QString("%1").arg(mult * val); + break; + + case MeterScale: // ... min, max + val = AudioLevel::dB_to_multiplier(meterdbs[i]); + text = QString("%1").arg(meterdbs[i]); + if (i == n) text = "0dB"; + if (i == 0) { + text = "-Inf"; + val = 0.0; + } + break; + + case dBScale: // ... min, max + val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10); + text = QString("%1").arg(-(10*n) + i * 10); + if (i == n) text = "0dB"; + if (i == 0) { + text = "-Inf"; + val = 0.0; + } + break; + } + + if (val < minVal || val > maxVal) continue; + + int y = getYForValue(scale, val, minVal, maxVal, rect.y(), h); + + int ny = y; + if (nval != 0.0) { + ny = getYForValue(scale, nval, minVal, maxVal, rect.y(), h); + } + +// std::cerr << "PaintAssistant::paintVerticalLevelScale: val = " +// << val << ", y = " << y << ", h = " << h << std::endl; + + bool spaceForLabel = (i == 0 || + abs(y - lastLabelledY) >= textHeight - 1); + + if (spaceForLabel) { + + int tx = 3; +// if (scale != LinearScale) { + if (paint.fontMetrics().width(text) < w - 10) { + tx = w - 10 - paint.fontMetrics().width(text); + } + + int ty = y; + + if (ty < paint.fontMetrics().ascent()) { + ty = paint.fontMetrics().ascent(); +// } else if (ty > rect.y() + h - paint.fontMetrics().descent()) { +// ty = rect.y() + h - paint.fontMetrics().descent(); + } else { + ty += toff; + } + + paint.drawText(tx, ty, text); + + lastLabelledY = ty - toff; + /* + if (ny != y) { + ty = ny; + if (ty < paint.fontMetrics().ascent()) { + ty = paint.fontMetrics().ascent(); + } else if (ty > h - paint.fontMetrics().descent()) { + ty = h - paint.fontMetrics().descent(); + } else { + ty += toff; + } + paint.drawText(tx, ty, text); + } + */ + paint.drawLine(w - 7, y, w, y); + if (vy) vy->push_back(y); + + if (ny != y) { + paint.drawLine(w - 7, ny, w, ny); + if (vy) vy->push_back(ny); + } + + } else { + + paint.drawLine(w - 4, y, w, y); + if (vy) vy->push_back(y); + + if (ny != y) { + paint.drawLine(w - 4, ny, w, ny); + if (vy) vy->push_back(ny); + } + } + } +} + +static int +dBscale(float sample, int m, float maxVal, float minVal) +{ + if (sample < 0.0) return dBscale(-sample, m, maxVal, minVal); + float dB = AudioLevel::multiplier_to_dB(sample); + float mindB = AudioLevel::multiplier_to_dB(minVal); + float maxdB = AudioLevel::multiplier_to_dB(maxVal); + if (dB < mindB) return 0; + if (dB > 0.0) return m; + return int(((dB - mindB) * m) / (maxdB - mindB) + 0.1); +} + +int +PaintAssistant::getYForValue(Scale scale, float value, + float minVal, float maxVal, + int minY, int height) +{ + int vy = 0; + +// int m = height/2; +// int my = minY + m; + + switch (scale) { + + case LinearScale: +// vy = my - int(m * value); + vy = minY + height - int(((value - minVal) / (maxVal - minVal)) * height); + break; + + case MeterScale: +// vy = my - AudioLevel::multiplier_to_preview(value, m); + vy = minY + height - AudioLevel::multiplier_to_preview + ((value - minVal) / (maxVal - minVal), height); + break; + + case dBScale: + vy = minY + height - dBscale(value, height, maxVal, minVal); + break; + } + + return vy; +} diff -r 000000000000 -r fc9323a41f5a layer/PaintAssistant.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/PaintAssistant.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,39 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PAINT_ASSISTANT_H_ +#define _PAINT_ASSISTANT_H_ + +#include +#include + +class QPainter; + +class PaintAssistant +{ +public: + enum Scale { LinearScale, MeterScale, dBScale }; + + static void paintVerticalLevelScale(QPainter &p, QRect rect, + float minVal, float maxVal, + Scale scale, int &multRtn, + std::vector *markCoordRtns = 0); + + static int getYForValue(Scale scale, float value, + float minVal, float maxVal, + int minY, int height); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/SliceLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SliceLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,860 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SliceLayer.h" + +#include "system/System.h" +#include "view/View.h" +#include "base/AudioLevel.h" +#include "base/RangeMapper.h" +#include "base/RealTime.h" + +#include "ColourMapper.h" +#include "PaintAssistant.h" + +#include +#include + +SliceLayer::SliceLayer() : + m_sliceableModel(0), + m_colour(Qt::darkBlue), + m_colourMap(0), + m_energyScale(dBScale), + m_samplingMode(SampleMean), + m_plotStyle(PlotSteps), + m_binScale(LinearBins), + m_normalize(false), + m_bias(false), + m_gain(1.0), + m_currentf0(0), + m_currentf1(0) +{ +} + +SliceLayer::~SliceLayer() +{ + +} + +void +SliceLayer::setSliceableModel(const Model *model) +{ + const DenseThreeDimensionalModel *sliceable = + dynamic_cast(model); + + if (model && !sliceable) { + std::cerr << "WARNING: SliceLayer::setSliceableModel(" << model + << "): model is not a DenseThreeDimensionalModel" << std::endl; + } + + if (m_sliceableModel == sliceable) return; + + m_sliceableModel = sliceable; + + connect(m_sliceableModel, SIGNAL(modelChanged()), + this, SIGNAL(modelChanged())); + + connect(m_sliceableModel, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_sliceableModel, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + emit modelReplaced(); +} + +void +SliceLayer::sliceableModelReplaced(const Model *orig, const Model *replacement) +{ + std::cerr << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << std::endl; + + if (orig == m_sliceableModel) { + setSliceableModel + (dynamic_cast(replacement)); + } +} + +void +SliceLayer::modelAboutToBeDeleted(Model *m) +{ + std::cerr << "SliceLayer::modelAboutToBeDeleted(" << m << ")" << std::endl; + + if (m == m_sliceableModel) { + setSliceableModel(0); + } +} + +QString +SliceLayer::getFeatureDescription(View *v, QPoint &p) const +{ + int minbin, maxbin, range; + return getFeatureDescription(v, p, true, minbin, maxbin, range); +} + +QString +SliceLayer::getFeatureDescription(View *v, QPoint &p, + bool includeBinDescription, + int &minbin, int &maxbin, int &range) const +{ + minbin = 0; + maxbin = 0; + if (!m_sliceableModel) return ""; + + int xorigin = m_xorigins[v]; + int w = v->width() - xorigin - 1; + + int mh = m_sliceableModel->getHeight(); + minbin = getBinForX(p.x() - xorigin, mh, w); + maxbin = getBinForX(p.x() - xorigin + 1, mh, w); + + if (minbin >= mh) minbin = mh - 1; + if (maxbin >= mh) maxbin = mh - 1; + if (minbin < 0) minbin = 0; + if (maxbin < 0) maxbin = 0; + + int sampleRate = m_sliceableModel->getSampleRate(); + + size_t f0 = m_currentf0; + size_t f1 = m_currentf1; + + RealTime rt0 = RealTime::frame2RealTime(f0, sampleRate); + RealTime rt1 = RealTime::frame2RealTime(f1, sampleRate); + + range = f1 - f0 + 1; + + if (includeBinDescription) { + + float minvalue = 0.f; + if (minbin < int(m_values.size())) minvalue = m_values[minbin]; + + float maxvalue = minvalue; + if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin]; + + if (minvalue > maxvalue) std::swap(minvalue, maxvalue); + + QString binstr; + if (maxbin != minbin) { + binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); + } else { + binstr = QString("%1").arg(minbin+1); + } + + QString valuestr; + if (maxvalue != minvalue) { + valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue); + } else { + valuestr = QString("%1").arg(minvalue); + } + + QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples\nBin:\t%4\n%5 value:\t%6") + .arg(QString::fromStdString(rt0.toText(true))) + .arg(QString::fromStdString(rt1.toText(true))) + .arg(range) + .arg(binstr) + .arg(m_samplingMode == NearestSample ? tr("First") : + m_samplingMode == SampleMean ? tr("Mean") : tr("Peak")) + .arg(valuestr); + + return description; + + } else { + + QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples") + .arg(QString::fromStdString(rt0.toText(true))) + .arg(QString::fromStdString(rt1.toText(true))) + .arg(range); + + return description; + } +} + +float +SliceLayer::getXForBin(int bin, int count, float w) const +{ + float x = 0; + + switch (m_binScale) { + + case LinearBins: + x = (float(w) * bin) / count; + break; + + case LogBins: + x = (float(w) * log10f(bin + 1)) / log10f(count + 1); + break; + + case InvertedLogBins: + x = w - (float(w) * log10f(count - bin - 1)) / log10f(count); + break; + } + + return x; +} + +int +SliceLayer::getBinForX(float x, int count, float w) const +{ + int bin = 0; + + switch (m_binScale) { + + case LinearBins: + bin = int((x * count) / w + 0.0001); + break; + + case LogBins: + bin = int(powf(10.f, (x * log10f(count + 1)) / w) - 1 + 0.0001); + break; + + case InvertedLogBins: + bin = count + 1 - int(powf(10.f, (log10f(count) * (w - x)) / float(w)) + 0.0001); + break; + } + + return bin; +} + +void +SliceLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_sliceableModel) return; + + paint.save(); + paint.setRenderHint(QPainter::Antialiasing, false); + + if (v->getViewManager() && v->getViewManager()->shouldShowScaleGuides()) { + if (!m_scalePoints.empty()) { + paint.setPen(QColor(240, 240, 240)); //!!! and dark background? + for (size_t i = 0; i < m_scalePoints.size(); ++i) { + paint.drawLine(0, m_scalePoints[i], rect.width(), m_scalePoints[i]); + } + } + } + + paint.setPen(m_colour); + +// int w = (v->width() * 2) / 3; + int xorigin = getVerticalScaleWidth(v, paint) + 1; //!!! (v->width() / 2) - (w / 2); + int w = v->width() - xorigin - 1; + + m_xorigins[v] = xorigin; // for use in getFeatureDescription + + int yorigin = v->height() - 20 - paint.fontMetrics().height() - 7; + int h = yorigin - paint.fontMetrics().height() - 8; + if (h < 0) return; + +// int h = (v->height() * 3) / 4; +// int yorigin = (v->height() / 2) + (h / 2); + + QPainterPath path; + float thresh = -80.f; + + size_t mh = m_sliceableModel->getHeight(); + + int divisor = 0; + + m_values.clear(); + for (size_t bin = 0; bin < mh; ++bin) { + m_values.push_back(0.f); + } + + size_t f0 = v->getCentreFrame(); + int f0x = v->getXForFrame(f0); + f0 = v->getFrameForX(f0x); + size_t f1 = v->getFrameForX(f0x + 1); + if (f1 > f0) --f1; + + size_t col0 = f0 / m_sliceableModel->getResolution(); + size_t col1 = col0; + if (m_samplingMode != NearestSample) { + col1 = f1 / m_sliceableModel->getResolution(); + } + f0 = col0 * m_sliceableModel->getResolution(); + f1 = (col1 + 1) * m_sliceableModel->getResolution() - 1; + + m_currentf0 = f0; + m_currentf1 = f1; + + for (size_t col = col0; col <= col1; ++col) { + for (size_t bin = 0; bin < mh; ++bin) { + float value = m_sliceableModel->getValueAt(col, bin); + if (m_bias) value *= bin + 1; + if (m_samplingMode == SamplePeak) { + if (value > m_values[bin]) m_values[bin] = value; + } else { + m_values[bin] += value; + } + } + ++divisor; + } + + float max = 0.f; + for (size_t bin = 0; bin < mh; ++bin) { + if (m_samplingMode == SampleMean) m_values[bin] /= divisor; + if (m_values[bin] > max) max = m_values[bin]; + } + if (max != 0.f && m_normalize) { + for (size_t bin = 0; bin < mh; ++bin) { + m_values[bin] /= max; + } + } + + float py = 0; + float nx = xorigin; + + ColourMapper mapper(m_colourMap, 0, 1); + + for (size_t bin = 0; bin < mh; ++bin) { + + float x = nx; + nx = xorigin + getXForBin(bin + 1, mh, w); + + float value = m_values[bin]; + + value *= m_gain; + float norm = 0.f; + float y = 0.f; + + switch (m_energyScale) { + + case dBScale: + { + float db = thresh; + if (value > 0.f) db = 10.f * log10f(value); + if (db < thresh) db = thresh; + norm = (db - thresh) / -thresh; + y = yorigin - (float(h) * norm); + break; + } + + case MeterScale: + y = AudioLevel::multiplier_to_preview(value, h); + norm = float(y) / float(h); + y = yorigin - y; + break; + + default: + norm = value; + y = yorigin - (float(h) * value); + break; + } + + if (m_plotStyle == PlotLines) { + + if (bin == 0) { + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + + } else if (m_plotStyle == PlotSteps) { + + if (bin == 0) { + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + path.lineTo(nx, y); + + } else if (m_plotStyle == PlotBlocks) { + + path.moveTo(x, yorigin); + path.lineTo(x, y); + path.lineTo(nx, y); + path.lineTo(nx, yorigin); + path.lineTo(x, yorigin); + + } else if (m_plotStyle == PlotFilledBlocks) { + + paint.fillRect(QRectF(x, y, nx - x, yorigin - y), mapper.map(norm)); + } + + py = y; + } + + if (m_plotStyle != PlotFilledBlocks) { + paint.drawPath(path); + } + paint.restore(); +/* + QPoint discard; + + if (v->getViewManager() && v->getViewManager()->shouldShowFrameCount() && + v->shouldIlluminateLocalFeatures(this, discard)) { + + int sampleRate = m_sliceableModel->getSampleRate(); + + QString startText = QString("%1 / %2") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (f0, sampleRate).toText(true))) + .arg(f0); + + QString endText = QString(" %1 / %2") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (f1, sampleRate).toText(true))) + .arg(f1); + + QString durationText = QString("(%1 / %2) ") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (f1 - f0 + 1, sampleRate).toText(true))) + .arg(f1 - f0 + 1); + + v->drawVisibleText + (paint, xorigin + 5, + paint.fontMetrics().ascent() + 5, + startText, View::OutlinedText); + + v->drawVisibleText + (paint, xorigin + 5, + paint.fontMetrics().ascent() + paint.fontMetrics().height() + 10, + endText, View::OutlinedText); + + v->drawVisibleText + (paint, xorigin + 5, + paint.fontMetrics().ascent() + 2*paint.fontMetrics().height() + 15, + durationText, View::OutlinedText); + } +*/ +} + +int +SliceLayer::getVerticalScaleWidth(View *, QPainter &paint) const +{ + if (m_energyScale == LinearScale) { + return max(paint.fontMetrics().width("0.0") + 13, + paint.fontMetrics().width("x10-10")); + } else { + return max(paint.fontMetrics().width(tr("0dB")), + paint.fontMetrics().width(tr("-Inf"))) + 13; + } +} + +void +SliceLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const +{ + float thresh = 0; + if (m_energyScale != LinearScale) { + thresh = AudioLevel::dB_to_multiplier(-80); //!!! thresh + } + +// int h = (rect.height() * 3) / 4; +// int y = (rect.height() / 2) - (h / 2); + + int yorigin = v->height() - 20 - paint.fontMetrics().height() - 6; + int h = yorigin - paint.fontMetrics().height() - 8; + if (h < 0) return; + + QRect actual(rect.x(), rect.y() + yorigin - h, rect.width(), h); + + int mult = 1; + + PaintAssistant::paintVerticalLevelScale + (paint, actual, thresh, 1.0 / m_gain, + PaintAssistant::Scale(m_energyScale), + mult, + const_cast *>(&m_scalePoints)); + + if (mult != 1 && mult != 0) { + int log = lrintf(log10f(mult)); + QString a = tr("x10"); + QString b = QString("%1").arg(-log); + paint.drawText(3, 8 + paint.fontMetrics().ascent(), a); + paint.drawText(3 + paint.fontMetrics().width(a), + 3 + paint.fontMetrics().ascent(), b); + } +} + +Layer::PropertyList +SliceLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Plot Type"); +// list.push_back("Sampling Mode"); + list.push_back("Scale"); + list.push_back("Normalize"); + list.push_back("Gain"); + list.push_back("Bin Scale"); + + return list; +} + +QString +SliceLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Plot Type") return tr("Plot Type"); + if (name == "Energy Scale") return tr("Scale"); + if (name == "Normalize") return tr("Normalize"); + if (name == "Gain") return tr("Gain"); + if (name == "Sampling Mode") return tr("Sampling Mode"); + if (name == "Bin Scale") return tr("Plot X Scale"); + return ""; +} + +Layer::PropertyType +SliceLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Gain") return RangeProperty; + if (name == "Normalize") return ToggleProperty; + return ValueProperty; +} + +QString +SliceLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Scale" || + name == "Normalize" || + name == "Sampling Mode" || + name == "Gain") return tr("Scale"); + if (name == "Plot Type" || + name == "Bin Scale") return tr("Plot Type"); + return QString(); +} + +int +SliceLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + int garbage0, garbage1, garbage2; + if (!min) min = &garbage0; + if (!max) max = &garbage1; + if (!deflt) deflt = &garbage2; + + if (name == "Gain") { + + *min = -50; + *max = 50; + *deflt = 0; + + std::cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << std::endl; + + val = lrint(log10(m_gain) * 20.0); + if (val < *min) val = *min; + if (val > *max) val = *max; + + } else if (name == "Normalize") { + + val = (m_normalize ? 1 : 0); + *deflt = 0; + + } else if (name == "Colour") { + + if (m_plotStyle == PlotFilledBlocks) { + + *min = 0; + *max = ColourMapper::getColourMapCount() - 1; + *deflt = 0; + + val = m_colourMap; + + } else { + + *min = 0; + *max = 5; + *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + } + + } else if (name == "Scale") { + + *min = 0; + *max = 2; + *deflt = (int)dBScale; + + val = (int)m_energyScale; + + } else if (name == "Sampling Mode") { + + *min = 0; + *max = 2; + *deflt = (int)SampleMean; + + val = (int)m_samplingMode; + + } else if (name == "Plot Type") { + + *min = 0; + *max = 3; + *deflt = (int)PlotSteps; + + val = (int)m_plotStyle; + + } else if (name == "Bin Scale") { + + *min = 0; + *max = 2; + *deflt = (int)LinearBins; +// *max = 1; // I don't think we really do want to offer inverted log + + val = (int)m_binScale; + + } else { + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +SliceLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + if (m_plotStyle == PlotFilledBlocks) { + return ColourMapper::getColourMapName(value); + } else { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + } + if (name == "Scale") { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Meter"); + case 2: return tr("dB"); + } + } + if (name == "Sampling Mode") { + switch (value) { + default: + case 0: return tr("Any"); + case 1: return tr("Mean"); + case 2: return tr("Peak"); + } + } + if (name == "Plot Type") { + switch (value) { + default: + case 0: return tr("Lines"); + case 1: return tr("Steps"); + case 2: return tr("Blocks"); + case 3: return tr("Colours"); + } + } + if (name == "Bin Scale") { + switch (value) { + default: + case 0: return tr("Linear Bins"); + case 1: return tr("Log Bins"); + case 2: return tr("Rev Log Bins"); + } + } + return tr(""); +} + +RangeMapper * +SliceLayer::getNewPropertyRangeMapper(const PropertyName &name) const +{ + if (name == "Gain") { + return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); + } + return 0; +} + +void +SliceLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Gain") { + setGain(pow(10, float(value)/20.0)); + } else if (name == "Colour") { + if (m_plotStyle == PlotFilledBlocks) { + setFillColourMap(value); + } else { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } + } else if (name == "Scale") { + switch (value) { + default: + case 0: setEnergyScale(LinearScale); break; + case 1: setEnergyScale(MeterScale); break; + case 2: setEnergyScale(dBScale); break; + } + } else if (name == "Plot Type") { + setPlotStyle(PlotStyle(value)); + } else if (name == "Sampling Mode") { + switch (value) { + default: + case 0: setSamplingMode(NearestSample); break; + case 1: setSamplingMode(SampleMean); break; + case 2: setSamplingMode(SamplePeak); break; + } + } else if (name == "Bin Scale") { + switch (value) { + default: + case 0: setBinScale(LinearBins); break; + case 1: setBinScale(LogBins); break; + case 2: setBinScale(InvertedLogBins); break; + } + } else if (name == "Normalize") { + setNormalize(value ? true : false); + } +} + +void +SliceLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +void +SliceLayer::setFillColourMap(int map) +{ + if (m_colourMap == map) return; + m_colourMap = map; + emit layerParametersChanged(); +} + +void +SliceLayer::setEnergyScale(EnergyScale scale) +{ + if (m_energyScale == scale) return; + m_energyScale = scale; + emit layerParametersChanged(); +} + +void +SliceLayer::setSamplingMode(SamplingMode mode) +{ + if (m_samplingMode == mode) return; + m_samplingMode = mode; + emit layerParametersChanged(); +} + +void +SliceLayer::setPlotStyle(PlotStyle style) +{ + if (m_plotStyle == style) return; + bool colourTypeChanged = (style == PlotFilledBlocks || + m_plotStyle == PlotFilledBlocks); + m_plotStyle = style; + if (colourTypeChanged) { + emit layerParameterRangesChanged(); + } + emit layerParametersChanged(); +} + +void +SliceLayer::setBinScale(BinScale scale) +{ + if (m_binScale == scale) return; + m_binScale = scale; + emit layerParametersChanged(); +} + +void +SliceLayer::setNormalize(bool n) +{ + if (m_normalize == n) return; + m_normalize = n; + emit layerParametersChanged(); +} + +void +SliceLayer::setGain(float gain) +{ + if (m_gain == gain) return; + m_gain = gain; + emit layerParametersChanged(); +} + +QString +SliceLayer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("colour=\"%1\" " + "colourScheme=\"%2\" " + "energyScale=\"%3\" " + "samplingMode=\"%4\" " + "gain=\"%5\" " + "normalize=\"%6\"") + .arg(encodeColour(m_colour)) + .arg(m_colourMap) + .arg(m_energyScale) + .arg(m_samplingMode) + .arg(m_gain) + .arg(m_normalize ? "true" : "false"); + + return Layer::toXmlString(indent, extraAttributes + " " + s); +} + +void +SliceLayer::setProperties(const QXmlAttributes &attributes) +{ + bool ok = false; + + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } + + EnergyScale scale = (EnergyScale) + attributes.value("energyScale").toInt(&ok); + if (ok) setEnergyScale(scale); + + SamplingMode mode = (SamplingMode) + attributes.value("samplingMode").toInt(&ok); + if (ok) setSamplingMode(mode); + + int colourMap = attributes.value("colourScheme").toInt(&ok); + if (ok) setFillColourMap(colourMap); + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) setGain(gain); + + bool normalize = (attributes.value("normalize").trimmed() == "true"); + setNormalize(normalize); +} + +bool +SliceLayer::getValueExtents(float &, float &, bool &, QString &) const +{ + return false; +} + diff -r 000000000000 -r fc9323a41f5a layer/SliceLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SliceLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,133 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SLICE_LAYER_H_ +#define _SLICE_LAYER_H_ + +#include "Layer.h" + +#include "base/Window.h" + +#include "data/model/DenseThreeDimensionalModel.h" + +#include + +class SliceLayer : public Layer +{ + Q_OBJECT + +public: + SliceLayer(); + ~SliceLayer(); + +// virtual void setModel(const Model *model); +// virtual const Model *getModel() const { return m_model; } + virtual const Model *getModel() const { return 0; } + + void setSliceableModel(const Model *model); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual int getVerticalScaleWidth(View *v, QPainter &) const; + virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; + virtual void setProperty(const PropertyName &, int value); + virtual void setProperties(const QXmlAttributes &); + + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const; + + virtual bool hasTimeXAxis() const { return false; } + + virtual bool isLayerScrollable(const View *) const { return false; } + + enum EnergyScale { LinearScale, MeterScale, dBScale }; + + enum SamplingMode { NearestSample, SampleMean, SamplePeak }; + + enum PlotStyle { PlotLines, PlotSteps, PlotBlocks, PlotFilledBlocks }; + + enum BinScale { LinearBins, LogBins, InvertedLogBins }; + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + void setFillColourMap(int); + int getFillColourMap() const { return m_colourMap; } + + void setEnergyScale(EnergyScale); + EnergyScale getEnergyScale() const { return m_energyScale; } + + void setSamplingMode(SamplingMode); + SamplingMode getSamplingMode() const { return m_samplingMode; } + + void setPlotStyle(PlotStyle style); + PlotStyle getPlotStyle() const { return m_plotStyle; } + + void setBinScale(BinScale scale); + BinScale getBinScale() const { return m_binScale; } + + void setGain(float gain); + float getGain() const; + + void setNormalize(bool n); + bool getNormalize() const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +public slots: + void sliceableModelReplaced(const Model *, const Model *); + void modelAboutToBeDeleted(Model *); + +protected: + float getXForBin(int bin, int totalBins, float w) const; + int getBinForX(float x, int totalBins, float w) const; + + virtual QString getFeatureDescription(View *v, QPoint &, + bool includeBinDescription, + int &minbin, int &maxbin, + int &range) const; + + const DenseThreeDimensionalModel *m_sliceableModel; + QColor m_colour; + int m_colourMap; + EnergyScale m_energyScale; + SamplingMode m_samplingMode; + PlotStyle m_plotStyle; + BinScale m_binScale; + bool m_normalize; + bool m_bias; + float m_gain; + mutable std::vector m_scalePoints; + mutable std::map m_xorigins; + mutable size_t m_currentf0; + mutable size_t m_currentf1; + mutable std::vector m_values; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/SliceableLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SliceableLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SLICEABLE_LAYER_H_ +#define _SLICEABLE_LAYER_H_ + +#include "Layer.h" + +/** + * Base class for layers that can be sliced, that is, that contain + * models appropriate for use in a SliceLayer. + */ + +class SliceableLayer : public Layer +{ + Q_OBJECT + +public: + // Get a model that can be sliced, i.e. a + // DenseThreeDimensionalModel. This may be the layer's usual + // model, or it may be a model derived from it (e.g. FFTModel in a + // spectrogram that was constructed from a DenseTimeValueModel). + // The SliceableLayer retains ownership of the model, and will + // emit sliceableModelReplaced if it is about to become invalid. + virtual const Model *getSliceableModel() const = 0; + +signals: + // Emitted when a model that was obtained through + // getSliceableModel is about to be deleted. If replacement is + // non-NULL, it may be used instead. + void sliceableModelReplaced(const Model *modelToBeReplaced, + const Model *replacement); +}; + +#endif + + + diff -r 000000000000 -r fc9323a41f5a layer/SpectrogramLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrogramLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,3106 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SpectrogramLayer.h" + +#include "view/View.h" +#include "base/Profiler.h" +#include "base/AudioLevel.h" +#include "base/Window.h" +#include "base/Pitch.h" +#include "base/Preferences.h" +#include "base/RangeMapper.h" +#include "ColourMapper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +//#define DEBUG_SPECTROGRAM_REPAINT 1 + +SpectrogramLayer::SpectrogramLayer(Configuration config) : + m_model(0), + m_channel(0), + m_windowSize(1024), + m_windowType(HanningWindow), + m_windowHopLevel(2), + m_zeroPadLevel(0), + m_fftSize(1024), + m_gain(1.0), + m_initialGain(1.0), + m_threshold(0.0), + m_initialThreshold(0.0), + m_colourRotation(0), + m_initialRotation(0), + m_minFrequency(10), + m_maxFrequency(8000), + m_initialMaxFrequency(8000), + m_colourScale(dBColourScale), + m_colourMap(0), + m_frequencyScale(LinearFrequencyScale), + m_binDisplay(AllBins), + m_normalizeColumns(false), + m_normalizeVisibleArea(false), + m_lastEmittedZoomStep(-1), + m_lastPaintBlockWidth(0), + m_updateTimer(0), + m_candidateFillStartFrame(0), + m_exiting(false), + m_sliceableModel(0) +{ + if (config == FullRangeDb) { + m_initialMaxFrequency = 0; + setMaxFrequency(0); + } else if (config == MelodicRange) { + setWindowSize(8192); + setWindowHopLevel(4); + m_initialMaxFrequency = 1500; + setMaxFrequency(1500); + setMinFrequency(40); + setColourScale(LinearColourScale); + setColourMap(ColourMapper::Sunset); + setFrequencyScale(LogFrequencyScale); +// setGain(20); + } else if (config == MelodicPeaks) { + setWindowSize(4096); + setWindowHopLevel(5); + m_initialMaxFrequency = 2000; + setMaxFrequency(2000); + setMinFrequency(40); + setFrequencyScale(LogFrequencyScale); + setColourScale(LinearColourScale); + setBinDisplay(PeakFrequencies); + setNormalizeColumns(true); + } + + Preferences *prefs = Preferences::getInstance(); + connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); + setWindowType(prefs->getWindowType()); + + initialisePalette(); +} + +SpectrogramLayer::~SpectrogramLayer() +{ + delete m_updateTimer; + m_updateTimer = 0; + + invalidateFFTModels(); +} + +void +SpectrogramLayer::setModel(const DenseTimeValueModel *model) +{ +// std::cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << std::endl; + + if (model == m_model) return; + + m_model = model; + invalidateFFTModels(); + + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(cacheInvalid(size_t, size_t))); + + emit modelReplaced(); +} + +Layer::PropertyList +SpectrogramLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Colour Scale"); + list.push_back("Window Size"); + list.push_back("Window Increment"); + list.push_back("Normalize Columns"); + list.push_back("Normalize Visible Area"); + list.push_back("Bin Display"); + list.push_back("Threshold"); + list.push_back("Gain"); + list.push_back("Colour Rotation"); +// list.push_back("Min Frequency"); +// list.push_back("Max Frequency"); + list.push_back("Frequency Scale"); +//// list.push_back("Zero Padding"); + return list; +} + +QString +SpectrogramLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Colour Scale") return tr("Colour Scale"); + if (name == "Window Size") return tr("Window Size"); + if (name == "Window Increment") return tr("Window Overlap"); + if (name == "Normalize Columns") return tr("Normalize Columns"); + if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + if (name == "Bin Display") return tr("Bin Display"); + if (name == "Threshold") return tr("Threshold"); + if (name == "Gain") return tr("Gain"); + if (name == "Colour Rotation") return tr("Colour Rotation"); + if (name == "Min Frequency") return tr("Min Frequency"); + if (name == "Max Frequency") return tr("Max Frequency"); + if (name == "Frequency Scale") return tr("Frequency Scale"); + if (name == "Zero Padding") return tr("Smoothing"); + return ""; +} + +Layer::PropertyType +SpectrogramLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Gain") return RangeProperty; + if (name == "Colour Rotation") return RangeProperty; + if (name == "Normalize Columns") return ToggleProperty; + if (name == "Normalize Visible Area") return ToggleProperty; + if (name == "Threshold") return RangeProperty; + if (name == "Zero Padding") return ToggleProperty; + return ValueProperty; +} + +QString +SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Bin Display" || + name == "Frequency Scale") return tr("Bins"); + if (name == "Window Size" || + name == "Window Increment" || + name == "Zero Padding") return tr("Window"); + if (name == "Colour" || + name == "Threshold" || + name == "Colour Rotation") return tr("Colour"); + if (name == "Normalize Columns" || + name == "Normalize Visible Area" || + name == "Gain" || + name == "Colour Scale") return tr("Scale"); + return QString(); +} + +int +SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + int garbage0, garbage1, garbage2; + if (!min) min = &garbage0; + if (!max) max = &garbage1; + if (!deflt) deflt = &garbage2; + + if (name == "Gain") { + + *min = -50; + *max = 50; + + *deflt = lrintf(log10(m_initialGain) * 20.0);; + if (*deflt < *min) *deflt = *min; + if (*deflt > *max) *deflt = *max; + + val = lrintf(log10(m_gain) * 20.0); + if (val < *min) val = *min; + if (val > *max) val = *max; + + } else if (name == "Threshold") { + + *min = -50; + *max = 0; + + *deflt = lrintf(AudioLevel::multiplier_to_dB(m_initialThreshold)); + if (*deflt < *min) *deflt = *min; + if (*deflt > *max) *deflt = *max; + + val = lrintf(AudioLevel::multiplier_to_dB(m_threshold)); + if (val < *min) val = *min; + if (val > *max) val = *max; + + } else if (name == "Colour Rotation") { + + *min = 0; + *max = 256; + *deflt = m_initialRotation; + + val = m_colourRotation; + + } else if (name == "Colour Scale") { + + *min = 0; + *max = 4; + *deflt = int(dBColourScale); + + val = (int)m_colourScale; + + } else if (name == "Colour") { + + *min = 0; + *max = ColourMapper::getColourMapCount() - 1; + *deflt = 0; + + val = m_colourMap; + + } else if (name == "Window Size") { + + *min = 0; + *max = 10; + *deflt = 5; + + val = 0; + int ws = m_windowSize; + while (ws > 32) { ws >>= 1; val ++; } + + } else if (name == "Window Increment") { + + *min = 0; + *max = 5; + *deflt = 2; + + val = m_windowHopLevel; + + } else if (name == "Zero Padding") { + + *min = 0; + *max = 1; + *deflt = 0; + + val = m_zeroPadLevel > 0 ? 1 : 0; + + } else if (name == "Min Frequency") { + + *min = 0; + *max = 9; + *deflt = 1; + + switch (m_minFrequency) { + case 0: default: val = 0; break; + case 10: val = 1; break; + case 20: val = 2; break; + case 40: val = 3; break; + case 100: val = 4; break; + case 250: val = 5; break; + case 500: val = 6; break; + case 1000: val = 7; break; + case 4000: val = 8; break; + case 10000: val = 9; break; + } + + } else if (name == "Max Frequency") { + + *min = 0; + *max = 9; + *deflt = 6; + + switch (m_maxFrequency) { + case 500: val = 0; break; + case 1000: val = 1; break; + case 1500: val = 2; break; + case 2000: val = 3; break; + case 4000: val = 4; break; + case 6000: val = 5; break; + case 8000: val = 6; break; + case 12000: val = 7; break; + case 16000: val = 8; break; + default: val = 9; break; + } + + } else if (name == "Frequency Scale") { + + *min = 0; + *max = 1; + *deflt = int(LinearFrequencyScale); + val = (int)m_frequencyScale; + + } else if (name == "Bin Display") { + + *min = 0; + *max = 2; + *deflt = int(AllBins); + val = (int)m_binDisplay; + + } else if (name == "Normalize Columns") { + + *deflt = 0; + val = (m_normalizeColumns ? 1 : 0); + + } else if (name == "Normalize Visible Area") { + + *deflt = 0; + val = (m_normalizeVisibleArea ? 1 : 0); + + } else { + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + return ColourMapper::getColourMapName(value); + } + if (name == "Colour Scale") { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Meter"); + case 2: return tr("dBV^2"); + case 3: return tr("dBV"); + case 4: return tr("Phase"); + } + } + if (name == "Window Size") { + return QString("%1").arg(32 << value); + } + if (name == "Window Increment") { + switch (value) { + default: + case 0: return tr("None"); + case 1: return tr("25 %"); + case 2: return tr("50 %"); + case 3: return tr("75 %"); + case 4: return tr("87.5 %"); + case 5: return tr("93.75 %"); + } + } + if (name == "Zero Padding") { + if (value == 0) return tr("None"); + return QString("%1x").arg(value + 1); + } + if (name == "Min Frequency") { + switch (value) { + default: + case 0: return tr("No min"); + case 1: return tr("10 Hz"); + case 2: return tr("20 Hz"); + case 3: return tr("40 Hz"); + case 4: return tr("100 Hz"); + case 5: return tr("250 Hz"); + case 6: return tr("500 Hz"); + case 7: return tr("1 KHz"); + case 8: return tr("4 KHz"); + case 9: return tr("10 KHz"); + } + } + if (name == "Max Frequency") { + switch (value) { + default: + case 0: return tr("500 Hz"); + case 1: return tr("1 KHz"); + case 2: return tr("1.5 KHz"); + case 3: return tr("2 KHz"); + case 4: return tr("4 KHz"); + case 5: return tr("6 KHz"); + case 6: return tr("8 KHz"); + case 7: return tr("12 KHz"); + case 8: return tr("16 KHz"); + case 9: return tr("No max"); + } + } + if (name == "Frequency Scale") { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Log"); + } + } + if (name == "Bin Display") { + switch (value) { + default: + case 0: return tr("All Bins"); + case 1: return tr("Peak Bins"); + case 2: return tr("Frequencies"); + } + } + return tr(""); +} + +RangeMapper * +SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const +{ + if (name == "Gain") { + return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); + } + if (name == "Threshold") { + return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); + } + return 0; +} + +void +SpectrogramLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Gain") { + setGain(pow(10, float(value)/20.0)); + } else if (name == "Threshold") { + if (value == -50) setThreshold(0.0); + else setThreshold(AudioLevel::dB_to_multiplier(value)); + } else if (name == "Colour Rotation") { + setColourRotation(value); + } else if (name == "Colour") { + setColourMap(value); + } else if (name == "Window Size") { + setWindowSize(32 << value); + } else if (name == "Window Increment") { + setWindowHopLevel(value); + } else if (name == "Zero Padding") { + setZeroPadLevel(value > 0.1 ? 3 : 0); + } else if (name == "Min Frequency") { + switch (value) { + default: + case 0: setMinFrequency(0); break; + case 1: setMinFrequency(10); break; + case 2: setMinFrequency(20); break; + case 3: setMinFrequency(40); break; + case 4: setMinFrequency(100); break; + case 5: setMinFrequency(250); break; + case 6: setMinFrequency(500); break; + case 7: setMinFrequency(1000); break; + case 8: setMinFrequency(4000); break; + case 9: setMinFrequency(10000); break; + } + int vs = getCurrentVerticalZoomStep(); + if (vs != m_lastEmittedZoomStep) { + emit verticalZoomChanged(); + m_lastEmittedZoomStep = vs; + } + } else if (name == "Max Frequency") { + switch (value) { + case 0: setMaxFrequency(500); break; + case 1: setMaxFrequency(1000); break; + case 2: setMaxFrequency(1500); break; + case 3: setMaxFrequency(2000); break; + case 4: setMaxFrequency(4000); break; + case 5: setMaxFrequency(6000); break; + case 6: setMaxFrequency(8000); break; + case 7: setMaxFrequency(12000); break; + case 8: setMaxFrequency(16000); break; + default: + case 9: setMaxFrequency(0); break; + } + int vs = getCurrentVerticalZoomStep(); + if (vs != m_lastEmittedZoomStep) { + emit verticalZoomChanged(); + m_lastEmittedZoomStep = vs; + } + } else if (name == "Colour Scale") { + switch (value) { + default: + case 0: setColourScale(LinearColourScale); break; + case 1: setColourScale(MeterColourScale); break; + case 2: setColourScale(dBSquaredColourScale); break; + case 3: setColourScale(dBColourScale); break; + case 4: setColourScale(PhaseColourScale); break; + } + } else if (name == "Frequency Scale") { + switch (value) { + default: + case 0: setFrequencyScale(LinearFrequencyScale); break; + case 1: setFrequencyScale(LogFrequencyScale); break; + } + } else if (name == "Bin Display") { + switch (value) { + default: + case 0: setBinDisplay(AllBins); break; + case 1: setBinDisplay(PeakBins); break; + case 2: setBinDisplay(PeakFrequencies); break; + } + } else if (name == "Normalize Columns") { + setNormalizeColumns(value ? true : false); + } else if (name == "Normalize Visible Area") { + setNormalizeVisibleArea(value ? true : false); + } +} + +void +SpectrogramLayer::invalidatePixmapCaches() +{ + for (ViewPixmapCache::iterator i = m_pixmapCaches.begin(); + i != m_pixmapCaches.end(); ++i) { + i->second.validArea = QRect(); + } +} + +void +SpectrogramLayer::invalidatePixmapCaches(size_t startFrame, size_t endFrame) +{ + for (ViewPixmapCache::iterator i = m_pixmapCaches.begin(); + i != m_pixmapCaches.end(); ++i) { + + //!!! when are views removed from the map? on setLayerDormant? + const View *v = i->first; + + if (startFrame < v->getEndFrame() && int(endFrame) >= v->getStartFrame()) { + i->second.validArea = QRect(); + } + } +} + +void +SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name) +{ + std::cerr << "SpectrogramLayer::preferenceChanged(" << name.toStdString() << ")" << std::endl; + + if (name == "Window Type") { + setWindowType(Preferences::getInstance()->getWindowType()); + return; + } + if (name == "Spectrogram Smoothing") { + invalidatePixmapCaches(); + invalidateMagnitudes(); + emit layerParametersChanged(); + } + if (name == "Tuning Frequency") { + emit layerParametersChanged(); + } +} + +void +SpectrogramLayer::setChannel(int ch) +{ + if (m_channel == ch) return; + + invalidatePixmapCaches(); + m_channel = ch; + invalidateFFTModels(); + + emit layerParametersChanged(); +} + +int +SpectrogramLayer::getChannel() const +{ + return m_channel; +} + +void +SpectrogramLayer::setWindowSize(size_t ws) +{ + if (m_windowSize == ws) return; + + invalidatePixmapCaches(); + + m_windowSize = ws; + m_fftSize = ws * (m_zeroPadLevel + 1); + + invalidateFFTModels(); + + emit layerParametersChanged(); +} + +size_t +SpectrogramLayer::getWindowSize() const +{ + return m_windowSize; +} + +void +SpectrogramLayer::setWindowHopLevel(size_t v) +{ + if (m_windowHopLevel == v) return; + + invalidatePixmapCaches(); + + m_windowHopLevel = v; + + invalidateFFTModels(); + + emit layerParametersChanged(); + +// fillCache(); +} + +size_t +SpectrogramLayer::getWindowHopLevel() const +{ + return m_windowHopLevel; +} + +void +SpectrogramLayer::setZeroPadLevel(size_t v) +{ + if (m_zeroPadLevel == v) return; + + invalidatePixmapCaches(); + + m_zeroPadLevel = v; + m_fftSize = m_windowSize * (v + 1); + + invalidateFFTModels(); + + emit layerParametersChanged(); +} + +size_t +SpectrogramLayer::getZeroPadLevel() const +{ + return m_zeroPadLevel; +} + +void +SpectrogramLayer::setWindowType(WindowType w) +{ + if (m_windowType == w) return; + + invalidatePixmapCaches(); + + m_windowType = w; + + invalidateFFTModels(); + + emit layerParametersChanged(); +} + +WindowType +SpectrogramLayer::getWindowType() const +{ + return m_windowType; +} + +void +SpectrogramLayer::setGain(float gain) +{ +// std::cerr << "SpectrogramLayer::setGain(" << gain << ") (my gain is now " +// << m_gain << ")" << std::endl; + + if (m_gain == gain) return; + + invalidatePixmapCaches(); + + m_gain = gain; + + emit layerParametersChanged(); +} + +float +SpectrogramLayer::getGain() const +{ + return m_gain; +} + +void +SpectrogramLayer::setThreshold(float threshold) +{ + if (m_threshold == threshold) return; + + invalidatePixmapCaches(); + + m_threshold = threshold; + + emit layerParametersChanged(); +} + +float +SpectrogramLayer::getThreshold() const +{ + return m_threshold; +} + +void +SpectrogramLayer::setMinFrequency(size_t mf) +{ + if (m_minFrequency == mf) return; + +// std::cerr << "SpectrogramLayer::setMinFrequency: " << mf << std::endl; + + invalidatePixmapCaches(); + invalidateMagnitudes(); + + m_minFrequency = mf; + + emit layerParametersChanged(); +} + +size_t +SpectrogramLayer::getMinFrequency() const +{ + return m_minFrequency; +} + +void +SpectrogramLayer::setMaxFrequency(size_t mf) +{ + if (m_maxFrequency == mf) return; + +// std::cerr << "SpectrogramLayer::setMaxFrequency: " << mf << std::endl; + + invalidatePixmapCaches(); + invalidateMagnitudes(); + + m_maxFrequency = mf; + + emit layerParametersChanged(); +} + +size_t +SpectrogramLayer::getMaxFrequency() const +{ + return m_maxFrequency; +} + +void +SpectrogramLayer::setColourRotation(int r) +{ + invalidatePixmapCaches(); + + if (r < 0) r = 0; + if (r > 256) r = 256; + int distance = r - m_colourRotation; + + if (distance != 0) { + rotatePalette(-distance); + m_colourRotation = r; + } + + emit layerParametersChanged(); +} + +void +SpectrogramLayer::setColourScale(ColourScale colourScale) +{ + if (m_colourScale == colourScale) return; + + invalidatePixmapCaches(); + + m_colourScale = colourScale; + + emit layerParametersChanged(); +} + +SpectrogramLayer::ColourScale +SpectrogramLayer::getColourScale() const +{ + return m_colourScale; +} + +void +SpectrogramLayer::setColourMap(int map) +{ + if (m_colourMap == map) return; + + invalidatePixmapCaches(); + + m_colourMap = map; + initialisePalette(); + + emit layerParametersChanged(); +} + +int +SpectrogramLayer::getColourMap() const +{ + return m_colourMap; +} + +void +SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) +{ + if (m_frequencyScale == frequencyScale) return; + + invalidatePixmapCaches(); + m_frequencyScale = frequencyScale; + + emit layerParametersChanged(); +} + +SpectrogramLayer::FrequencyScale +SpectrogramLayer::getFrequencyScale() const +{ + return m_frequencyScale; +} + +void +SpectrogramLayer::setBinDisplay(BinDisplay binDisplay) +{ + if (m_binDisplay == binDisplay) return; + + invalidatePixmapCaches(); + m_binDisplay = binDisplay; + + emit layerParametersChanged(); +} + +SpectrogramLayer::BinDisplay +SpectrogramLayer::getBinDisplay() const +{ + return m_binDisplay; +} + +void +SpectrogramLayer::setNormalizeColumns(bool n) +{ + if (m_normalizeColumns == n) return; + + invalidatePixmapCaches(); + invalidateMagnitudes(); + m_normalizeColumns = n; + + emit layerParametersChanged(); +} + +bool +SpectrogramLayer::getNormalizeColumns() const +{ + return m_normalizeColumns; +} + +void +SpectrogramLayer::setNormalizeVisibleArea(bool n) +{ + if (m_normalizeVisibleArea == n) return; + + invalidatePixmapCaches(); + invalidateMagnitudes(); + m_normalizeVisibleArea = n; + + emit layerParametersChanged(); +} + +bool +SpectrogramLayer::getNormalizeVisibleArea() const +{ + return m_normalizeVisibleArea; +} + +void +SpectrogramLayer::setLayerDormant(const View *v, bool dormant) +{ + if (dormant) { + + if (isLayerDormant(v)) { + return; + } + + Layer::setLayerDormant(v, true); + + invalidatePixmapCaches(); + m_pixmapCaches.erase(v); + + if (m_fftModels.find(v) != m_fftModels.end()) { + + if (m_sliceableModel == m_fftModels[v].first) { + bool replaced = false; + for (ViewFFTMap::iterator i = m_fftModels.begin(); + i != m_fftModels.end(); ++i) { + if (i->second.first != m_sliceableModel) { + emit sliceableModelReplaced(m_sliceableModel, i->second.first); + replaced = true; + break; + } + } + if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); + } + + delete m_fftModels[v].first; + m_fftModels.erase(v); + } + + } else { + + Layer::setLayerDormant(v, false); + } +} + +void +SpectrogramLayer::cacheInvalid() +{ + invalidatePixmapCaches(); + invalidateMagnitudes(); +} + +void +SpectrogramLayer::cacheInvalid(size_t, size_t) +{ + // for now (or forever?) + cacheInvalid(); +} + +void +SpectrogramLayer::fillTimerTimedOut() +{ + if (!m_model) return; + + bool allDone = true; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::fillTimerTimedOut: have " << m_fftModels.size() << " FFT models associated with views" << std::endl; +#endif + + for (ViewFFTMap::iterator i = m_fftModels.begin(); + i != m_fftModels.end(); ++i) { + + const FFTModel *model = i->second.first; + size_t lastFill = i->second.second; + + if (model) { + + size_t fill = model->getFillExtent(); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent for " << model << ": " << fill << ", last " << lastFill << ", total " << m_model->getEndFrame() << std::endl; +#endif + + if (fill >= lastFill) { + if (fill >= m_model->getEndFrame() && lastFill > 0) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "complete!" << std::endl; +#endif + invalidatePixmapCaches(); + i->second.second = -1; + emit modelChanged(); + + } else if (fill > lastFill) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: emitting modelChanged(" + << lastFill << "," << fill << ")" << std::endl; +#endif + invalidatePixmapCaches(lastFill, fill); + i->second.second = fill; + emit modelChanged(lastFill, fill); + } + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" + << m_model->getStartFrame() << "," << m_model->getEndFrame() << ")" << std::endl; +#endif + invalidatePixmapCaches(); + i->second.second = fill; + emit modelChanged(m_model->getStartFrame(), m_model->getEndFrame()); + } + + if (i->second.second >= 0) { + allDone = false; + } + } + } + + if (allDone) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: all complete!" << std::endl; +#endif + delete m_updateTimer; + m_updateTimer = 0; + } +} + +bool +SpectrogramLayer::hasLightBackground() const +{ + return (m_colourMap == (int)ColourMapper::BlackOnWhite); +} + +void +SpectrogramLayer::initialisePalette() +{ + int formerRotation = m_colourRotation; + + if (m_colourMap == (int)ColourMapper::BlackOnWhite) { + m_palette.setColour(NO_VALUE, Qt::white); + } else { + m_palette.setColour(NO_VALUE, Qt::black); + } + + ColourMapper mapper(m_colourMap, 1.f, 255.f); + + for (int pixel = 1; pixel < 256; ++pixel) { + m_palette.setColour(pixel, mapper.map(pixel)); + } + + m_crosshairColour = mapper.getContrastingColour(); + + m_colourRotation = 0; + rotatePalette(m_colourRotation - formerRotation); + m_colourRotation = formerRotation; +} + +void +SpectrogramLayer::rotatePalette(int distance) +{ + QColor newPixels[256]; + + newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); + + for (int pixel = 1; pixel < 256; ++pixel) { + int target = pixel + distance; + while (target < 1) target += 255; + while (target > 255) target -= 255; + newPixels[target] = m_palette.getColour(pixel); + } + + for (int pixel = 0; pixel < 256; ++pixel) { + m_palette.setColour(pixel, newPixels[pixel]); + } +} + +float +SpectrogramLayer::calculateFrequency(size_t bin, + size_t windowSize, + size_t windowIncrement, + size_t sampleRate, + float oldPhase, + float newPhase, + bool &steadyState) +{ + // At frequency f, phase shift of 2pi (one cycle) happens in 1/f sec. + // At hopsize h and sample rate sr, one hop happens in h/sr sec. + // At window size w, for bin b, f is b*sr/w. + // thus 2pi phase shift happens in w/(b*sr) sec. + // We need to know what phase shift we expect from h/sr sec. + // -> 2pi * ((h/sr) / (w/(b*sr))) + // = 2pi * ((h * b * sr) / (w * sr)) + // = 2pi * (h * b) / w. + + float frequency = (float(bin) * sampleRate) / windowSize; + + float expectedPhase = + oldPhase + (2.0 * M_PI * bin * windowIncrement) / windowSize; + + float phaseError = princargf(newPhase - expectedPhase); + + if (fabsf(phaseError) < (1.1f * (windowIncrement * M_PI) / windowSize)) { + + // The new frequency estimate based on the phase error + // resulting from assuming the "native" frequency of this bin + + float newFrequency = + (sampleRate * (expectedPhase + phaseError - oldPhase)) / + (2 * M_PI * windowIncrement); + + steadyState = true; + return newFrequency; + } + + steadyState = false; + return frequency; +} + +unsigned char +SpectrogramLayer::getDisplayValue(View *v, float input) const +{ + int value; + + float min = 0.f; + float max = 1.f; + + if (m_normalizeVisibleArea) { + min = m_viewMags[v].getMin(); + max = m_viewMags[v].getMax(); + } else if (!m_normalizeColumns) { + if (m_colourScale == LinearColourScale //|| +// m_colourScale == MeterColourScale) { + ) { + max = 0.1f; + } + } + + float thresh = -80.f; + + if (max == 0.f) max = 1.f; + if (max == min) min = max - 0.0001f; + + switch (m_colourScale) { + + default: + case LinearColourScale: + value = int(((input - min) / (max - min)) * 255.f) + 1; + break; + + case MeterColourScale: + value = AudioLevel::multiplier_to_preview + ((input - min) / (max - min), 254) + 1; + break; + + case dBSquaredColourScale: + input = ((input - min) * (input - min)) / ((max - min) * (max - min)); + if (input > 0.f) { + input = 10.f * log10f(input); + } else { + input = thresh; + } + if (min > 0.f) { + thresh = 10.f * log10f(min * min); + if (thresh < -80.f) thresh = -80.f; + } + input = (input - thresh) / (-thresh); + if (input < 0.f) input = 0.f; + if (input > 1.f) input = 1.f; + value = int(input * 255.f) + 1; + break; + + case dBColourScale: + //!!! experiment with normalizing the visible area this way. + //In any case, we need to have some indication of what the dB + //scale is relative to. + input = (input - min) / (max - min); + if (input > 0.f) { + input = 10.f * log10f(input); + } else { + input = thresh; + } + if (min > 0.f) { + thresh = 10.f * log10f(min); + if (thresh < -80.f) thresh = -80.f; + } + input = (input - thresh) / (-thresh); + if (input < 0.f) input = 0.f; + if (input > 1.f) input = 1.f; + value = int(input * 255.f) + 1; + break; + + case PhaseColourScale: + value = int((input * 127.0 / M_PI) + 128); + break; + } + + if (value > UCHAR_MAX) value = UCHAR_MAX; + if (value < 0) value = 0; + return value; +} + +float +SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const +{ + //!!! unused + + int value = uc; + float input; + + //!!! incorrect for normalizing visible area (and also out of date) + + switch (m_colourScale) { + + default: + case LinearColourScale: + input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50); + break; + + case MeterColourScale: + input = AudioLevel::preview_to_multiplier(value - 1, 255) + / (m_normalizeColumns ? 1.0 : 50.0); + break; + + case dBSquaredColourScale: + input = float(value - 1) / 255.0; + input = (input * 80.0) - 80.0; + input = powf(10.0, input) / 20.0; + value = int(input); + break; + + case dBColourScale: + input = float(value - 1) / 255.0; + input = (input * 80.0) - 80.0; + input = powf(10.0, input) / 20.0; + value = int(input); + break; + + case PhaseColourScale: + input = float(value - 128) * M_PI / 127.0; + break; + } + + return input; +} + +float +SpectrogramLayer::getEffectiveMinFrequency() const +{ + int sr = m_model->getSampleRate(); + float minf = float(sr) / m_fftSize; + + if (m_minFrequency > 0.0) { + size_t minbin = size_t((double(m_minFrequency) * m_fftSize) / sr + 0.01); + if (minbin < 1) minbin = 1; + minf = minbin * sr / m_fftSize; + } + + return minf; +} + +float +SpectrogramLayer::getEffectiveMaxFrequency() const +{ + int sr = m_model->getSampleRate(); + float maxf = float(sr) / 2; + + if (m_maxFrequency > 0.0) { + size_t maxbin = size_t((double(m_maxFrequency) * m_fftSize) / sr + 0.1); + if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; + maxf = maxbin * sr / m_fftSize; + } + + return maxf; +} + +bool +SpectrogramLayer::getYBinRange(View *v, int y, float &q0, float &q1) const +{ + int h = v->height(); + if (y < 0 || y >= h) return false; + + int sr = m_model->getSampleRate(); + float minf = getEffectiveMinFrequency(); + float maxf = getEffectiveMaxFrequency(); + + bool logarithmic = (m_frequencyScale == LogFrequencyScale); + + //!!! wrong for smoothing -- wrong fft size for fft model + + q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); + q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); + + // Now map these on to actual bins + + int b0 = int((q0 * m_fftSize) / sr); + int b1 = int((q1 * m_fftSize) / sr); + + //!!! this is supposed to return fractions-of-bins, as it were, hence the floats + q0 = b0; + q1 = b1; + +// q0 = (b0 * sr) / m_fftSize; +// q1 = (b1 * sr) / m_fftSize; + + return true; +} + +bool +SpectrogramLayer::getXBinRange(View *v, int x, float &s0, float &s1) const +{ + size_t modelStart = m_model->getStartFrame(); + size_t modelEnd = m_model->getEndFrame(); + + // Each pixel column covers an exact range of sample frames: + int f0 = v->getFrameForX(x) - modelStart; + int f1 = v->getFrameForX(x + 1) - modelStart - 1; + + if (f1 < int(modelStart) || f0 > int(modelEnd)) { + return false; + } + + // And that range may be drawn from a possibly non-integral + // range of spectrogram windows: + + size_t windowIncrement = getWindowIncrement(); + s0 = float(f0) / windowIncrement; + s1 = float(f1) / windowIncrement; + + return true; +} + +bool +SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const +{ + float s0 = 0, s1 = 0; + if (!getXBinRange(v, x, s0, s1)) return false; + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + int windowIncrement = getWindowIncrement(); + int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2; + int w1 = s1i * windowIncrement + windowIncrement + + (m_windowSize - windowIncrement)/2 - 1; + + min = RealTime::frame2RealTime(w0, m_model->getSampleRate()); + max = RealTime::frame2RealTime(w1, m_model->getSampleRate()); + return true; +} + +bool +SpectrogramLayer::getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) +const +{ + float q0 = 0, q1 = 0; + if (!getYBinRange(v, y, q0, q1)) return false; + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + int sr = m_model->getSampleRate(); + + for (int q = q0i; q <= q1i; ++q) { + if (q == q0i) freqMin = (sr * q) / m_fftSize; + if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; + } + return true; +} + +bool +SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y, + float &freqMin, float &freqMax, + float &adjFreqMin, float &adjFreqMax) +const +{ + FFTModel *fft = getFFTModel(v); + if (!fft) return false; + + float s0 = 0, s1 = 0; + if (!getXBinRange(v, x, s0, s1)) return false; + + float q0 = 0, q1 = 0; + if (!getYBinRange(v, y, q0, q1)) return false; + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + int sr = m_model->getSampleRate(); + + size_t windowSize = m_windowSize; + size_t windowIncrement = getWindowIncrement(); + + bool haveAdj = false; + + bool peaksOnly = (m_binDisplay == PeakBins || + m_binDisplay == PeakFrequencies); + + for (int q = q0i; q <= q1i; ++q) { + + for (int s = s0i; s <= s1i; ++s) { + + if (!fft->isColumnAvailable(s)) continue; + + float binfreq = (sr * q) / m_windowSize; + if (q == q0i) freqMin = binfreq; + if (q == q1i) freqMax = binfreq; + + if (peaksOnly && !fft->isLocalPeak(s, q)) continue; + + if (!fft->isOverThreshold(s, q, m_threshold)) continue; + + float freq = binfreq; + bool steady = false; + + if (s < int(fft->getWidth()) - 1) { + + freq = calculateFrequency(q, + windowSize, + windowIncrement, + sr, + fft->getPhaseAt(s, q), + fft->getPhaseAt(s+1, q), + steady); + + if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq; + if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq; + + haveAdj = true; + } + } + } + + if (!haveAdj) { + adjFreqMin = adjFreqMax = 0.0; + } + + return haveAdj; +} + +bool +SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y, + float &min, float &max, + float &phaseMin, float &phaseMax) const +{ + float q0 = 0, q1 = 0; + if (!getYBinRange(v, y, q0, q1)) return false; + + float s0 = 0, s1 = 0; + if (!getXBinRange(v, x, s0, s1)) return false; + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + bool rv = false; + + size_t zp = getZeroPadLevel(v); + q0i *= zp + 1; + q1i *= zp + 1; + + FFTModel *fft = getFFTModel(v); + + if (fft) { + + int cw = fft->getWidth(); + int ch = fft->getHeight(); + + min = 0.0; + max = 0.0; + phaseMin = 0.0; + phaseMax = 0.0; + bool have = false; + + for (int q = q0i; q <= q1i; ++q) { + for (int s = s0i; s <= s1i; ++s) { + if (s >= 0 && q >= 0 && s < cw && q < ch) { + + if (!fft->isColumnAvailable(s)) continue; + + float value; + + value = fft->getPhaseAt(s, q); + if (!have || value < phaseMin) { phaseMin = value; } + if (!have || value > phaseMax) { phaseMax = value; } + + value = fft->getMagnitudeAt(s, q); + if (!have || value < min) { min = value; } + if (!have || value > max) { max = value; } + + have = true; + } + } + } + + if (have) { + rv = true; + } + } + + return rv; +} + +size_t +SpectrogramLayer::getZeroPadLevel(const View *v) const +{ + //!!! tidy all this stuff + + if (m_binDisplay != AllBins) return 0; + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + + if (smoothing == Preferences::NoSpectrogramSmoothing || + smoothing == Preferences::SpectrogramInterpolated) return 0; + + if (m_frequencyScale == LogFrequencyScale) return 3; + + int sr = m_model->getSampleRate(); + + size_t maxbin = m_fftSize / 2; + if (m_maxFrequency > 0) { + maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); + if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; + } + + size_t minbin = 1; + if (m_minFrequency > 0) { + minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); + if (minbin < 1) minbin = 1; + if (minbin >= maxbin) minbin = maxbin - 1; + } + + float perPixel = + float(v->height()) / + float((maxbin - minbin) / (m_zeroPadLevel + 1)); + + if (perPixel > 2.8) { + return 3; // 4x oversampling + } else if (perPixel > 1.5) { + return 1; // 2x + } else { + return 0; // 1x + } +} + +size_t +SpectrogramLayer::getFFTSize(const View *v) const +{ + return m_fftSize * (getZeroPadLevel(v) + 1); +} + +FFTModel * +SpectrogramLayer::getFFTModel(const View *v) const +{ + if (!m_model) return 0; + + size_t fftSize = getFFTSize(v); + + if (m_fftModels.find(v) != m_fftModels.end()) { + if (m_fftModels[v].first == 0) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << std::endl; +#endif + return 0; + } + if (m_fftModels[v].first->getHeight() != fftSize / 2 + 1) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[v].first->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << std::endl; +#endif + delete m_fftModels[v].first; + m_fftModels.erase(v); + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << std::endl; +#endif + return m_fftModels[v].first; + } + } + + if (m_fftModels.find(v) == m_fftModels.end()) { + + FFTModel *model = new FFTModel(m_model, + m_channel, + m_windowType, + m_windowSize, + getWindowIncrement(), + fftSize, + true, + m_candidateFillStartFrame); + + if (!model->isOK()) { + QMessageBox::critical + (0, tr("FFT cache failed"), + tr("Failed to create the FFT model for this spectrogram.\n" + "There may be insufficient memory or disc space to continue.")); + delete model; + m_fftModels[v] = FFTFillPair(0, 0); + return 0; + } + + if (!m_sliceableModel) { +#ifdef DEBUG_SPECTROGRAM + std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << std::endl; +#endif + ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); + m_sliceableModel = model; + } + + m_fftModels[v] = FFTFillPair(model, 0); + + model->resume(); + + delete m_updateTimer; + m_updateTimer = new QTimer((SpectrogramLayer *)this); + connect(m_updateTimer, SIGNAL(timeout()), + this, SLOT(fillTimerTimedOut())); + m_updateTimer->start(200); + } + + return m_fftModels[v].first; +} + +const Model * +SpectrogramLayer::getSliceableModel() const +{ + if (m_sliceableModel) return m_sliceableModel; + if (m_fftModels.empty()) return 0; + m_sliceableModel = m_fftModels.begin()->second.first; + return m_sliceableModel; +} + +void +SpectrogramLayer::invalidateFFTModels() +{ + for (ViewFFTMap::iterator i = m_fftModels.begin(); + i != m_fftModels.end(); ++i) { + delete i->second.first; + } + + m_fftModels.clear(); + + if (m_sliceableModel) { + std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << std::endl; + emit sliceableModelReplaced(m_sliceableModel, 0); + m_sliceableModel = 0; + } +} + +void +SpectrogramLayer::invalidateMagnitudes() +{ + m_viewMags.clear(); + for (std::vector::iterator i = m_columnMags.begin(); + i != m_columnMags.end(); ++i) { + *i = MagnitudeRange(); + } +} + +bool +SpectrogramLayer::updateViewMagnitudes(View *v) const +{ + MagnitudeRange mag; + + int x0 = 0, x1 = v->width(); + float s00 = 0, s01 = 0, s10 = 0, s11 = 0; + + if (!getXBinRange(v, x0, s00, s01)) { + s00 = s01 = m_model->getStartFrame() / getWindowIncrement(); + } + + if (!getXBinRange(v, x1, s10, s11)) { + s10 = s11 = m_model->getEndFrame() / getWindowIncrement(); + } + + int s0 = int(min(s00, s10) + 0.0001); + int s1 = int(max(s01, s11) + 0.0001); + +// std::cerr << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << std::endl; + + if (int(m_columnMags.size()) <= s1) { + m_columnMags.resize(s1 + 1); + } + + for (int s = s0; s <= s1; ++s) { + if (m_columnMags[s].isSet()) { + mag.sample(m_columnMags[s]); + } + } + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " + << s0 << " -> " << s1 << " inclusive" << std::endl; +#endif + + if (!mag.isSet()) return false; + if (mag == m_viewMags[v]) return false; + m_viewMags[v] = mag; + return true; +} + +void +SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + Profiler profiler("SpectrogramLayer::paint", true); +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << std::endl; + + std::cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << std::endl; +#endif + + long startFrame = v->getStartFrame(); + if (startFrame < 0) m_candidateFillStartFrame = 0; + else m_candidateFillStartFrame = startFrame; + + if (!m_model || !m_model->isOK() || !m_model->isReady()) { + return; + } + + if (isLayerDormant(v)) { + std::cerr << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << std::endl; + } + + // Need to do this even if !isLayerDormant, as that could mean v + // is not in the dormancy map at all -- we need it to be present + // and accountable for when determining whether we need the cache + // in the cache-fill thread above. + //!!! no longer use cache-fill thread + const_cast(this)->Layer::setLayerDormant(v, false); + + size_t fftSize = getFFTSize(v); + FFTModel *fft = getFFTModel(v); + if (!fft) { + std::cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << std::endl; + return; + } + + PixmapCache &cache = m_pixmapCaches[v]; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): pixmap cache valid area " << cache.validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << std::endl; +#endif + +#ifdef DEBUG_SPECTROGRAM_REPAINT + bool stillCacheing = (m_updateTimer != 0); + std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl; +#endif + + int zoomLevel = v->getZoomLevel(); + + int x0 = 0; + int x1 = v->width(); + + bool recreateWholePixmapCache = true; + + x0 = rect.left(); + x1 = rect.right() + 1; + + if (cache.validArea.width() > 0) { + + if (int(cache.zoomLevel) == zoomLevel && + cache.pixmap.width() == v->width() && + cache.pixmap.height() == v->height()) { + + if (v->getXForFrame(cache.startFrame) == + v->getXForFrame(startFrame) && + cache.validArea.x() <= x0 && + cache.validArea.x() + cache.validArea.width() >= x1) { + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl; +#endif + + paint.drawPixmap(rect, cache.pixmap, rect); + illuminateLocalFeatures(v, paint); + return; + + } else { + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl; +#endif + + recreateWholePixmapCache = false; + + int dx = v->getXForFrame(cache.startFrame) - + v->getXForFrame(startFrame); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << cache.pixmap.width() << "x" << cache.pixmap.height() << ")" << std::endl; +#endif + + if (dx != 0 && + dx > -cache.pixmap.width() && + dx < cache.pixmap.width()) { + +#if defined(Q_WS_WIN32) || defined(Q_WS_MAC) + // Copying a pixmap to itself doesn't work + // properly on Windows or Mac (it only works when + // moving in one direction). + + //!!! Need a utility function for this + + static QPixmap *tmpPixmap = 0; + if (!tmpPixmap || + tmpPixmap->width() != cache.pixmap.width() || + tmpPixmap->height() != cache.pixmap.height()) { + delete tmpPixmap; + tmpPixmap = new QPixmap(cache.pixmap.width(), + cache.pixmap.height()); + } + QPainter cachePainter; + cachePainter.begin(tmpPixmap); + cachePainter.drawPixmap(0, 0, cache.pixmap); + cachePainter.end(); + cachePainter.begin(&cache.pixmap); + cachePainter.drawPixmap(dx, 0, *tmpPixmap); + cachePainter.end(); +#else + QPainter cachePainter(&cache.pixmap); + cachePainter.drawPixmap(dx, 0, cache.pixmap); + cachePainter.end(); +#endif + + int px = cache.validArea.x(); + int pw = cache.validArea.width(); + + if (dx < 0) { + x0 = cache.pixmap.width() + dx; + x1 = cache.pixmap.width(); + px += dx; + if (px < 0) { + pw += px; + px = 0; + if (pw < 0) pw = 0; + } + } else { + x0 = 0; + x1 = dx; + px += dx; + if (px + pw > cache.pixmap.width()) { + pw = int(cache.pixmap.width()) - px; + if (pw < 0) pw = 0; + } + } + + cache.validArea = + QRect(px, cache.validArea.y(), + pw, cache.validArea.height()); + + paint.drawPixmap(rect & cache.validArea, + cache.pixmap, + rect & cache.validArea); + } + } + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl; + if (int(cache.zoomLevel) != zoomLevel) { + std::cerr << "(cache zoomLevel " << cache.zoomLevel + << " != " << zoomLevel << ")" << std::endl; + } + if (cache.pixmap.width() != v->width()) { + std::cerr << "(cache width " << cache.pixmap.width() + << " != " << v->width(); + } + if (cache.pixmap.height() != v->height()) { + std::cerr << "(cache height " << cache.pixmap.height() + << " != " << v->height(); + } +#endif + cache.validArea = QRect(); + } + } + + if (updateViewMagnitudes(v)) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; +#endif + recreateWholePixmapCache = true; + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; +#endif + } + + if (recreateWholePixmapCache) { + x0 = 0; + x1 = v->width(); + } + + struct timeval tv; + (void)gettimeofday(&tv, 0); + RealTime mainPaintStart = RealTime::fromTimeval(tv); + + int paintBlockWidth = m_lastPaintBlockWidth; + + if (paintBlockWidth == 0) { + paintBlockWidth = (300000 / zoomLevel); + } else { + RealTime lastTime = m_lastPaintTime; + while (lastTime > RealTime::fromMilliseconds(200) && + paintBlockWidth > 50) { + paintBlockWidth /= 2; + lastTime = lastTime / 2; + } + while (lastTime < RealTime::fromMilliseconds(90) && + paintBlockWidth < 1500) { + paintBlockWidth *= 2; + lastTime = lastTime * 2; + } + } + + if (paintBlockWidth < 20) paintBlockWidth = 20; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << std::endl; +#endif + + // We always paint the full height when refreshing the cache. + // Smaller heights can be used when painting direct from cache + // (further up in this function), but we want to ensure the cache + // is coherent without having to worry about vertical matching of + // required and valid areas as well as horizontal. + + int h = v->height(); + + if (cache.validArea.width() > 0) { + + int vx0 = 0, vx1 = 0; + vx0 = cache.validArea.x(); + vx1 = cache.validArea.x() + cache.validArea.width(); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << std::endl; +#endif + if (x0 < vx0) { + if (x0 + paintBlockWidth < vx0) { + x0 = vx0 - paintBlockWidth; + } else { + x0 = 0; + } + } else if (x0 > vx1) { + x0 = vx1; + } + + if (x1 < vx0) { + x1 = vx0; + } else if (x1 > vx1) { + if (vx1 + paintBlockWidth < x1) { + x1 = vx1 + paintBlockWidth; + } else { + x1 = v->width(); + } + } + + cache.validArea = QRect + (min(vx0, x0), cache.validArea.y(), + max(vx1 - min(vx0, x0), + x1 - min(vx0, x0)), + cache.validArea.height()); + + } else { + if (x1 > x0 + paintBlockWidth) { + int sfx = x1; + if (startFrame < 0) sfx = v->getXForFrame(0); + if (sfx >= x0 && sfx + paintBlockWidth <= x1) { + x0 = sfx; + x1 = x0 + paintBlockWidth; + } else { + int mid = (x1 + x0) / 2; + x0 = mid - paintBlockWidth/2; + x1 = x0 + paintBlockWidth; + } + } + cache.validArea = QRect(x0, 0, x1 - x0, h); + } + + int w = x1 - x0; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl; +#endif + + if (m_drawBuffer.width() < w || m_drawBuffer.height() < h) { + m_drawBuffer = QImage(w, h, QImage::Format_RGB32); + } + + m_drawBuffer.fill(m_palette.getColour(0).rgb()); + + int sr = m_model->getSampleRate(); + + // Set minFreq and maxFreq to the frequency extents of the possibly + // zero-padded visible bin range, and displayMinFreq and displayMaxFreq + // to the actual scale frequency extents (presumably not zero padded). + + size_t maxbin = fftSize / 2; + if (m_maxFrequency > 0) { + maxbin = int((double(m_maxFrequency) * fftSize) / sr + 0.1); + if (maxbin > fftSize / 2) maxbin = fftSize / 2; + } + + size_t minbin = 1; + if (m_minFrequency > 0) { + minbin = int((double(m_minFrequency) * fftSize) / sr + 0.1); + if (minbin < 1) minbin = 1; + if (minbin >= maxbin) minbin = maxbin - 1; + } + + float minFreq = (float(minbin) * sr) / fftSize; + float maxFreq = (float(maxbin) * sr) / fftSize; + + float displayMinFreq = minFreq; + float displayMaxFreq = maxFreq; + + if (fftSize != m_fftSize) { + displayMinFreq = getEffectiveMinFrequency(); + displayMaxFreq = getEffectiveMaxFrequency(); + } + + /*float ymag[h]; + float ydiv[h]; + float yval[maxbin + 1]; //!!! cache this?*/ + float *ymag = (float*) malloc(h*sizeof(float)); + float *ydiv = (float*) malloc(h*sizeof(float)); + float *yval = (float*) malloc((maxbin + 1)*sizeof(float)); + + size_t increment = getWindowIncrement(); + + bool logarithmic = (m_frequencyScale == LogFrequencyScale); + + for (size_t q = minbin; q <= maxbin; ++q) { + float f0 = (float(q) * sr) / fftSize; + yval[q] = v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, + logarithmic); +// std::cerr << "min: " << minFreq << ", max: " << maxFreq << ", yval[" << q << "]: " << yval[q] << std::endl; + } + + MagnitudeRange overallMag = m_viewMags[v]; + bool overallMagChanged = false; + + bool fftSuspended = false; + + bool interpolate = false; + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + if (smoothing == Preferences::SpectrogramInterpolated || + smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { + if (m_binDisplay != PeakBins && + m_binDisplay != PeakFrequencies) { + interpolate = true; + } + } + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << ((float(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << std::endl; +#endif + + bool runOutOfData = false; + + for (int x = 0; x < w; ++x) { + + if (runOutOfData) break; + + for (int y = 0; y < h; ++y) { + ymag[y] = 0.f; + ydiv[y] = 0.f; + } + + float s0 = 0, s1 = 0; + + if (!getXBinRange(v, x0 + x, s0, s1)) { + assert(x <= m_drawBuffer.width()); + continue; + } + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + if (s1i >= int(fft->getWidth())) { + if (s0i >= int(fft->getWidth())) { + continue; + } else { + s1i = s0i; + } + } + + for (int s = s0i; s <= s1i; ++s) { + + if (!fft->isColumnAvailable(s)) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Met unavailable column at col " << s << std::endl; +#endif +// continue; + runOutOfData = true; + break; + } + + if (!fftSuspended) { + fft->suspendWrites(); + fftSuspended = true; + } + + MagnitudeRange mag; + + for (size_t q = minbin; q < maxbin; ++q) { + + float y0 = yval[q + 1]; + float y1 = yval[q]; + + if (m_binDisplay == PeakBins || + m_binDisplay == PeakFrequencies) { + if (!fft->isLocalPeak(s, q)) continue; + } + + if (m_threshold != 0.f && + !fft->isOverThreshold(s, q, m_threshold)) { + continue; + } + + float sprop = 1.0; + if (s == s0i) sprop *= (s + 1) - s0; + if (s == s1i) sprop *= s1 - s; + + if (m_binDisplay == PeakFrequencies && + s < int(fft->getWidth()) - 1) { + + bool steady = false; + float f = calculateFrequency(q, + m_windowSize, + increment, + sr, + fft->getPhaseAt(s, q), + fft->getPhaseAt(s+1, q), + steady); + + y0 = y1 = v->getYForFrequency + (f, displayMinFreq, displayMaxFreq, logarithmic); + } + + int y0i = int(y0 + 0.001); + int y1i = int(y1); + + float value; + + if (m_colourScale == PhaseColourScale) { + value = fft->getPhaseAt(s, q); + } else if (m_normalizeColumns) { + value = fft->getNormalizedMagnitudeAt(s, q); + mag.sample(value); + value *= m_gain; + } else { + value = fft->getMagnitudeAt(s, q); + mag.sample(value); + value *= m_gain; + } + + if (interpolate) { + + int ypi = y0i; + if (q < maxbin - 1) ypi = int(yval[q + 2]); + + for (int y = ypi; y <= y1i; ++y) { + + if (y < 0 || y >= h) continue; + + float yprop = sprop; + float iprop = yprop; + + if (ypi < y0i && y <= y0i) { + + float half = float(y0i - ypi) / 2; + float dist = y - (ypi + half); + + if (dist >= 0) { + iprop = (iprop * dist) / half; + ymag[y] += iprop * value; + } + } else { + if (y1i > y0i) { + + float half = float(y1i - y0i) / 2; + float dist = y - (y0i + half); + + if (dist >= 0) { + iprop = (iprop * (half - dist)) / half; + } + } + + ymag[y] += iprop * value; + ydiv[y] += yprop; + } + } + + } else { + + for (int y = y0i; y <= y1i; ++y) { + + if (y < 0 || y >= h) continue; + + float yprop = sprop; + if (y == y0i) yprop *= (y + 1) - y0; + if (y == y1i) yprop *= y1 - y; + + for (int y = y0i; y <= y1i; ++y) { + + if (y < 0 || y >= h) continue; + + float yprop = sprop; + if (y == y0i) yprop *= (y + 1) - y0; + if (y == y1i) yprop *= y1 - y; + ymag[y] += yprop * value; + ydiv[y] += yprop; + } + } + } + } + + if (mag.isSet()) { + + if (s >= int(m_columnMags.size())) { + std::cerr << "INTERNAL ERROR: " << s << " >= " + << m_columnMags.size() << " at SpectrogramLayer.cpp:2087" << std::endl; + } + + m_columnMags[s].sample(mag); + + if (overallMag.sample(mag)) { + //!!! scaling would change here + overallMagChanged = true; +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Overall mag changed (again?) at column " << s << ", to [" << overallMag.getMin() << "->" << overallMag.getMax() << "]" << std::endl; +#endif + } + } + } + + for (int y = 0; y < h; ++y) { + + if (ydiv[y] > 0.0) { + + unsigned char pixel = 0; + + float avg = ymag[y] / ydiv[y]; + pixel = getDisplayValue(v, avg); + + assert(x <= m_drawBuffer.width()); + QColor c = m_palette.getColour(pixel); + m_drawBuffer.setPixel(x, y, + qRgb(c.red(), c.green(), c.blue())); + } + } + } + + if (overallMagChanged) { + m_viewMags[v] = overallMag; +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << std::endl; +#endif + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; +#endif + } + + Profiler profiler2("SpectrogramLayer::paint: draw image", true); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Painting " << w << "x" << rect.height() + << " from draw buffer at " << 0 << "," << rect.y() + << " to window at " << x0 << "," << rect.y() << std::endl; +#endif + + paint.drawImage(x0, rect.y(), m_drawBuffer, 0, rect.y(), w, rect.height()); + + if (recreateWholePixmapCache) { + cache.pixmap = QPixmap(v->width(), h); + } + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "Painting " << w << "x" << h + << " from draw buffer at " << 0 << "," << 0 + << " to cache at " << x0 << "," << 0 << std::endl; +#endif + + QPainter cachePainter(&cache.pixmap); + cachePainter.drawImage(x0, 0, m_drawBuffer, 0, 0, w, h); + cachePainter.end(); + + if (!m_normalizeVisibleArea || !overallMagChanged) { + + cache.startFrame = startFrame; + cache.zoomLevel = zoomLevel; + + if (cache.validArea.x() > 0) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint() updating left (0, " + << cache.validArea.x() << ")" << std::endl; +#endif + v->update(0, 0, cache.validArea.x(), h); + } + + if (cache.validArea.x() + cache.validArea.width() < + cache.pixmap.width()) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint() updating right (" + << cache.validArea.x() + cache.validArea.width() + << ", " + << cache.pixmap.width() - (cache.validArea.x() + + cache.validArea.width()) + << ")" << std::endl; +#endif + v->update(cache.validArea.x() + cache.validArea.width(), + 0, + cache.pixmap.width() - (cache.validArea.x() + + cache.validArea.width()), + h); + } + } else { + // overallMagChanged + cache.validArea = QRect(); + v->update(); + } + + illuminateLocalFeatures(v, paint); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint() returning" << std::endl; +#endif + + m_lastPaintBlockWidth = paintBlockWidth; + (void)gettimeofday(&tv, 0); + m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; + + if (fftSuspended) fft->resume(); +} + +void +SpectrogramLayer::illuminateLocalFeatures(View *v, QPainter &paint) const +{ + QPoint localPos; + if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) { + return; + } + +// std::cerr << "SpectrogramLayer: illuminateLocalFeatures(" +// << localPos.x() << "," << localPos.y() << ")" << std::endl; + + float s0, s1; + float f0, f1; + + if (getXBinRange(v, localPos.x(), s0, s1) && + getYBinSourceRange(v, localPos.y(), f0, f1)) { + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + int x0 = v->getXForFrame(s0i * getWindowIncrement()); + int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement()); + + int y1 = int(getYForFrequency(v, f1)); + int y0 = int(getYForFrequency(v, f0)); + +// std::cerr << "SpectrogramLayer: illuminate " +// << x0 << "," << y1 << " -> " << x1 << "," << y0 << std::endl; + + paint.setPen(Qt::white); + + //!!! should we be using paintCrosshairs for this? + + paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1); + } +} + +float +SpectrogramLayer::getYForFrequency(View *v, float frequency) const +{ + return v->getYForFrequency(frequency, + getEffectiveMinFrequency(), + getEffectiveMaxFrequency(), + m_frequencyScale == LogFrequencyScale); +} + +float +SpectrogramLayer::getFrequencyForY(View *v, int y) const +{ + return v->getFrequencyForY(y, + getEffectiveMinFrequency(), + getEffectiveMaxFrequency(), + m_frequencyScale == LogFrequencyScale); +} + +int +SpectrogramLayer::getCompletion(View *v) const +{ + if (m_updateTimer == 0) return 100; + if (m_fftModels.find(v) == m_fftModels.end()) return 100; + + size_t completion = m_fftModels[v].first->getCompletion(); +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl; +#endif + return completion; +} + +bool +SpectrogramLayer::getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const +{ + if (!m_model) return false; + + int sr = m_model->getSampleRate(); + min = float(sr) / m_fftSize; + max = float(sr) / 2; + + logarithmic = (m_frequencyScale == LogFrequencyScale); + unit = "Hz"; + return true; +} + +bool +SpectrogramLayer::getDisplayExtents(float &min, float &max) const +{ + min = getEffectiveMinFrequency(); + max = getEffectiveMaxFrequency(); +// std::cerr << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << std::endl; + return true; +} + +bool +SpectrogramLayer::setDisplayExtents(float min, float max) +{ + if (!m_model) return false; + + std::cerr << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << std::endl; + + if (min < 0) min = 0; + if (max > m_model->getSampleRate()/2) max = m_model->getSampleRate()/2; + + size_t minf = lrintf(min); + size_t maxf = lrintf(max); + + if (m_minFrequency == minf && m_maxFrequency == maxf) return true; + + invalidatePixmapCaches(); + invalidateMagnitudes(); + + m_minFrequency = minf; + m_maxFrequency = maxf; + + emit layerParametersChanged(); + + int vs = getCurrentVerticalZoomStep(); + if (vs != m_lastEmittedZoomStep) { + emit verticalZoomChanged(); + m_lastEmittedZoomStep = vs; + } + + return true; +} + +bool +SpectrogramLayer::snapToFeatureFrame(View *, int &frame, + size_t &resolution, + SnapType snap) const +{ + resolution = getWindowIncrement(); + int left = (frame / resolution) * resolution; + int right = left + resolution; + + switch (snap) { + case SnapLeft: frame = left; break; + case SnapRight: frame = right; break; + case SnapNearest: + case SnapNeighbouring: + if (frame - left > right - frame) frame = right; + else frame = left; + break; + } + + return true; +} + +bool +SpectrogramLayer::getCrosshairExtents(View *v, QPainter &, + QPoint cursorPos, + std::vector &extents) const +{ + QRect vertical(cursorPos.x() - 12, 0, 12, v->height()); + extents.push_back(vertical); + + QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1); + extents.push_back(horizontal); + + return true; +} + +void +SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint, + QPoint cursorPos) const +{ + paint.save(); + paint.setPen(m_crosshairColour); + + paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); + paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height()); + + float fundamental = getFrequencyForY(v, cursorPos.y()); + + int harmonic = 2; + + while (harmonic < 100) { + + float hy = lrintf(getYForFrequency(v, fundamental * harmonic)); + if (hy < 0 || hy > v->height()) break; + + int len = 7; + + if (harmonic % 2 == 0) { + if (harmonic % 4 == 0) { + len = 12; + } else { + len = 10; + } + } + + paint.drawLine(cursorPos.x() - len, + int(hy), + cursorPos.x(), + int(hy)); + + ++harmonic; + } + + paint.restore(); +} + +QString +SpectrogramLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + int y = pos.y(); + + if (!m_model || !m_model->isOK()) return ""; + + float magMin = 0, magMax = 0; + float phaseMin = 0, phaseMax = 0; + float freqMin = 0, freqMax = 0; + float adjFreqMin = 0, adjFreqMax = 0; + QString pitchMin, pitchMax; + RealTime rtMin, rtMax; + + bool haveValues = false; + + if (!getXBinSourceRange(v, x, rtMin, rtMax)) { + return ""; + } + if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) { + haveValues = true; + } + + QString adjFreqText = "", adjPitchText = ""; + + if (m_binDisplay == PeakFrequencies) { + + if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, + adjFreqMin, adjFreqMax)) { + return ""; + } + + if (adjFreqMin != adjFreqMax) { + adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n") + .arg(adjFreqMin).arg(adjFreqMax); + } else { + adjFreqText = tr("Peak Frequency:\t%1 Hz\n") + .arg(adjFreqMin); + } + + QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin); + QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax); + + if (pmin != pmax) { + adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax); + } else { + adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin); + } + + } else { + + if (!getYBinSourceRange(v, y, freqMin, freqMax)) return ""; + } + + QString text; + + if (rtMin != rtMax) { + text += tr("Time:\t%1 - %2\n") + .arg(rtMin.toText(true).c_str()) + .arg(rtMax.toText(true).c_str()); + } else { + text += tr("Time:\t%1\n") + .arg(rtMin.toText(true).c_str()); + } + + if (freqMin != freqMax) { + text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n") + .arg(adjFreqText) + .arg(freqMin) + .arg(freqMax) + .arg(adjPitchText) + .arg(Pitch::getPitchLabelForFrequency(freqMin)) + .arg(Pitch::getPitchLabelForFrequency(freqMax)); + } else { + text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n") + .arg(adjFreqText) + .arg(freqMin) + .arg(adjPitchText) + .arg(Pitch::getPitchLabelForFrequency(freqMin)); + } + + if (haveValues) { + float dbMin = AudioLevel::multiplier_to_dB(magMin); + float dbMax = AudioLevel::multiplier_to_dB(magMax); + QString dbMinString; + QString dbMaxString; + if (dbMin == AudioLevel::DB_FLOOR) { + dbMinString = tr("-Inf"); + } else { + dbMinString = QString("%1").arg(lrintf(dbMin)); + } + if (dbMax == AudioLevel::DB_FLOOR) { + dbMaxString = tr("-Inf"); + } else { + dbMaxString = QString("%1").arg(lrintf(dbMax)); + } + if (lrintf(dbMin) != lrintf(dbMax)) { + text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString); + } else { + text += tr("dB:\t%1").arg(dbMinString); + } + if (phaseMin != phaseMax) { + text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax); + } else { + text += tr("\nPhase:\t%1").arg(phaseMin); + } + } + + return text; +} + +int +SpectrogramLayer::getColourScaleWidth(QPainter &paint) const +{ + int cw; + + cw = paint.fontMetrics().width("-80dB"); + + return cw; +} + +int +SpectrogramLayer::getVerticalScaleWidth(View *, QPainter &paint) const +{ + if (!m_model || !m_model->isOK()) return 0; + + int cw = getColourScaleWidth(paint); + + int tw = paint.fontMetrics().width(QString("%1") + .arg(m_maxFrequency > 0 ? + m_maxFrequency - 1 : + m_model->getSampleRate() / 2)); + + int fw = paint.fontMetrics().width(tr("43Hz")); + if (tw < fw) tw = fw; + + int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); + + return cw + tickw + tw + 13; +} + +void +SpectrogramLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + Profiler profiler("SpectrogramLayer::paintVerticalScale", true); + + //!!! cache this? + + int h = rect.height(), w = rect.width(); + + int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); + int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); + + size_t bins = m_fftSize / 2; + int sr = m_model->getSampleRate(); + + if (m_maxFrequency > 0) { + bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); + if (bins > m_fftSize / 2) bins = m_fftSize / 2; + } + + int cw = getColourScaleWidth(paint); + int cbw = paint.fontMetrics().width("dB"); + + int py = -1; + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + if (h > textHeight * 3 + 10) { + + int topLines = 2; + if (m_colourScale == PhaseColourScale) topLines = 1; + + int ch = h - textHeight * (topLines + 1) - 8; +// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top, bottom; + float min = m_viewMags[v].getMin(); + float max = m_viewMags[v].getMax(); + + float dBmin = AudioLevel::multiplier_to_dB(min); + float dBmax = AudioLevel::multiplier_to_dB(max); + + if (dBmax < -60.f) dBmax = -60.f; + else top = QString("%1").arg(lrintf(dBmax)); + + if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; + bottom = QString("%1").arg(lrintf(dBmin)); + + //!!! & phase etc + + if (m_colourScale != PhaseColourScale) { + paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, + 2 + textHeight + toff, "dBFS"); + } + +// paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + int lasty = 0; + int lastdb = 0; + + for (int i = 0; i < ch; ++i) { + + float dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); + int idb = int(dBval); + + float value = AudioLevel::dB_to_multiplier(dBval); + int colour = getDisplayValue(v, value * m_gain); + + paint.setPen(m_palette.getColour(colour)); + + int y = textHeight * topLines + 4 + ch - i; + + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + + if (i == 0) { + lasty = y; + lastdb = idb; + } else if (i < ch - paint.fontMetrics().ascent() && + idb != lastdb && + ((abs(y - lasty) > textHeight && + idb % 10 == 0) || + (abs(y - lasty) > paint.fontMetrics().ascent() && + idb % 5 == 0))) { + paint.setPen(Qt::black); + QString text = QString("%1").arg(idb); + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), + y + toff + textHeight/2, text); + paint.setPen(Qt::white); + paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); + lasty = y; + lastdb = idb; + } + } + paint.restore(); + } + + paint.drawLine(cw + 7, 0, cw + 7, h); + + int bin = -1; + + for (int y = 0; y < v->height(); ++y) { + + float q0, q1; + if (!getYBinRange(v, v->height() - y, q0, q1)) continue; + + int vy; + + if (int(q0) > bin) { + vy = y; + bin = int(q0); + } else { + continue; + } + + int freq = (sr * bin) / m_fftSize; + + if (py >= 0 && (vy - py) < textHeight - 1) { + if (m_frequencyScale == LinearFrequencyScale) { + paint.drawLine(w - tickw, h - vy, w, h - vy); + } + continue; + } + + QString text = QString("%1").arg(freq); + if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC + paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); + + if (h - vy - textHeight >= -2) { + int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw); + paint.drawText(tx, h - vy + toff, text); + } + + py = vy; + } + + if (m_frequencyScale == LogFrequencyScale) { + + paint.drawLine(w - pkw - 1, 0, w - pkw - 1, h); + + float minf = getEffectiveMinFrequency(); + float maxf = getEffectiveMaxFrequency(); + + int py = h, ppy = h; + paint.setBrush(paint.pen().color()); + + for (int i = 0; i < 128; ++i) { + + float f = Pitch::getFrequencyForPitch(i); + int y = lrintf(v->getYForFrequency(f, minf, maxf, true)); + + if (y < -2) break; + if (y > h + 2) { + continue; + } + + int n = (i % 12); + + if (n == 1) { + // C# -- fill the C from here + if (ppy - y > 2) { + paint.fillRect(w - pkw, +// y - (py - y) / 2 - (py - y) / 4, + y, + pkw, + (py + ppy) / 2 - y, +// py - y + 1, + Qt::gray); + } + } + + if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { + // black notes + paint.drawLine(w - pkw, y, w, y); + int rh = ((py - y) / 4) * 2; + if (rh < 2) rh = 2; + paint.drawRect(w - pkw, y - (py-y)/4, pkw/2, rh); + } else if (n == 0 || n == 5) { + // C, F + if (py < h) { + paint.drawLine(w - pkw, (y + py) / 2, w, (y + py) / 2); + } + } + + ppy = py; + py = y; + } + } +} + +class SpectrogramRangeMapper : public RangeMapper +{ +public: + SpectrogramRangeMapper(int sr, int /* fftsize */) : + m_dist(float(sr) / 2), + m_s2(sqrtf(sqrtf(2))) { } + ~SpectrogramRangeMapper() { } + + virtual int getPositionForValue(float value) const { + + float dist = m_dist; + + int n = 0; + + while (dist > (value + 0.00001) && dist > 0.1f) { + dist /= m_s2; + ++n; + } + + return n; + } + + virtual float getValueForPosition(int position) const { + + // Vertical zoom step 0 shows the entire range from DC -> + // Nyquist frequency. Step 1 shows 2^(1/4) of the range of + // step 0, and so on until the visible range is smaller than + // the frequency step between bins at the current fft size. + + float dist = m_dist; + + int n = 0; + while (n < position) { + dist /= m_s2; + ++n; + } + + return dist; + } + + virtual QString getUnit() const { return "Hz"; } + +protected: + float m_dist; + float m_s2; +}; + +int +SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const +{ + if (!m_model) return 0; + + int sr = m_model->getSampleRate(); + + SpectrogramRangeMapper mapper(sr, m_fftSize); + +// int maxStep = mapper.getPositionForValue((float(sr) / m_fftSize) + 0.001); + int maxStep = mapper.getPositionForValue(0); + int minStep = mapper.getPositionForValue(float(sr) / 2); + + defaultStep = mapper.getPositionForValue(m_initialMaxFrequency) - minStep; + +// std::cerr << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << m_initialMaxFrequency << ")" << std::endl; + + return maxStep - minStep; +} + +int +SpectrogramLayer::getCurrentVerticalZoomStep() const +{ + if (!m_model) return 0; + + float dmin, dmax; + getDisplayExtents(dmin, dmax); + + SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); + int n = mapper.getPositionForValue(dmax - dmin); +// std::cerr << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << std::endl; + return n; +} + +void +SpectrogramLayer::setVerticalZoomStep(int step) +{ + //!!! does not do the right thing for log scale + + if (!m_model) return; + + float dmin, dmax; + getDisplayExtents(dmin, dmax); + + int sr = m_model->getSampleRate(); + SpectrogramRangeMapper mapper(sr, m_fftSize); + float ddist = mapper.getValueForPosition(step); + + float dmid = (dmax + dmin) / 2; + float newmin = dmid - ddist / 2; + float newmax = dmid + ddist / 2; + + float mmin, mmax; + mmin = 0; + mmax = float(sr) / 2; + + if (newmin < mmin) { + newmax += (mmin - newmin); + newmin = mmin; + } + if (newmax > mmax) { + newmax = mmax; + } + +// std::cerr << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << ddist << ")" << std::endl; + + setMinFrequency(int(newmin)); + setMaxFrequency(int(newmax)); +} + +RangeMapper * +SpectrogramLayer::getNewVerticalZoomRangeMapper() const +{ + if (!m_model) return 0; + return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); +} + +QString +SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("channel=\"%1\" " + "windowSize=\"%2\" " + "windowHopLevel=\"%3\" " + "gain=\"%4\" " + "threshold=\"%5\" ") + .arg(m_channel) + .arg(m_windowSize) + .arg(m_windowHopLevel) + .arg(m_gain) + .arg(m_threshold); + + s += QString("minFrequency=\"%1\" " + "maxFrequency=\"%2\" " + "colourScale=\"%3\" " + "colourScheme=\"%4\" " + "colourRotation=\"%5\" " + "frequencyScale=\"%6\" " + "binDisplay=\"%7\" " + "normalizeColumns=\"%8\" " + "normalizeVisibleArea=\"%9\"") + .arg(m_minFrequency) + .arg(m_maxFrequency) + .arg(m_colourScale) + .arg(m_colourMap) + .arg(m_colourRotation) + .arg(m_frequencyScale) + .arg(m_binDisplay) + .arg(m_normalizeColumns ? "true" : "false") + .arg(m_normalizeVisibleArea ? "true" : "false"); + + return Layer::toXmlString(indent, extraAttributes + " " + s); +} + +void +SpectrogramLayer::setProperties(const QXmlAttributes &attributes) +{ + bool ok = false; + + int channel = attributes.value("channel").toInt(&ok); + if (ok) setChannel(channel); + + size_t windowSize = attributes.value("windowSize").toUInt(&ok); + if (ok) setWindowSize(windowSize); + + size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); + if (ok) setWindowHopLevel(windowHopLevel); + else { + size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok); + // a percentage value + if (ok) { + if (windowOverlap == 0) setWindowHopLevel(0); + else if (windowOverlap == 25) setWindowHopLevel(1); + else if (windowOverlap == 50) setWindowHopLevel(2); + else if (windowOverlap == 75) setWindowHopLevel(3); + else if (windowOverlap == 90) setWindowHopLevel(4); + } + } + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) setGain(gain); + + float threshold = attributes.value("threshold").toFloat(&ok); + if (ok) setThreshold(threshold); + + size_t minFrequency = attributes.value("minFrequency").toUInt(&ok); + if (ok) { + std::cerr << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << std::endl; + setMinFrequency(minFrequency); + } + + size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok); + if (ok) { + std::cerr << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << std::endl; + setMaxFrequency(maxFrequency); + } + + ColourScale colourScale = (ColourScale) + attributes.value("colourScale").toInt(&ok); + if (ok) setColourScale(colourScale); + + int colourMap = attributes.value("colourScheme").toInt(&ok); + if (ok) setColourMap(colourMap); + + int colourRotation = attributes.value("colourRotation").toInt(&ok); + if (ok) setColourRotation(colourRotation); + + FrequencyScale frequencyScale = (FrequencyScale) + attributes.value("frequencyScale").toInt(&ok); + if (ok) setFrequencyScale(frequencyScale); + + BinDisplay binDisplay = (BinDisplay) + attributes.value("binDisplay").toInt(&ok); + if (ok) setBinDisplay(binDisplay); + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + setNormalizeColumns(normalizeColumns); + + bool normalizeVisibleArea = + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); +} + diff -r 000000000000 -r fc9323a41f5a layer/SpectrogramLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrogramLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,404 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPECTROGRAM_LAYER_H_ +#define _SPECTROGRAM_LAYER_H_ + +#include "SliceableLayer.h" +#include "base/Window.h" +#include "base/RealTime.h" +#include "base/Thread.h" +#include "base/PropertyContainer.h" +#include "data/model/PowerOfSqrtTwoZoomConstraint.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/FFTModel.h" + +#include +#include +#include +#include + +class View; +class QPainter; +class QImage; +class QPixmap; +class QTimer; +class FFTModel; + + +/** + * SpectrogramLayer represents waveform data (obtained from a + * DenseTimeValueModel) in spectrogram form. + */ + +class SpectrogramLayer : public SliceableLayer, + public PowerOfSqrtTwoZoomConstraint +{ + Q_OBJECT + +public: + enum Configuration { FullRangeDb, MelodicRange, MelodicPeaks }; + + SpectrogramLayer(Configuration = FullRangeDb); + ~SpectrogramLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { return this; } + virtual const Model *getModel() const { return m_model; } + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(View *v, QPainter &) const; + virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; + + virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos, + std::vector &extents) const; + virtual void paintCrosshairs(View *, QPainter &, QPoint) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual bool hasLightBackground() const; + + void setModel(const DenseTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; + virtual void setProperty(const PropertyName &, int value); + + /** + * Specify the channel to use from the source model. + * A value of -1 means to mix all available channels. + * The default is channel 0. + */ + void setChannel(int); + int getChannel() const; + + void setWindowSize(size_t); + size_t getWindowSize() const; + + void setWindowHopLevel(size_t level); + size_t getWindowHopLevel() const; + + void setWindowType(WindowType type); + WindowType getWindowType() const; + + void setZeroPadLevel(size_t level); + size_t getZeroPadLevel() const; + + /** + * Set the gain multiplier for sample values in this view. + * The default is 1.0. + */ + void setGain(float gain); + float getGain() const; + + /** + * Set the threshold for sample values to qualify for being shown + * in the FFT, in voltage units. + * + * The default is 0.0. + */ + void setThreshold(float threshold); + float getThreshold() const; + + void setMinFrequency(size_t); + size_t getMinFrequency() const; + + void setMaxFrequency(size_t); // 0 -> no maximum + size_t getMaxFrequency() const; + + enum ColourScale { + LinearColourScale, + MeterColourScale, + dBSquaredColourScale, + dBColourScale, + PhaseColourScale + }; + + /** + * Specify the scale for sample levels. See WaveformLayer for + * details of meter and dB scaling. The default is dBColourScale. + */ + void setColourScale(ColourScale); + ColourScale getColourScale() const; + + enum FrequencyScale { + LinearFrequencyScale, + LogFrequencyScale + }; + + /** + * Specify the scale for the y axis. + */ + void setFrequencyScale(FrequencyScale); + FrequencyScale getFrequencyScale() const; + + enum BinDisplay { + AllBins, + PeakBins, + PeakFrequencies + }; + + /** + * Specify the processing of frequency bins for the y axis. + */ + void setBinDisplay(BinDisplay); + BinDisplay getBinDisplay() const; + + void setNormalizeColumns(bool n); + bool getNormalizeColumns() const; + + void setNormalizeVisibleArea(bool n); + bool getNormalizeVisibleArea() const; + + void setColourMap(int map); + int getColourMap() const; + + /** + * Specify the colourmap rotation for the colour scale. + */ + void setColourRotation(int); + int getColourRotation() const; + + virtual VerticalPosition getPreferredFrameCountPosition() const { + return PositionTop; + } + + virtual bool isLayerOpaque() const { return true; } + virtual bool isLayerColourSignificant() const { return true; } + + float getYForFrequency(View *v, float frequency) const; + float getFrequencyForY(View *v, int y) const; + + virtual int getCompletion(View *v) const; + + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const; + + virtual bool getDisplayExtents(float &min, float &max) const; + + virtual bool setDisplayExtents(float min, float max); + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + + virtual void setLayerDormant(const View *v, bool dormant); + + virtual bool isLayerScrollable(const View *) const { return false; } + + virtual int getVerticalZoomSteps(int &defaultStep) const; + virtual int getCurrentVerticalZoomStep() const; + virtual void setVerticalZoomStep(int); + virtual RangeMapper *getNewVerticalZoomRangeMapper() const; + + virtual const Model *getSliceableModel() const; + +protected slots: + void cacheInvalid(); + void cacheInvalid(size_t startFrame, size_t endFrame); + + void preferenceChanged(PropertyContainer::PropertyName name); + + void fillTimerTimedOut(); + +protected: + const DenseTimeValueModel *m_model; // I do not own this + + int m_channel; + size_t m_windowSize; + WindowType m_windowType; + size_t m_windowHopLevel; + size_t m_zeroPadLevel; + size_t m_fftSize; + float m_gain; + float m_initialGain; + float m_threshold; + float m_initialThreshold; + int m_colourRotation; + int m_initialRotation; + size_t m_minFrequency; + size_t m_maxFrequency; + size_t m_initialMaxFrequency; + ColourScale m_colourScale; + int m_colourMap; + QColor m_crosshairColour; + FrequencyScale m_frequencyScale; + BinDisplay m_binDisplay; + bool m_normalizeColumns; + bool m_normalizeVisibleArea; + int m_lastEmittedZoomStep; + + mutable int m_lastPaintBlockWidth; + mutable RealTime m_lastPaintTime; + + enum { NO_VALUE = 0 }; // colour index for unused pixels + + class Palette + { + public: + QColor getColour(unsigned char index) const { + return m_colours[index]; + } + + void setColour(unsigned char index, QColor colour) { + m_colours[index] = colour; + } + + private: + QColor m_colours[256]; + }; + + Palette m_palette; + + struct PixmapCache + { + QPixmap pixmap; + QRect validArea; + long startFrame; + size_t zoomLevel; + }; + typedef std::map ViewPixmapCache; + void invalidatePixmapCaches(); + void invalidatePixmapCaches(size_t startFrame, size_t endFrame); + mutable ViewPixmapCache m_pixmapCaches; + mutable QImage m_drawBuffer; + + mutable QTimer *m_updateTimer; + + mutable size_t m_candidateFillStartFrame; + bool m_exiting; + + void initialisePalette(); + void rotatePalette(int distance); + + static float calculateFrequency(size_t bin, + size_t windowSize, + size_t windowIncrement, + size_t sampleRate, + float previousPhase, + float currentPhase, + bool &steadyState); + + unsigned char getDisplayValue(View *v, float input) const; + float getInputForDisplayValue(unsigned char uc) const; + + int getColourScaleWidth(QPainter &) const; + + void illuminateLocalFeatures(View *v, QPainter &painter) const; + + float getEffectiveMinFrequency() const; + float getEffectiveMaxFrequency() const; + + struct LayerRange { + long startFrame; + int zoomLevel; + size_t modelStart; + size_t modelEnd; + }; + bool getXBinRange(View *v, int x, float &windowMin, float &windowMax) const; + bool getYBinRange(View *v, int y, float &freqBinMin, float &freqBinMax) const; + + bool getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) const; + bool getAdjustedYBinSourceRange(View *v, int x, int y, + float &freqMin, float &freqMax, + float &adjFreqMin, float &adjFreqMax) const; + bool getXBinSourceRange(View *v, int x, RealTime &timeMin, RealTime &timeMax) const; + bool getXYBinSourceRange(View *v, int x, int y, float &min, float &max, + float &phaseMin, float &phaseMax) const; + + size_t getWindowIncrement() const { + if (m_windowHopLevel == 0) return m_windowSize; + else if (m_windowHopLevel == 1) return (m_windowSize * 3) / 4; + else return m_windowSize / (1 << (m_windowHopLevel - 1)); + } + + size_t getZeroPadLevel(const View *v) const; + size_t getFFTSize(const View *v) const; + FFTModel *getFFTModel(const View *v) const; + void invalidateFFTModels(); + + typedef std::pair FFTFillPair; // model, last fill + typedef std::map ViewFFTMap; + typedef std::vector FloatVector; + mutable ViewFFTMap m_fftModels; + mutable Model *m_sliceableModel; + + class MagnitudeRange { + public: + MagnitudeRange() : m_min(0), m_max(0) { } + bool operator==(const MagnitudeRange &r) { + return r.m_min == m_min && r.m_max == m_max; + } + bool isSet() const { return (m_min != 0 || m_max != 0); } + void set(float min, float max) { + m_min = convert(min); + m_max = convert(max); + if (m_max < m_min) m_max = m_min; + } + bool sample(float f) { + unsigned int ui = convert(f); + bool changed = false; + if (isSet()) { + if (ui < m_min) { m_min = ui; changed = true; } + if (ui > m_max) { m_max = ui; changed = true; } + } else { + m_max = m_min = ui; + changed = true; + } + return changed; + } + bool sample(const MagnitudeRange &r) { + bool changed = false; + if (isSet()) { + if (r.m_min < m_min) { m_min = r.m_min; changed = true; } + if (r.m_max > m_max) { m_max = r.m_max; changed = true; } + } else { + m_min = r.m_min; + m_max = r.m_max; + changed = true; + } + return changed; + } + float getMin() const { return float(m_min) / UINT_MAX; } + float getMax() const { return float(m_max) / UINT_MAX; } + private: + unsigned int m_min; + unsigned int m_max; + unsigned int convert(float f) { + if (f < 0.f) f = 0.f; + if (f > 1.f) f = 1.f; + return (unsigned int)(f * UINT_MAX); + } + }; + + typedef std::map ViewMagMap; + mutable ViewMagMap m_viewMags; + mutable std::vector m_columnMags; + void invalidateMagnitudes(); + bool updateViewMagnitudes(View *v) const; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/SpectrumLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrumLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,368 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SpectrumLayer.h" + +#include "system/System.h" +#include "data/model/FFTModel.h" +#include "view/View.h" +#include "base/AudioLevel.h" +#include "base/Preferences.h" +#include "base/RangeMapper.h" + +SpectrumLayer::SpectrumLayer() : + m_originModel(0), + m_channel(-1), + m_channelSet(false), + m_windowSize(1024), + m_windowType(HanningWindow), + m_windowHopLevel(2) +{ + Preferences *prefs = Preferences::getInstance(); + connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); + setWindowType(prefs->getWindowType()); + + setBinScale(LogBins); +} + +SpectrumLayer::~SpectrumLayer() +{ + //!!! delete parent's model +// for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i]; +} + +void +SpectrumLayer::setModel(DenseTimeValueModel *model) +{ + if (m_originModel == model) return; + m_originModel = model; + setupFFT(); +} + +void +SpectrumLayer::setupFFT() +{ + FFTModel *oldFFT = dynamic_cast + (const_cast(m_sliceableModel)); + + if (oldFFT) { + setSliceableModel(0); + delete oldFFT; + } + + FFTModel *newFFT = new FFTModel(m_originModel, + m_channel, + m_windowType, + m_windowSize, + getWindowIncrement(), + m_windowSize, + true); + + setSliceableModel(newFFT); + + newFFT->resume(); +} + +void +SpectrumLayer::setChannel(int channel) +{ + m_channelSet = true; + + FFTModel *fft = dynamic_cast + (const_cast(m_sliceableModel)); + + if (m_channel == channel) { + if (fft) fft->resume(); + return; + } + + m_channel = channel; + + if (!fft) setupFFT(); + + emit layerParametersChanged(); +} + +Layer::PropertyList +SpectrumLayer::getProperties() const +{ + PropertyList list = SliceLayer::getProperties(); + list.push_back("Window Size"); + list.push_back("Window Increment"); + return list; +} + +QString +SpectrumLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Window Size") return tr("Window Size"); + if (name == "Window Increment") return tr("Window Overlap"); + return SliceLayer::getPropertyLabel(name); +} + +Layer::PropertyType +SpectrumLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Window Size") return ValueProperty; + if (name == "Window Increment") return ValueProperty; + return SliceLayer::getPropertyType(name); +} + +QString +SpectrumLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Window Size" || + name == "Window Increment") return tr("Window"); + return SliceLayer::getPropertyGroupName(name); +} + +int +SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + int garbage0, garbage1, garbage2; + if (!min) min = &garbage0; + if (!max) max = &garbage1; + if (!deflt) deflt = &garbage2; + + if (name == "Window Size") { + + *min = 0; + *max = 10; + *deflt = 5; + + val = 0; + int ws = m_windowSize; + while (ws > 32) { ws >>= 1; val ++; } + + } else if (name == "Window Increment") { + + *min = 0; + *max = 5; + *deflt = 2; + + val = m_windowHopLevel; + + } else { + + val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +SpectrumLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Window Size") { + return QString("%1").arg(32 << value); + } + if (name == "Window Increment") { + switch (value) { + default: + case 0: return tr("None"); + case 1: return tr("25 %"); + case 2: return tr("50 %"); + case 3: return tr("75 %"); + case 4: return tr("87.5 %"); + case 5: return tr("93.75 %"); + } + } + return SliceLayer::getPropertyValueLabel(name, value); +} + +RangeMapper * +SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const +{ + return SliceLayer::getNewPropertyRangeMapper(name); +} + +void +SpectrumLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Window Size") { + setWindowSize(32 << value); + } else if (name == "Window Increment") { + setWindowHopLevel(value); + } else { + SliceLayer::setProperty(name, value); + } +} + +void +SpectrumLayer::setWindowSize(size_t ws) +{ + if (m_windowSize == ws) return; + m_windowSize = ws; + setupFFT(); + emit layerParametersChanged(); +} + +void +SpectrumLayer::setWindowHopLevel(size_t v) +{ + if (m_windowHopLevel == v) return; + m_windowHopLevel = v; + setupFFT(); + emit layerParametersChanged(); +} + +void +SpectrumLayer::setWindowType(WindowType w) +{ + if (m_windowType == w) return; + m_windowType = w; + setupFFT(); + emit layerParametersChanged(); +} + +void +SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) +{ + if (name == "Window Type") { + setWindowType(Preferences::getInstance()->getWindowType()); + return; + } +} + +bool +SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const +{ + return false; +} + +QString +SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const +{ + if (!m_sliceableModel) return ""; + + int minbin = 0, maxbin = 0, range = 0; + QString genericDesc = SliceLayer::getFeatureDescription + (v, p, false, minbin, maxbin, range); + + if (genericDesc == "") return ""; + + float minvalue = 0.f; + if (minbin < int(m_values.size())) minvalue = m_values[minbin]; + + float maxvalue = minvalue; + if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin]; + + if (minvalue > maxvalue) std::swap(minvalue, maxvalue); + + QString binstr; + QString hzstr; + int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) / + m_windowSize); + int maxfreq = lrintf((max(maxbin, minbin+1) + * m_sliceableModel->getSampleRate()) / + m_windowSize); + + if (maxbin != minbin) { + binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); + } else { + binstr = QString("%1").arg(minbin+1); + } + if (minfreq != maxfreq) { + hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq); + } else { + hzstr = tr("%1 Hz").arg(minfreq); + } + + QString valuestr; + if (maxvalue != minvalue) { + valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue); + } else { + valuestr = QString("%1").arg(minvalue); + } + + QString dbstr; + float mindb = AudioLevel::multiplier_to_dB(minvalue); + float maxdb = AudioLevel::multiplier_to_dB(maxvalue); + QString mindbstr; + QString maxdbstr; + if (mindb == AudioLevel::DB_FLOOR) { + mindbstr = tr("-Inf"); + } else { + mindbstr = QString("%1").arg(lrintf(mindb)); + } + if (maxdb == AudioLevel::DB_FLOOR) { + maxdbstr = tr("-Inf"); + } else { + maxdbstr = QString("%1").arg(lrintf(maxdb)); + } + if (lrintf(mindb) != lrintf(maxdb)) { + dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr); + } else { + dbstr = tr("%1").arg(mindbstr); + } + + QString description; + + if (range > int(m_sliceableModel->getResolution())) { + description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6") + .arg(genericDesc) + .arg(binstr) + .arg(hzstr) + .arg(m_samplingMode == NearestSample ? tr("First") : + m_samplingMode == SampleMean ? tr("Mean") : tr("Peak")) + .arg(valuestr) + .arg(dbstr); + } else { + description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5") + .arg(genericDesc) + .arg(binstr) + .arg(hzstr) + .arg(valuestr) + .arg(dbstr); + } + + return description; +} + + +QString +SpectrumLayer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("windowSize=\"%1\" " + "windowHopLevel=\"%2\"") + .arg(m_windowSize) + .arg(m_windowHopLevel); + + return SliceLayer::toXmlString(indent, extraAttributes + " " + s); +} + +void +SpectrumLayer::setProperties(const QXmlAttributes &attributes) +{ + SliceLayer::setProperties(attributes); + + bool ok = false; + + size_t windowSize = attributes.value("windowSize").toUInt(&ok); + if (ok) setWindowSize(windowSize); + + size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); + if (ok) setWindowHopLevel(windowHopLevel); +} + + diff -r 000000000000 -r fc9323a41f5a layer/SpectrumLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrumLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,100 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SPECTRUM_LAYER_H_ +#define _SPECTRUM_LAYER_H_ + +#include "SliceLayer.h" + +#include "base/Window.h" + +#include "data/model/DenseTimeValueModel.h" + +#include + +class FFTModel; + +class SpectrumLayer : public SliceLayer +{ + Q_OBJECT + +public: + SpectrumLayer(); + ~SpectrumLayer(); + + void setModel(DenseTimeValueModel *model); + virtual const Model *getModel() const { return m_originModel; } + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; + virtual void setProperty(const PropertyName &, int value); + virtual void setProperties(const QXmlAttributes &); + + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const; + + virtual bool isLayerScrollable(const View *) const { return false; } + + void setChannel(int); + int getChannel() const { return m_channel; } + + void setWindowSize(size_t); + size_t getWindowSize() const { return m_windowSize; } + + void setWindowHopLevel(size_t level); + size_t getWindowHopLevel() const { return m_windowHopLevel; } + + void setWindowType(WindowType type); + WindowType getWindowType() const { return m_windowType; } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +protected slots: + void preferenceChanged(PropertyContainer::PropertyName name); + +protected: + // make this SliceLayer method unavailable to the general public +// virtual void setModel(DenseThreeDimensionalModel *model) { +// SliceLayer::setModel(model); +// } + + DenseTimeValueModel *m_originModel; + int m_channel; + bool m_channelSet; + size_t m_windowSize; + WindowType m_windowType; + size_t m_windowHopLevel; + + void setupFFT(); + + size_t getWindowIncrement() const { + if (m_windowHopLevel == 0) return m_windowSize; + else if (m_windowHopLevel == 1) return (m_windowSize * 3) / 4; + else return m_windowSize / (1 << (m_windowHopLevel - 1)); + } +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/TextLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TextLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,778 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TextLayer.h" + +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "view/View.h" + +#include "data/model/TextModel.h" + +#include +#include +#include + +#include +#include + +TextLayer::TextLayer() : + Layer(), + m_model(0), + m_editing(false), + m_originalPoint(0, 0.0, tr("Empty Label")), + m_editingPoint(0, 0.0, tr("Empty Label")), + m_editingCommand(0), + m_colour(255, 150, 50) // orange +{ + +} + +void +TextLayer::setModel(TextModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + +// std::cerr << "TextLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +TextLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + return list; +} + +QString +TextLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + return ""; +} + +Layer::PropertyType +TextLayer::getPropertyType(const PropertyName &) const +{ + return ValueProperty; +} + +int +TextLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + //!!! factor this colour handling stuff out into a colour manager class + + int val = 0; + + if (name == "Colour") { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + + } else { + + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +TextLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + return tr(""); +} + +void +TextLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour") { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } +} + +bool +TextLayer::getValueExtents(float &, float &, bool &, QString &) const +{ + return false; +} + +void +TextLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +bool +TextLayer::isLayerScrollable(const View *v) const +{ + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + + +TextModel::PointList +TextLayer::getLocalPoints(View *v, int x, int y) const +{ + if (!m_model) return TextModel::PointList(); + + long frame0 = v->getFrameForX(-150); + long frame1 = v->getFrameForX(v->width() + 150); + + TextModel::PointList points(m_model->getPoints(frame0, frame1)); + + TextModel::PointList rv; + QFontMetrics metrics = QPainter().fontMetrics(); + + for (TextModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + const TextModel::Point &p(*i); + + int px = v->getXForFrame(p.frame); + int py = getYForHeight(v, p.height); + + QString label = p.label; + if (label == "") { + label = tr(""); + } + + QRect rect = metrics.boundingRect + (QRect(0, 0, 150, 200), + Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label); + + if (py + rect.height() > v->height()) { + if (rect.height() > v->height()) py = 0; + else py = v->height() - rect.height() - 1; + } + + if (x >= px && x < px + rect.width() && + y >= py && y < py + rect.height()) { + rv.insert(p); + } + } + + return rv; +} + +QString +TextLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return ""; + + TextModel::PointList points = getLocalPoints(v, x, pos.y()); + + if (points.empty()) { + if (!m_model->isReady()) { + return tr("In progress"); + } else { + return ""; + } + } + + long useFrame = points.begin()->frame; + + RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); + + QString text; + + if (points.begin()->label == "") { + text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3")) + .arg(rt.toText(true).c_str()) + .arg(points.begin()->height) + .arg(points.begin()->label); + } + + pos = QPoint(v->getXForFrame(useFrame), + getYForHeight(v, points.begin()->height)); + return text; +} + + +//!!! too much overlap with TimeValueLayer/TimeInstantLayer + +bool +TextLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + TextModel::PointList points; + + if (snap == SnapNeighbouring) { + + points = getLocalPoints(v, v->getXForFrame(frame), -1); + if (points.empty()) return false; + frame = points.begin()->frame; + return true; + } + + points = m_model->getPoints(frame, frame); + int snapped = frame; + bool found = false; + + for (TextModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (snap == SnapRight) { + + if (i->frame > frame) { + snapped = i->frame; + found = true; + break; + } + + } else if (snap == SnapLeft) { + + if (i->frame <= frame) { + snapped = i->frame; + found = true; // don't break, as the next may be better + } else { + break; + } + + } else { // nearest + + TextModel::PointList::const_iterator j = i; + ++j; + + if (j == points.end()) { + + snapped = i->frame; + found = true; + break; + + } else if (j->frame >= frame) { + + if (j->frame - frame < frame - i->frame) { + snapped = j->frame; + } else { + snapped = i->frame; + } + found = true; + break; + } + } + } + + frame = snapped; + return found; +} + +int +TextLayer::getYForHeight(View *v, float height) const +{ + int h = v->height(); + return h - int(height * h); +} + +float +TextLayer::getHeightForY(View *v, int y) const +{ + int h = v->height(); + return float(h - y) / h; +} + +void +TextLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("TextLayer::paint", true); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1); + + TextModel::PointList points(m_model->getPoints(frame0, frame1)); + if (points.empty()) return; + + QColor brushColour(m_colour); + + int h, s, val; + brushColour.getHsv(&h, &s, &val); + brushColour.setHsv(h, s, 255, 100); + + QColor penColour; + if (v->hasLightBackground()) { + penColour = Qt::black; + } else { + penColour = Qt::white; + } + +// std::cerr << "TextLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + QPoint localPos; + long illuminateFrame = -1; + + if (v->shouldIlluminateLocalFeatures(this, localPos)) { + TextModel::PointList localPoints = getLocalPoints(v, localPos.x(), + localPos.y()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + int boxMaxWidth = 150; + int boxMaxHeight = 200; + + paint.save(); + paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height()); + + for (TextModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const TextModel::Point &p(*i); + + int x = v->getXForFrame(p.frame); + int y = getYForHeight(v, p.height); + + if (illuminateFrame == p.frame) { + paint.setBrush(penColour); + if (v->hasLightBackground()) { + paint.setPen(Qt::white); + } else { + paint.setPen(Qt::black); + } + } else { + paint.setPen(penColour); + paint.setBrush(brushColour); + } + + QString label = p.label; + if (label == "") { + label = tr(""); + } + + QRect boxRect = paint.fontMetrics().boundingRect + (QRect(0, 0, boxMaxWidth, boxMaxHeight), + Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label); + + QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height()); + boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2); + + if (y + boxRect.height() > v->height()) { + if (boxRect.height() > v->height()) y = 0; + else y = v->height() - boxRect.height() - 1; + } + + boxRect = QRect(x, y, boxRect.width(), boxRect.height()); + textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height()); + +// boxRect = QRect(x, y, boxRect.width(), boxRect.height()); +// textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height()); + + paint.setRenderHint(QPainter::Antialiasing, false); + paint.drawRect(boxRect); + + paint.setRenderHint(QPainter::Antialiasing, true); + paint.drawText(textRect, + Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, + label); + +/// if (p.label != "") { +/// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label); +/// } + } + + paint.restore(); + + // looks like save/restore doesn't deal with this: + paint.setRenderHint(QPainter::Antialiasing, false); +} + +void +TextLayer::drawStart(View *v, QMouseEvent *e) +{ +// std::cerr << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) { + std::cerr << "TextLayer::drawStart: no model" << std::endl; + return; + } + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float height = getHeightForY(v, e->y()); + + m_editingPoint = TextModel::Point(frame, height, ""); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) m_editingCommand->finish(); + m_editingCommand = new TextModel::EditCommand(m_model, "Add Label"); + m_editingCommand->addPoint(m_editingPoint); + + m_editing = true; +} + +void +TextLayer::drawDrag(View *v, QMouseEvent *e) +{ +// std::cerr << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float height = getHeightForY(v, e->y()); + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingPoint.height = height; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TextLayer::drawEnd(View *v, QMouseEvent *) +{ +// std::cerr << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + + bool ok = false; + QString label = QInputDialog::getText(v, tr("Enter label"), + tr("Please enter a new label:"), + QLineEdit::Normal, "", &ok); + + if (ok) { + TextModel::RelabelCommand *command = + new TextModel::RelabelCommand(m_model, m_editingPoint, label); + m_editingCommand->addCommand(command); + } + + m_editingCommand->finish(); + m_editingCommand = 0; + m_editing = false; +} + +void +TextLayer::editStart(View *v, QMouseEvent *e) +{ +// std::cerr << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + TextModel::PointList points = getLocalPoints(v, e->x(), e->y()); + if (points.empty()) return; + + m_editOrigin = e->pos(); + m_editingPoint = *points.begin(); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) { + m_editingCommand->finish(); + m_editingCommand = 0; + } + + m_editing = true; +} + +void +TextLayer::editDrag(View *v, QMouseEvent *e) +{ + if (!m_model || !m_editing) return; + + long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x()); + float heightDiff = getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y()); + + long frame = m_originalPoint.frame + frameDiff; + float height = m_originalPoint.height + heightDiff; + +// long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = (frame / m_model->getResolution()) * m_model->getResolution(); + +// float height = getHeightForY(v, e->y()); + + if (!m_editingCommand) { + m_editingCommand = new TextModel::EditCommand(m_model, tr("Drag Label")); + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingPoint.height = height; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TextLayer::editEnd(View *, QMouseEvent *) +{ +// std::cerr << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + + if (m_editingCommand) { + + QString newName = m_editingCommand->getName(); + + if (m_editingPoint.frame != m_originalPoint.frame) { + if (m_editingPoint.height != m_originalPoint.height) { + newName = tr("Move Label"); + } else { + newName = tr("Move Label Horizontally"); + } + } else { + newName = tr("Move Label Vertically"); + } + + m_editingCommand->setName(newName); + m_editingCommand->finish(); + } + + m_editingCommand = 0; + m_editing = false; +} + +void +TextLayer::editOpen(View *v, QMouseEvent *e) +{ + std::cerr << "TextLayer::editOpen" << std::endl; + + if (!m_model) return; + + TextModel::PointList points = getLocalPoints(v, e->x(), e->y()); + if (points.empty()) return; + + QString label = points.begin()->label; + + bool ok = false; + label = QInputDialog::getText(v, tr("Enter label"), + tr("Please enter a new label:"), + QLineEdit::Normal, label, &ok); + if (ok && label != points.begin()->label) { + TextModel::RelabelCommand *command = + new TextModel::RelabelCommand(m_model, *points.begin(), label); + CommandHistory::getInstance()->addCommand(command); + } +} + +void +TextLayer::moveSelection(Selection s, size_t newStartFrame) +{ + if (!m_model) return; + + TextModel::EditCommand *command = + new TextModel::EditCommand(m_model, tr("Drag Selection")); + + TextModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (TextModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + TextModel::Point newPoint(*i); + newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TextLayer::resizeSelection(Selection s, Selection newSize) +{ + if (!m_model) return; + + TextModel::EditCommand *command = + new TextModel::EditCommand(m_model, tr("Resize Selection")); + + TextModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + double ratio = + double(newSize.getEndFrame() - newSize.getStartFrame()) / + double(s.getEndFrame() - s.getStartFrame()); + + for (TextModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + + double target = i->frame; + target = newSize.getStartFrame() + + double(target - s.getStartFrame()) * ratio; + + TextModel::Point newPoint(*i); + newPoint.frame = lrint(target); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TextLayer::deleteSelection(Selection s) +{ + if (!m_model) return; + + TextModel::EditCommand *command = + new TextModel::EditCommand(m_model, tr("Delete Selection")); + + TextModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (TextModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) command->deletePoint(*i); + } + + command->finish(); +} + +void +TextLayer::copy(Selection s, Clipboard &to) +{ + if (!m_model) return; + + TextModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (TextModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) { + Clipboard::Point point(i->frame, i->height, i->label); + to.addPoint(point); + } + } +} + +bool +TextLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */) +{ + if (!m_model) return false; + + const Clipboard::PointList &points = from.getPoints(); + + TextModel::EditCommand *command = + new TextModel::EditCommand(m_model, tr("Paste")); + + float valueMin = 0.0, valueMax = 1.0; + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + if (i->haveValue()) { + if (i->getValue() < valueMin) valueMin = i->getValue(); + if (i->getValue() > valueMax) valueMax = i->getValue(); + } + } + if (valueMax < valueMin + 1.0) valueMax = valueMin + 1.0; + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + size_t frame = 0; + if (frameOffset > 0 || -frameOffset < i->getFrame()) { + frame = i->getFrame() + frameOffset; + } + TextModel::Point newPoint(frame); + + if (i->haveValue()) { + newPoint.height = (i->getValue() - valueMin) / (valueMax - valueMin); + } else { + newPoint.height = 0.5; + } + + if (i->haveLabel()) { + newPoint.label = i->getLabel(); + } else if (i->haveValue()) { + newPoint.label = QString("%1").arg(i->getValue()); + } else { + newPoint.label = tr("New Point"); + } + + command->addPoint(newPoint); + } + + command->finish(); + return true; +} + +QString +TextLayer::toXmlString(QString indent, QString extraAttributes) const +{ + return Layer::toXmlString(indent, extraAttributes + + QString(" colour=\"%1\"") + .arg(encodeColour(m_colour))); +} + +void +TextLayer::setProperties(const QXmlAttributes &attributes) +{ + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } +} + diff -r 000000000000 -r fc9323a41f5a layer/TextLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TextLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,106 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TEXT_LAYER_H_ +#define _TEXT_LAYER_H_ + +#include "Layer.h" +#include "data/model/TextModel.h" + +#include +#include + +class View; +class QPainter; + +class TextLayer : public Layer +{ + Q_OBJECT + +public: + TextLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual void drawStart(View *v, QMouseEvent *); + virtual void drawDrag(View *v, QMouseEvent *); + virtual void drawEnd(View *v, QMouseEvent *); + + virtual void editStart(View *v, QMouseEvent *); + virtual void editDrag(View *v, QMouseEvent *); + virtual void editEnd(View *v, QMouseEvent *); + + virtual void moveSelection(Selection s, size_t newStartFrame); + virtual void resizeSelection(Selection s, Selection newSize); + virtual void deleteSelection(Selection s); + + virtual void copy(Selection s, Clipboard &to); + virtual bool paste(const Clipboard &from, int frameOffset, + bool interactive); + + virtual void editOpen(View *, QMouseEvent *); // on double-click + + virtual const Model *getModel() const { return m_model; } + void setModel(TextModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerEditable() const { return true; } + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + +protected: + int getYForHeight(View *v, float height) const; + float getHeightForY(View *v, int y) const; + + TextModel::PointList getLocalPoints(View *v, int x, int y) const; + + TextModel *m_model; + bool m_editing; + QPoint m_editOrigin; + TextModel::Point m_originalPoint; + TextModel::Point m_editingPoint; + TextModel::EditCommand *m_editingCommand; + QColor m_colour; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/TimeInstantLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeInstantLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,782 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TimeInstantLayer.h" + +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "view/View.h" +#include "base/Profiler.h" +#include "base/Clipboard.h" + +#include "data/model/SparseOneDimensionalModel.h" + +#include "widgets/ItemEditDialog.h" + +#include +#include + +#include +#include + +TimeInstantLayer::TimeInstantLayer() : + Layer(), + m_model(0), + m_editing(false), + m_editingPoint(0, tr("New Point")), + m_editingCommand(0), + m_colour(QColor(200, 50, 255)), + m_plotStyle(PlotInstants) +{ + +} + +void +TimeInstantLayer::setModel(SparseOneDimensionalModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + std::cerr << "TimeInstantLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +TimeInstantLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Plot Type"); + return list; +} + +QString +TimeInstantLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Plot Type") return tr("Plot Type"); + return ""; +} + +Layer::PropertyType +TimeInstantLayer::getPropertyType(const PropertyName &) const +{ + return ValueProperty; +} + +int +TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + if (name == "Colour") { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + + } else if (name == "Plot Type") { + + if (min) *min = 0; + if (max) *max = 1; + if (deflt) *deflt = 0; + + val = int(m_plotStyle); + + } else { + + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +TimeInstantLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } else if (name == "Plot Type") { + switch (value) { + default: + case 0: return tr("Instants"); + case 1: return tr("Segmentation"); + } + } + return tr(""); +} + +void +TimeInstantLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour") { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } else if (name == "Plot Type") { + setPlotStyle(PlotStyle(value)); + } +} + +void +TimeInstantLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +void +TimeInstantLayer::setPlotStyle(PlotStyle style) +{ + if (m_plotStyle == style) return; + m_plotStyle = style; + emit layerParametersChanged(); +} + +bool +TimeInstantLayer::isLayerScrollable(const View *v) const +{ + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +SparseOneDimensionalModel::PointList +TimeInstantLayer::getLocalPoints(View *v, int x) const +{ + // Return a set of points that all have the same frame number, the + // nearest to the given x coordinate, and that are within a + // certain fuzz distance of that x coordinate. + + if (!m_model) return SparseOneDimensionalModel::PointList(); + + long frame = v->getFrameForX(x); + + SparseOneDimensionalModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + SparseOneDimensionalModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + SparseOneDimensionalModel::PointList nextPoints = + m_model->getNextPoints(frame); + + SparseOneDimensionalModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (long(prevPoints.begin()->frame) < v->getStartFrame() && + !(nextPoints.begin()->frame > v->getEndFrame())) { + usePoints = nextPoints; + } else if (nextPoints.begin()->frame - frame < + frame - prevPoints.begin()->frame) { + usePoints = nextPoints; + } + + if (!usePoints.empty()) { + int fuzz = 2; + int px = v->getXForFrame(usePoints.begin()->frame); + if ((px > x && px - x > fuzz) || + (px < x && x - px > fuzz + 1)) { + usePoints.clear(); + } + } + + return usePoints; +} + +QString +TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return ""; + + SparseOneDimensionalModel::PointList points = getLocalPoints(v, x); + + if (points.empty()) { + if (!m_model->isReady()) { + return tr("In progress"); + } else { + return tr("No local points"); + } + } + + long useFrame = points.begin()->frame; + + RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); + + QString text; + + if (points.begin()->label == "") { + text = QString(tr("Time:\t%1\nNo label")) + .arg(rt.toText(true).c_str()); + } else { + text = QString(tr("Time:\t%1\nLabel:\t%2")) + .arg(rt.toText(true).c_str()) + .arg(points.begin()->label); + } + + pos = QPoint(v->getXForFrame(useFrame), pos.y()); + return text; +} + +bool +TimeInstantLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + SparseOneDimensionalModel::PointList points; + + if (snap == SnapNeighbouring) { + + points = getLocalPoints(v, v->getXForFrame(frame)); + if (points.empty()) return false; + frame = points.begin()->frame; + return true; + } + + points = m_model->getPoints(frame, frame); + int snapped = frame; + bool found = false; + + for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (snap == SnapRight) { + + if (i->frame >= frame) { + snapped = i->frame; + found = true; + break; + } + + } else if (snap == SnapLeft) { + + if (i->frame <= frame) { + snapped = i->frame; + found = true; // don't break, as the next may be better + } else { + break; + } + + } else { // nearest + + SparseOneDimensionalModel::PointList::const_iterator j = i; + ++j; + + if (j == points.end()) { + + snapped = i->frame; + found = true; + break; + + } else if (j->frame >= frame) { + + if (j->frame - frame < frame - i->frame) { + snapped = j->frame; + } else { + snapped = i->frame; + } + found = true; + break; + } + } + } + + frame = snapped; + return found; +} + +void +TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + +// Profiler profiler("TimeInstantLayer::paint", true); + + int x0 = rect.left(), x1 = rect.right(); + + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1); + + SparseOneDimensionalModel::PointList points(m_model->getPoints + (frame0, frame1)); + + bool odd = false; + if (m_plotStyle == PlotSegmentation && !points.empty()) { + int index = m_model->getIndexOf(*points.begin()); + odd = ((index % 2) == 1); + } + + paint.setPen(m_colour); + + QColor brushColour(m_colour); + brushColour.setAlpha(100); + paint.setBrush(brushColour); + + QColor oddBrushColour(brushColour); + if (m_plotStyle == PlotSegmentation) { + if (m_colour == Qt::black) { + oddBrushColour = Qt::gray; + } else if (m_colour == Qt::darkRed) { + oddBrushColour = Qt::red; + } else if (m_colour == Qt::darkBlue) { + oddBrushColour = Qt::blue; + } else if (m_colour == Qt::darkGreen) { + oddBrushColour = Qt::green; + } else { + oddBrushColour = oddBrushColour.light(150); + } + oddBrushColour.setAlpha(100); + } + +// std::cerr << "TimeInstantLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + QPoint localPos; + long illuminateFrame = -1; + + if (v->shouldIlluminateLocalFeatures(this, localPos)) { + SparseOneDimensionalModel::PointList localPoints = + getLocalPoints(v, localPos.x()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + int prevX = -1; + int textY = v->getTextLabelHeight(this, paint); + + for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const SparseOneDimensionalModel::Point &p(*i); + SparseOneDimensionalModel::PointList::const_iterator j = i; + ++j; + + int x = v->getXForFrame(p.frame); + if (x == prevX && p.frame != illuminateFrame) continue; + + int iw = v->getXForFrame(p.frame + m_model->getResolution()) - x; + if (iw < 2) { + if (iw < 1) { + iw = 2; + if (j != points.end()) { + int nx = v->getXForFrame(j->frame); + if (nx < x + 3) iw = 1; + } + } else { + iw = 2; + } + } + + if (p.frame == illuminateFrame) { + paint.setPen(Qt::black); //!!! + } else { + paint.setPen(brushColour); + } + + if (m_plotStyle == PlotInstants) { + if (iw > 1) { + paint.drawRect(x, 0, iw - 1, v->height() - 1); + } else { + paint.drawLine(x, 0, x, v->height() - 1); + } + } else { + + if (odd) paint.setBrush(oddBrushColour); + else paint.setBrush(brushColour); + + int nx; + + if (j != points.end()) { + const SparseOneDimensionalModel::Point &q(*j); + nx = v->getXForFrame(q.frame); + } else { + nx = v->getXForFrame(m_model->getEndFrame()); + } + + if (nx >= x) { + + if (illuminateFrame != p.frame && + (nx < x + 5 || x >= v->width() - 1)) { + paint.setPen(Qt::NoPen); + } + + paint.drawRect(x, -1, nx - x, v->height() + 1); + } + + odd = !odd; + } + + paint.setPen(m_colour); + + if (p.label != "") { + + // only draw if there's enough room from here to the next point + + int lw = paint.fontMetrics().width(p.label); + bool good = true; + + if (j != points.end()) { + int nx = v->getXForFrame(j->frame); + if (nx >= x && nx - x - iw - 3 <= lw) good = false; + } + + if (good) { + paint.drawText(x + iw + 2, textY, p.label); + } + } + + prevX = x; + } +} + +void +TimeInstantLayer::drawStart(View *v, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << std::endl; + + if (!m_model) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + m_editingPoint = SparseOneDimensionalModel::Point(frame, tr("New Point")); + + if (m_editingCommand) m_editingCommand->finish(); + m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model, + tr("Draw Point")); + m_editingCommand->addPoint(m_editingPoint); + + m_editing = true; +} + +void +TimeInstantLayer::drawDrag(View *v, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TimeInstantLayer::drawEnd(View *, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << std::endl; + if (!m_model || !m_editing) return; + QString newName = tr("Add Point at %1 s") + .arg(RealTime::frame2RealTime(m_editingPoint.frame, + m_model->getSampleRate()) + .toText(false).c_str()); + m_editingCommand->setName(newName); + m_editingCommand->finish(); + m_editingCommand = 0; + m_editing = false; +} + +void +TimeInstantLayer::editStart(View *v, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << std::endl; + + if (!m_model) return; + + SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + m_editingPoint = *points.begin(); + + if (m_editingCommand) { + m_editingCommand->finish(); + m_editingCommand = 0; + } + + m_editing = true; +} + +void +TimeInstantLayer::editDrag(View *v, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + if (!m_editingCommand) { + m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model, + tr("Drag Point")); + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TimeInstantLayer::editEnd(View *, QMouseEvent *e) +{ + std::cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << std::endl; + if (!m_model || !m_editing) return; + if (m_editingCommand) { + QString newName = tr("Move Point to %1 s") + .arg(RealTime::frame2RealTime(m_editingPoint.frame, + m_model->getSampleRate()) + .toText(false).c_str()); + m_editingCommand->setName(newName); + m_editingCommand->finish(); + } + m_editingCommand = 0; + m_editing = false; +} + +void +TimeInstantLayer::editOpen(View *v, QMouseEvent *e) +{ + if (!m_model) return; + + SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + SparseOneDimensionalModel::Point point = *points.begin(); + + ItemEditDialog *dialog = new ItemEditDialog + (m_model->getSampleRate(), + ItemEditDialog::ShowTime | + ItemEditDialog::ShowText); + + dialog->setFrameTime(point.frame); + dialog->setText(point.label); + + if (dialog->exec() == QDialog::Accepted) { + + SparseOneDimensionalModel::Point newPoint = point; + newPoint.frame = dialog->getFrameTime(); + newPoint.label = dialog->getText(); + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(m_model, tr("Edit Point")); + command->deletePoint(point); + command->addPoint(newPoint); + command->finish(); + } + + delete dialog; +} + +void +TimeInstantLayer::moveSelection(Selection s, size_t newStartFrame) +{ + if (!m_model) return; + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(m_model, + tr("Drag Selection")); + + SparseOneDimensionalModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseOneDimensionalModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + SparseOneDimensionalModel::Point newPoint(*i); + newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TimeInstantLayer::resizeSelection(Selection s, Selection newSize) +{ + if (!m_model) return; + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(m_model, + tr("Resize Selection")); + + SparseOneDimensionalModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + double ratio = + double(newSize.getEndFrame() - newSize.getStartFrame()) / + double(s.getEndFrame() - s.getStartFrame()); + + for (SparseOneDimensionalModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + + double target = i->frame; + target = newSize.getStartFrame() + + double(target - s.getStartFrame()) * ratio; + + SparseOneDimensionalModel::Point newPoint(*i); + newPoint.frame = lrint(target); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TimeInstantLayer::deleteSelection(Selection s) +{ + if (!m_model) return; + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(m_model, + tr("Delete Selection")); + + SparseOneDimensionalModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseOneDimensionalModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) command->deletePoint(*i); + } + + command->finish(); +} + +void +TimeInstantLayer::copy(Selection s, Clipboard &to) +{ + if (!m_model) return; + + SparseOneDimensionalModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseOneDimensionalModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) { + Clipboard::Point point(i->frame, i->label); + to.addPoint(point); + } + } +} + +bool +TimeInstantLayer::paste(const Clipboard &from, int frameOffset, bool) +{ + if (!m_model) return false; + + const Clipboard::PointList &points = from.getPoints(); + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste")); + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + size_t frame = 0; + if (frameOffset > 0 || -frameOffset < i->getFrame()) { + frame = i->getFrame() + frameOffset; + } + SparseOneDimensionalModel::Point newPoint(frame); + if (i->haveLabel()) { + newPoint.label = i->getLabel(); + } else if (i->haveValue()) { + newPoint.label = QString("%1").arg(i->getValue()); + } + + command->addPoint(newPoint); + } + + command->finish(); + return true; +} + +QString +TimeInstantLayer::toXmlString(QString indent, QString extraAttributes) const +{ + return Layer::toXmlString(indent, extraAttributes + + QString(" colour=\"%1\" plotStyle=\"%2\"") + .arg(encodeColour(m_colour)).arg(m_plotStyle)); +} + +void +TimeInstantLayer::setProperties(const QXmlAttributes &attributes) +{ + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } + + bool ok; + PlotStyle style = (PlotStyle) + attributes.value("plotStyle").toInt(&ok); + if (ok) setPlotStyle(style); +} + diff -r 000000000000 -r fc9323a41f5a layer/TimeInstantLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeInstantLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,113 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TIME_INSTANT_LAYER_H_ +#define _TIME_INSTANT_LAYER_H_ + +#include "Layer.h" +#include "data/model/SparseOneDimensionalModel.h" + +#include +#include + +class View; +class QPainter; + +class TimeInstantLayer : public Layer +{ + Q_OBJECT + +public: + TimeInstantLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual void drawStart(View *v, QMouseEvent *); + virtual void drawDrag(View *v, QMouseEvent *); + virtual void drawEnd(View *v, QMouseEvent *); + + virtual void editStart(View *v, QMouseEvent *); + virtual void editDrag(View *v, QMouseEvent *); + virtual void editEnd(View *v, QMouseEvent *); + + virtual void editOpen(View *, QMouseEvent *); + + virtual void moveSelection(Selection s, size_t newStartFrame); + virtual void resizeSelection(Selection s, Selection newSize); + virtual void deleteSelection(Selection s); + + virtual void copy(Selection s, Clipboard &to); + virtual bool paste(const Clipboard &from, int frameOffset, + bool interactive); + + virtual const Model *getModel() const { return m_model; } + void setModel(SparseOneDimensionalModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + enum PlotStyle { + PlotInstants, + PlotSegmentation + }; + + void setPlotStyle(PlotStyle style); + PlotStyle getPlotStyle() const { return m_plotStyle; } + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerEditable() const { return true; } + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool needsTextLabelHeight() const { return m_model->hasTextLabels(); } + + virtual bool getValueExtents(float &, float &, bool &, QString &) const { + return false; + } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + +protected: + SparseOneDimensionalModel::PointList getLocalPoints(View *v, int) const; + + SparseOneDimensionalModel *m_model; + bool m_editing; + SparseOneDimensionalModel::Point m_editingPoint; + SparseOneDimensionalModel::EditCommand *m_editingCommand; + QColor m_colour; + PlotStyle m_plotStyle; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/TimeRulerLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeRulerLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,312 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TimeRulerLayer.h" + +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "view/View.h" + +#include + +#include + +using std::cerr; +using std::endl; + +TimeRulerLayer::TimeRulerLayer() : + Layer(), + m_model(0), + m_colour(Qt::black), + m_labelHeight(LabelTop) +{ + +} + +void +TimeRulerLayer::setModel(Model *model) +{ + if (m_model == model) return; + m_model = model; + emit modelReplaced(); +} + +void +TimeRulerLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +Layer::PropertyList +TimeRulerLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + return list; +} + +QString +TimeRulerLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + return ""; +} + +Layer::PropertyType +TimeRulerLayer::getPropertyType(const PropertyName &) const +{ + return ValueProperty; +} + +int +TimeRulerLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + if (name == "Colour") { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + + } else { + + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +TimeRulerLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + return tr(""); +} + +void +TimeRulerLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour") { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } +} + +void +TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const +{ +// std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() +// << ") [" << rect.width() << "x" << rect.height() << "]" << std::endl; + + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + + long startFrame = v->getStartFrame(); + long endFrame = v->getEndFrame(); + + int zoomLevel = v->getZoomLevel(); + + long rectStart = startFrame + (rect.x() - 100) * zoomLevel; + long rectEnd = startFrame + (rect.x() + rect.width() + 100) * zoomLevel; +// if (rectStart < startFrame) rectStart = startFrame; +// if (rectEnd > endFrame) rectEnd = endFrame; + +// std::cerr << "TimeRulerLayer::paint: calling paint.save()" << std::endl; + paint.save(); +//!!! paint.setClipRect(v->rect()); + + int minPixelSpacing = 50; + + RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate); + RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate); +// cerr << "startFrame " << startFrame << ", endFrame " << v->getEndFrame() << ", rtStart " << rtStart << ", rtEnd " << rtEnd << endl; + int count = v->width() / minPixelSpacing; + if (count < 1) count = 1; + RealTime rtGap = (rtEnd - rtStart) / count; +// cerr << "rtGap is " << rtGap << endl; + + int incms; + bool quarter = false; + + if (rtGap.sec > 0) { + incms = 1000; + int s = rtGap.sec; + if (s > 0) { incms *= 5; s /= 5; } + if (s > 0) { incms *= 2; s /= 2; } + if (s > 0) { incms *= 6; s /= 6; quarter = true; } + if (s > 0) { incms *= 5; s /= 5; quarter = false; } + if (s > 0) { incms *= 2; s /= 2; } + if (s > 0) { incms *= 6; s /= 6; quarter = true; } + while (s > 0) { + incms *= 10; + s /= 10; + quarter = false; + } + } else { + incms = 1; + int ms = rtGap.msec(); + if (ms > 0) { incms *= 10; ms /= 10; } + if (ms > 0) { incms *= 10; ms /= 10; } + if (ms > 0) { incms *= 5; ms /= 5; } + if (ms > 0) { incms *= 2; ms /= 2; } + } +// cerr << "incms is " << incms << endl; + + RealTime rt = RealTime::frame2RealTime(rectStart, sampleRate); + long ms = rt.sec * 1000 + rt.msec(); + ms = (ms / incms) * incms - incms; + + RealTime incRt = RealTime::fromMilliseconds(incms); + long incFrame = RealTime::realTime2Frame(incRt, sampleRate); + int incX = incFrame / zoomLevel; + int ticks = 10; + if (incX < minPixelSpacing * 2) { + ticks = quarter ? 4 : 5; + } + + QRect oldClipRect = rect; + QRect newClipRect(oldClipRect.x() - 25, oldClipRect.y(), + oldClipRect.width() + 50, oldClipRect.height()); + paint.setClipRect(newClipRect); + paint.setClipRect(rect); + + QColor greyColour(m_colour); + if (m_colour == Qt::black) { + greyColour = QColor(200,200,200); + } else { + greyColour = m_colour.light(150); + } + + while (1) { + + rt = RealTime::fromMilliseconds(ms); + ms += incms; + + long frame = RealTime::realTime2Frame(rt, sampleRate); + if (frame >= rectEnd) break; + + int x = (frame - startFrame) / zoomLevel; + if (x < rect.x() || x >= rect.x() + rect.width()) continue; + + paint.setPen(greyColour); + paint.drawLine(x, 0, x, v->height()); + + paint.setPen(m_colour); + paint.drawLine(x, 0, x, 5); + paint.drawLine(x, v->height() - 6, x, v->height() - 1); + + QString text(QString::fromStdString(rt.toText())); + + int y; + QFontMetrics metrics = paint.fontMetrics(); + switch (m_labelHeight) { + default: + case LabelTop: + y = 6 + metrics.ascent(); + break; + case LabelMiddle: + y = v->height() / 2 - metrics.height() / 2 + metrics.ascent(); + break; + case LabelBottom: + y = v->height() - metrics.height() + metrics.ascent() - 6; + } + + int tw = metrics.width(text); + + if (v->getViewManager() && v->getViewManager()->getOverlayMode() != + ViewManager::NoOverlays) { + + if (v->getLayer(0) == this) { + // backmost layer, don't worry about outlining the text + paint.drawText(x+2 - tw/2, y, text); + } else { + v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText); + } + } + + paint.setPen(greyColour); + + for (int i = 1; i < ticks; ++i) { + rt = rt + (incRt / ticks); + frame = RealTime::realTime2Frame(rt, sampleRate); + x = (frame - startFrame) / zoomLevel; + int sz = 5; + if (ticks == 10) { + if ((i % 2) == 1) { + if (i == 5) { + paint.drawLine(x, 0, x, v->height()); + } else sz = 3; + } else { + sz = 7; + } + } + paint.drawLine(x, 0, x, sz); + paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1); + } + } + + paint.restore(); +} + +QString +TimeRulerLayer::toXmlString(QString indent, QString extraAttributes) const +{ + return Layer::toXmlString(indent, extraAttributes + + QString(" colour=\"%1\"").arg(encodeColour(m_colour))); +} + +void +TimeRulerLayer::setProperties(const QXmlAttributes &attributes) +{ + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } +} + diff -r 000000000000 -r fc9323a41f5a layer/TimeRulerLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeRulerLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,71 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TIME_RULER_H_ +#define _TIME_RULER_H_ + +#include "Layer.h" + +#include +#include + +class View; +class Model; +class QPainter; + +class TimeRulerLayer : public Layer +{ + Q_OBJECT + +public: + TimeRulerLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + void setModel(Model *); + virtual const Model *getModel() const { return m_model; } + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + enum LabelHeight { LabelTop, LabelMiddle, LabelBottom }; + void setLabelHeight(LabelHeight h) { m_labelHeight = h; } + LabelHeight getLabelHeight() const { return m_labelHeight; } + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + virtual bool getValueExtents(float &, float &, bool &, QString &) const { + return false; + } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + +protected: + Model *m_model; + QColor m_colour; + LabelHeight m_labelHeight; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/TimeValueLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeValueLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1447 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TimeValueLayer.h" + +#include "system/System.h" +#include "data/model/Model.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "base/LogRange.h" +#include "view/View.h" + +#include "data/model/SparseTimeValueModel.h" + +#include "widgets/ItemEditDialog.h" +#include "widgets/ListInputDialog.h" + +#include "SpectrogramLayer.h" // for optional frequency alignment +#include "ColourMapper.h" + +#include +#include +#include +#include + +#include +#include + +TimeValueLayer::TimeValueLayer() : + Layer(), + m_model(0), + m_editing(false), + m_originalPoint(0, 0.0, tr("New Point")), + m_editingPoint(0, 0.0, tr("New Point")), + m_editingCommand(0), + m_colour(Qt::darkGreen), + m_colourMap(0), + m_plotStyle(PlotConnectedPoints), + m_verticalScale(AutoAlignScale) +{ + +} + +void +TimeValueLayer::setModel(SparseTimeValueModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + +// std::cerr << "TimeValueLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +TimeValueLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Plot Type"); + list.push_back("Vertical Scale"); + list.push_back("Scale Units"); + return list; +} + +QString +TimeValueLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Plot Type") return tr("Plot Type"); + if (name == "Vertical Scale") return tr("Vertical Scale"); + if (name == "Scale Units") return tr("Scale Units"); + return ""; +} + +Layer::PropertyType +TimeValueLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Scale Units") return UnitsProperty; + else return ValueProperty; +} + +QString +TimeValueLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Vertical Scale" || name == "Scale Units") { + return tr("Scale"); + } + return QString(); +} + +int +TimeValueLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + //!!! factor this colour handling stuff out into a colour manager class + + int val = 0; + + if (name == "Colour") { + + if (m_plotStyle == PlotSegmentation) { + + if (min) *min = 0; + if (max) *max = ColourMapper::getColourMapCount() - 1; + if (deflt) *deflt = 0; + + val = m_colourMap; + + } else { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + } + + } else if (name == "Plot Type") { + + if (min) *min = 0; + if (max) *max = 5; + if (deflt) *deflt = int(PlotConnectedPoints); + + val = int(m_plotStyle); + + } else if (name == "Vertical Scale") { + + if (min) *min = 0; + if (max) *max = 3; + if (deflt) *deflt = int(AutoAlignScale); + + val = int(m_verticalScale); + + } else if (name == "Scale Units") { + + if (deflt) *deflt = 0; + if (m_model) { + val = UnitDatabase::getInstance()->getUnitId + (m_model->getScaleUnits()); + } + + } else { + + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +TimeValueLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + if (m_plotStyle == PlotSegmentation) { + return ColourMapper::getColourMapName(value); + } else { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + } else if (name == "Plot Type") { + switch (value) { + default: + case 0: return tr("Points"); + case 1: return tr("Stems"); + case 2: return tr("Connected Points"); + case 3: return tr("Lines"); + case 4: return tr("Curve"); + case 5: return tr("Segmentation"); + } + } else if (name == "Vertical Scale") { + switch (value) { + default: + case 0: return tr("Auto-Align"); + case 1: return tr("Linear"); + case 2: return tr("Log"); + case 3: return tr("+/-1"); + } + } + return tr(""); +} + +void +TimeValueLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Colour") { + if (m_plotStyle == PlotSegmentation) { + setFillColourMap(value); + } else { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } + } else if (name == "Plot Type") { + setPlotStyle(PlotStyle(value)); + } else if (name == "Vertical Scale") { + setVerticalScale(VerticalScale(value)); + } else if (name == "Scale Units") { + if (m_model) { + m_model->setScaleUnits + (UnitDatabase::getInstance()->getUnitById(value)); + emit modelChanged(); + } + } +} + +void +TimeValueLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +void +TimeValueLayer::setFillColourMap(int map) +{ + if (m_colourMap == map) return; + m_colourMap = map; + emit layerParametersChanged(); +} + +void +TimeValueLayer::setPlotStyle(PlotStyle style) +{ + if (m_plotStyle == style) return; + bool colourTypeChanged = (style == PlotSegmentation || + m_plotStyle == PlotSegmentation); + m_plotStyle = style; + if (colourTypeChanged) { + emit layerParameterRangesChanged(); + } + emit layerParametersChanged(); +} + +void +TimeValueLayer::setVerticalScale(VerticalScale scale) +{ + if (m_verticalScale == scale) return; + m_verticalScale = scale; + emit layerParametersChanged(); +} + +bool +TimeValueLayer::isLayerScrollable(const View *v) const +{ + // We don't illuminate sections in the line or curve modes, so + // they're always scrollable + + if (m_plotStyle == PlotLines || + m_plotStyle == PlotCurve) return true; + + QPoint discard; + return !v->shouldIlluminateLocalFeatures(this, discard); +} + +bool +TimeValueLayer::getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const +{ + if (!m_model) return false; + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + logarithmic = (m_verticalScale == LogScale); + unit = m_model->getScaleUnits(); + return true; +} + +bool +TimeValueLayer::getDisplayExtents(float &min, float &max) const +{ + if (!m_model || m_verticalScale == AutoAlignScale) return false; + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + return true; +} + +SparseTimeValueModel::PointList +TimeValueLayer::getLocalPoints(View *v, int x) const +{ + if (!m_model) return SparseTimeValueModel::PointList(); + + long frame = v->getFrameForX(x); + + SparseTimeValueModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + SparseTimeValueModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + SparseTimeValueModel::PointList nextPoints = + m_model->getNextPoints(frame); + + SparseTimeValueModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (long(prevPoints.begin()->frame) < v->getStartFrame() && + !(nextPoints.begin()->frame > v->getEndFrame())) { + usePoints = nextPoints; + } else if (nextPoints.begin()->frame - frame < + frame - prevPoints.begin()->frame) { + usePoints = nextPoints; + } + + if (!usePoints.empty()) { + int fuzz = 2; + int px = v->getXForFrame(usePoints.begin()->frame); + if ((px > x && px - x > fuzz) || + (px < x && x - px > fuzz + 1)) { + usePoints.clear(); + } + } + + return usePoints; +} + +QString +TimeValueLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return ""; + + SparseTimeValueModel::PointList points = getLocalPoints(v, x); + + if (points.empty()) { + if (!m_model->isReady()) { + return tr("In progress"); + } else { + return tr("No local points"); + } + } + + long useFrame = points.begin()->frame; + + RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); + + QString text; + QString unit = m_model->getScaleUnits(); + if (unit != "") unit = " " + unit; + + if (points.begin()->label == "") { + text = QString(tr("Time:\t%1\nValue:\t%2%3\nNo label")) + .arg(rt.toText(true).c_str()) + .arg(points.begin()->value) + .arg(unit); + } else { + text = QString(tr("Time:\t%1\nValue:\t%2%3\nLabel:\t%4")) + .arg(rt.toText(true).c_str()) + .arg(points.begin()->value) + .arg(unit) + .arg(points.begin()->label); + } + + pos = QPoint(v->getXForFrame(useFrame), + getYForValue(v, points.begin()->value)); + return text; +} + +bool +TimeValueLayer::snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const +{ + if (!m_model) { + return Layer::snapToFeatureFrame(v, frame, resolution, snap); + } + + resolution = m_model->getResolution(); + SparseTimeValueModel::PointList points; + + if (snap == SnapNeighbouring) { + + points = getLocalPoints(v, v->getXForFrame(frame)); + if (points.empty()) return false; + frame = points.begin()->frame; + return true; + } + + points = m_model->getPoints(frame, frame); + int snapped = frame; + bool found = false; + + for (SparseTimeValueModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (snap == SnapRight) { + + if (i->frame > frame) { + snapped = i->frame; + found = true; + break; + } + + } else if (snap == SnapLeft) { + + if (i->frame <= frame) { + snapped = i->frame; + found = true; // don't break, as the next may be better + } else { + break; + } + + } else { // nearest + + SparseTimeValueModel::PointList::const_iterator j = i; + ++j; + + if (j == points.end()) { + + snapped = i->frame; + found = true; + break; + + } else if (j->frame >= frame) { + + if (j->frame - frame < frame - i->frame) { + snapped = j->frame; + } else { + snapped = i->frame; + } + found = true; + break; + } + } + } + + frame = snapped; + return found; +} + +void +TimeValueLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const +{ + min = 0.0; + max = 0.0; + log = false; + + if (m_verticalScale == AutoAlignScale) { + + if (!v->getValueExtents(m_model->getScaleUnits(), min, max, log)) { + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + } else if (log) { + LogRange::mapRange(min, max); + } + + } else if (m_verticalScale == PlusMinusOneScale) { + + min = -1.0; + max = 1.0; + + } else { + + min = m_model->getValueMinimum(); + max = m_model->getValueMaximum(); + + if (m_verticalScale == LogScale) { + LogRange::mapRange(min, max); + log = true; + } + } + + if (max == min) max = min + 1.0; +} + +int +TimeValueLayer::getYForValue(View *v, float val) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + +// std::cerr << "getYForValue(" << val << "): min " << min << ", max " +// << max << ", log " << logarithmic << std::endl; + + if (logarithmic) { + val = LogRange::map(val); + } + + return int(h - ((val - min) * h) / (max - min)); +} + +float +TimeValueLayer::getValueForY(View *v, int y) const +{ + float min = 0.0, max = 0.0; + bool logarithmic = false; + int h = v->height(); + + getScaleExtents(v, min, max, logarithmic); + + float val = min + (float(h - y) * float(max - min)) / h; + + if (logarithmic) { + val = powf(10.f, val); + } + + return val; +} + +QColor +TimeValueLayer::getColourForValue(View *v, float val) const +{ + float min, max; + bool log; + getScaleExtents(v, min, max, log); + + if (min > max) std::swap(min, max); + if (max == min) max = min + 1; + + if (log) { + LogRange::mapRange(min, max); + val = LogRange::map(val); + } + +// std::cerr << "TimeValueLayer::getColourForValue: min " << min << ", max " +// << max << ", log " << log << ", value " << val << std::endl; + + QColor solid = ColourMapper(m_colourMap, min, max).map(val); + return QColor(solid.red(), solid.green(), solid.blue(), 120); +} + +void +TimeValueLayer::paint(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("TimeValueLayer::paint", true); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1); + + SparseTimeValueModel::PointList points(m_model->getPoints + (frame0, frame1)); + if (points.empty()) return; + + paint.setPen(m_colour); + + QColor brushColour(m_colour); + brushColour.setAlpha(80); + paint.setBrush(brushColour); + +// std::cerr << "TimeValueLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + float min = m_model->getValueMinimum(); + float max = m_model->getValueMaximum(); + if (max == min) max = min + 1.0; + + int origin = int(nearbyint(v->height() - + (-min * v->height()) / (max - min))); + + QPoint localPos; + long illuminateFrame = -1; + + if (v->shouldIlluminateLocalFeatures(this, localPos)) { + SparseTimeValueModel::PointList localPoints = + getLocalPoints(v, localPos.x()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + int w = + v->getXForFrame(frame0 + m_model->getResolution()) - + v->getXForFrame(frame0); + + paint.save(); + + QPainterPath path; + int pointCount = 0; + + int textY = 0; + if (m_plotStyle == PlotSegmentation) { + textY = v->getTextLabelHeight(this, paint); + } + + for (SparseTimeValueModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const SparseTimeValueModel::Point &p(*i); + + int x = v->getXForFrame(p.frame); + int y = getYForValue(v, p.value); + + if (m_plotStyle != PlotSegmentation) { + textY = y - paint.fontMetrics().height() + + paint.fontMetrics().ascent(); + } + + bool haveNext = false; + int nx = v->getXForFrame(v->getModelsEndFrame()); +// m_model->getEndFrame()); + int ny = y; + + SparseTimeValueModel::PointList::const_iterator j = i; + ++j; + + if (j != points.end()) { + const SparseTimeValueModel::Point &q(*j); + nx = v->getXForFrame(q.frame); + ny = getYForValue(v, q.value); + haveNext = true; + } + +// std::cout << "frame = " << p.frame << ", x = " << x << ", haveNext = " << haveNext +// << ", nx = " << nx << std::endl; + + int labelY = y; + + if (w < 1) w = 1; + paint.setPen(m_colour); + + if (m_plotStyle == PlotSegmentation) { + paint.setPen(Qt::black); + paint.setBrush(getColourForValue(v, p.value)); + labelY = v->height(); + } else if (m_plotStyle == PlotLines || + m_plotStyle == PlotCurve) { + paint.setBrush(Qt::NoBrush); + } else { + paint.setBrush(brushColour); + } + + if (m_plotStyle == PlotStems) { + paint.setPen(brushColour); + if (y < origin - 1) { + paint.drawRect(x + w/2, y + 1, 1, origin - y); + } else if (y > origin + 1) { + paint.drawRect(x + w/2, origin, 1, y - origin - 1); + } + paint.setPen(m_colour); + } + + if (illuminateFrame == p.frame) { + + //!!! aside from the problem of choosing a colour, it'd be + //better to save the highlighted rects and draw them at + //the end perhaps + + //!!! not equipped to illuminate the right section in line + //or curve mode + + if (m_plotStyle != PlotCurve && + m_plotStyle != PlotLines) { + paint.setPen(Qt::black);//!!! + if (m_plotStyle != PlotSegmentation) { + paint.setBrush(Qt::black);//!!! + } + } + } + + if (m_plotStyle != PlotLines && + m_plotStyle != PlotCurve && + m_plotStyle != PlotSegmentation) { + paint.drawRect(x, y - 1, w, 2); + } + + if (m_plotStyle == PlotConnectedPoints || + m_plotStyle == PlotLines || + m_plotStyle == PlotCurve) { + + if (haveNext) { + + if (m_plotStyle == PlotConnectedPoints) { + + paint.save(); + paint.setPen(brushColour); + paint.drawLine(x + w, y, nx, ny); + paint.restore(); + + } else if (m_plotStyle == PlotLines) { + + paint.drawLine(x + w/2, y, nx + w/2, ny); + + } else { + + float x0 = x + float(w)/2; + float x1 = nx + float(w)/2; + + float y0 = y; + float y1 = ny; + + if (pointCount == 0) { + path.moveTo((x0 + x1) / 2, (y0 + y1) / 2); + } + ++pointCount; + + if (nx - x > 5) { + path.cubicTo(x0, y0, + x0, y0, + (x0 + x1) / 2, (y0 + y1) / 2); + + // // or + // path.quadTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); + + } else { + path.lineTo((x0 + x1) / 2, (y0 + y1) / 2); + } + } + } + } + + if (m_plotStyle == PlotSegmentation) { + +// std::cerr << "drawing rect" << std::endl; + + if (nx <= x) continue; + + if (illuminateFrame != p.frame && + (nx < x + 5 || x >= v->width() - 1)) { + paint.setPen(Qt::NoPen); + } + + paint.drawRect(x, -1, nx - x, v->height() + 1); + } + + if (p.label != "") { + if (!haveNext || nx > x + 6 + paint.fontMetrics().width(p.label)) { + paint.drawText(x + 5, textY, p.label); + } + } + } + + if (m_plotStyle == PlotCurve && !path.isEmpty()) { + paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->width()); + paint.drawPath(path); + } + + paint.restore(); + + // looks like save/restore doesn't deal with this: + paint.setRenderHint(QPainter::Antialiasing, false); +} + +int +TimeValueLayer::getVerticalScaleWidth(View *, QPainter &paint) const +{ + int w = paint.fontMetrics().width("-000.000"); + if (m_plotStyle == PlotSegmentation) return w + 20; + else return w + 10; +} + +void +TimeValueLayer::paintVerticalScale(View *v, QPainter &paint, QRect) const +{ + if (!m_model) return; + + int h = v->height(); + + int n = 10; + + float max = m_model->getValueMaximum(); + float min = m_model->getValueMinimum(); + float val = min; + float inc = (max - val) / n; + + char buffer[40]; + + int w = getVerticalScaleWidth(v, paint); + + int tx = 5; + + int boxx = 5, boxy = 5; + if (m_model->getScaleUnits() != "") { + boxy += paint.fontMetrics().height(); + } + int boxw = 10, boxh = h - boxy - 5; + + if (m_plotStyle == PlotSegmentation) { + tx += boxx + boxw; + paint.drawRect(boxx, boxy, boxw, boxh); + } + + if (m_plotStyle == PlotSegmentation) { + paint.save(); + for (int y = 0; y < boxh; ++y) { + float val = ((boxh - y) * (max - min)) / boxh + min; + paint.setPen(getColourForValue(v, val)); + paint.drawLine(boxx + 1, y + boxy + 1, boxx + boxw, y + boxy + 1); + } + paint.restore(); + } + + for (int i = 0; i < n; ++i) { + + int y, ty; + bool drawText = true; + + if (m_plotStyle == PlotSegmentation) { + y = boxy + int(boxh - ((val - min) * boxh) / (max - min)); + ty = y; + } else { + if (i == n-1) { + if (m_model->getScaleUnits() != "") drawText = false; + } + y = getYForValue(v, val); + ty = y - paint.fontMetrics().height() + + paint.fontMetrics().ascent(); + } + + sprintf(buffer, "%.3f", val); + QString label = QString(buffer); + + if (m_plotStyle != PlotSegmentation) { + paint.drawLine(w - 5, y, w, y); + } else { + paint.drawLine(boxx + boxw - boxw/3, y, boxx + boxw, y); + } + + if (drawText) paint.drawText(tx, ty, label); + val += inc; + } + + if (m_model->getScaleUnits() != "") { + paint.drawText(5, 5 + paint.fontMetrics().ascent(), + m_model->getScaleUnits()); + } +} + +void +TimeValueLayer::drawStart(View *v, QMouseEvent *e) +{ +// std::cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + long frame = v->getFrameForX(e->x()); + long resolution = m_model->getResolution(); + if (frame < 0) frame = 0; + frame = (frame / resolution) * resolution; + + float value = getValueForY(v, e->y()); + + bool havePoint = false; + + SparseTimeValueModel::PointList points = getLocalPoints(v, e->x()); + if (!points.empty()) { + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (((i->frame / resolution) * resolution) != frame) { +// std::cerr << "ignoring out-of-range frame at " << i->frame << std::endl; + continue; + } + m_editingPoint = *i; + havePoint = true; + } + } + + if (!havePoint) { + m_editingPoint = SparseTimeValueModel::Point + (frame, value, tr("New Point")); + } + + m_originalPoint = m_editingPoint; + + if (m_editingCommand) m_editingCommand->finish(); + m_editingCommand = new SparseTimeValueModel::EditCommand(m_model, + tr("Draw Point")); + if (!havePoint) { + m_editingCommand->addPoint(m_editingPoint); + } + + m_editing = true; +} + +void +TimeValueLayer::drawDrag(View *v, QMouseEvent *e) +{ +// std::cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + long resolution = m_model->getResolution(); + if (frame < 0) frame = 0; + frame = (frame / resolution) * resolution; + + float value = getValueForY(v, e->y()); + + SparseTimeValueModel::PointList points = getLocalPoints(v, e->x()); + +// std::cerr << points.size() << " points" << std::endl; + + bool havePoint = false; + + if (!points.empty()) { + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (i->frame == m_editingPoint.frame && + i->value == m_editingPoint.value) { + // std::cerr << "ignoring current editing point at " << i->frame << ", " << i->value << std::endl; + continue; + } + if (((i->frame / resolution) * resolution) != frame) { + // std::cerr << "ignoring out-of-range frame at " << i->frame << std::endl; + continue; + } + // std::cerr << "adjusting to new point at " << i->frame << ", " << i->value << std::endl; + m_editingPoint = *i; + m_originalPoint = m_editingPoint; + m_editingCommand->deletePoint(m_editingPoint); + havePoint = true; + } + } + + if (!havePoint) { + if (frame == m_editingPoint.frame) { + m_editingCommand->deletePoint(m_editingPoint); + } + } + +// m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingPoint.value = value; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TimeValueLayer::drawEnd(View *, QMouseEvent *) +{ +// std::cerr << "TimeValueLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + m_editingCommand->finish(); + m_editingCommand = 0; + m_editing = false; +} + +void +TimeValueLayer::editStart(View *v, QMouseEvent *e) +{ +// std::cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model) return; + + SparseTimeValueModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + m_editingPoint = *points.begin(); + m_originalPoint = m_editingPoint; + + if (m_editingCommand) { + m_editingCommand->finish(); + m_editingCommand = 0; + } + + m_editing = true; +} + +void +TimeValueLayer::editDrag(View *v, QMouseEvent *e) +{ +// std::cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl; + + if (!m_model || !m_editing) return; + + long frame = v->getFrameForX(e->x()); + if (frame < 0) frame = 0; + frame = frame / m_model->getResolution() * m_model->getResolution(); + + float value = getValueForY(v, e->y()); + + if (!m_editingCommand) { + m_editingCommand = new SparseTimeValueModel::EditCommand(m_model, + tr("Drag Point")); + } + + m_editingCommand->deletePoint(m_editingPoint); + m_editingPoint.frame = frame; + m_editingPoint.value = value; + m_editingCommand->addPoint(m_editingPoint); +} + +void +TimeValueLayer::editEnd(View *, QMouseEvent *) +{ +// std::cerr << "TimeValueLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; + if (!m_model || !m_editing) return; + + if (m_editingCommand) { + + QString newName = m_editingCommand->getName(); + + if (m_editingPoint.frame != m_originalPoint.frame) { + if (m_editingPoint.value != m_originalPoint.value) { + newName = tr("Edit Point"); + } else { + newName = tr("Relocate Point"); + } + } else { + newName = tr("Change Point Value"); + } + + m_editingCommand->setName(newName); + m_editingCommand->finish(); + } + + m_editingCommand = 0; + m_editing = false; +} + +void +TimeValueLayer::editOpen(View *v, QMouseEvent *e) +{ + if (!m_model) return; + + SparseTimeValueModel::PointList points = getLocalPoints(v, e->x()); + if (points.empty()) return; + + SparseTimeValueModel::Point point = *points.begin(); + + ItemEditDialog *dialog = new ItemEditDialog + (m_model->getSampleRate(), + ItemEditDialog::ShowTime | + ItemEditDialog::ShowValue | + ItemEditDialog::ShowText, + m_model->getScaleUnits()); + + dialog->setFrameTime(point.frame); + dialog->setValue(point.value); + dialog->setText(point.label); + + if (dialog->exec() == QDialog::Accepted) { + + SparseTimeValueModel::Point newPoint = point; + newPoint.frame = dialog->getFrameTime(); + newPoint.value = dialog->getValue(); + newPoint.label = dialog->getText(); + + SparseTimeValueModel::EditCommand *command = + new SparseTimeValueModel::EditCommand(m_model, tr("Edit Point")); + command->deletePoint(point); + command->addPoint(newPoint); + command->finish(); + } + + delete dialog; +} + +void +TimeValueLayer::moveSelection(Selection s, size_t newStartFrame) +{ + if (!m_model) return; + + SparseTimeValueModel::EditCommand *command = + new SparseTimeValueModel::EditCommand(m_model, + tr("Drag Selection")); + + SparseTimeValueModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + SparseTimeValueModel::Point newPoint(*i); + newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TimeValueLayer::resizeSelection(Selection s, Selection newSize) +{ + if (!m_model) return; + + SparseTimeValueModel::EditCommand *command = + new SparseTimeValueModel::EditCommand(m_model, + tr("Resize Selection")); + + SparseTimeValueModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + double ratio = + double(newSize.getEndFrame() - newSize.getStartFrame()) / + double(s.getEndFrame() - s.getStartFrame()); + + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + + double target = i->frame; + target = newSize.getStartFrame() + + double(target - s.getStartFrame()) * ratio; + + SparseTimeValueModel::Point newPoint(*i); + newPoint.frame = lrint(target); + command->deletePoint(*i); + command->addPoint(newPoint); + } + } + + command->finish(); +} + +void +TimeValueLayer::deleteSelection(Selection s) +{ + if (!m_model) return; + + SparseTimeValueModel::EditCommand *command = + new SparseTimeValueModel::EditCommand(m_model, + tr("Delete Selected Points")); + + SparseTimeValueModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + + if (s.contains(i->frame)) { + command->deletePoint(*i); + } + } + + command->finish(); +} + +void +TimeValueLayer::copy(Selection s, Clipboard &to) +{ + if (!m_model) return; + + SparseTimeValueModel::PointList points = + m_model->getPoints(s.getStartFrame(), s.getEndFrame()); + + for (SparseTimeValueModel::PointList::iterator i = points.begin(); + i != points.end(); ++i) { + if (s.contains(i->frame)) { + Clipboard::Point point(i->frame, i->value, i->label); + to.addPoint(point); + } + } +} + +bool +TimeValueLayer::paste(const Clipboard &from, int frameOffset, + bool interactive) +{ + if (!m_model) return false; + + const Clipboard::PointList &points = from.getPoints(); + + SparseTimeValueModel::EditCommand *command = + new SparseTimeValueModel::EditCommand(m_model, tr("Paste")); + + enum ValueAvailability { + UnknownAvailability, + NoValues, + SomeValues, + AllValues + }; + enum ValueGeneration { + GenerateNone, + GenerateFromCounter, + GenerateFromFrameNumber, + GenerateFromRealTime, + GenerateFromRealTimeDifference, + GenerateFromTempo, + GenerateFromExistingNeighbour, + GenerateFromLabels + }; + + ValueGeneration generation = GenerateNone; + + bool haveUsableLabels = false; + bool haveExistingItems = !(m_model->isEmpty()); + + if (interactive) { + + ValueAvailability availability = UnknownAvailability; + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + + if (availability == UnknownAvailability) { + if (i->haveValue()) availability = AllValues; + else availability = NoValues; + continue; + } + + if (i->haveValue()) { + if (availability == NoValues) { + availability = SomeValues; + } + } else { + if (availability == AllValues) { + availability = SomeValues; + } + } + + if (!haveUsableLabels) { + if (i->haveLabel()) { + if (i->getLabel().contains(QRegExp("[0-9]"))) { + haveUsableLabels = true; + } + } + } + + if (availability == SomeValues && haveUsableLabels) break; + } + + if (availability == NoValues || availability == SomeValues) { + + QString text; + if (availability == NoValues) { + text = tr("The items you are pasting do not have values.\nWhat values do you want to use for these items?"); + } else { + text = tr("Some of the items you are pasting do not have values.\nWhat values do you want to use for these items?"); + } + + QStringList options; + std::vector genopts; + + options << tr("Zero for all items"); + genopts.push_back(int(GenerateNone)); + + options << tr("Whole numbers counting from 1"); + genopts.push_back(int(GenerateFromCounter)); + + options << tr("Item's audio sample frame number"); + genopts.push_back(int(GenerateFromFrameNumber)); + + options << tr("Item's time in seconds"); + genopts.push_back(int(GenerateFromRealTime)); + + options << tr("Duration from the item to the following item"); + genopts.push_back(int(GenerateFromRealTimeDifference)); + + options << tr("Tempo in bpm derived from the duration"); + genopts.push_back(int(GenerateFromTempo)); + + if (haveExistingItems) { + options << tr("Value of the nearest existing item"); + genopts.push_back(int(GenerateFromExistingNeighbour)); + } + + if (haveUsableLabels) { + options << tr("Value extracted from the item's label (where possible)"); + genopts.push_back(int(GenerateFromLabels)); + } + + + static int prevSelection = 0; + + bool ok = false; + QString selected = ListInputDialog::getItem + (0, tr("Choose value calculation"), + text, options, prevSelection, &ok); + + if (!ok) return false; + int selection = 0; + generation = GenerateNone; + + for (QStringList::const_iterator i = options.begin(); + i != options.end(); ++i) { + if (selected == *i) { + generation = ValueGeneration(genopts[selection]); + break; + } + ++selection; + } + + prevSelection = selection; + } + } + + int counter = 1; + float prevBpm = 120.f; + + for (Clipboard::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + if (!i->haveFrame()) continue; + size_t frame = 0; + if (frameOffset > 0 || -frameOffset < i->getFrame()) { + frame = i->getFrame() + frameOffset; + } + SparseTimeValueModel::Point newPoint(frame); + + if (i->haveLabel()) { + newPoint.label = i->getLabel(); + } else if (i->haveValue()) { + newPoint.label = QString("%1").arg(i->getValue()); + } + + if (i->haveValue()) { + newPoint.value = i->getValue(); + } else { + + switch (generation) { + + case GenerateNone: + newPoint.value = 0; + break; + + case GenerateFromCounter: + newPoint.value = counter; + break; + + case GenerateFromFrameNumber: + newPoint.value = frame; + break; + + case GenerateFromRealTime: + newPoint.value = float(frame) / float(m_model->getSampleRate()); + break; + + case GenerateFromRealTimeDifference: + case GenerateFromTempo: + { + size_t nextFrame = frame; + Clipboard::PointList::const_iterator j = i; + for (; j != points.end(); ++j) { + if (!j->haveFrame()) continue; + if (j != i) break; + } + if (j != points.end()) { + nextFrame = j->getFrame(); + } + if (generation == GenerateFromRealTimeDifference) { + newPoint.value = float(nextFrame - frame) / + float(m_model->getSampleRate()); + } else { + float bpm = prevBpm; + if (nextFrame > frame) { + bpm = (60.f * m_model->getSampleRate()) / + (nextFrame - frame); + } + newPoint.value = bpm; + prevBpm = bpm; + } + break; + } + + case GenerateFromExistingNeighbour: + { + SparseTimeValueModel::PointList points = + m_model->getPoints(frame); + if (points.empty()) points = m_model->getPreviousPoints(frame); + if (points.empty()) points = m_model->getNextPoints(frame); + if (points.empty()) { + newPoint.value = 0.f; + } else { + newPoint.value = points.begin()->value; + } + } + + case GenerateFromLabels: + if (i->haveLabel()) { + // more forgiving than QString::toFloat() + newPoint.value = atof(i->getLabel().toLocal8Bit()); + } else { + newPoint.value = 0.f; + } + } + } + + command->addPoint(newPoint); + + ++counter; + } + + command->finish(); + return true; +} + +QString +TimeValueLayer::toXmlString(QString indent, QString extraAttributes) const +{ + return Layer::toXmlString(indent, extraAttributes + + QString(" colour=\"%1\" plotStyle=\"%2\"") + .arg(encodeColour(m_colour)).arg(m_plotStyle)); +} + +void +TimeValueLayer::setProperties(const QXmlAttributes &attributes) +{ + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } + + bool ok; + PlotStyle style = (PlotStyle) + attributes.value("plotStyle").toInt(&ok); + if (ok) setPlotStyle(style); +} + diff -r 000000000000 -r fc9323a41f5a layer/TimeValueLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeValueLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,145 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TIME_VALUE_LAYER_H_ +#define _TIME_VALUE_LAYER_H_ + +#include "Layer.h" +#include "data/model/SparseTimeValueModel.h" + +#include +#include + +class View; +class QPainter; + +class TimeValueLayer : public Layer +{ + Q_OBJECT + +public: + TimeValueLayer(); + + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(View *v, QPainter &) const; + virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual bool snapToFeatureFrame(View *v, int &frame, + size_t &resolution, + SnapType snap) const; + + virtual void drawStart(View *v, QMouseEvent *); + virtual void drawDrag(View *v, QMouseEvent *); + virtual void drawEnd(View *v, QMouseEvent *); + + virtual void editStart(View *v, QMouseEvent *); + virtual void editDrag(View *v, QMouseEvent *); + virtual void editEnd(View *v, QMouseEvent *); + + virtual void editOpen(View *v, QMouseEvent *); + + virtual void moveSelection(Selection s, size_t newStartFrame); + virtual void resizeSelection(Selection s, Selection newSize); + virtual void deleteSelection(Selection s); + + virtual void copy(Selection s, Clipboard &to); + virtual bool paste(const Clipboard &from, int frameOffset, + bool interactive); + + virtual const Model *getModel() const { return m_model; } + void setModel(SparseTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + void setFillColourMap(int); + int getFillColourMap() const { return m_colourMap; } + + enum PlotStyle { + PlotPoints, + PlotStems, + PlotConnectedPoints, + PlotLines, + PlotCurve, + PlotSegmentation + }; + + void setPlotStyle(PlotStyle style); + PlotStyle getPlotStyle() const { return m_plotStyle; } + + enum VerticalScale { + AutoAlignScale, + LinearScale, + LogScale, + PlusMinusOneScale + }; + + void setVerticalScale(VerticalScale scale); + VerticalScale getVerticalScale() const { return m_verticalScale; } + + virtual bool isLayerScrollable(const View *v) const; + + virtual bool isLayerEditable() const { return true; } + + virtual int getCompletion(View *) const { return m_model->getCompletion(); } + + virtual bool needsTextLabelHeight() const { + return m_plotStyle == PlotSegmentation && m_model->hasTextLabels(); + } + + virtual bool getValueExtents(float &min, float &max, + bool &logarithmic, QString &unit) const; + + virtual bool getDisplayExtents(float &min, float &max) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + +protected: + void getScaleExtents(View *, float &min, float &max, bool &log) const; + int getYForValue(View *, float value) const; + float getValueForY(View *, int y) const; + QColor getColourForValue(View *v, float value) const; + + SparseTimeValueModel::PointList getLocalPoints(View *v, int) const; + + SparseTimeValueModel *m_model; + bool m_editing; + SparseTimeValueModel::Point m_originalPoint; + SparseTimeValueModel::Point m_editingPoint; + SparseTimeValueModel::EditCommand *m_editingCommand; + QColor m_colour; + int m_colourMap; + PlotStyle m_plotStyle; + VerticalScale m_verticalScale; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a layer/WaveformLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/WaveformLayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1260 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WaveformLayer.h" + +#include "base/AudioLevel.h" +#include "view/View.h" +#include "base/Profiler.h" +#include "base/RangeMapper.h" + +#include +#include + +#include +#include + +//#define DEBUG_WAVEFORM_PAINT 1 + +using std::cerr; +using std::endl; + +WaveformLayer::WaveformLayer() : + Layer(), + m_model(0), + m_gain(1.0f), + m_autoNormalize(false), + m_colour(Qt::black), +// m_colour(QColor(84, 177, 248)), + m_showMeans(true), + m_greyscale(true), + m_channelMode(SeparateChannels), + m_channel(-1), + m_scale(LinearScale), + m_aggressive(false), + m_cache(0), + m_cacheValid(false) +{ + +} + +WaveformLayer::~WaveformLayer() +{ + delete m_cache; +} + +void +WaveformLayer::setModel(const RangeSummarisableTimeValueModel *model) +{ + bool channelsChanged = false; + if (m_channel == -1) { + if (!m_model) { + if (model) { + channelsChanged = true; + } + } else { + if (model && + m_model->getChannelCount() != model->getChannelCount()) { + channelsChanged = true; + } + } + } + + m_model = model; + m_cacheValid = false; + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + emit modelReplaced(); + + if (channelsChanged) emit layerParametersChanged(); +} + +Layer::PropertyList +WaveformLayer::getProperties() const +{ + PropertyList list; + list.push_back("Colour"); + list.push_back("Scale"); + list.push_back("Gain"); + list.push_back("Normalize Visible Area"); + + if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) { + list.push_back("Channels"); + } + + return list; +} + +QString +WaveformLayer::getPropertyLabel(const PropertyName &name) const +{ + if (name == "Colour") return tr("Colour"); + if (name == "Scale") return tr("Scale"); + if (name == "Gain") return tr("Gain"); + if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + if (name == "Channels") return tr("Channels"); + return ""; +} + +Layer::PropertyType +WaveformLayer::getPropertyType(const PropertyName &name) const +{ + if (name == "Gain") return RangeProperty; + if (name == "Normalize Visible Area") return ToggleProperty; + if (name == "Colour") return ValueProperty; + if (name == "Channels") return ValueProperty; + if (name == "Scale") return ValueProperty; + return InvalidProperty; +} + +QString +WaveformLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == "Gain" || + name == "Normalize Visible Area" || + name == "Scale") return tr("Scale"); + return QString(); +} + +int +WaveformLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max, int *deflt) const +{ + int val = 0; + + int garbage0, garbage1, garbage2; + if (!min) min = &garbage0; + if (!max) max = &garbage1; + if (!deflt) deflt = &garbage2; + + if (name == "Gain") { + + *min = -50; + *max = 50; + *deflt = 0; + + val = lrint(log10(m_gain) * 20.0); + if (val < *min) val = *min; + if (val > *max) val = *max; + + } else if (name == "Normalize Visible Area") { + + val = (m_autoNormalize ? 1 : 0); + *deflt = 0; + + } else if (name == "Colour") { + + *min = 0; + *max = 5; + *deflt = 0; + + if (m_colour == Qt::black) val = 0; + else if (m_colour == Qt::darkRed) val = 1; + else if (m_colour == Qt::darkBlue || + m_colour == QColor(84, 177, 248)) val = 2; + else if (m_colour == Qt::darkGreen) val = 3; + else if (m_colour == QColor(200, 50, 255)) val = 4; + else if (m_colour == QColor(255, 150, 50)) val = 5; + + } else if (name == "Channels") { + + *min = 0; + *max = 2; + *deflt = 0; + if (m_channelMode == MixChannels) val = 1; + else if (m_channelMode == MergeChannels) val = 2; + else val = 0; + + } else if (name == "Scale") { + + *min = 0; + *max = 2; + *deflt = 0; + + val = (int)m_scale; + + } else { + val = Layer::getPropertyRangeAndValue(name, min, max, deflt); + } + + return val; +} + +QString +WaveformLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == "Colour") { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + if (name == "Scale") { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Meter"); + case 2: return tr("dB"); + } + } + if (name == "Channels") { + switch (value) { + default: + case 0: return tr("Separate"); + case 1: return tr("Mean"); + case 2: return tr("Butterfly"); + } + } + return tr(""); +} + +RangeMapper * +WaveformLayer::getNewPropertyRangeMapper(const PropertyName &name) const +{ + if (name == "Gain") { + return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); + } + return 0; +} + +void +WaveformLayer::setProperty(const PropertyName &name, int value) +{ + if (name == "Gain") { + setGain(pow(10, float(value)/20.0)); + } else if (name == "Normalize Visible Area") { + setAutoNormalize(value ? true : false); + } else if (name == "Colour") { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; +// case 2: setBaseColour(QColor(84, 177, 248)); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } else if (name == "Channels") { + if (value == 1) setChannelMode(MixChannels); + else if (value == 2) setChannelMode(MergeChannels); + else setChannelMode(SeparateChannels); + } else if (name == "Scale") { + switch (value) { + default: + case 0: setScale(LinearScale); break; + case 1: setScale(MeterScale); break; + case 2: setScale(dBScale); break; + } + } +} + +void +WaveformLayer::setGain(float gain) +{ + if (m_gain == gain) return; + m_gain = gain; + m_cacheValid = false; + emit layerParametersChanged(); + emit verticalZoomChanged(); +} + +void +WaveformLayer::setAutoNormalize(bool autoNormalize) +{ + if (m_autoNormalize == autoNormalize) return; + m_autoNormalize = autoNormalize; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setShowMeans(bool showMeans) +{ + if (m_showMeans == showMeans) return; + m_showMeans = showMeans; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setUseGreyscale(bool useGreyscale) +{ + if (m_greyscale == useGreyscale) return; + m_greyscale = useGreyscale; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setChannelMode(ChannelMode channelMode) +{ + if (m_channelMode == channelMode) return; + m_channelMode = channelMode; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setChannel(int channel) +{ +// std::cerr << "WaveformLayer::setChannel(" << channel << ")" << std::endl; + + if (m_channel == channel) return; + m_channel = channel; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setScale(Scale scale) +{ + if (m_scale == scale) return; + m_scale = scale; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setAggressiveCacheing(bool aggressive) +{ + if (m_aggressive == aggressive) return; + m_aggressive = aggressive; + m_cacheValid = false; + emit layerParametersChanged(); +} + +int +WaveformLayer::getCompletion(View *) const +{ + int completion = 100; + if (!m_model || !m_model->isOK()) return completion; + if (m_model->isReady(&completion)) return 100; + return completion; +} + +bool +WaveformLayer::getValueExtents(float &min, float &max, + bool &, QString &unit) const +{ + if (m_scale == LinearScale) { + min = 0.0; + max = 1.0; + unit = "V"; + } else if (m_scale == MeterScale) { + return false; //!!! + } else { + min = AudioLevel::multiplier_to_dB(0.0); + max = AudioLevel::multiplier_to_dB(1.0); + unit = "dB"; + } + return true; +} + +int +WaveformLayer::dBscale(float sample, int m) const +{ +//!!! if (sample < 0.0) return -dBscale(-sample, m); + if (sample < 0.0) return dBscale(-sample, m); + float dB = AudioLevel::multiplier_to_dB(sample); + if (dB < -50.0) return 0; + if (dB > 0.0) return m; + return int(((dB + 50.0) * m) / 50.0 + 0.1); +} + +size_t +WaveformLayer::getChannelArrangement(size_t &min, size_t &max, + bool &merging, bool &mixing) + const +{ + if (!m_model || !m_model->isOK()) return 0; + + size_t channels = m_model->getChannelCount(); + if (channels == 0) return 0; + + size_t rawChannels = channels; + + if (m_channel == -1) { + min = 0; + if (m_channelMode == MergeChannels || + m_channelMode == MixChannels) { + max = 0; + channels = 1; + } else { + max = channels - 1; + } + } else { + min = m_channel; + max = m_channel; + rawChannels = 1; + channels = 1; + } + + merging = (m_channelMode == MergeChannels && rawChannels > 1); + mixing = (m_channelMode == MixChannels && rawChannels > 1); + +// std::cerr << "WaveformLayer::getChannelArrangement: min " << min << ", max " << max << ", merging " << merging << ", channels " << channels << std::endl; + + return channels; +} + +bool +WaveformLayer::isLayerScrollable(const View *) const +{ + return !m_autoNormalize; +} + +static float meterdbs[] = { -40, -30, -20, -15, -10, + -5, -3, -2, -1, -0.5, 0 }; + +void +WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + long startFrame = v->getStartFrame(); + int zoomLevel = v->getZoomLevel(); + +#ifdef DEBUG_WAVEFORM_PAINT + Profiler profiler("WaveformLayer::paint", true); + std::cerr << "WaveformLayer::paint (" << rect.x() << "," << rect.y() + << ") [" << rect.width() << "x" << rect.height() << "]: zoom " << zoomLevel << ", start " << startFrame << std::endl; +#endif + + size_t channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false, mixingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, + mergingChannels, mixingChannels); + if (channels == 0) return; + + int w = v->width(); + int h = v->height(); + + bool ready = m_model->isReady(); + QPainter *paint; + + if (m_aggressive) { + +#ifdef DEBUG_WAVEFORM_PAINT + std::cerr << "WaveformLayer::paint: aggressive is true" << std::endl; +#endif + + if (m_cacheValid && (zoomLevel != m_cacheZoomLevel)) { + m_cacheValid = false; + } + + if (!m_cache || m_cache->width() != w || m_cache->height() != h) { +#ifdef DEBUG_WAVEFORM_PAINT + if (m_cache) { + std::cerr << "WaveformLayer::paint: cache size " << m_cache->width() << "x" << m_cache->height() << " differs from view size " << w << "x" << h << ": regenerating aggressive cache" << std::endl; + } +#endif + delete m_cache; + m_cache = new QPixmap(w, h); + m_cacheValid = false; + } + + if (m_cacheValid) { + viewPainter.drawPixmap(rect, *m_cache, rect); + return; + } + + paint = new QPainter(m_cache); + + paint->setPen(Qt::NoPen); + paint->setBrush(v->palette().background()); + paint->drawRect(rect); + + paint->setPen(Qt::black); + paint->setBrush(Qt::NoBrush); + + } else { + paint = &viewPainter; + } + + paint->setRenderHint(QPainter::Antialiasing, false); + + int x0 = 0, x1 = w - 1; + int y0 = 0, y1 = h - 1; + + x0 = rect.left(); + x1 = rect.right(); + y0 = rect.top(); + y1 = rect.bottom(); + + if (x0 > 0) --x0; + if (x1 < v->width()) ++x1; + + long frame0 = v->getFrameForX(x0); + long frame1 = v->getFrameForX(x1 + 1); + +#ifdef DEBUG_WAVEFORM_PAINT + std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" << std::endl; +#endif + + RangeSummarisableTimeValueModel::RangeBlock *ranges = + new RangeSummarisableTimeValueModel::RangeBlock; + + RangeSummarisableTimeValueModel::RangeBlock *otherChannelRanges = 0; + RangeSummarisableTimeValueModel::Range range; + + QColor greys[3]; + if (m_colour == Qt::black) { + for (int i = 0; i < 3; ++i) { // 0 lightest, 2 darkest + int level = 192 - 64 * i; + greys[i] = QColor(level, level, level); + } + } else { + int hue, sat, val; + m_colour.getHsv(&hue, &sat, &val); + for (int i = 0; i < 3; ++i) { // 0 lightest, 2 darkest + if (v->hasLightBackground()) { + greys[i] = QColor::fromHsv(hue, sat * (i + 1) / 4, val); + } else { + greys[i] = QColor::fromHsv(hue, sat * (3 - i) / 4, val); + } + } + } + + QColor midColour = m_colour; + if (midColour == Qt::black) { + midColour = Qt::gray; + } else if (v->hasLightBackground()) { + midColour = midColour.light(150); + } else { + midColour = midColour.light(50); + } + + while (m_effectiveGains.size() <= maxChannel) { + m_effectiveGains.push_back(m_gain); + } + + for (size_t ch = minChannel; ch <= maxChannel; ++ch) { + + int prevRangeBottom = -1, prevRangeTop = -1; + QColor prevRangeBottomColour = m_colour, prevRangeTopColour = m_colour; + + m_effectiveGains[ch] = m_gain; + + if (m_autoNormalize) { + RangeSummarisableTimeValueModel::Range range = + m_model->getRange(ch, startFrame < 0 ? 0 : startFrame, + v->getEndFrame()); + if (mergingChannels || mixingChannels) { + RangeSummarisableTimeValueModel::Range otherRange = + m_model->getRange(1, startFrame < 0 ? 0 : startFrame, + v->getEndFrame()); + range.max = max(range.max, otherRange.max); + range.min = min(range.min, otherRange.min); + range.absmean = min(range.absmean, otherRange.absmean); + } + m_effectiveGains[ch] = 1.0 / max(fabsf(range.max), + fabsf(range.min)); + } + + float gain = m_effectiveGains[ch]; + + int m = (h / channels) / 2; + int my = m + (((ch - minChannel) * h) / channels); + +// std::cerr << "ch = " << ch << ", channels = " << channels << ", m = " << m << ", my = " << my << ", h = " << h << std::endl; + + if (my - m > y1 || my + m < y0) continue; + + if ((m_scale == dBScale || m_scale == MeterScale) && + m_channelMode != MergeChannels) { + m = (h / channels); + my = m + (((ch - minChannel) * h) / channels); + } + + paint->setPen(greys[0]); + paint->drawLine(x0, my, x1, my); + + int n = 10; + int py = -1; + + if (v->hasLightBackground() && + v->getViewManager() && + v->getViewManager()->shouldShowScaleGuides()) { + + paint->setPen(QColor(240, 240, 240)); + + for (int i = 1; i < n; ++i) { + + float val = 0.0, nval = 0.0; + + switch (m_scale) { + + case LinearScale: + val = (i * gain) / n; + if (i > 0) nval = -val; + break; + + case MeterScale: + val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; + break; + + case dBScale: + val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; + break; + } + + if (val < -1.0 || val > 1.0) continue; + + int y = getYForValue(v, m_scale, val, ch, minChannel, maxChannel); + + if (py >= 0 && abs(y - py) < 10) continue; + else py = y; + + int ny = y; + if (nval != 0.0) { + ny = getYForValue(v, m_scale, nval, ch, minChannel, maxChannel); + } + + paint->drawLine(x0, y, x1, y); + if (ny != y) { + paint->drawLine(x0, ny, x1, ny); + } + } + } + + if (frame1 <= 0) continue; + + size_t modelZoomLevel = zoomLevel; + + m_model->getRanges + (ch, frame0 < 0 ? 0 : frame0, frame1, *ranges, modelZoomLevel); + + if (mergingChannels || mixingChannels) { + if (m_model->getChannelCount() > 1) { + if (!otherChannelRanges) { + otherChannelRanges = + new RangeSummarisableTimeValueModel::RangeBlock; + } + m_model->getRanges + (1, frame0 < 0 ? 0 : frame0, frame1, *otherChannelRanges, + modelZoomLevel); + } else { + if (otherChannelRanges != ranges) delete otherChannelRanges; + otherChannelRanges = ranges; + } + } + + for (int x = x0; x <= x1; ++x) { + + range = RangeSummarisableTimeValueModel::Range(); + size_t index = x - x0; + size_t maxIndex = index; + + if (frame0 < 0) { + if (index < size_t(-frame0 / zoomLevel)) { + continue; + } else { + index -= -frame0 / zoomLevel; + maxIndex = index; + } + } + + if (int(modelZoomLevel) != zoomLevel) { + + index = size_t((double(index) * zoomLevel) / modelZoomLevel); + + if (int(modelZoomLevel) < zoomLevel) { + // Peaks may be missed! The model should avoid + // this by rounding zoom levels up rather than + // down, but we'd better cope in case it doesn't + maxIndex = index; + } else { + maxIndex = size_t((double(index + 1) * zoomLevel) + / modelZoomLevel) - 1; + } + } + + if (ranges && index < ranges->size()) { + + range = (*ranges)[index]; + + if (maxIndex > index && maxIndex < ranges->size()) { + range.max = max(range.max, (*ranges)[maxIndex].max); + range.min = min(range.min, (*ranges)[maxIndex].min); + range.absmean = (range.absmean + + (*ranges)[maxIndex].absmean) / 2; + } + + } else { + continue; + } + + int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; + + if (mergingChannels) { + + if (otherChannelRanges && index < otherChannelRanges->size()) { + + range.max = fabsf(range.max); + range.min = -fabsf((*otherChannelRanges)[index].max); + range.absmean = (range.absmean + + (*otherChannelRanges)[index].absmean) / 2; + + if (maxIndex > index && maxIndex < otherChannelRanges->size()) { + // let's not concern ourselves about the mean + range.min = min + (range.min, + -fabsf((*otherChannelRanges)[maxIndex].max)); + } + } + + } else if (mixingChannels) { + + if (otherChannelRanges && index < otherChannelRanges->size()) { + + range.max = (range.max + (*otherChannelRanges)[index].max) / 2; + range.min = (range.min + (*otherChannelRanges)[index].min) / 2; + range.absmean = (range.absmean + (*otherChannelRanges)[index].absmean) / 2; + } + } + + int greyLevels = 1; + if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; + + switch (m_scale) { + + case LinearScale: + rangeBottom = int( m * greyLevels * range.min * gain); + rangeTop = int( m * greyLevels * range.max * gain); + meanBottom = int(-m * range.absmean * gain); + meanTop = int( m * range.absmean * gain); + break; + + case dBScale: + if (!mergingChannels) { + int db0 = dBscale(range.min * gain, m); + int db1 = dBscale(range.max * gain, m); + rangeTop = max(db0, db1); + meanTop = min(db0, db1); + if (mixingChannels) rangeBottom = meanTop; + else rangeBottom = dBscale(range.absmean * gain, m); + meanBottom = rangeBottom; + } else { + rangeBottom = -dBscale(range.min * gain, m * greyLevels); + rangeTop = dBscale(range.max * gain, m * greyLevels); + meanBottom = -dBscale(range.absmean * gain, m); + meanTop = dBscale(range.absmean * gain, m); + } + break; + + case MeterScale: + if (!mergingChannels) { + int r0 = abs(AudioLevel::multiplier_to_preview(range.min * gain, m)); + int r1 = abs(AudioLevel::multiplier_to_preview(range.max * gain, m)); + rangeTop = max(r0, r1); + meanTop = min(r0, r1); + if (mixingChannels) rangeBottom = meanTop; + else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean * gain, m); + meanBottom = rangeBottom; + } else { + rangeBottom = AudioLevel::multiplier_to_preview(range.min * gain, m * greyLevels); + rangeTop = AudioLevel::multiplier_to_preview(range.max * gain, m * greyLevels); + meanBottom = -AudioLevel::multiplier_to_preview(range.absmean * gain, m); + meanTop = AudioLevel::multiplier_to_preview(range.absmean * gain, m); + } + break; + } + + rangeBottom = my * greyLevels - rangeBottom; + rangeTop = my * greyLevels - rangeTop; + meanBottom = my - meanBottom; + meanTop = my - meanTop; + + int topFill = (rangeTop % greyLevels); + if (topFill > 0) topFill = greyLevels - topFill; + + int bottomFill = (rangeBottom % greyLevels); + + rangeTop = rangeTop / greyLevels; + rangeBottom = rangeBottom / greyLevels; + + bool clipped = false; + + if (rangeTop < my - m) { rangeTop = my - m; } + if (rangeTop > my + m) { rangeTop = my + m; } + if (rangeBottom < my - m) { rangeBottom = my - m; } + if (rangeBottom > my + m) { rangeBottom = my + m; } + + if (range.max <= -1.0 || + range.max >= 1.0) clipped = true; + + if (meanBottom > rangeBottom) meanBottom = rangeBottom; + if (meanTop < rangeTop) meanTop = rangeTop; + + bool drawMean = m_showMeans; + if (meanTop == rangeTop) { + if (meanTop < meanBottom) ++meanTop; + else drawMean = false; + } + if (meanBottom == rangeBottom && m_scale == LinearScale) { + if (meanBottom > meanTop) --meanBottom; + else drawMean = false; + } + + if (x != x0 && prevRangeBottom != -1) { + if (prevRangeBottom > rangeBottom && + prevRangeTop > rangeBottom) { +// paint->setPen(midColour); + paint->setPen(m_colour); + paint->drawLine(x-1, prevRangeTop, x, rangeBottom); + paint->setPen(prevRangeTopColour); + paint->drawPoint(x-1, prevRangeTop); + } else if (prevRangeBottom < rangeTop && + prevRangeTop < rangeTop) { +// paint->setPen(midColour); + paint->setPen(m_colour); + paint->drawLine(x-1, prevRangeBottom, x, rangeTop); + paint->setPen(prevRangeBottomColour); + paint->drawPoint(x-1, prevRangeBottom); + } + } + + if (ready) { + if (clipped /*!!! || + range.min * gain <= -1.0 || + range.max * gain >= 1.0 */) { + paint->setPen(Qt::red); + } else { + paint->setPen(m_colour); + } + } else { + paint->setPen(midColour); + } + + paint->drawLine(x, rangeBottom, x, rangeTop); + + prevRangeTopColour = m_colour; + prevRangeBottomColour = m_colour; + + if (m_greyscale && (m_scale == LinearScale) && ready) { + if (!clipped) { + if (rangeTop < rangeBottom) { + if (topFill > 0 && + (!drawMean || (rangeTop < meanTop - 1))) { + paint->setPen(greys[topFill - 1]); + paint->drawPoint(x, rangeTop); + prevRangeTopColour = greys[topFill - 1]; + } + if (bottomFill > 0 && + (!drawMean || (rangeBottom > meanBottom + 1))) { + paint->setPen(greys[bottomFill - 1]); + paint->drawPoint(x, rangeBottom); + prevRangeBottomColour = greys[bottomFill - 1]; + } + } + } + } + + if (drawMean) { + paint->setPen(midColour); + paint->drawLine(x, meanBottom, x, meanTop); + } + + prevRangeBottom = rangeBottom; + prevRangeTop = rangeTop; + } + } + + if (m_aggressive) { + + if (ready && rect == v->rect()) { + m_cacheValid = true; + m_cacheZoomLevel = zoomLevel; + } + paint->end(); + delete paint; + viewPainter.drawPixmap(rect, *m_cache, rect); + } + + if (otherChannelRanges != ranges) delete otherChannelRanges; + delete ranges; +} + +QString +WaveformLayer::getFeatureDescription(View *v, QPoint &pos) const +{ + int x = pos.x(); + + if (!m_model || !m_model->isOK()) return ""; + + long f0 = v->getFrameForX(x); + long f1 = v->getFrameForX(x + 1); + + if (f0 < 0) f0 = 0; + if (f1 <= f0) return ""; + + QString text; + + RealTime rt0 = RealTime::frame2RealTime(f0, m_model->getSampleRate()); + RealTime rt1 = RealTime::frame2RealTime(f1, m_model->getSampleRate()); + + if (f1 != f0 + 1 && (rt0.sec != rt1.sec || rt0.msec() != rt1.msec())) { + text += tr("Time:\t%1 - %2") + .arg(rt0.toText(true).c_str()) + .arg(rt1.toText(true).c_str()); + } else { + text += tr("Time:\t%1") + .arg(rt0.toText(true).c_str()); + } + + size_t channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false, mixingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, + mergingChannels, mixingChannels); + if (channels == 0) return ""; + + for (size_t ch = minChannel; ch <= maxChannel; ++ch) { + + size_t blockSize = v->getZoomLevel(); + RangeSummarisableTimeValueModel::RangeBlock ranges; + m_model->getRanges(ch, f0, f1, ranges, blockSize); + + if (ranges.empty()) continue; + + RangeSummarisableTimeValueModel::Range range = ranges[0]; + + QString label = tr("Level:"); + if (minChannel != maxChannel) { + if (ch == 0) label = tr("Left:"); + else if (ch == 1) label = tr("Right:"); + else label = tr("Channel %1").arg(ch + 1); + } + + bool singleValue = false; + float min, max; + + if (fabs(range.min) < 0.01) { + min = range.min; + max = range.max; + singleValue = (min == max); + } else { + int imin = int(range.min * 1000); + int imax = int(range.max * 1000); + singleValue = (imin == imax); + min = float(imin)/1000; + max = float(imax)/1000; + } + + int db = int(AudioLevel::multiplier_to_dB(max(fabsf(range.min), + fabsf(range.max))) + * 100); + + if (!singleValue) { + text += tr("\n%1\t%2 - %3 (%4 dB peak)") + .arg(label).arg(min).arg(max).arg(float(db)/100); + } else { + text += tr("\n%1\t%2 (%3 dB peak)") + .arg(label).arg(min).arg(float(db)/100); + } + } + + return text; +} + +int +WaveformLayer::getYForValue(View *v, Scale scale, float value, size_t channel, + size_t minChannel, size_t maxChannel) const +{ + if (maxChannel < minChannel || channel < minChannel) return 0; + + int h = v->height(); + + int channels = maxChannel - minChannel + 1; + int m = (h / channels) / 2; + int my = m + (((channel - minChannel) * h) / channels); + + if ((m_scale == dBScale || m_scale == MeterScale) && + m_channelMode != MergeChannels) { + m = (h / channels); + my = m + (((channel - minChannel) * h) / channels); + } + + int vy = 0; + + switch (scale) { + + case LinearScale: + vy = int(m * value); + break; + + case MeterScale: + vy = AudioLevel::multiplier_to_preview(value, m); + break; + + case dBScale: + vy = dBscale(value, m); + break; + } + + return my - vy; +} + +int +WaveformLayer::getVerticalScaleWidth(View *, QPainter &paint) const +{ + if (m_scale == LinearScale) { + return paint.fontMetrics().width("0.0") + 13; + } else { + return max(paint.fontMetrics().width(tr("0dB")), + paint.fontMetrics().width(tr("-Inf"))) + 13; + } +} + +void +WaveformLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + size_t channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false, mixingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, + mergingChannels, mixingChannels); + if (channels == 0) return; + + int h = rect.height(), w = rect.width(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1; + + float gain = m_gain; + + for (size_t ch = minChannel; ch <= maxChannel; ++ch) { + + int lastLabelledY = -1; + + if (ch < m_effectiveGains.size()) gain = m_effectiveGains[ch]; + + int n = 10; + + for (int i = 0; i <= n; ++i) { + + float val = 0.0, nval = 0.0; + QString text = ""; + + switch (m_scale) { + + case LinearScale: + val = (i * gain) / n; + text = QString("%1").arg(float(i) / n); + if (i == 0) text = "0.0"; + else { + nval = -val; + if (i == n) text = "1.0"; + } + break; + + case MeterScale: + val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; + text = QString("%1").arg(meterdbs[i]); + if (i == n) text = tr("0dB"); + if (i == 0) { + text = tr("-Inf"); + val = 0.0; + } + break; + + case dBScale: + val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; + text = QString("%1").arg(-(10*n) + i * 10); + if (i == n) text = tr("0dB"); + if (i == 0) { + text = tr("-Inf"); + val = 0.0; + } + break; + } + + if (val < -1.0 || val > 1.0) continue; + + int y = getYForValue(v, m_scale, val, ch, minChannel, maxChannel); + + int ny = y; + if (nval != 0.0) { + ny = getYForValue(v, m_scale, nval, ch, minChannel, maxChannel); + } + + bool spaceForLabel = (i == 0 || + abs(y - lastLabelledY) >= textHeight - 1); + + if (spaceForLabel) { + + int tx = 3; + if (m_scale != LinearScale) { + tx = w - 10 - paint.fontMetrics().width(text); + } + + int ty = y; + if (ty < paint.fontMetrics().ascent()) { + ty = paint.fontMetrics().ascent(); + } else if (ty > h - paint.fontMetrics().descent()) { + ty = h - paint.fontMetrics().descent(); + } else { + ty += toff; + } + paint.drawText(tx, ty, text); + + lastLabelledY = ty - toff; + + if (ny != y) { + ty = ny; + if (ty < paint.fontMetrics().ascent()) { + ty = paint.fontMetrics().ascent(); + } else if (ty > h - paint.fontMetrics().descent()) { + ty = h - paint.fontMetrics().descent(); + } else { + ty += toff; + } + paint.drawText(tx, ty, text); + } + + paint.drawLine(w - 7, y, w, y); + if (ny != y) paint.drawLine(w - 7, ny, w, ny); + + } else { + + paint.drawLine(w - 4, y, w, y); + if (ny != y) paint.drawLine(w - 4, ny, w, ny); + } + } + } +} + +QString +WaveformLayer::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += QString("gain=\"%1\" " + "colour=\"%2\" " + "showMeans=\"%3\" " + "greyscale=\"%4\" " + "channelMode=\"%5\" " + "channel=\"%6\" " + "scale=\"%7\" " + "aggressive=\"%8\" " + "autoNormalize=\"%9\"") + .arg(m_gain) + .arg(encodeColour(m_colour)) + .arg(m_showMeans) + .arg(m_greyscale) + .arg(m_channelMode) + .arg(m_channel) + .arg(m_scale) + .arg(m_aggressive) + .arg(m_autoNormalize); + + return Layer::toXmlString(indent, extraAttributes + " " + s); +} + +void +WaveformLayer::setProperties(const QXmlAttributes &attributes) +{ + bool ok = false; + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) setGain(gain); + + QString colourSpec = attributes.value("colour"); + if (colourSpec != "") { + QColor colour(colourSpec); + if (colour.isValid()) { + setBaseColour(QColor(colourSpec)); + } + } + + bool showMeans = (attributes.value("showMeans") == "1" || + attributes.value("showMeans") == "true"); + setShowMeans(showMeans); + + bool greyscale = (attributes.value("greyscale") == "1" || + attributes.value("greyscale") == "true"); + setUseGreyscale(greyscale); + + ChannelMode channelMode = (ChannelMode) + attributes.value("channelMode").toInt(&ok); + if (ok) setChannelMode(channelMode); + + int channel = attributes.value("channel").toInt(&ok); + if (ok) setChannel(channel); + + Scale scale = (Scale) + attributes.value("scale").toInt(&ok); + if (ok) setScale(scale); + + bool aggressive = (attributes.value("aggressive") == "1" || + attributes.value("aggressive") == "true"); + setUseGreyscale(aggressive); + + bool autoNormalize = (attributes.value("autoNormalize") == "1" || + attributes.value("autoNormalize") == "true"); + setAutoNormalize(autoNormalize); +} + +int +WaveformLayer::getVerticalZoomSteps(int &defaultStep) const +{ + defaultStep = 50; + return 100; +} + +int +WaveformLayer::getCurrentVerticalZoomStep() const +{ + int val = lrint(log10(m_gain) * 20.0) + 50; + if (val < 0) val = 0; + if (val > 100) val = 100; + return val; +} + +void +WaveformLayer::setVerticalZoomStep(int step) +{ + setGain(pow(10, float(step - 50) / 20.0)); +} + diff -r 000000000000 -r fc9323a41f5a layer/WaveformLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/WaveformLayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,220 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WAVEFORM_LAYER_H_ +#define _WAVEFORM_LAYER_H_ + +#include +#include + +#include "Layer.h" + +#include "data/model/RangeSummarisableTimeValueModel.h" + +class View; +class QPainter; +class QPixmap; + +class WaveformLayer : public Layer +{ + Q_OBJECT + +public: + WaveformLayer(); + ~WaveformLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { + return m_model ? m_model->getZoomConstraint() : 0; + } + virtual const Model *getModel() const { return m_model; } + virtual void paint(View *v, QPainter &paint, QRect rect) const; + + virtual QString getFeatureDescription(View *v, QPoint &) const; + + virtual int getVerticalScaleWidth(View *v, QPainter &) const; + virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; + + void setModel(const RangeSummarisableTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; + virtual void setProperty(const PropertyName &, int value); + + /** + * Set the gain multiplier for sample values in this view. + * + * The default is 1.0. + */ + void setGain(float gain); + float getGain() const { return m_gain; } + + /** + * Toggle automatic normalization of the currently visible waveform. + */ + void setAutoNormalize(bool); + bool getAutoNormalize() const { return m_autoNormalize; } + + /** + * Set the basic display colour for waveforms. + * + * The default is black. + *!!! NB should default to white if the associated View !hasLightBackground() + */ + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + /** + * Set whether to display mean values as a lighter-coloured area + * beneath the peaks. Rendering will be slightly faster without + * but arguably prettier with. + * + * The default is to display means. + */ + void setShowMeans(bool); + bool getShowMeans() const { return m_showMeans; } + + /** + * Set whether to use shades of grey (or of the base colour) to + * provide additional perceived vertical resolution (i.e. using + * half-filled pixels to represent levels that only just meet the + * pixel unit boundary). This provides a small improvement in + * waveform quality at a small cost in rendering speed. + * + * The default is to use greyscale. + */ + void setUseGreyscale(bool); + bool getUseGreyscale() const { return m_greyscale; } + + + enum ChannelMode { SeparateChannels, MixChannels, MergeChannels }; + + /** + * Specify whether multi-channel audio data should be displayed + * with a separate axis per channel (SeparateChannels), with a + * single synthetic axis showing channel 0 above the axis and + * channel 1 below (MergeChannels), or with a single axis showing + * the average of the channels (MixChannels). + * + * MergeChannels does not work for files with more than 2 + * channels. + * + * The default is SeparateChannels. + */ + void setChannelMode(ChannelMode); + ChannelMode getChannelMode() const { return m_channelMode; } + + + /** + * Specify the channel to use from the source model. A value of + * -1 means to show all available channels (laid out to the + * channel mode). The default is -1. + */ + void setChannel(int); + int getChannel() const { return m_channel; } + + + enum Scale { LinearScale, MeterScale, dBScale }; + + /** + * Specify the vertical scale for sample levels. With LinearScale, + * the scale is directly proportional to the raw [-1, +1) + * floating-point audio sample values. With dBScale the + * vertical scale is proportional to dB level (truncated at + * -50dB). MeterScale provides a hybrid variable scale based on + * IEC meter scale, intended to provide a clear overview at + * relatively small heights. + * + * Note that the effective gain (see setGain()) is applied before + * vertical scaling. + * + * The default is LinearScale. + */ + void setScale(Scale); + Scale getScale() const { return m_scale; } + + /** + * Enable or disable aggressive pixmap cacheing. If enabled, + * waveforms will be rendered to an off-screen pixmap and + * refreshed from there instead of being redrawn from the peak + * data each time. This may be faster if the data and zoom level + * do not change often, but it may be slower for frequently zoomed + * data and it will only work if the waveform is the "bottom" + * layer on the displayed widget, as each refresh will erase + * anything beneath the waveform. + * + * This is intended specifically for a panner widget display in + * which the waveform never moves, zooms, or changes, but some + * graphic such as a panner outline is frequently redrawn over the + * waveform. This situation would necessitate a lot of waveform + * refresh if the default cacheing strategy was used. + * + * The default is not to use aggressive cacheing. + */ + void setAggressiveCacheing(bool); + bool getAggressiveCacheing() const { return m_aggressive; } + + virtual bool isLayerScrollable(const View *) const; + + virtual int getCompletion(View *) const; + + virtual bool getValueExtents(float &min, float &max, + bool &log, QString &unit) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + void setProperties(const QXmlAttributes &attributes); + + virtual int getVerticalZoomSteps(int &defaultStep) const; + virtual int getCurrentVerticalZoomStep() const; + virtual void setVerticalZoomStep(int); + +protected: + int dBscale(float sample, int m) const; + + const RangeSummarisableTimeValueModel *m_model; // I do not own this + + /// Return value is number of channels displayed + size_t getChannelArrangement(size_t &min, size_t &max, + bool &merging, bool &mixing) const; + + int getYForValue(View *v, Scale scale, float value, size_t channel, + size_t minChannel, size_t maxChannel) const; + + float m_gain; + bool m_autoNormalize; + QColor m_colour; + bool m_showMeans; + bool m_greyscale; + ChannelMode m_channelMode; + int m_channel; + Scale m_scale; + bool m_aggressive; + + mutable std::vector m_effectiveGains; + + mutable QPixmap *m_cache; + mutable bool m_cacheValid; + mutable int m_cacheZoomLevel; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a layer/layer.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/layer.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,45 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = fftw3f +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += xml + +TARGET = svlayer + +DEPENDPATH += . .. +INCLUDEPATH += . .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += Colour3DPlotLayer.h \ + ColourMapper.h \ + Layer.h \ + LayerFactory.h \ + NoteLayer.h \ + PaintAssistant.h \ + SpectrogramLayer.h \ + SpectrumLayer.h \ + SliceLayer.h \ + SliceableLayer.h \ + TextLayer.h \ + TimeInstantLayer.h \ + TimeRulerLayer.h \ + TimeValueLayer.h \ + WaveformLayer.h +SOURCES += Colour3DPlotLayer.cpp \ + ColourMapper.cpp \ + Layer.cpp \ + LayerFactory.cpp \ + NoteLayer.cpp \ + PaintAssistant.cpp \ + SpectrogramLayer.cpp \ + SpectrumLayer.cpp \ + SliceLayer.cpp \ + TextLayer.cpp \ + TimeInstantLayer.cpp \ + TimeRulerLayer.cpp \ + TimeValueLayer.cpp \ + WaveformLayer.cpp diff -r 000000000000 -r fc9323a41f5a layer/svlayer.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/svlayer.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a plugin/DSSIPluginFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/DSSIPluginFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,420 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include "DSSIPluginFactory.h" +#include + +#include + +#include "DSSIPluginInstance.h" +#include "PluginIdentifier.h" + +//!!! +#include "plugin/plugins/SamplePlayer.h" + +#include "system/System.h" + +#ifdef HAVE_LRDF +#include "lrdf.h" +#endif // HAVE_LRDF + + +DSSIPluginFactory::DSSIPluginFactory() : + LADSPAPluginFactory() +{ + m_hostDescriptor.DSSI_API_Version = 2; + m_hostDescriptor.request_transport_information = NULL; + m_hostDescriptor.request_midi_send = DSSIPluginInstance::requestMidiSend; + m_hostDescriptor.request_non_rt_thread = DSSIPluginInstance::requestNonRTThread; + m_hostDescriptor.midi_send = DSSIPluginInstance::midiSend; +} + +DSSIPluginFactory::~DSSIPluginFactory() +{ + // nothing else to do here either +} + +void +DSSIPluginFactory::enumeratePlugins(std::vector &list) +{ + for (std::vector::iterator i = m_identifiers.begin(); + i != m_identifiers.end(); ++i) { + + const DSSI_Descriptor *ddesc = getDSSIDescriptor(*i); + if (!ddesc) continue; + + const LADSPA_Descriptor *descriptor = ddesc->LADSPA_Plugin; + if (!descriptor) continue; + +// std::cerr << "DSSIPluginFactory::enumeratePlugins: Name " << (descriptor->Name ? descriptor->Name : "NONE" ) << std::endl; + + list.push_back(*i); + list.push_back(descriptor->Name); + list.push_back(QString("%1").arg(descriptor->UniqueID)); + list.push_back(descriptor->Label); + list.push_back(descriptor->Maker); + list.push_back(descriptor->Copyright); + list.push_back((ddesc->run_synth || ddesc->run_multiple_synths) ? "true" : "false"); + list.push_back(ddesc->run_multiple_synths ? "true" : "false"); + list.push_back(m_taxonomy[*i]); + list.push_back(QString("%1").arg(descriptor->PortCount)); + + for (unsigned long p = 0; p < descriptor->PortCount; ++p) { + + int type = 0; + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) { + type |= PortType::Control; + } else { + type |= PortType::Audio; + } + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) { + type |= PortType::Input; + } else { + type |= PortType::Output; + } + + list.push_back(QString("%1").arg(p)); + list.push_back(descriptor->PortNames[p]); + list.push_back(QString("%1").arg(type)); + list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p))); + list.push_back(QString("%1").arg(getPortMinimum(descriptor, p))); + list.push_back(QString("%1").arg(getPortMaximum(descriptor, p))); + list.push_back(QString("%1").arg(getPortDefault(descriptor, p))); + } + } + + unloadUnusedLibraries(); +} + +RealTimePluginInstance * +DSSIPluginFactory::instantiatePlugin(QString identifier, + int instrument, + int position, + unsigned int sampleRate, + unsigned int blockSize, + unsigned int channels) +{ + const DSSI_Descriptor *descriptor = getDSSIDescriptor(identifier); + + if (descriptor) { + + DSSIPluginInstance *instance = + new DSSIPluginInstance + (this, instrument, identifier, position, sampleRate, blockSize, channels, + descriptor); + + m_instances.insert(instance); + + return instance; + } + + return 0; +} + +const DSSI_Descriptor * +DSSIPluginFactory::getDSSIDescriptor(QString identifier) +{ + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + + if (soname == PluginIdentifier::BUILTIN_PLUGIN_SONAME) { + if (label == "sample_player") { + const DSSI_Descriptor *descriptor = SamplePlayer::getDescriptor(0); + if (descriptor) { + descriptor->receive_host_descriptor(&m_hostDescriptor); + } + return descriptor; + } else { + return 0; + } + } + + bool firstInLibrary = false; + + if (m_libraryHandles.find(soname) == m_libraryHandles.end()) { + loadLibrary(soname); + if (m_libraryHandles.find(soname) == m_libraryHandles.end()) { + std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: loadLibrary failed for " << soname.toStdString() << std::endl; + return 0; + } + firstInLibrary = true; + } + + void *libraryHandle = m_libraryHandles[soname]; + + DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function) + DLSYM(libraryHandle, "dssi_descriptor"); + + if (!fn) { + std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No descriptor function in library " << soname.toStdString() << std::endl; + return 0; + } + + const DSSI_Descriptor *descriptor = 0; + + int index = 0; + while ((descriptor = fn(index))) { + if (descriptor->LADSPA_Plugin->Label == label) { + if (firstInLibrary && (descriptor->DSSI_API_Version >= 2)) { + descriptor->receive_host_descriptor(&m_hostDescriptor); + } + return descriptor; + } + ++index; + } + + std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No such plugin as " << label.toStdString() << " in library " << soname.toStdString() << std::endl; + + return 0; +} + +const LADSPA_Descriptor * +DSSIPluginFactory::getLADSPADescriptor(QString identifier) +{ + const DSSI_Descriptor *dssiDescriptor = getDSSIDescriptor(identifier); + if (dssiDescriptor) return dssiDescriptor->LADSPA_Plugin; + else return 0; +} + + +std::vector +DSSIPluginFactory::getPluginPath() +{ + std::vector pathList; + std::string path; + + char *cpath = getenv("DSSI_PATH"); + if (cpath) path = cpath; + + if (path == "") { + + path = DEFAULT_DSSI_PATH; + + char *home = getenv("HOME"); + if (home) { + std::string::size_type f; + while ((f = path.find("$HOME")) != std::string::npos && + f < path.length()) { + path.replace(f, 5, home); + } + } + +#ifdef _WIN32 + char *pfiles = getenv("ProgramFiles"); + if (!pfiles) pfiles = "C:\\Program Files"; + { + std::string::size_type f; + while ((f = path.find("%ProgramFiles%")) != std::string::npos && + f < path.length()) { + path.replace(f, 14, pfiles); + } + } +#endif + } + + std::string::size_type index = 0, newindex = 0; + + while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) { + pathList.push_back(path.substr(index, newindex - index).c_str()); + index = newindex + 1; + } + + pathList.push_back(path.substr(index).c_str()); + + return pathList; +} + + +std::vector +DSSIPluginFactory::getLRDFPath(QString &baseUri) +{ + std::vector lrdfPaths; + +#ifdef HAVE_LRDF + std::vector pathList = getPluginPath(); + + lrdfPaths.push_back("/usr/local/share/dssi/rdf"); + lrdfPaths.push_back("/usr/share/dssi/rdf"); + + lrdfPaths.push_back("/usr/local/share/ladspa/rdf"); + lrdfPaths.push_back("/usr/share/ladspa/rdf"); + + for (std::vector::iterator i = pathList.begin(); + i != pathList.end(); ++i) { + lrdfPaths.push_back(*i + "/rdf"); + } + +#ifdef DSSI_BASE + baseUri = DSSI_BASE; +#else + baseUri = "http://dssi.sourceforge.net/ontology#"; +#endif +#endif + + return lrdfPaths; +} + + +void +DSSIPluginFactory::discoverPlugins(QString soname) +{ + // Note that soname is expected to be a full path at this point, + // of a file that is known to exist + + void *libraryHandle = DLOPEN(soname, RTLD_LAZY); + + if (!libraryHandle) { + std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: couldn't load plugin library " + << soname.toStdString() << " - " << DLERROR() << std::endl; + return; + } + + DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function) + DLSYM(libraryHandle, "dssi_descriptor"); + + if (!fn) { + std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No descriptor function in " << soname.toStdString() << std::endl; + return; + } + + const DSSI_Descriptor *descriptor = 0; + + int index = 0; + while ((descriptor = fn(index))) { + + const LADSPA_Descriptor *ladspaDescriptor = descriptor->LADSPA_Plugin; + if (!ladspaDescriptor) { + std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No LADSPA descriptor for plugin " << index << " in " << soname.toStdString() << std::endl; + ++index; + continue; + } + + RealTimePluginDescriptor *rtd = new RealTimePluginDescriptor; + rtd->name = ladspaDescriptor->Name; + rtd->label = ladspaDescriptor->Label; + rtd->maker = ladspaDescriptor->Maker; + rtd->copyright = ladspaDescriptor->Copyright; + rtd->category = ""; + rtd->isSynth = (descriptor->run_synth || + descriptor->run_multiple_synths); + rtd->parameterCount = 0; + rtd->audioInputPortCount = 0; + rtd->audioOutputPortCount = 0; + rtd->controlOutputPortCount = 0; + + QString identifier = PluginIdentifier::createIdentifier + ("dssi", soname, ladspaDescriptor->Label); + +#ifdef HAVE_LRDF + char *def_uri = 0; + lrdf_defaults *defs = 0; + + QString category = m_taxonomy[identifier]; + + if (category == "" && m_lrdfTaxonomy[ladspaDescriptor->UniqueID] != "") { + m_taxonomy[identifier] = m_lrdfTaxonomy[ladspaDescriptor->UniqueID]; + category = m_taxonomy[identifier]; + } + + if (category == "" && ladspaDescriptor->Name != 0) { + std::string name = ladspaDescriptor->Name; + if (name.length() > 4 && + name.substr(name.length() - 4) == " VST") { + if (descriptor->run_synth || descriptor->run_multiple_synths) { + category = "VST instruments"; + } else { + category = "VST effects"; + } + m_taxonomy[identifier] = category; + } + } + + rtd->category = category.toStdString(); + +// std::cerr << "Plugin id is " << ladspaDescriptor->UniqueID +// << ", identifier is \"" << identifier.toStdString() +// << "\", category is \"" << category.toStdString() +// << "\", name is " << ladspaDescriptor->Name +// << ", label is " << ladspaDescriptor->Label +// << std::endl; + + def_uri = lrdf_get_default_uri(ladspaDescriptor->UniqueID); + if (def_uri) { + defs = lrdf_get_setting_values(def_uri); + } + + unsigned int controlPortNumber = 1; + + for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) { + + if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) { + + if (def_uri && defs) { + + for (unsigned int j = 0; j < defs->count; j++) { + if (defs->items[j].pid == controlPortNumber) { +// std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << ladspaDescriptor->PortNames[i] << std::endl; + m_portDefaults[ladspaDescriptor->UniqueID][i] = + defs->items[j].value; + } + } + } + + ++controlPortNumber; + } + } +#endif // HAVE_LRDF + + for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) { + if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) { + if (LADSPA_IS_PORT_INPUT(ladspaDescriptor->PortDescriptors[i])) { + ++rtd->parameterCount; + } else { + if (strcmp(ladspaDescriptor->PortNames[i], "latency") && + strcmp(ladspaDescriptor->PortNames[i], "_latency")) { + ++rtd->controlOutputPortCount; + rtd->controlOutputPortNames.push_back + (ladspaDescriptor->PortNames[i]); + } + } + } else { + if (LADSPA_IS_PORT_INPUT(ladspaDescriptor->PortDescriptors[i])) { + ++rtd->audioInputPortCount; + } else if (LADSPA_IS_PORT_OUTPUT(ladspaDescriptor->PortDescriptors[i])) { + ++rtd->audioOutputPortCount; + } + } + } + + m_identifiers.push_back(identifier); + + m_rtDescriptors[identifier] = rtd; + + ++index; + } + + if (DLCLOSE(libraryHandle) != 0) { + std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl; + return; + } +} + + + diff -r 000000000000 -r fc9323a41f5a plugin/DSSIPluginFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/DSSIPluginFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,64 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _DSSI_PLUGIN_FACTORY_H_ +#define _DSSI_PLUGIN_FACTORY_H_ + +#define DSSI_API_LEVEL 2 + +#include "LADSPAPluginFactory.h" +#include "api/dssi.h" + +#include + +class DSSIPluginInstance; + +class DSSIPluginFactory : public LADSPAPluginFactory +{ +public: + virtual ~DSSIPluginFactory(); + + virtual void enumeratePlugins(std::vector &list); + + virtual RealTimePluginInstance *instantiatePlugin(QString identifier, + int clientId, + int position, + unsigned int sampleRate, + unsigned int blockSize, + unsigned int channels); + +protected: + DSSIPluginFactory(); + friend class RealTimePluginFactory; + + virtual std::vector getPluginPath(); + + virtual std::vector getLRDFPath(QString &baseUri); + + virtual void discoverPlugins(QString soName); + + virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier); + virtual const DSSI_Descriptor *getDSSIDescriptor(QString identifier); + + DSSI_Host_Descriptor m_hostDescriptor; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a plugin/DSSIPluginInstance.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/DSSIPluginInstance.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1313 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include +#include + +#include "DSSIPluginInstance.h" +#include "PluginIdentifier.h" +#include "LADSPAPluginFactory.h" + +//#define DEBUG_DSSI 1 +//#define DEBUG_DSSI_PROCESS 1 + +#define EVENT_BUFFER_SIZE 1023 + +#ifdef DEBUG_DSSI +static std::ostream &operator<<(std::ostream& o, const QString &s) +{ + o << s.toLocal8Bit().data(); + return o; +} +#endif + +DSSIPluginInstance::GroupMap DSSIPluginInstance::m_groupMap; +snd_seq_event_t **DSSIPluginInstance::m_groupLocalEventBuffers = 0; +size_t DSSIPluginInstance::m_groupLocalEventBufferCount = 0; +Scavenger > DSSIPluginInstance::m_bufferScavenger(2, 10); +std::map > DSSIPluginInstance::m_threads; + + +DSSIPluginInstance::DSSIPluginInstance(RealTimePluginFactory *factory, + int clientId, + QString identifier, + int position, + unsigned long sampleRate, + size_t blockSize, + int idealChannelCount, + const DSSI_Descriptor* descriptor) : + RealTimePluginInstance(factory, identifier), + m_client(clientId), + m_position(position), + m_descriptor(descriptor), + m_programCacheValid(false), + m_eventBuffer(EVENT_BUFFER_SIZE), + m_blockSize(blockSize), + m_idealChannelCount(idealChannelCount), + m_sampleRate(sampleRate), + m_latencyPort(0), + m_run(false), + m_bypassed(false), + m_grouped(false), + m_haveLastEventSendTime(false) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::DSSIPluginInstance(" << identifier << ")" + << std::endl; +#endif + + init(); + + m_inputBuffers = new sample_t*[m_audioPortsIn.size()]; + m_outputBuffers = new sample_t*[m_outputBufferCount]; + + for (size_t i = 0; i < m_audioPortsIn.size(); ++i) { + m_inputBuffers[i] = new sample_t[blockSize]; + } + for (size_t i = 0; i < m_outputBufferCount; ++i) { + m_outputBuffers[i] = new sample_t[blockSize]; + } + + m_ownBuffers = true; + + m_pending.lsb = m_pending.msb = m_pending.program = -1; + + instantiate(sampleRate); + if (isOK()) { + connectPorts(); + activate(); + initialiseGroupMembership(); + } +} + +std::string +DSSIPluginInstance::getIdentifier() const +{ + return m_descriptor->LADSPA_Plugin->Label; +} + +std::string +DSSIPluginInstance::getName() const +{ + return m_descriptor->LADSPA_Plugin->Name; +} + +std::string +DSSIPluginInstance::getDescription() const +{ + return ""; +} + +std::string +DSSIPluginInstance::getMaker() const +{ + return m_descriptor->LADSPA_Plugin->Maker; +} + +int +DSSIPluginInstance::getPluginVersion() const +{ + return 1; +} + +std::string +DSSIPluginInstance::getCopyright() const +{ + return m_descriptor->LADSPA_Plugin->Copyright; +} + +DSSIPluginInstance::ParameterList +DSSIPluginInstance::getParameterDescriptors() const +{ + ParameterList list; + LADSPAPluginFactory *f = dynamic_cast(m_factory); + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + + ParameterDescriptor pd; + unsigned int pn = m_controlPortsIn[i].first; + + pd.identifier = m_descriptor->LADSPA_Plugin->PortNames[pn]; + pd.name = pd.identifier; + pd.description = ""; + pd.minValue = f->getPortMinimum(m_descriptor->LADSPA_Plugin, pn); + pd.maxValue = f->getPortMaximum(m_descriptor->LADSPA_Plugin, pn); + pd.defaultValue = f->getPortDefault(m_descriptor->LADSPA_Plugin, pn); + + float q = f->getPortQuantization(m_descriptor->LADSPA_Plugin, pn); + if (q == 0.0) { + pd.isQuantized = false; + } else { + pd.isQuantized = true; + pd.quantizeStep = q; + } + + list.push_back(pd); + } + + return list; +} + +float +DSSIPluginInstance::getParameter(std::string id) const +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::getParameter(" << id << ")" << std::endl; +#endif + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + if (id == m_descriptor->LADSPA_Plugin->PortNames[m_controlPortsIn[i].first]) { +#ifdef DEBUG_DSSI + std::cerr << "Matches port " << i << std::endl; +#endif + float v = getParameterValue(i); +#ifdef DEBUG_DSSI + std::cerr << "Returning " << v << std::endl; +#endif + return v; + } + } + + return 0.0; +} + +void +DSSIPluginInstance::setParameter(std::string id, float value) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::setParameter(" << id << ", " << value << ")" << std::endl; +#endif + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + if (id == m_descriptor->LADSPA_Plugin->PortNames[m_controlPortsIn[i].first]) { + setParameterValue(i, value); + break; + } + } +} + +void +DSSIPluginInstance::init() +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::init" << std::endl; +#endif + + // Discover ports numbers and identities + // + const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin; + + for (unsigned long i = 0; i < descriptor->PortCount; ++i) + { + if (LADSPA_IS_PORT_AUDIO(descriptor->PortDescriptors[i])) + { + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) { + m_audioPortsIn.push_back(i); + } else { + m_audioPortsOut.push_back(i); + } + } + else + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) + { + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) { + + LADSPA_Data *data = new LADSPA_Data(0.0); + + m_controlPortsIn.push_back(std::pair + (i, data)); + + m_backupControlPortsIn.push_back(0.0); + + } else { + LADSPA_Data *data = new LADSPA_Data(0.0); + m_controlPortsOut.push_back( + std::pair(i, data)); + if (!strcmp(descriptor->PortNames[i], "latency") || + !strcmp(descriptor->PortNames[i], "_latency")) { +#ifdef DEBUG_DSSI + std::cerr << "Wooo! We have a latency port!" << std::endl; +#endif + m_latencyPort = data; + } + } + } +#ifdef DEBUG_DSSI + else + std::cerr << "DSSIPluginInstance::DSSIPluginInstance - " + << "unrecognised port type" << std::endl; +#endif + } + + m_outputBufferCount = max(m_idealChannelCount, m_audioPortsOut.size()); +} + +size_t +DSSIPluginInstance::getLatency() +{ + size_t latency = 0; + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::getLatency(): m_latencyPort " << m_latencyPort << ", m_run " << m_run << std::endl; +#endif + + if (m_latencyPort) { + if (!m_run) { + for (size_t i = 0; i < getAudioInputCount(); ++i) { + for (size_t j = 0; j < m_blockSize; ++j) { + m_inputBuffers[i][j] = 0.f; + } + } + run(Vamp::RealTime::zeroTime); + } + latency = (size_t)(*m_latencyPort + 0.1); + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::getLatency(): latency is " << latency << std::endl; +#endif + + return latency; +} + +void +DSSIPluginInstance::silence() +{ + if (m_instanceHandle != 0) { + deactivate(); + activate(); + } +} + +void +DSSIPluginInstance::discardEvents() +{ + m_eventBuffer.reset(); +} + +void +DSSIPluginInstance::setIdealChannelCount(size_t channels) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::setIdealChannelCount: channel count " + << channels << " (was " << m_idealChannelCount << ")" << std::endl; +#endif + + if (channels == m_idealChannelCount) { + silence(); + return; + } + + if (m_instanceHandle != 0) { + deactivate(); + } + + m_idealChannelCount = channels; + + if (channels > m_outputBufferCount) { + + for (size_t i = 0; i < m_outputBufferCount; ++i) { + delete[] m_outputBuffers[i]; + } + + delete[] m_outputBuffers; + + m_outputBufferCount = channels; + + m_outputBuffers = new sample_t*[m_outputBufferCount]; + + for (size_t i = 0; i < m_outputBufferCount; ++i) { + m_outputBuffers[i] = new sample_t[m_blockSize]; + } + + connectPorts(); + } + + if (m_instanceHandle != 0) { + activate(); + } +} + +void +DSSIPluginInstance::detachFromGroup() +{ + if (!m_grouped) return; + m_groupMap[m_identifier].erase(this); + m_grouped = false; +} + +void +DSSIPluginInstance::initialiseGroupMembership() +{ + if (!m_descriptor->run_multiple_synths) { + m_grouped = false; + return; + } + + //!!! GroupMap is not actually thread-safe. + + size_t pluginsInGroup = m_groupMap[m_identifier].size(); + + if (++pluginsInGroup > m_groupLocalEventBufferCount) { + + size_t nextBufferCount = pluginsInGroup * 2; + + snd_seq_event_t **eventLocalBuffers = new snd_seq_event_t *[nextBufferCount]; + + for (size_t i = 0; i < m_groupLocalEventBufferCount; ++i) { + eventLocalBuffers[i] = m_groupLocalEventBuffers[i]; + } + for (size_t i = m_groupLocalEventBufferCount; i < nextBufferCount; ++i) { + eventLocalBuffers[i] = new snd_seq_event_t[EVENT_BUFFER_SIZE]; + } + + if (m_groupLocalEventBuffers) { + m_bufferScavenger.claim(new ScavengerArrayWrapper + (m_groupLocalEventBuffers)); + } + + m_groupLocalEventBuffers = eventLocalBuffers; + m_groupLocalEventBufferCount = nextBufferCount; + } + + m_grouped = true; + m_groupMap[m_identifier].insert(this); +} + +DSSIPluginInstance::~DSSIPluginInstance() +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::~DSSIPluginInstance" << std::endl; +#endif + + if (m_threads.find(m_instanceHandle) != m_threads.end()) { + + for (std::set::iterator i = + m_threads[m_instanceHandle].begin(); + i != m_threads[m_instanceHandle].end(); ++i) { + + (*i)->setExiting(); + (*i)->wait(); + delete *i; + } + + m_threads.erase(m_instanceHandle); + } + + detachFromGroup(); + + if (m_instanceHandle != 0) { + deactivate(); + } + + cleanup(); + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) + delete m_controlPortsIn[i].second; + + for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) + delete m_controlPortsOut[i].second; + + m_controlPortsIn.clear(); + m_controlPortsOut.clear(); + + if (m_ownBuffers) { + for (size_t i = 0; i < m_audioPortsIn.size(); ++i) { + delete[] m_inputBuffers[i]; + } + for (size_t i = 0; i < m_outputBufferCount; ++i) { + delete[] m_outputBuffers[i]; + } + + delete[] m_inputBuffers; + delete[] m_outputBuffers; + } + + m_audioPortsIn.clear(); + m_audioPortsOut.clear(); +} + + +void +DSSIPluginInstance::instantiate(unsigned long sampleRate) +{ +#ifdef DEBUG_DSSI + std::cout << "DSSIPluginInstance::instantiate - plugin \"unique\" id = " + << m_descriptor->LADSPA_Plugin->UniqueID << std::endl; +#endif + if (!m_descriptor) return; + + const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin; + + if (!descriptor->instantiate) { + std::cerr << "Bad plugin: plugin id " << descriptor->UniqueID + << ":" << descriptor->Label + << " has no instantiate method!" << std::endl; + return; + } + + m_instanceHandle = descriptor->instantiate(descriptor, sampleRate); + + if (m_instanceHandle) { + + if (m_descriptor->get_midi_controller_for_port) { + + for (unsigned long i = 0; i < descriptor->PortCount; ++i) { + + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) && + LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) { + + int controller = m_descriptor->get_midi_controller_for_port + (m_instanceHandle, i); + + if (controller != 0 && controller != 32 && + DSSI_IS_CC(controller)) { + + m_controllerMap[DSSI_CC_NUMBER(controller)] = i; + } + } + } + } + } +} + +void +DSSIPluginInstance::checkProgramCache() const +{ + if (m_programCacheValid) return; + m_cachedPrograms.clear(); + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::checkProgramCache" << std::endl; +#endif + + if (!m_descriptor || !m_descriptor->get_program) { + m_programCacheValid = true; + return; + } + + unsigned long index = 0; + const DSSI_Program_Descriptor *programDescriptor; + while ((programDescriptor = m_descriptor->get_program(m_instanceHandle, index))) { + ++index; + ProgramDescriptor d; + d.bank = programDescriptor->Bank; + d.program = programDescriptor->Program; + d.name = programDescriptor->Name; + m_cachedPrograms.push_back(d); + } + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::checkProgramCache: have " << m_cachedPrograms.size() << " programs" << std::endl; +#endif + + m_programCacheValid = true; +} + +DSSIPluginInstance::ProgramList +DSSIPluginInstance::getPrograms() const +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::getPrograms" << std::endl; +#endif + + if (!m_descriptor) return ProgramList(); + + checkProgramCache(); + + ProgramList programs; + + for (std::vector::iterator i = m_cachedPrograms.begin(); + i != m_cachedPrograms.end(); ++i) { + programs.push_back(i->name); + } + + return programs; +} + +std::string +DSSIPluginInstance::getProgram(int bank, int program) const +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::getProgram(" << bank << "," << program << ")" << std::endl; +#endif + + if (!m_descriptor) return std::string(); + + checkProgramCache(); + + for (std::vector::iterator i = m_cachedPrograms.begin(); + i != m_cachedPrograms.end(); ++i) { + if (i->bank == bank && i->program == program) return i->name; + } + + return std::string(); +} + +unsigned long +DSSIPluginInstance::getProgram(std::string name) const +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::getProgram(" << name << ")" << std::endl; +#endif + + if (!m_descriptor) return 0; + + checkProgramCache(); + + unsigned long rv; + + for (std::vector::iterator i = m_cachedPrograms.begin(); + i != m_cachedPrograms.end(); ++i) { + if (i->name == name) { + rv = i->bank; + rv = (rv << 16) + i->program; + return rv; + } + } + + return 0; +} + +std::string +DSSIPluginInstance::getCurrentProgram() const +{ + return m_program; +} + +void +DSSIPluginInstance::selectProgram(std::string program) +{ + selectProgramAux(program, true); +} + +void +DSSIPluginInstance::selectProgramAux(std::string program, bool backupPortValues) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::selectProgram(" << program << ")" << std::endl; +#endif + + if (!m_descriptor) return; + + checkProgramCache(); + + if (!m_descriptor->select_program) return; + + bool found = false; + unsigned long bankNo = 0, programNo = 0; + + for (std::vector::iterator i = m_cachedPrograms.begin(); + i != m_cachedPrograms.end(); ++i) { + + if (i->name == program) { + + bankNo = i->bank; + programNo = i->program; + found = true; + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): found at bank " << bankNo << ", program " << programNo << std::endl; +#endif + + break; + } + } + + if (!found) return; + m_program = program; + + // DSSI select_program is an audio context call + m_processLock.lock(); + m_descriptor->select_program(m_instanceHandle, bankNo, programNo); + m_processLock.unlock(); + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): made select_program(" << bankNo << "," << programNo << ") call" << std::endl; +#endif + + if (backupPortValues) { + for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) { + m_backupControlPortsIn[i] = *m_controlPortsIn[i].second; + } + } +} + +void +DSSIPluginInstance::activate() +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::activate" << std::endl; +#endif + + if (!m_descriptor || !m_descriptor->LADSPA_Plugin->activate) return; + m_descriptor->LADSPA_Plugin->activate(m_instanceHandle); + + if (m_program != "") { +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::activate: restoring program " << m_program << std::endl; +#endif + selectProgramAux(m_program, false); + } + + for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) { +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << std::endl; +#endif + *m_controlPortsIn[i].second = m_backupControlPortsIn[i]; + } +} + +void +DSSIPluginInstance::connectPorts() +{ + if (!m_descriptor || !m_descriptor->LADSPA_Plugin->connect_port) return; +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::connectPorts: " << m_audioPortsIn.size() + << " audio ports in, " << m_audioPortsOut.size() << " out, " + << m_outputBufferCount << " output buffers" << std::endl; +#endif + + assert(sizeof(LADSPA_Data) == sizeof(float)); + assert(sizeof(sample_t) == sizeof(float)); + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + int inbuf = 0, outbuf = 0; + + for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) { + m_descriptor->LADSPA_Plugin->connect_port + (m_instanceHandle, + m_audioPortsIn[i], + (LADSPA_Data *)m_inputBuffers[inbuf]); + ++inbuf; + } + + for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) { + m_descriptor->LADSPA_Plugin->connect_port + (m_instanceHandle, + m_audioPortsOut[i], + (LADSPA_Data *)m_outputBuffers[outbuf]); + ++outbuf; + } + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + m_descriptor->LADSPA_Plugin->connect_port + (m_instanceHandle, + m_controlPortsIn[i].first, + m_controlPortsIn[i].second); + + if (f) { + float defaultValue = f->getPortDefault + (m_descriptor->LADSPA_Plugin, m_controlPortsIn[i].first); + *m_controlPortsIn[i].second = defaultValue; + m_backupControlPortsIn[i] = defaultValue; +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::connectPorts: set control port " << i << " to default value " << defaultValue << std::endl; +#endif + } + } + + for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) { + m_descriptor->LADSPA_Plugin->connect_port + (m_instanceHandle, + m_controlPortsOut[i].first, + m_controlPortsOut[i].second); + } +} + +unsigned int +DSSIPluginInstance::getParameterCount() const +{ + return m_controlPortsIn.size(); +} + +void +DSSIPluginInstance::setParameterValue(unsigned int parameter, float value) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::setParameterValue(" << parameter << ") to " << value << std::endl; +#endif + if (parameter >= m_controlPortsIn.size()) return; + + unsigned int portNumber = m_controlPortsIn[parameter].first; + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + if (f) { + if (value < f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber)) { + value = f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber); + } + if (value > f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber)) { + value = f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber); + } + } + + (*m_controlPortsIn[parameter].second) = value; + m_backupControlPortsIn[parameter] = value; +} + +void +DSSIPluginInstance::setPortValueFromController(unsigned int port, int cv) +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::setPortValueFromController(" << port << ") to " << cv << std::endl; +#endif + + const LADSPA_Descriptor *p = m_descriptor->LADSPA_Plugin; + LADSPA_PortRangeHintDescriptor d = p->PortRangeHints[port].HintDescriptor; + LADSPA_Data lb = p->PortRangeHints[port].LowerBound; + LADSPA_Data ub = p->PortRangeHints[port].UpperBound; + + float value = (float)cv; + + if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) { + if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) { + /* unbounded: might as well leave the value alone. */ + } else { + /* bounded above only. just shift the range. */ + value = ub - 127.0f + value; + } + } else { + if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) { + /* bounded below only. just shift the range. */ + value = lb + value; + } else { + /* bounded both ends. more interesting. */ + /* XXX !!! todo: fill in logarithmic, sample rate &c */ + value = lb + ((ub - lb) * value / 127.0f); + } + } + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + if (m_controlPortsIn[i].first == port) { + setParameterValue(i, value); + } + } +} + +float +DSSIPluginInstance::getControlOutputValue(size_t output) const +{ + if (output > m_controlPortsOut.size()) return 0.0; + return (*m_controlPortsOut[output].second); +} + +float +DSSIPluginInstance::getParameterValue(unsigned int parameter) const +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::getParameterValue(" << parameter << ")" << std::endl; +#endif + if (parameter >= m_controlPortsIn.size()) return 0.0; + return (*m_controlPortsIn[parameter].second); +} + +float +DSSIPluginInstance::getParameterDefault(unsigned int parameter) const +{ + if (parameter >= m_controlPortsIn.size()) return 0.0; + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + if (f) { + return f->getPortDefault(m_descriptor->LADSPA_Plugin, + m_controlPortsIn[parameter].first); + } else { + return 0.0f; + } +} + +std::string +DSSIPluginInstance::configure(std::string key, + std::string value) +{ + if (!m_descriptor || !m_descriptor->configure) return std::string(); + + if (key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY.toStdString()) { +#ifdef DSSI_PROJECT_DIRECTORY_KEY + key = DSSI_PROJECT_DIRECTORY_KEY; +#else + return std::string(); +#endif + } + + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::configure(" << key << "," << value << ")" << std::endl; +#endif + + char *message = m_descriptor->configure(m_instanceHandle, + key.c_str(), + value.c_str()); + + m_programCacheValid = false; + + m_configurationData[key] = value; + + std::string qm; + + // Ignore return values from reserved key configuration calls such + // as project directory +#ifdef DSSI_RESERVED_CONFIGURE_PREFIX + if (QString(key.c_str()).startsWith(DSSI_RESERVED_CONFIGURE_PREFIX)) { + return qm; + } +#endif + + if (message) { + if (m_descriptor->LADSPA_Plugin && m_descriptor->LADSPA_Plugin->Label) { + qm = std::string(m_descriptor->LADSPA_Plugin->Label) + ": "; + } + qm = qm + message; + free(message); + + std::cerr << "DSSIPluginInstance::configure: warning: configure returned message: \"" << qm << "\"" << std::endl; + } + + return qm; +} + +void +DSSIPluginInstance::sendEvent(const Vamp::RealTime &eventTime, + const void *e) +{ +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::sendEvent: last was " << m_lastEventSendTime << " (valid " << m_haveLastEventSendTime << "), this is " << eventTime << std::endl; +#endif + + // The process mechanism only works correctly if the events are + // sorted. It's the responsibility of the caller to ensure that: + // we will happily drop events here if we find the timeline going + // backwards. + if (m_haveLastEventSendTime && + m_lastEventSendTime > eventTime) { +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "... clearing down" << std::endl; +#endif + m_haveLastEventSendTime = false; + clearEvents(); + } + + snd_seq_event_t *event = (snd_seq_event_t *)e; +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::sendEvent at " << eventTime << std::endl; +#endif + snd_seq_event_t ev(*event); + + ev.time.time.tv_sec = eventTime.sec; + ev.time.time.tv_nsec = eventTime.nsec; + + // DSSI doesn't use MIDI channels, it uses run_multiple_synths instead. + ev.data.note.channel = 0; + + m_eventBuffer.write(&ev, 1); + + m_lastEventSendTime = eventTime; + m_haveLastEventSendTime = true; +} + +void +DSSIPluginInstance::clearEvents() +{ + m_haveLastEventSendTime = false; + m_eventBuffer.reset(); +} + +bool +DSSIPluginInstance::handleController(snd_seq_event_t *ev) +{ + int controller = ev->data.control.param; + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::handleController " << controller << std::endl; +#endif + + if (controller == 0) { // bank select MSB + + m_pending.msb = ev->data.control.value; + + } else if (controller == 32) { // bank select LSB + + m_pending.lsb = ev->data.control.value; + + } else if (controller > 0 && controller < 128) { + + if (m_controllerMap.find(controller) != m_controllerMap.end()) { + int port = m_controllerMap[controller]; + setPortValueFromController(port, ev->data.control.value); + } else { + return true; // pass through to plugin + } + } + + return false; +} + +void +DSSIPluginInstance::run(const Vamp::RealTime &blockTime) +{ + static snd_seq_event_t localEventBuffer[EVENT_BUFFER_SIZE]; + int evCount = 0; + + bool needLock = false; + if (m_descriptor->select_program) needLock = true; + + if (needLock) { + if (!m_processLock.tryLock()) { + for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) { + memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t)); + } + return; + } + } + + if (m_grouped) { + runGrouped(blockTime); + goto done; + } + + if (!m_descriptor || !m_descriptor->run_synth) { + m_eventBuffer.skip(m_eventBuffer.getReadSpace()); + m_haveLastEventSendTime = false; + if (m_descriptor->LADSPA_Plugin->run) { + m_descriptor->LADSPA_Plugin->run(m_instanceHandle, m_blockSize); + } else { + for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) { + memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t)); + } + } + m_run = true; + if (needLock) m_processLock.unlock(); + return; + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::run(" << blockTime << ")" << std::endl; +#endif + +#ifdef DEBUG_DSSI_PROCESS + if (m_eventBuffer.getReadSpace() > 0) { + std::cerr << "DSSIPluginInstance::run: event buffer has " + << m_eventBuffer.getReadSpace() << " event(s) in it" << std::endl; + } +#endif + + while (m_eventBuffer.getReadSpace() > 0) { + + snd_seq_event_t *ev = localEventBuffer + evCount; + *ev = m_eventBuffer.peekOne(); + bool accept = true; + + Vamp::RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec); + + int frameOffset = 0; + if (evTime > blockTime) { + frameOffset = Vamp::RealTime::realTime2Frame(evTime - blockTime, m_sampleRate); + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::run: evTime " << evTime << ", blockTime " << blockTime << ", frameOffset " << frameOffset + << ", blockSize " << m_blockSize << std::endl; + std::cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << std::endl; +#endif + + if (frameOffset >= int(m_blockSize)) break; + if (frameOffset < 0) { + frameOffset = 0; + if (ev->type == SND_SEQ_EVENT_NOTEON) { + m_eventBuffer.skip(1); + continue; + } + } + + ev->time.tick = frameOffset; + m_eventBuffer.skip(1); + + if (ev->type == SND_SEQ_EVENT_CONTROLLER) { + accept = handleController(ev); + } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) { + m_pending.program = ev->data.control.value; + accept = false; + } + + if (accept) { + if (++evCount >= EVENT_BUFFER_SIZE) break; + } + } + + if (m_pending.program >= 0 && m_descriptor->select_program) { + + int program = m_pending.program; + int bank = m_pending.lsb + 128 * m_pending.msb; + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::run: making select_program(" << bank << "," << program << ") call" << std::endl; +#endif + + m_pending.lsb = m_pending.msb = m_pending.program = -1; + m_descriptor->select_program(m_instanceHandle, bank, program); + +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::run: made select_program(" << bank << "," << program << ") call" << std::endl; +#endif + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::run: running with " << evCount << " events" + << std::endl; +#endif + + m_descriptor->run_synth(m_instanceHandle, m_blockSize, + localEventBuffer, evCount); + +#ifdef DEBUG_DSSI_PROCESS +// for (int i = 0; i < m_blockSize; ++i) { +// std::cout << m_outputBuffers[0][i] << " "; +// if (i % 8 == 0) std::cout << std::endl; +// } +#endif + + done: + if (needLock) m_processLock.unlock(); + + if (m_audioPortsOut.size() == 0) { + // copy inputs to outputs + for (size_t ch = 0; ch < m_idealChannelCount; ++ch) { + size_t sch = ch % m_audioPortsIn.size(); + for (size_t i = 0; i < m_blockSize; ++i) { + m_outputBuffers[ch][i] = m_inputBuffers[sch][i]; + } + } + } else if (m_idealChannelCount < m_audioPortsOut.size()) { + if (m_idealChannelCount == 1) { + // mix down to mono + for (size_t ch = 1; ch < m_audioPortsOut.size(); ++ch) { + for (size_t i = 0; i < m_blockSize; ++i) { + m_outputBuffers[0][i] += m_outputBuffers[ch][i]; + } + } + } + } else if (m_idealChannelCount > m_audioPortsOut.size()) { + // duplicate + for (size_t ch = m_audioPortsOut.size(); ch < m_idealChannelCount; ++ch) { + size_t sch = (ch - m_audioPortsOut.size()) % m_audioPortsOut.size(); + for (size_t i = 0; i < m_blockSize; ++i) { + m_outputBuffers[ch][i] = m_outputBuffers[sch][i]; + } + } + } + + m_lastRunTime = blockTime; + m_run = true; +} + +void +DSSIPluginInstance::runGrouped(const Vamp::RealTime &blockTime) +{ + // If something else in our group has just been called for this + // block time (but we haven't) then we should just write out the + // results and return; if we have just been called for this block + // time or nothing else in the group has been, we should run the + // whole group. + + bool needRun = true; + + PluginSet &s = m_groupMap[m_identifier]; + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): this is " << this << "; " << s.size() << " elements in m_groupMap[" << m_identifier << "]" << std::endl; +#endif + + if (m_lastRunTime != blockTime) { + for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) { + DSSIPluginInstance *instance = *i; + if (instance != this && instance->m_lastRunTime == blockTime) { +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): plugin " << instance << " has already been run" << std::endl; +#endif + needRun = false; + } + } + } + + if (!needRun) { +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): already run, returning" << std::endl; +#endif + return; + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): I'm the first, running" << std::endl; +#endif + + size_t index = 0; + unsigned long *counts = (unsigned long *) + alloca(m_groupLocalEventBufferCount * sizeof(unsigned long)); + LADSPA_Handle *instances = (LADSPA_Handle *) + alloca(m_groupLocalEventBufferCount * sizeof(LADSPA_Handle)); + + for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) { + + if (index >= m_groupLocalEventBufferCount) break; + + DSSIPluginInstance *instance = *i; + counts[index] = 0; + instances[index] = instance->m_instanceHandle; + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): running " << instance << std::endl; +#endif + + if (instance->m_pending.program >= 0 && + instance->m_descriptor->select_program) { + int program = instance->m_pending.program; + int bank = instance->m_pending.lsb + 128 * instance->m_pending.msb; + instance->m_pending.lsb = instance->m_pending.msb = instance->m_pending.program = -1; + instance->m_descriptor->select_program + (instance->m_instanceHandle, bank, program); + } + + while (instance->m_eventBuffer.getReadSpace() > 0) { + + snd_seq_event_t *ev = m_groupLocalEventBuffers[index] + counts[index]; + *ev = instance->m_eventBuffer.peekOne(); + bool accept = true; + + Vamp::RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec); + + int frameOffset = 0; + if (evTime > blockTime) { + frameOffset = Vamp::RealTime::realTime2Frame(evTime - blockTime, m_sampleRate); + } + +#ifdef DEBUG_DSSI_PROCESS + std::cerr << "DSSIPluginInstance::runGrouped: evTime " << evTime << ", frameOffset " << frameOffset + << ", block size " << m_blockSize << std::endl; +#endif + + if (frameOffset >= int(m_blockSize)) break; + if (frameOffset < 0) frameOffset = 0; + + ev->time.tick = frameOffset; + instance->m_eventBuffer.skip(1); + + if (ev->type == SND_SEQ_EVENT_CONTROLLER) { + accept = instance->handleController(ev); + } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) { + instance->m_pending.program = ev->data.control.value; + accept = false; + } + + if (accept) { + if (++counts[index] >= EVENT_BUFFER_SIZE) break; + } + } + + ++index; + } + + m_descriptor->run_multiple_synths(index, + instances, + m_blockSize, + m_groupLocalEventBuffers, + counts); +} + +int +DSSIPluginInstance::requestMidiSend(LADSPA_Handle /* instance */, + unsigned char /* ports */, + unsigned char /* channels */) +{ + // This is called from a non-RT context (during instantiate) + + std::cerr << "DSSIPluginInstance::requestMidiSend" << std::endl; + return 1; +} + +void +DSSIPluginInstance::midiSend(LADSPA_Handle /* instance */, + snd_seq_event_t * /* events */, + unsigned long /* eventCount */) +{ + // This is likely to be called from an RT context + + std::cerr << "DSSIPluginInstance::midiSend" << std::endl; +} + +void +DSSIPluginInstance::NonRTPluginThread::run() +{ + while (!m_exiting) { + m_runFunction(m_handle); + usleep(100000); + } +} + +int +DSSIPluginInstance::requestNonRTThread(LADSPA_Handle instance, + void (*runFunction)(LADSPA_Handle)) +{ + NonRTPluginThread *thread = new NonRTPluginThread(instance, runFunction); + m_threads[instance].insert(thread); + thread->start(); + return 0; +} + +void +DSSIPluginInstance::deactivate() +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << std::endl; +#endif + if (!m_descriptor || !m_descriptor->LADSPA_Plugin->deactivate) return; + + for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) { + m_backupControlPortsIn[i] = *m_controlPortsIn[i].second; + } + + m_descriptor->LADSPA_Plugin->deactivate(m_instanceHandle); +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << " done" << std::endl; +#endif + + m_bufferScavenger.scavenge(); +} + +void +DSSIPluginInstance::cleanup() +{ +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << std::endl; +#endif + if (!m_descriptor) return; + + if (!m_descriptor->LADSPA_Plugin->cleanup) { + std::cerr << "Bad plugin: plugin id " + << m_descriptor->LADSPA_Plugin->UniqueID + << ":" << m_descriptor->LADSPA_Plugin->Label + << " has no cleanup method!" << std::endl; + return; + } + + m_descriptor->LADSPA_Plugin->cleanup(m_instanceHandle); + m_instanceHandle = 0; +#ifdef DEBUG_DSSI + std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << " done" << std::endl; +#endif +} + diff -r 000000000000 -r fc9323a41f5a plugin/DSSIPluginInstance.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/DSSIPluginInstance.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,223 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _DSSIPLUGININSTANCE_H_ +#define _DSSIPLUGININSTANCE_H_ + +#define DSSI_API_LEVEL 2 + +#include +#include +#include +#include +#include + +#include "api/dssi.h" + +#include "base/RingBuffer.h" +#include "base/Thread.h" +#include "RealTimePluginInstance.h" +#include "base/Scavenger.h" + +class DSSIPluginInstance : public RealTimePluginInstance +{ +public: + virtual ~DSSIPluginInstance(); + + virtual bool isOK() const { return m_instanceHandle != 0; } + + int getClientId() const { return m_client; } + virtual QString getPluginIdentifier() const { return m_identifier; } + int getPosition() const { return m_position; } + + virtual std::string getIdentifier() const; + virtual std::string getName() const; + virtual std::string getDescription() const; + virtual std::string getMaker() const; + virtual int getPluginVersion() const; + virtual std::string getCopyright() const; + + virtual void run(const Vamp::RealTime &); + + virtual unsigned int getParameterCount() const; + virtual void setParameterValue(unsigned int parameter, float value); + virtual float getParameterValue(unsigned int parameter) const; + virtual float getParameterDefault(unsigned int parameter) const; + + virtual ParameterList getParameterDescriptors() const; + virtual float getParameter(std::string) const; + virtual void setParameter(std::string, float); + + virtual std::string configure(std::string key, std::string value); + virtual void sendEvent(const Vamp::RealTime &eventTime, + const void *event); + virtual void clearEvents(); + + virtual size_t getBufferSize() const { return m_blockSize; } + virtual size_t getAudioInputCount() const { return m_audioPortsIn.size(); } + virtual size_t getAudioOutputCount() const { return m_idealChannelCount; } + virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; } + virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; } + + virtual size_t getControlOutputCount() const { return m_controlPortsOut.size(); } + virtual float getControlOutputValue(size_t n) const; + + virtual ProgramList getPrograms() const; + virtual std::string getCurrentProgram() const; + virtual std::string getProgram(int bank, int program) const; + virtual unsigned long getProgram(std::string name) const; + virtual void selectProgram(std::string program); + + virtual bool isBypassed() const { return m_bypassed; } + virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; } + + virtual size_t getLatency(); + + virtual void silence(); + virtual void discardEvents(); + virtual void setIdealChannelCount(size_t channels); // may re-instantiate + + virtual bool isInGroup() const { return m_grouped; } + virtual void detachFromGroup(); + + virtual std::string getType() const { return "DSSI Real-Time Plugin"; } + +protected: + // To be constructed only by DSSIPluginFactory + friend class DSSIPluginFactory; + + // Constructor that creates the buffers internally + // + DSSIPluginInstance(RealTimePluginFactory *factory, + int client, + QString identifier, + int position, + unsigned long sampleRate, + size_t blockSize, + int idealChannelCount, + const DSSI_Descriptor* descriptor); + + void init(); + void instantiate(unsigned long sampleRate); + void cleanup(); + void activate(); + void deactivate(); + void connectPorts(); + + bool handleController(snd_seq_event_t *ev); + void setPortValueFromController(unsigned int portNumber, int controlValue); + void selectProgramAux(std::string program, bool backupPortValues); + void checkProgramCache() const; + + void initialiseGroupMembership(); + void runGrouped(const Vamp::RealTime &); + + // For use in DSSIPluginFactory (set in the DSSI_Host_Descriptor): + static int requestMidiSend(LADSPA_Handle instance, + unsigned char ports, + unsigned char channels); + static void midiSend(LADSPA_Handle instance, + snd_seq_event_t *events, + unsigned long eventCount); + static int requestNonRTThread(LADSPA_Handle instance, + void (*runFunction)(LADSPA_Handle)); + + int m_client; + int m_position; + LADSPA_Handle m_instanceHandle; + const DSSI_Descriptor *m_descriptor; + + std::vector > m_controlPortsIn; + std::vector > m_controlPortsOut; + + std::vector m_backupControlPortsIn; + + std::map m_controllerMap; + + std::vector m_audioPortsIn; + std::vector m_audioPortsOut; + + struct ProgramControl { + int msb; + int lsb; + int program; + }; + ProgramControl m_pending; + + struct ProgramDescriptor { + int bank; + int program; + std::string name; + }; + mutable std::vector m_cachedPrograms; + mutable bool m_programCacheValid; + + RingBuffer m_eventBuffer; + + size_t m_blockSize; + sample_t **m_inputBuffers; + sample_t **m_outputBuffers; + bool m_ownBuffers; + size_t m_idealChannelCount; + size_t m_outputBufferCount; + size_t m_sampleRate; + float *m_latencyPort; + bool m_run; + + bool m_bypassed; + std::string m_program; + bool m_grouped; + Vamp::RealTime m_lastRunTime; + + Vamp::RealTime m_lastEventSendTime; + bool m_haveLastEventSendTime; + + QMutex m_processLock; + + typedef std::set PluginSet; + typedef std::map GroupMap; + static GroupMap m_groupMap; + static snd_seq_event_t **m_groupLocalEventBuffers; + static size_t m_groupLocalEventBufferCount; + + static Scavenger > m_bufferScavenger; + + class NonRTPluginThread : public Thread + { + public: + NonRTPluginThread(LADSPA_Handle handle, + void (*runFunction)(LADSPA_Handle)) : + m_handle(handle), + m_runFunction(runFunction), + m_exiting(false) { } + + virtual void run(); + void setExiting() { m_exiting = true; } + + protected: + LADSPA_Handle m_handle; + void (*m_runFunction)(LADSPA_Handle); + bool m_exiting; + }; + static std::map > m_threads; +}; + +#endif // _DSSIPLUGININSTANCE_H_ + diff -r 000000000000 -r fc9323a41f5a plugin/FeatureExtractionPluginFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/FeatureExtractionPluginFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,399 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FeatureExtractionPluginFactory.h" +#include "PluginIdentifier.h" + +#include "vamp/vamp.h" +#include "vamp-sdk/PluginHostAdapter.h" + +#include "system/System.h" + +#include +#include +#include +#include + +#include + +//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 + +static FeatureExtractionPluginFactory *_nativeInstance = 0; + +FeatureExtractionPluginFactory * +FeatureExtractionPluginFactory::instance(QString pluginType) +{ + if (pluginType == "vamp") { + if (!_nativeInstance) { +// std::cerr << "FeatureExtractionPluginFactory::instance(" << pluginType.toStdString() +// << "): creating new FeatureExtractionPluginFactory" << std::endl; + _nativeInstance = new FeatureExtractionPluginFactory(); + } + return _nativeInstance; + } + + else return 0; +} + +FeatureExtractionPluginFactory * +FeatureExtractionPluginFactory::instanceFor(QString identifier) +{ + QString type, soName, label; + PluginIdentifier::parseIdentifier(identifier, type, soName, label); + return instance(type); +} + +std::vector +FeatureExtractionPluginFactory::getPluginPath() +{ + if (!m_pluginPath.empty()) return m_pluginPath; + + std::vector p = Vamp::PluginHostAdapter::getPluginPath(); + for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str()); + return m_pluginPath; +} + +std::vector +FeatureExtractionPluginFactory::getAllPluginIdentifiers() +{ + FeatureExtractionPluginFactory *factory; + std::vector rv; + + factory = instance("vamp"); + if (factory) { + std::vector tmp = factory->getPluginIdentifiers(); + for (size_t i = 0; i < tmp.size(); ++i) { + rv.push_back(tmp[i]); + } + } + + // Plugins can change the locale, revert it to default. + setlocale(LC_ALL, "C"); + return rv; +} + +std::vector +FeatureExtractionPluginFactory::getPluginIdentifiers() +{ + std::vector rv; + std::vector path = getPluginPath(); + + for (std::vector::iterator i = path.begin(); i != path.end(); ++i) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << i->toStdString() << std::endl; +#endif + + QDir pluginDir(*i, PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + + for (unsigned int j = 0; j < pluginDir.count(); ++j) { + + QString soname = pluginDir.filePath(pluginDir[j]); + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname.toStdString() << std::endl; +#endif + + void *libraryHandle = DLOPEN(soname, RTLD_LAZY); + + if (!libraryHandle) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname.toStdString() << ": " << DLERROR() << std::endl; + continue; + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << std::endl; +#endif + + VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) + DLSYM(libraryHandle, "vampGetPluginDescriptor"); + + if (!fn) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname.toStdString() << std::endl; + if (DLCLOSE(libraryHandle) != 0) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname.toStdString() << std::endl; + } + continue; + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << std::endl; +#endif + + const VampPluginDescriptor *descriptor = 0; + int index = 0; + + std::map known; + bool ok = true; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + + if (known.find(descriptor->identifier) != known.end()) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library " + << soname.toStdString() + << " returns the same plugin identifier \"" + << descriptor->identifier << "\" at indices " + << known[descriptor->identifier] << " and " + << index << std::endl; + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << std::endl; + ok = false; + break; + } else { + known[descriptor->identifier] = index; + } + + ++index; + } + + if (ok) { + + index = 0; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + + QString id = PluginIdentifier::createIdentifier + ("vamp", soname, descriptor->identifier); + rv.push_back(id); +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id.toStdString() << " at index " << index << std::endl; +#endif + ++index; + } + } + + if (DLCLOSE(libraryHandle) != 0) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname.toStdString() << std::endl; + } + } + } + + generateTaxonomy(); + + return rv; +} + +QString +FeatureExtractionPluginFactory::findPluginFile(QString soname, QString inDir) +{ + QString file = ""; + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile(\"" + << soname.toStdString() << "\", \"" << inDir.toStdString() << "\")" + << std::endl; +#endif + + if (inDir != "") { + + QDir dir(inDir, PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + if (!dir.exists()) return ""; + + file = dir.filePath(QFileInfo(soname).fileName()); + + if (QFileInfo(file).exists() && QFileInfo(file).isFile()) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile: " + << "found trivially at " << file.toStdString() << std::endl; +#endif + + return file; + } + + for (unsigned int j = 0; j < dir.count(); ++j) { + file = dir.filePath(dir[j]); + if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile: " + << "found at " << file.toStdString() << std::endl; +#endif + + return file; + } + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): " + << "not found" << std::endl; +#endif + + return ""; + + } else { + + QFileInfo fi(soname); + + if (fi.isAbsolute() && fi.exists() && fi.isFile()) { +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile: " + << "found trivially at " << soname.toStdString() << std::endl; +#endif + return soname; + } + + if (fi.isAbsolute() && fi.absolutePath() != "") { + file = findPluginFile(soname, fi.absolutePath()); + if (file != "") return file; + } + + std::vector path = getPluginPath(); + for (std::vector::iterator i = path.begin(); + i != path.end(); ++i) { + if (*i != "") { + file = findPluginFile(soname, *i); + if (file != "") return file; + } + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::findPluginFile: " + << "not found" << std::endl; +#endif + + return ""; + } +} + +Vamp::Plugin * +FeatureExtractionPluginFactory::instantiatePlugin(QString identifier, + float inputSampleRate) +{ + Vamp::Plugin *rv = 0; + + const VampPluginDescriptor *descriptor = 0; + int index = 0; + + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + if (type != "vamp") { + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type.toStdString() << std::endl; + return 0; + } + + QString found = findPluginFile(soname); + + if (found == "") { + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find library file " << soname.toStdString() << std::endl; + return 0; + } else if (found != soname) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname.toStdString() << ", found at " << found.toStdString() << std::endl; + std::cerr << soname.toStdString() << " -> " << found.toStdString() << std::endl; +#endif + + } + + soname = found; + + void *libraryHandle = DLOPEN(soname, RTLD_LAZY); + + if (!libraryHandle) { + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to load library " << soname.toStdString() << ": " << DLERROR() << std::endl; + return 0; + } + + VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) + DLSYM(libraryHandle, "vampGetPluginDescriptor"); + + if (!fn) { + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname.toStdString() << std::endl; + goto done; + } + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + if (label == descriptor->identifier) break; + ++index; + } + + if (!descriptor) { + std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find plugin \"" << label.toStdString() << "\" in library " << soname.toStdString() << std::endl; + goto done; + } + + rv = new Vamp::PluginHostAdapter(descriptor, inputSampleRate); + +// std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << std::endl; + + //!!! need to dlclose() when plugins from a given library are unloaded + +done: + if (!rv) { + if (DLCLOSE(libraryHandle) != 0) { + std::cerr << "WARNING: FeatureExtractionPluginFactory::instantiatePlugin: Failed to unload library " << soname.toStdString() << std::endl; + } + } + +// std::cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label.toStdString() << " from library " << soname.toStdString() << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << std::endl; + + return rv; +} + +QString +FeatureExtractionPluginFactory::getPluginCategory(QString identifier) +{ + return m_taxonomy[identifier]; +} + +void +FeatureExtractionPluginFactory::generateTaxonomy() +{ + std::vector pluginPath = getPluginPath(); + std::vector path; + + for (size_t i = 0; i < pluginPath.size(); ++i) { + if (pluginPath[i].contains("/lib/")) { + QString p(pluginPath[i]); + path.push_back(p); + p.replace("/lib/", "/share/"); + path.push_back(p); + } + path.push_back(pluginPath[i]); + } + + for (size_t i = 0; i < path.size(); ++i) { + + QDir dir(path[i], "*.cat"); + +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i].toStdString() << " has " << dir.count() << " .cat files" << std::endl; + for (unsigned int j = 0; j < dir.count(); ++j) { + + QFile file(path[i] + "/" + dir[j]); + +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i].toStdString() + "/" + dir[j].toStdString()) << std::endl; + + if (file.open(QIODevice::ReadOnly)) { +// std::cerr << "...opened" << std::endl; + QTextStream stream(&file); + QString line; + + while (!stream.atEnd()) { + line = stream.readLine(); +// std::cerr << "line is: \"" << line.toStdString() << "\"" << std::endl; + QString id = PluginIdentifier::canonicalise + (line.section("::", 0, 0)); + QString cat = line.section("::", 1, 1); + m_taxonomy[id] = cat; +// std::cerr << "FeatureExtractionPluginFactory: set id \"" << id.toStdString() << "\" to cat \"" << cat.toStdString() << "\"" << std::endl; + } + } + } + } +} diff -r 000000000000 -r fc9323a41f5a plugin/FeatureExtractionPluginFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/FeatureExtractionPluginFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_ +#define _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_ + +#include +#include +#include + +namespace Vamp { class Plugin; } + +class FeatureExtractionPluginFactory +{ +public: + virtual ~FeatureExtractionPluginFactory() { } + + static FeatureExtractionPluginFactory *instance(QString pluginType); + static FeatureExtractionPluginFactory *instanceFor(QString identifier); + static std::vector getAllPluginIdentifiers(); + + virtual std::vector getPluginPath(); + + virtual std::vector getPluginIdentifiers(); + + virtual QString findPluginFile(QString soname, QString inDir = ""); + + // We don't set blockSize or channels on this -- they're + // negotiated and handled via initialize() on the plugin + virtual Vamp::Plugin *instantiatePlugin(QString identifier, + float inputSampleRate); + + /** + * Get category metadata about a plugin (without instantiating it). + */ + virtual QString getPluginCategory(QString identifier); + +protected: + std::vector m_pluginPath; + std::map m_taxonomy; + + void generateTaxonomy(); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/LADSPAPluginFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/LADSPAPluginFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,863 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and Richard Bown. +*/ + +#include "LADSPAPluginFactory.h" +#include + +#include +#include +#include + +#include + +#include "LADSPAPluginInstance.h" +#include "PluginIdentifier.h" + +#include "system/System.h" +#include "base/Preferences.h" + +//#define DEBUG_LADSPA_PLUGIN_FACTORY 1 + +#ifdef HAVE_LRDF +#include "lrdf.h" +#endif // HAVE_LRDF + + +LADSPAPluginFactory::LADSPAPluginFactory() +{ +#ifdef HAVE_LRDF + lrdf_init(); +#endif +} + +LADSPAPluginFactory::~LADSPAPluginFactory() +{ + for (std::set::iterator i = m_instances.begin(); + i != m_instances.end(); ++i) { + (*i)->setFactory(0); + delete *i; + } + m_instances.clear(); + unloadUnusedLibraries(); + +#ifdef HAVE_LRDF + lrdf_cleanup(); +#endif // HAVE_LRDF +} + +const std::vector & +LADSPAPluginFactory::getPluginIdentifiers() const +{ + return m_identifiers; +} + +void +LADSPAPluginFactory::enumeratePlugins(std::vector &list) +{ + for (std::vector::iterator i = m_identifiers.begin(); + i != m_identifiers.end(); ++i) { + + const LADSPA_Descriptor *descriptor = getLADSPADescriptor(*i); + + if (!descriptor) { + std::cerr << "WARNING: LADSPAPluginFactory::enumeratePlugins: couldn't get descriptor for identifier " << i->toStdString() << std::endl; + continue; + } + + list.push_back(*i); + list.push_back(descriptor->Name); + list.push_back(QString("%1").arg(descriptor->UniqueID)); + list.push_back(descriptor->Label); + list.push_back(descriptor->Maker); + list.push_back(descriptor->Copyright); + list.push_back("false"); // is synth + list.push_back("false"); // is grouped + + if (m_taxonomy.find(*i) != m_taxonomy.end() && m_taxonomy[*i] != "") { +// std::cerr << "LADSPAPluginFactory: cat for " << i->toStdString()<< " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << std::endl; + list.push_back(m_taxonomy[*i]); + } else { + list.push_back(""); +// std::cerr << "LADSPAPluginFactory: cat for " << i->toStdString() << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << std::endl; + + } + + list.push_back(QString("%1").arg(descriptor->PortCount)); + + for (unsigned long p = 0; p < descriptor->PortCount; ++p) { + + int type = 0; + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) { + type |= PortType::Control; + } else { + type |= PortType::Audio; + } + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) { + type |= PortType::Input; + } else { + type |= PortType::Output; + } + + list.push_back(QString("%1").arg(p)); + list.push_back(descriptor->PortNames[p]); + list.push_back(QString("%1").arg(type)); + list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p))); + list.push_back(QString("%1").arg(getPortMinimum(descriptor, p))); + list.push_back(QString("%1").arg(getPortMaximum(descriptor, p))); + list.push_back(QString("%1").arg(getPortDefault(descriptor, p))); + } + } + + unloadUnusedLibraries(); +} + +const RealTimePluginDescriptor * +LADSPAPluginFactory::getPluginDescriptor(QString identifier) const +{ + std::map::const_iterator i = + m_rtDescriptors.find(identifier); + + if (i != m_rtDescriptors.end()) { + return i->second; + } + + return 0; +} + +float +LADSPAPluginFactory::getPortMinimum(const LADSPA_Descriptor *descriptor, int port) +{ + LADSPA_PortRangeHintDescriptor d = + descriptor->PortRangeHints[port].HintDescriptor; + + float minimum = 0.0; + + if (LADSPA_IS_HINT_BOUNDED_BELOW(d)) { + float lb = descriptor->PortRangeHints[port].LowerBound; + minimum = lb; + } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) { + float ub = descriptor->PortRangeHints[port].UpperBound; + minimum = min(0.0, ub - 1.0); + } + + if (LADSPA_IS_HINT_SAMPLE_RATE(d)) { + minimum *= m_sampleRate; + } + + return minimum; +} + +float +LADSPAPluginFactory::getPortMaximum(const LADSPA_Descriptor *descriptor, int port) +{ + LADSPA_PortRangeHintDescriptor d = + descriptor->PortRangeHints[port].HintDescriptor; + + float maximum = 1.0; + + if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) { + float ub = descriptor->PortRangeHints[port].UpperBound; + maximum = ub; + } else { + float lb = descriptor->PortRangeHints[port].LowerBound; + maximum = lb + 1.0; + } + + if (LADSPA_IS_HINT_SAMPLE_RATE(d)) { + maximum *= m_sampleRate; + } + + return maximum; +} + +float +LADSPAPluginFactory::getPortDefault(const LADSPA_Descriptor *descriptor, int port) +{ + float minimum = getPortMinimum(descriptor, port); + float maximum = getPortMaximum(descriptor, port); + float deft; + + if (m_portDefaults.find(descriptor->UniqueID) != + m_portDefaults.end()) { + if (m_portDefaults[descriptor->UniqueID].find(port) != + m_portDefaults[descriptor->UniqueID].end()) { + + deft = m_portDefaults[descriptor->UniqueID][port]; + if (deft < minimum) deft = minimum; + if (deft > maximum) deft = maximum; + return deft; + } + } + + LADSPA_PortRangeHintDescriptor d = + descriptor->PortRangeHints[port].HintDescriptor; + + bool logarithmic = LADSPA_IS_HINT_LOGARITHMIC(d); + + if (!LADSPA_IS_HINT_HAS_DEFAULT(d)) { + + deft = minimum; + + } else if (LADSPA_IS_HINT_DEFAULT_MINIMUM(d)) { + + deft = minimum; + + } else if (LADSPA_IS_HINT_DEFAULT_LOW(d)) { + + if (logarithmic) { + deft = powf(10, log10(minimum) * 0.75 + + log10(maximum) * 0.25); + } else { + deft = minimum * 0.75 + maximum * 0.25; + } + + } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(d)) { + + if (logarithmic) { + deft = powf(10, log10(minimum) * 0.5 + + log10(maximum) * 0.5); + } else { + deft = minimum * 0.5 + maximum * 0.5; + } + + } else if (LADSPA_IS_HINT_DEFAULT_HIGH(d)) { + + if (logarithmic) { + deft = powf(10, log10(minimum) * 0.25 + + log10(maximum) * 0.75); + } else { + deft = minimum * 0.25 + maximum * 0.75; + } + + } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(d)) { + + deft = maximum; + + } else if (LADSPA_IS_HINT_DEFAULT_0(d)) { + + deft = 0.0; + + } else if (LADSPA_IS_HINT_DEFAULT_1(d)) { + + deft = 1.0; + + } else if (LADSPA_IS_HINT_DEFAULT_100(d)) { + + deft = 100.0; + + } else if (LADSPA_IS_HINT_DEFAULT_440(d)) { + +// deft = 440.0; + deft = Preferences::getInstance()->getTuningFrequency(); + + } else { + + deft = minimum; + } + + if (LADSPA_IS_HINT_SAMPLE_RATE(d)) { + deft *= m_sampleRate; + } + + return deft; +} + +float +LADSPAPluginFactory::getPortQuantization(const LADSPA_Descriptor *descriptor, int port) +{ + int displayHint = getPortDisplayHint(descriptor, port); + if (displayHint & PortHint::Toggled) { + return lrintf(getPortMaximum(descriptor, port)) - + lrintf(getPortMinimum(descriptor, port)); + } + if (displayHint & PortHint::Integer) { + return 1.0; + } + return 0.0; +} + +int +LADSPAPluginFactory::getPortDisplayHint(const LADSPA_Descriptor *descriptor, int port) +{ + LADSPA_PortRangeHintDescriptor d = + descriptor->PortRangeHints[port].HintDescriptor; + int hint = PortHint::NoHint; + + if (LADSPA_IS_HINT_TOGGLED(d)) hint |= PortHint::Toggled; + if (LADSPA_IS_HINT_INTEGER(d)) hint |= PortHint::Integer; + if (LADSPA_IS_HINT_LOGARITHMIC(d)) hint |= PortHint::Logarithmic; + + return hint; +} + + +RealTimePluginInstance * +LADSPAPluginFactory::instantiatePlugin(QString identifier, + int instrument, + int position, + unsigned int sampleRate, + unsigned int blockSize, + unsigned int channels) +{ + const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier); + + if (descriptor) { + + LADSPAPluginInstance *instance = + new LADSPAPluginInstance + (this, instrument, identifier, position, sampleRate, blockSize, channels, + descriptor); + + m_instances.insert(instance); + +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "LADSPAPluginFactory::instantiatePlugin(" + << identifier.toStdString() << ": now have " << m_instances.size() << " instances" << std::endl; +#endif + + return instance; + } + + return 0; +} + +void +LADSPAPluginFactory::releasePlugin(RealTimePluginInstance *instance, + QString identifier) +{ + if (m_instances.find(instance) == m_instances.end()) { + std::cerr << "WARNING: LADSPAPluginFactory::releasePlugin: Not one of mine!" + << std::endl; + return; + } + + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + + m_instances.erase(instance); + + bool stillInUse = false; + + for (std::set::iterator ii = m_instances.begin(); + ii != m_instances.end(); ++ii) { + QString itype, isoname, ilabel; + PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel); + if (isoname == soname) { +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname.toStdString() << " is still in use for plugin " << ilabel.toStdString() << std::endl; +#endif + stillInUse = true; + break; + } + } + + if (!stillInUse) { + if (soname != PluginIdentifier::BUILTIN_PLUGIN_SONAME) { +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname.toStdString() << " no longer in use, unloading" << std::endl; +#endif + unloadLibrary(soname); + } + } + +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "LADSPAPluginFactory::releasePlugin(" + << identifier.toStdString() << ": now have " << m_instances.size() << " instances" << std::endl; +#endif +} + +const LADSPA_Descriptor * +LADSPAPluginFactory::getLADSPADescriptor(QString identifier) +{ + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + + if (m_libraryHandles.find(soname) == m_libraryHandles.end()) { + loadLibrary(soname); + if (m_libraryHandles.find(soname) == m_libraryHandles.end()) { + std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: loadLibrary failed for " << soname.toStdString() << std::endl; + return 0; + } + } + + void *libraryHandle = m_libraryHandles[soname]; + + LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function) + DLSYM(libraryHandle, "ladspa_descriptor"); + + if (!fn) { + std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No descriptor function in library " << soname.toStdString() << std::endl; + return 0; + } + + const LADSPA_Descriptor *descriptor = 0; + + int index = 0; + while ((descriptor = fn(index))) { + if (descriptor->Label == label) return descriptor; + ++index; + } + + std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No such plugin as " << label.toStdString() << " in library " << soname.toStdString() << std::endl; + + return 0; +} + +void +LADSPAPluginFactory::loadLibrary(QString soName) +{ + void *libraryHandle = DLOPEN(soName, RTLD_NOW); + if (libraryHandle) { + m_libraryHandles[soName] = libraryHandle; + std::cerr << "LADSPAPluginFactory::loadLibrary: Loaded library \"" << soName.toStdString() << "\"" << std::endl; + return; + } + + if (QFileInfo(soName).exists()) { + DLERROR(); + std::cerr << "LADSPAPluginFactory::loadLibrary: Library \"" << soName.toStdString() << "\" exists, but failed to load it" << std::endl; + return; + } + + std::vector pathList = getPluginPath(); + + QString fileName = QFile(soName).fileName(); + QString base = QFileInfo(soName).baseName(); + + for (std::vector::iterator i = pathList.begin(); + i != pathList.end(); ++i) { + +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "Looking at: " << (*i).toStdString() << std::endl; +#endif + + QDir dir(*i, PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + + if (QFileInfo(dir.filePath(fileName)).exists()) { +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "Loading: " << fileName.toStdString() << std::endl; +#endif + libraryHandle = DLOPEN(dir.filePath(fileName), RTLD_NOW); + if (libraryHandle) { + m_libraryHandles[soName] = libraryHandle; + return; + } + } + + for (unsigned int j = 0; j < dir.count(); ++j) { + QString file = dir.filePath(dir[j]); + if (QFileInfo(file).baseName() == base) { +#ifdef DEBUG_LADSPA_PLUGIN_FACTORY + std::cerr << "Loading: " << file.toStdString() << std::endl; +#endif + libraryHandle = DLOPEN(file, RTLD_NOW); + if (libraryHandle) { + m_libraryHandles[soName] = libraryHandle; + return; + } + } + } + } + + std::cerr << "LADSPAPluginFactory::loadLibrary: Failed to locate plugin library \"" << soName.toStdString() << "\"" << std::endl; +} + +void +LADSPAPluginFactory::unloadLibrary(QString soName) +{ + LibraryHandleMap::iterator li = m_libraryHandles.find(soName); + if (li != m_libraryHandles.end()) { +// std::cerr << "unloading " << soname.toStdString() << std::endl; + DLCLOSE(m_libraryHandles[soName]); + m_libraryHandles.erase(li); + } +} + +void +LADSPAPluginFactory::unloadUnusedLibraries() +{ + std::vector toUnload; + + for (LibraryHandleMap::iterator i = m_libraryHandles.begin(); + i != m_libraryHandles.end(); ++i) { + + bool stillInUse = false; + + for (std::set::iterator ii = m_instances.begin(); + ii != m_instances.end(); ++ii) { + + QString itype, isoname, ilabel; + PluginIdentifier::parseIdentifier((*ii)->getPluginIdentifier(), itype, isoname, ilabel); + if (isoname == i->first) { + stillInUse = true; + break; + } + } + + if (!stillInUse) toUnload.push_back(i->first); + } + + for (std::vector::iterator i = toUnload.begin(); + i != toUnload.end(); ++i) { + if (*i != PluginIdentifier::BUILTIN_PLUGIN_SONAME) { + unloadLibrary(*i); + } + } +} + + +// It is only later, after they've gone, +// I realize they have delivered a letter. +// It's a letter from my wife. "What are you doing +// there?" my wife asks. "Are you drinking?" +// I study the postmark for hours. Then it, too, begins to fade. +// I hope someday to forget all this. + + +std::vector +LADSPAPluginFactory::getPluginPath() +{ + std::vector pathList; + std::string path; + + char *cpath = getenv("LADSPA_PATH"); + if (cpath) path = cpath; + + if (path == "") { + + path = DEFAULT_LADSPA_PATH; + + char *home = getenv("HOME"); + if (home) { + std::string::size_type f; + while ((f = path.find("$HOME")) != std::string::npos && + f < path.length()) { + path.replace(f, 5, home); + } + } + +#ifdef _WIN32 + char *pfiles = getenv("ProgramFiles"); + if (!pfiles) pfiles = "C:\\Program Files"; + { + std::string::size_type f; + while ((f = path.find("%ProgramFiles%")) != std::string::npos && + f < path.length()) { + path.replace(f, 14, pfiles); + } + } +#endif + } + + std::string::size_type index = 0, newindex = 0; + + while ((newindex = path.find(PATH_SEPARATOR, index)) < path.size()) { + pathList.push_back(path.substr(index, newindex - index).c_str()); + index = newindex + 1; + } + + pathList.push_back(path.substr(index).c_str()); + + return pathList; +} + + +std::vector +LADSPAPluginFactory::getLRDFPath(QString &baseUri) +{ + std::vector lrdfPaths; + +#ifdef HAVE_LRDF + std::vector pathList = getPluginPath(); + + lrdfPaths.push_back("/usr/local/share/ladspa/rdf"); + lrdfPaths.push_back("/usr/share/ladspa/rdf"); + + for (std::vector::iterator i = pathList.begin(); + i != pathList.end(); ++i) { + lrdfPaths.push_back(*i + "/rdf"); + } + + baseUri = LADSPA_BASE; +#endif + + return lrdfPaths; +} + +void +LADSPAPluginFactory::discoverPlugins() +{ + std::vector pathList = getPluginPath(); + +// std::cerr << "LADSPAPluginFactory::discoverPlugins - " +// << "discovering plugins; path is "; +// for (std::vector::iterator i = pathList.begin(); +// i != pathList.end(); ++i) { +// std::cerr << "[" << i->toStdString() << "] "; +// } +// std::cerr << std::endl; + +#ifdef HAVE_LRDF + // read the description files + // + QString baseUri; + std::vector lrdfPaths = getLRDFPath(baseUri); + + bool haveSomething = false; + + for (size_t i = 0; i < lrdfPaths.size(); ++i) { + QDir dir(lrdfPaths[i], "*.rdf;*.rdfs"); + for (unsigned int j = 0; j < dir.count(); ++j) { + if (!lrdf_read_file(QString("file:" + lrdfPaths[i] + "/" + dir[j]).toStdString().c_str())) { +// std::cerr << "LADSPAPluginFactory: read RDF file " << (lrdfPaths[i] + "/" + dir[j]) << std::endl; + haveSomething = true; + } + } + } + + if (haveSomething) { + generateTaxonomy(baseUri + "Plugin", ""); + } +#endif // HAVE_LRDF + + generateFallbackCategories(); + + for (std::vector::iterator i = pathList.begin(); + i != pathList.end(); ++i) { + + QDir pluginDir(*i, PLUGIN_GLOB); + + for (unsigned int j = 0; j < pluginDir.count(); ++j) { + discoverPlugins(QString("%1/%2").arg(*i).arg(pluginDir[j])); + } + } +} + +void +LADSPAPluginFactory::discoverPlugins(QString soname) +{ + void *libraryHandle = DLOPEN(soname, RTLD_LAZY); + + if (!libraryHandle) { + std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: couldn't load plugin library " + << soname.toStdString() << " - " << DLERROR() << std::endl; + return; + } + + LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function) + DLSYM(libraryHandle, "ladspa_descriptor"); + + if (!fn) { + std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: No descriptor function in " << soname.toStdString() << std::endl; + return; + } + + const LADSPA_Descriptor *descriptor = 0; + + int index = 0; + while ((descriptor = fn(index))) { + + RealTimePluginDescriptor *rtd = new RealTimePluginDescriptor; + rtd->name = descriptor->Name; + rtd->label = descriptor->Label; + rtd->maker = descriptor->Maker; + rtd->copyright = descriptor->Copyright; + rtd->category = ""; + rtd->isSynth = false; + rtd->parameterCount = 0; + rtd->audioInputPortCount = 0; + rtd->audioOutputPortCount = 0; + rtd->controlOutputPortCount = 0; + + QString identifier = PluginIdentifier::createIdentifier + ("ladspa", soname, descriptor->Label); + +#ifdef HAVE_LRDF + char *def_uri = 0; + lrdf_defaults *defs = 0; + + if (m_lrdfTaxonomy[descriptor->UniqueID] != "") { + m_taxonomy[identifier] = m_lrdfTaxonomy[descriptor->UniqueID]; +// std::cerr << "set id \"" << identifier.toStdString() << "\" to cat \"" << m_taxonomy[identifier].toStdString() << "\" from LRDF" << std::endl; +// std::cout << identifier.toStdString() << "::" << m_taxonomy[identifier].toStdString() << std::endl; + } + + QString category = m_taxonomy[identifier]; + + if (category == "" && descriptor->Name != 0) { + std::string name = descriptor->Name; + if (name.length() > 4 && + name.substr(name.length() - 4) == " VST") { + category = "VST effects"; + m_taxonomy[identifier] = category; + } + } + + rtd->category = category.toStdString(); + +// std::cerr << "Plugin id is " << descriptor->UniqueID +// << ", category is \"" << (category ? category : QString("(none)")) +// << "\", name is " << descriptor->Name +// << ", label is " << descriptor->Label +// << std::endl; + + def_uri = lrdf_get_default_uri(descriptor->UniqueID); + if (def_uri) { + defs = lrdf_get_setting_values(def_uri); + } + + unsigned int controlPortNumber = 1; + + for (unsigned long i = 0; i < descriptor->PortCount; i++) { + + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) { + + if (def_uri && defs) { + + for (unsigned int j = 0; j < defs->count; j++) { + if (defs->items[j].pid == controlPortNumber) { +// std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << descriptor->PortNames[i] << std::endl; + m_portDefaults[descriptor->UniqueID][i] = + defs->items[j].value; + } + } + } + + ++controlPortNumber; + } + } +#endif // HAVE_LRDF + + for (unsigned long i = 0; i < descriptor->PortCount; i++) { + if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) { + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) { + ++rtd->parameterCount; + } else { + if (strcmp(descriptor->PortNames[i], "latency") && + strcmp(descriptor->PortNames[i], "_latency")) { + ++rtd->controlOutputPortCount; + rtd->controlOutputPortNames.push_back + (descriptor->PortNames[i]); + } + } + } else { + if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) { + ++rtd->audioInputPortCount; + } else if (LADSPA_IS_PORT_OUTPUT(descriptor->PortDescriptors[i])) { + ++rtd->audioOutputPortCount; + } + } + } + + m_identifiers.push_back(identifier); + + m_rtDescriptors[identifier] = rtd; + + ++index; + } + + if (DLCLOSE(libraryHandle) != 0) { + std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl; + return; + } +} + +void +LADSPAPluginFactory::generateFallbackCategories() +{ + std::vector pluginPath = getPluginPath(); + std::vector path; + + for (size_t i = 0; i < pluginPath.size(); ++i) { + if (pluginPath[i].contains("/lib/")) { + QString p(pluginPath[i]); + path.push_back(p); + p.replace("/lib/", "/share/"); + path.push_back(p); +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << p.toStdString() << std::endl; + } + path.push_back(pluginPath[i]); +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << pluginPath[i].toStdString() << std::endl; + } + + for (size_t i = 0; i < path.size(); ++i) { + + QDir dir(path[i], "*.cat"); + +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i].toStdString() << " has " << dir.count() << " .cat files" << std::endl; + for (unsigned int j = 0; j < dir.count(); ++j) { + + QFile file(path[i] + "/" + dir[j]); + +// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i].toStdString() + "/" + dir[j].toStdString()) << std::endl; + + if (file.open(QIODevice::ReadOnly)) { +// std::cerr << "...opened" << std::endl; + QTextStream stream(&file); + QString line; + + while (!stream.atEnd()) { + line = stream.readLine(); +// std::cerr << "line is: \"" << line.toStdString() << "\"" << std::endl; + QString id = PluginIdentifier::canonicalise + (line.section("::", 0, 0)); + QString cat = line.section("::", 1, 1); + m_taxonomy[id] = cat; +// std::cerr << "set id \"" << id.toStdString() << "\" to cat \"" << cat.toStdString() << "\"" << std::endl; + } + } + } + } +} + +void +LADSPAPluginFactory::generateTaxonomy(QString uri, QString base) +{ +#ifdef HAVE_LRDF + lrdf_uris *uris = lrdf_get_instances(uri.toStdString().c_str()); + + if (uris != NULL) { + for (unsigned int i = 0; i < uris->count; ++i) { + m_lrdfTaxonomy[lrdf_get_uid(uris->items[i])] = base; + } + lrdf_free_uris(uris); + } + + uris = lrdf_get_subclasses(uri.toStdString().c_str()); + + if (uris != NULL) { + for (unsigned int i = 0; i < uris->count; ++i) { + char *label = lrdf_get_label(uris->items[i]); + generateTaxonomy(uris->items[i], + base + (base.length() > 0 ? " > " : "") + label); + } + lrdf_free_uris(uris); + } +#endif +} + +QString +LADSPAPluginFactory::getPluginCategory(QString identifier) +{ + return m_taxonomy[identifier]; +} + diff -r 000000000000 -r fc9323a41f5a plugin/LADSPAPluginFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/LADSPAPluginFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,96 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and Richard Bown. +*/ + +#ifndef _LADSPA_PLUGIN_FACTORY_H_ +#define _LADSPA_PLUGIN_FACTORY_H_ + +#include "RealTimePluginFactory.h" +#include "api/ladspa.h" + +#include +#include +#include +#include + +class LADSPAPluginInstance; + +class LADSPAPluginFactory : public RealTimePluginFactory +{ +public: + virtual ~LADSPAPluginFactory(); + + virtual void discoverPlugins(); + + virtual const std::vector &getPluginIdentifiers() const; + + virtual void enumeratePlugins(std::vector &list); + + virtual const RealTimePluginDescriptor *getPluginDescriptor(QString identifier) const; + + virtual RealTimePluginInstance *instantiatePlugin(QString identifier, + int clientId, + int position, + unsigned int sampleRate, + unsigned int blockSize, + unsigned int channels); + + virtual QString getPluginCategory(QString identifier); + + float getPortMinimum(const LADSPA_Descriptor *, int port); + float getPortMaximum(const LADSPA_Descriptor *, int port); + float getPortDefault(const LADSPA_Descriptor *, int port); + float getPortQuantization(const LADSPA_Descriptor *, int port); + int getPortDisplayHint(const LADSPA_Descriptor *, int port); + +protected: + LADSPAPluginFactory(); + friend class RealTimePluginFactory; + + virtual std::vector getPluginPath(); + + virtual std::vector getLRDFPath(QString &baseUri); + + virtual void discoverPlugins(QString soName); + virtual void generateTaxonomy(QString uri, QString base); + virtual void generateFallbackCategories(); + + virtual void releasePlugin(RealTimePluginInstance *, QString); + + virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier); + + void loadLibrary(QString soName); + void unloadLibrary(QString soName); + void unloadUnusedLibraries(); + + std::vector m_identifiers; + std::map m_rtDescriptors; + + std::map m_taxonomy; + std::map m_lrdfTaxonomy; + std::map > m_portDefaults; + + std::set m_instances; + + typedef std::map LibraryHandleMap; + LibraryHandleMap m_libraryHandles; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a plugin/LADSPAPluginInstance.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/LADSPAPluginInstance.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,563 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam, Richard Bown, and QMUL. +*/ + +#include +#include + +#include "LADSPAPluginInstance.h" +#include "LADSPAPluginFactory.h" + +#ifdef HAVE_LRDF +#include "lrdf.h" +#endif // HAVE_LRDF + +//#define DEBUG_LADSPA 1 + +#include + + +LADSPAPluginInstance::LADSPAPluginInstance(RealTimePluginFactory *factory, + int clientId, + QString identifier, + int position, + unsigned long sampleRate, + size_t blockSize, + int idealChannelCount, + const LADSPA_Descriptor* descriptor) : + RealTimePluginInstance(factory, identifier), + m_client(clientId), + m_position(position), + m_instanceCount(0), + m_descriptor(descriptor), + m_blockSize(blockSize), + m_sampleRate(sampleRate), + m_latencyPort(0), + m_run(false), + m_bypassed(false) +{ + init(idealChannelCount); + + if (m_audioPortsIn.size() == 0) { + m_inputBuffers = 0; + } else { + m_inputBuffers = new sample_t*[m_instanceCount * m_audioPortsIn.size()]; + } + + if (m_audioPortsOut.size() == 0) { + m_outputBuffers = 0; + } else { + m_outputBuffers = new sample_t*[m_instanceCount * m_audioPortsOut.size()]; + } + + for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) { + m_inputBuffers[i] = new sample_t[blockSize]; + } + for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) { + m_outputBuffers[i] = new sample_t[blockSize]; + } + + m_ownBuffers = true; + + instantiate(sampleRate); + if (isOK()) { + connectPorts(); + activate(); + } +} + +std::string +LADSPAPluginInstance::getIdentifier() const +{ + return m_descriptor->Label; +} + +std::string +LADSPAPluginInstance::getName() const +{ + return m_descriptor->Name; +} + +std::string +LADSPAPluginInstance::getDescription() const +{ + return ""; +} + +std::string +LADSPAPluginInstance::getMaker() const +{ + return m_descriptor->Maker; +} + +int +LADSPAPluginInstance::getPluginVersion() const +{ + return 1; +} + +std::string +LADSPAPluginInstance::getCopyright() const +{ + return m_descriptor->Copyright; +} + +LADSPAPluginInstance::ParameterList +LADSPAPluginInstance::getParameterDescriptors() const +{ + ParameterList list; + LADSPAPluginFactory *f = dynamic_cast(m_factory); + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + + ParameterDescriptor pd; + unsigned int pn = m_controlPortsIn[i].first; + + pd.identifier = m_descriptor->PortNames[pn]; + pd.name = pd.identifier; + pd.description = ""; + pd.minValue = f->getPortMinimum(m_descriptor, pn); + pd.maxValue = f->getPortMaximum(m_descriptor, pn); + pd.defaultValue = f->getPortDefault(m_descriptor, pn); + + float q = f->getPortQuantization(m_descriptor, pn); + if (q == 0.0) { + pd.isQuantized = false; + } else { + pd.isQuantized = true; + pd.quantizeStep = q; + } + + bool haveLabels = false; + +#ifdef HAVE_LRDF + if (pd.isQuantized && pd.quantizeStep == 1.0) { + + lrdf_defaults *defaults = + lrdf_get_scale_values(m_descriptor->UniqueID, pn); + + if (defaults) { + if (defaults->count > 0) { + std::map values; + size_t v = 0; + for (size_t i = 0; i < defaults->count; ++i) { + v = size_t(lrintf(fabsf(defaults->items[i].value))); + values[v] = defaults->items[i].label; + } + for (size_t i = 0; i <= v; ++i) { + pd.valueNames.push_back(values[i]); + } + haveLabels = true; + } + lrdf_free_setting_values(defaults); + } + } +#endif + + if (haveLabels) { + pd.name = QString(pd.name.c_str()) + .replace(QRegExp("\\([^\\(\\)]+=[^\\(\\)]+\\)$"), "") + .toStdString(); + } else { + static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$"); + if (unitRE.indexIn(pd.name.c_str()) >= 0) { + pd.unit = unitRE.cap(1).toStdString(); + pd.name = QString(pd.name.c_str()) + .replace(unitRE, "").toStdString(); + } + } + + list.push_back(pd); + } + + return list; +} + +float +LADSPAPluginInstance::getParameter(std::string id) const +{ + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + if (id == m_descriptor->PortNames[m_controlPortsIn[i].first]) { + return getParameterValue(i); + } + } + + return 0.0; +} + +void +LADSPAPluginInstance::setParameter(std::string id, float value) +{ + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + if (id == m_descriptor->PortNames[m_controlPortsIn[i].first]) { +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::setParameter: Found id " + << id << " at control port " << i << std::endl; +#endif + setParameterValue(i, value); + break; + } + } +} + +void +LADSPAPluginInstance::init(int idealChannelCount) +{ +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::init(" << idealChannelCount << "): plugin has " + << m_descriptor->PortCount << " ports" << std::endl; +#endif + + // Discover ports numbers and identities + // + for (unsigned long i = 0; i < m_descriptor->PortCount; ++i) { + + if (LADSPA_IS_PORT_AUDIO(m_descriptor->PortDescriptors[i])) { + + if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) { +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio in" << std::endl; +#endif + m_audioPortsIn.push_back(i); + } else { +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio out" << std::endl; +#endif + m_audioPortsOut.push_back(i); + } + + } else if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[i])) { + + if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) { + +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::init: port " << i << " is control in" << std::endl; +#endif + LADSPA_Data *data = new LADSPA_Data(0.0); + m_controlPortsIn.push_back( + std::pair(i, data)); + + } else { + +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::init: port " << i << " is control out" << std::endl; +#endif + LADSPA_Data *data = new LADSPA_Data(0.0); + m_controlPortsOut.push_back( + std::pair(i, data)); + if (!strcmp(m_descriptor->PortNames[i], "latency") || + !strcmp(m_descriptor->PortNames[i], "_latency")) { +#ifdef DEBUG_LADSPA + std::cerr << "Wooo! We have a latency port!" << std::endl; +#endif + m_latencyPort = data; + } + + } + } +#ifdef DEBUG_LADSPA + else + std::cerr << "LADSPAPluginInstance::init - " + << "unrecognised port type" << std::endl; +#endif + } + + m_instanceCount = 1; + + if (idealChannelCount > 0) { + if (m_audioPortsIn.size() == 1) { + // mono plugin: duplicate it if need be + m_instanceCount = idealChannelCount; + } + } +} + +size_t +LADSPAPluginInstance::getLatency() +{ + if (m_latencyPort) { + if (!m_run) { + for (size_t i = 0; i < getAudioInputCount(); ++i) { + for (size_t j = 0; j < m_blockSize; ++j) { + m_inputBuffers[i][j] = 0.f; + } + } + run(Vamp::RealTime::zeroTime); + } + if (*m_latencyPort > 0) return (size_t)*m_latencyPort; + } + return 0; +} + +void +LADSPAPluginInstance::silence() +{ + if (isOK()) { + deactivate(); + activate(); + } +} + +void +LADSPAPluginInstance::setIdealChannelCount(size_t channels) +{ + if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) { + silence(); + return; + } + + if (isOK()) { + deactivate(); + } + + //!!! don't we need to reallocate inputBuffers and outputBuffers? + + cleanup(); + m_instanceCount = channels; + instantiate(m_sampleRate); + if (isOK()) { + connectPorts(); + activate(); + } +} + + +LADSPAPluginInstance::~LADSPAPluginInstance() +{ +#ifdef DEBUG_LADSPA + std::cerr << "LADSPAPluginInstance::~LADSPAPluginInstance" << std::endl; +#endif + + if (m_instanceHandles.size() != 0) { // "isOK()" + deactivate(); + } + + cleanup(); + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) + delete m_controlPortsIn[i].second; + + for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) + delete m_controlPortsOut[i].second; + + m_controlPortsIn.clear(); + m_controlPortsOut.clear(); + + if (m_ownBuffers) { + for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) { + delete[] m_inputBuffers[i]; + } + for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) { + delete[] m_outputBuffers[i]; + } + + delete[] m_inputBuffers; + delete[] m_outputBuffers; + } + + m_audioPortsIn.clear(); + m_audioPortsOut.clear(); +} + + +void +LADSPAPluginInstance::instantiate(unsigned long sampleRate) +{ +#ifdef DEBUG_LADSPA + std::cout << "LADSPAPluginInstance::instantiate - plugin unique id = " + << m_descriptor->UniqueID << std::endl; +#endif + if (!m_descriptor) return; + + if (!m_descriptor->instantiate) { + std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID + << ":" << m_descriptor->Label + << " has no instantiate method!" << std::endl; + return; + } + + for (size_t i = 0; i < m_instanceCount; ++i) { + m_instanceHandles.push_back + (m_descriptor->instantiate(m_descriptor, sampleRate)); + } +} + +void +LADSPAPluginInstance::activate() +{ + if (!m_descriptor || !m_descriptor->activate) return; + + for (std::vector::iterator hi = m_instanceHandles.begin(); + hi != m_instanceHandles.end(); ++hi) { + m_descriptor->activate(*hi); + } +} + +void +LADSPAPluginInstance::connectPorts() +{ + if (!m_descriptor || !m_descriptor->connect_port) return; + + assert(sizeof(LADSPA_Data) == sizeof(float)); + assert(sizeof(sample_t) == sizeof(float)); + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + int inbuf = 0, outbuf = 0; + + for (std::vector::iterator hi = m_instanceHandles.begin(); + hi != m_instanceHandles.end(); ++hi) { + + for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) { + m_descriptor->connect_port(*hi, + m_audioPortsIn[i], + (LADSPA_Data *)m_inputBuffers[inbuf]); + ++inbuf; + } + + for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) { + m_descriptor->connect_port(*hi, + m_audioPortsOut[i], + (LADSPA_Data *)m_outputBuffers[outbuf]); + ++outbuf; + } + + // If there is more than one instance, they all share the same + // control port ins (and outs, for the moment, because we + // don't actually do anything with the outs anyway -- but they + // do have to be connected as the plugin can't know if they're + // not and will write to them anyway). + + for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) { + m_descriptor->connect_port(*hi, + m_controlPortsIn[i].first, + m_controlPortsIn[i].second); + if (f) { + float defaultValue = f->getPortDefault + (m_descriptor, m_controlPortsIn[i].first); + *m_controlPortsIn[i].second = defaultValue; + } + } + + for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) { + m_descriptor->connect_port(*hi, + m_controlPortsOut[i].first, + m_controlPortsOut[i].second); + } + } +} + +unsigned int +LADSPAPluginInstance::getParameterCount() const +{ + return m_controlPortsIn.size(); +} + +void +LADSPAPluginInstance::setParameterValue(unsigned int parameter, float value) +{ + if (parameter >= m_controlPortsIn.size()) return; + + unsigned int portNumber = m_controlPortsIn[parameter].first; + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + if (f) { + if (value < f->getPortMinimum(m_descriptor, portNumber)) { + value = f->getPortMinimum(m_descriptor, portNumber); + } + if (value > f->getPortMaximum(m_descriptor, portNumber)) { + value = f->getPortMaximum(m_descriptor, portNumber); + } + } + + (*m_controlPortsIn[parameter].second) = value; +} + +float +LADSPAPluginInstance::getControlOutputValue(size_t output) const +{ + if (output > m_controlPortsOut.size()) return 0.0; + return (*m_controlPortsOut[output].second); +} + +float +LADSPAPluginInstance::getParameterValue(unsigned int parameter) const +{ + if (parameter >= m_controlPortsIn.size()) return 0.0; + return (*m_controlPortsIn[parameter].second); +} + +float +LADSPAPluginInstance::getParameterDefault(unsigned int parameter) const +{ + if (parameter >= m_controlPortsIn.size()) return 0.0; + + LADSPAPluginFactory *f = dynamic_cast(m_factory); + if (f) { + return f->getPortDefault(m_descriptor, m_controlPortsIn[parameter].first); + } else { + return 0.0f; + } +} + +void +LADSPAPluginInstance::run(const Vamp::RealTime &) +{ + if (!m_descriptor || !m_descriptor->run) return; + + for (std::vector::iterator hi = m_instanceHandles.begin(); + hi != m_instanceHandles.end(); ++hi) { + + m_descriptor->run(*hi, m_blockSize); + } + + m_run = true; +} + +void +LADSPAPluginInstance::deactivate() +{ + if (!m_descriptor || !m_descriptor->deactivate) return; + + for (std::vector::iterator hi = m_instanceHandles.begin(); + hi != m_instanceHandles.end(); ++hi) { + m_descriptor->deactivate(*hi); + } +} + +void +LADSPAPluginInstance::cleanup() +{ + if (!m_descriptor) return; + + if (!m_descriptor->cleanup) { + std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID + << ":" << m_descriptor->Label + << " has no cleanup method!" << std::endl; + return; + } + + for (std::vector::iterator hi = m_instanceHandles.begin(); + hi != m_instanceHandles.end(); ++hi) { + m_descriptor->cleanup(*hi); + } + + m_instanceHandles.clear(); +} + + diff -r 000000000000 -r fc9323a41f5a plugin/LADSPAPluginInstance.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/LADSPAPluginInstance.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,132 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and Richard Bown. +*/ + +#ifndef _LADSPAPLUGININSTANCE_H_ +#define _LADSPAPLUGININSTANCE_H_ + +#include +#include +#include + +#include "api/ladspa.h" +#include "RealTimePluginInstance.h" + +// LADSPA plugin instance. LADSPA is a variable block size API, but +// for one reason and another it's more convenient to use a fixed +// block size in this wrapper. +// +class LADSPAPluginInstance : public RealTimePluginInstance +{ +public: + virtual ~LADSPAPluginInstance(); + + virtual bool isOK() const { return m_instanceHandles.size() != 0; } + + int getClientId() const { return m_client; } + virtual QString getPluginIdentifier() const { return m_identifier; } + int getPosition() const { return m_position; } + + virtual std::string getIdentifier() const; + virtual std::string getName() const; + virtual std::string getDescription() const; + virtual std::string getMaker() const; + virtual int getPluginVersion() const; + virtual std::string getCopyright() const; + + virtual void run(const Vamp::RealTime &rt); + + virtual unsigned int getParameterCount() const; + virtual void setParameterValue(unsigned int parameter, float value); + virtual float getParameterValue(unsigned int parameter) const; + virtual float getParameterDefault(unsigned int parameter) const; + + virtual ParameterList getParameterDescriptors() const; + virtual float getParameter(std::string) const; + virtual void setParameter(std::string, float); + + virtual size_t getBufferSize() const { return m_blockSize; } + virtual size_t getAudioInputCount() const { return m_instanceCount * m_audioPortsIn.size(); } + virtual size_t getAudioOutputCount() const { return m_instanceCount * m_audioPortsOut.size(); } + virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; } + virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; } + + virtual size_t getControlOutputCount() const { return m_controlPortsOut.size(); } + virtual float getControlOutputValue(size_t n) const; + + virtual bool isBypassed() const { return m_bypassed; } + virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; } + + virtual size_t getLatency(); + + virtual void silence(); + virtual void setIdealChannelCount(size_t channels); // may re-instantiate + + virtual std::string getType() const { return "LADSPA Real-Time Plugin"; } + +protected: + // To be constructed only by LADSPAPluginFactory + friend class LADSPAPluginFactory; + + // Constructor that creates the buffers internally + // + LADSPAPluginInstance(RealTimePluginFactory *factory, + int client, + QString identifier, + int position, + unsigned long sampleRate, + size_t blockSize, + int idealChannelCount, + const LADSPA_Descriptor* descriptor); + + void init(int idealChannelCount = 0); + void instantiate(unsigned long sampleRate); + void cleanup(); + void activate(); + void deactivate(); + + // Connection of data (and behind the scenes control) ports + // + void connectPorts(); + + int m_client; + int m_position; + std::vector m_instanceHandles; + size_t m_instanceCount; + const LADSPA_Descriptor *m_descriptor; + + std::vector > m_controlPortsIn; + std::vector > m_controlPortsOut; + + std::vector m_audioPortsIn; + std::vector m_audioPortsOut; + + size_t m_blockSize; + sample_t **m_inputBuffers; + sample_t **m_outputBuffers; + bool m_ownBuffers; + size_t m_sampleRate; + float *m_latencyPort; + bool m_run; + + bool m_bypassed; +}; + +#endif // _LADSPAPLUGININSTANCE_H_ + diff -r 000000000000 -r fc9323a41f5a plugin/PluginIdentifier.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginIdentifier.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and QMUL. +*/ + +#include "PluginIdentifier.h" +#include +#include + +QString +PluginIdentifier::createIdentifier(QString type, + QString soName, + QString label) +{ + QString identifier = type + ":" + QFileInfo(soName).baseName() + ":" + label; + return identifier; +} + +QString +PluginIdentifier::canonicalise(QString identifier) +{ + QString type, soName, label; + parseIdentifier(identifier, type, soName, label); + return createIdentifier(type, soName, label); +} + +void +PluginIdentifier::parseIdentifier(QString identifier, + QString &type, + QString &soName, + QString &label) +{ + type = identifier.section(':', 0, 0); + soName = identifier.section(':', 1, 1); + label = identifier.section(':', 2); +} + +bool +PluginIdentifier::areIdentifiersSimilar(QString id1, QString id2) +{ + QString type1, type2, soName1, soName2, label1, label2; + + parseIdentifier(id1, type1, soName1, label1); + parseIdentifier(id2, type2, soName2, label2); + + if (type1 != type2 || label1 != label2) return false; + + bool similar = (soName1.section('/', -1).section('.', 0, 0) == + soName2.section('/', -1).section('.', 0, 0)); + + return similar; +} + +QString +PluginIdentifier::BUILTIN_PLUGIN_SONAME = "_builtin"; + +QString +PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY = "__QMUL__:__RESERVED__:ProjectDirectoryKey"; + diff -r 000000000000 -r fc9323a41f5a plugin/PluginIdentifier.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginIdentifier.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and QMUL. +*/ + +#ifndef _PLUGIN_IDENTIFIER_H_ +#define _PLUGIN_IDENTIFIER_H_ + +#include + + +// A plugin identifier is simply a string; this class provides methods +// to parse it into its constituent bits (plugin type, DLL path and label). + +class PluginIdentifier { + +public: + + static QString createIdentifier(QString type, QString soName, QString label); + + static QString canonicalise(QString identifier); + + static void parseIdentifier(QString identifier, + QString &type, QString &soName, QString &label); + + static bool areIdentifiersSimilar(QString id1, QString id2); + + // Fake soName for use with plugins that are actually compiled in + static QString BUILTIN_PLUGIN_SONAME; + + // Not strictly related to identifiers + static QString RESERVED_PROJECT_DIRECTORY_KEY; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/PluginXml.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginXml.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,233 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PluginXml.h" + +#include +#include + +#include +#include +#include +#include + +#include "vamp-sdk/PluginBase.h" +#include "RealTimePluginInstance.h" + +#include + +PluginXml::PluginXml(Vamp::PluginBase *plugin) : + m_plugin(plugin) +{ +} + +PluginXml::~PluginXml() { } + +QString +PluginXml::encodeConfigurationChars(QString text) +{ + QString rv(text); + rv.replace(";", "[[SEMICOLON]]"); + rv.replace("=", "[[EQUALS]]"); + return rv; +} + +QString +PluginXml::decodeConfigurationChars(QString text) +{ + QString rv(text); + rv.replace("[[SEMICOLON]]", ";"); + rv.replace("[[EQUALS]]", "="); + return rv; +} + +QString +PluginXml::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + s += indent; + + s += QString("getIdentifier().c_str()))) + .arg(encodeEntities(QString(m_plugin->getName().c_str()))) + .arg(encodeEntities(QString(m_plugin->getDescription().c_str()))) + .arg(encodeEntities(QString(m_plugin->getMaker().c_str()))) + .arg(m_plugin->getPluginVersion()) + .arg(encodeEntities(QString(m_plugin->getCopyright().c_str()))) + .arg(extraAttributes); + + if (!m_plugin->getPrograms().empty()) { + s += QString("program=\"%1\" ") + .arg(encodeEntities(m_plugin->getCurrentProgram().c_str())); + } + + Vamp::PluginBase::ParameterList parameters = + m_plugin->getParameterDescriptors(); + + for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin(); + i != parameters.end(); ++i) { + +// std::cerr << "PluginXml::toXmlString: parameter name \"" +// << i->name.c_str() << "\" has value " +// << m_plugin->getParameter(i->name) << std::endl; + + s += QString("param-%1=\"%2\" ") + .arg(stripInvalidParameterNameCharacters(QString(i->identifier.c_str()))) + .arg(m_plugin->getParameter(i->identifier)); + } + + RealTimePluginInstance *rtpi = + dynamic_cast(m_plugin); + if (rtpi) { + std::map configurePairs = + rtpi->getConfigurePairs(); + QString config; + for (std::map::iterator i = configurePairs.begin(); + i != configurePairs.end(); ++i) { + QString key = i->first.c_str(); + QString value = i->second.c_str(); + key = encodeConfigurationChars(key); + value = encodeConfigurationChars(value); + if (config != "") config += ";"; + config += QString("%1=%2").arg(key).arg(value); + } + if (config != "") { + s += QString("configuration=\"%1\" ") + .arg(encodeEntities(config)); + } + } + + s += "/>\n"; + return s; +} + +#define CHECK_ATTRIBUTE(ATTRIBUTE, ACCESSOR) \ + QString ATTRIBUTE = attrs.value(#ATTRIBUTE); \ + if (ATTRIBUTE != "" && ATTRIBUTE != ACCESSOR().c_str()) { \ + std::cerr << "WARNING: PluginXml::setParameters: Plugin " \ + << #ATTRIBUTE << " does not match (attributes have \"" \ + << ATTRIBUTE.toStdString() << "\", my " \ + << #ATTRIBUTE << " is \"" << ACCESSOR() << "\")" << std::endl; \ + } + +void +PluginXml::setParameters(const QXmlAttributes &attrs) +{ + CHECK_ATTRIBUTE(identifier, m_plugin->getIdentifier); + CHECK_ATTRIBUTE(name, m_plugin->getName); + CHECK_ATTRIBUTE(description, m_plugin->getDescription); + CHECK_ATTRIBUTE(maker, m_plugin->getMaker); + CHECK_ATTRIBUTE(copyright, m_plugin->getCopyright); + + bool ok; + int version = attrs.value("version").trimmed().toInt(&ok); + if (ok && version != m_plugin->getPluginVersion()) { + std::cerr << "WARNING: PluginXml::setParameters: Plugin version does not match (attributes have " << version << ", my version is " << m_plugin->getPluginVersion() << ")" << std::endl; + } + + RealTimePluginInstance *rtpi = + dynamic_cast(m_plugin); + if (rtpi) { + QString config = attrs.value("configuration"); + if (config != "") { + QStringList configList = config.split(";"); + for (QStringList::iterator i = configList.begin(); + i != configList.end(); ++i) { + QStringList kv = i->split("="); + if (kv.count() < 2) { + std::cerr << "WARNING: PluginXml::setParameters: Malformed configure pair string: \"" << i->toStdString() << "\"" << std::endl; + continue; + } + QString key(kv[0]), value(kv[1]); + key = decodeConfigurationChars(key); + value = decodeConfigurationChars(value); + rtpi->configure(key.toStdString(), value.toStdString()); + } + } + } + + if (!m_plugin->getPrograms().empty()) { + m_plugin->selectProgram(attrs.value("program").toStdString()); + } + + Vamp::PluginBase::ParameterList parameters = + m_plugin->getParameterDescriptors(); + + for (Vamp::PluginBase::ParameterList::const_iterator i = + parameters.begin(); i != parameters.end(); ++i) { + + QString pname = QString("param-%1") + .arg(stripInvalidParameterNameCharacters + (QString(i->identifier.c_str()))); + + if (attrs.value(pname) == "") { +// std::cerr << "PluginXml::setParameters: no parameter \"" << i->name << "\" (attribute \"" << name.toStdString() << "\")" << std::endl; + continue; + } + + bool ok; + float value = attrs.value(pname).trimmed().toFloat(&ok); + if (ok) { +// std::cerr << "PluginXml::setParameters: setting parameter \"" +// << i->identifier << "\" to value " << value << std::endl; + m_plugin->setParameter(i->identifier, value); + } else { + std::cerr << "WARNING: PluginXml::setParameters: Invalid value \"" << attrs.value(pname).toStdString() << "\" for parameter \"" << i->identifier << "\" (attribute \"" << pname.toStdString() << "\")" << std::endl; + } + } +} + +void +PluginXml::setParametersFromXml(QString xml) +{ + QDomDocument doc; + + QString error; + int errorLine; + int errorColumn; + +// std::cerr << "PluginXml::setParametersFromXml: XML is \"" +// << xml.toLocal8Bit().data() << "\"" << std::endl; + + if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) { + std::cerr << "PluginXml::setParametersFromXml: Error in parsing XML: " << error.toStdString() << " at line " << errorLine << ", column " << errorColumn << std::endl; + std::cerr << "Input follows:" << std::endl; + std::cerr << xml.toStdString() << std::endl; + std::cerr << "Input ends." << std::endl; + return; + } + + QDomElement pluginElt = doc.firstChildElement("plugin"); + QDomNamedNodeMap attrNodes = pluginElt.attributes(); + QXmlAttributes attrs; + + for (unsigned int i = 0; i < attrNodes.length(); ++i) { + QDomAttr attr = attrNodes.item(i).toAttr(); + if (attr.isNull()) continue; +// std::cerr << "PluginXml::setParametersFromXml: Adding attribute \"" << attr.name().toStdString() +// << "\" with value \"" << attr.value().toStdString() << "\"" << std::endl; + attrs.append(attr.name(), "", "", attr.value()); + } + + setParameters(attrs); +} + +QString +PluginXml::stripInvalidParameterNameCharacters(QString s) const +{ + s.replace(QRegExp("[^a-zA-Z0-9_]*"), ""); + return s; +} + diff -r 000000000000 -r fc9323a41f5a plugin/PluginXml.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginXml.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLUGIN_XML_H_ +#define _PLUGIN_XML_H_ + +#include "base/XmlExportable.h" + +namespace Vamp { class PluginBase; } + +class QXmlAttributes; + +class PluginXml : public XmlExportable +{ +public: + PluginXml(Vamp::PluginBase *plugin); + virtual ~PluginXml(); + + /** + * Export plugin settings to XML. + */ + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + /** + * Set the parameters and program of a plugin from a set of XML + * attributes. This is a partial inverse of toXmlString. + */ + virtual void setParameters(const QXmlAttributes &); + + /** + * Set the parameters and program of a plugin from an XML plugin + * element as returned by toXmlString. This is a partial inverse + * of toXmlString. + */ + virtual void setParametersFromXml(QString xml); + + static QString encodeConfigurationChars(QString text); + static QString decodeConfigurationChars(QString text); + +protected: + QString stripInvalidParameterNameCharacters(QString) const; + + Vamp::PluginBase *m_plugin; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/RealTimePluginFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/RealTimePluginFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,126 @@ +// -*- c-basic-offset: 4 indent-tabs-mode: nil -*- + +/* + Rosegarden-4 + A sequencer and musical notation editor. + + This program is Copyright 2000-2006 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RealTimePluginFactory.h" +#include "PluginIdentifier.h" + +#include "LADSPAPluginFactory.h" +#include "DSSIPluginFactory.h" + +#include + +int RealTimePluginFactory::m_sampleRate = 48000; + +static LADSPAPluginFactory *_ladspaInstance = 0; +static LADSPAPluginFactory *_dssiInstance = 0; + +RealTimePluginFactory::~RealTimePluginFactory() +{ +} + +RealTimePluginFactory * +RealTimePluginFactory::instance(QString pluginType) +{ + if (pluginType == "ladspa") { + if (!_ladspaInstance) { +// std::cerr << "RealTimePluginFactory::instance(" << pluginType.toStdString() +// << "): creating new LADSPAPluginFactory" << std::endl; + _ladspaInstance = new LADSPAPluginFactory(); + _ladspaInstance->discoverPlugins(); + } + return _ladspaInstance; + } else if (pluginType == "dssi") { + if (!_dssiInstance) { +// std::cerr << "RealTimePluginFactory::instance(" << pluginType.toStdString() +// << "): creating new DSSIPluginFactory" << std::endl; + _dssiInstance = new DSSIPluginFactory(); + _dssiInstance->discoverPlugins(); + } + return _dssiInstance; + } + + else return 0; +} + +RealTimePluginFactory * +RealTimePluginFactory::instanceFor(QString identifier) +{ + QString type, soName, label; + PluginIdentifier::parseIdentifier(identifier, type, soName, label); + return instance(type); +} + +std::vector +RealTimePluginFactory::getAllPluginIdentifiers() +{ + RealTimePluginFactory *factory; + std::vector rv; + + // Query DSSI plugins before LADSPA ones. + // This is to provide for the interesting possibility of plugins + // providing either DSSI or LADSPA versions of themselves, + // returning both versions if the LADSPA identifiers are queried + // first but only the DSSI version if the DSSI identifiers are + // queried first. + + factory = instance("dssi"); + if (factory) { + const std::vector &tmp = factory->getPluginIdentifiers(); + for (size_t i = 0; i < tmp.size(); ++i) { + rv.push_back(tmp[i]); + } + } + + factory = instance("ladspa"); + if (factory) { + const std::vector &tmp = factory->getPluginIdentifiers(); + for (size_t i = 0; i < tmp.size(); ++i) { + rv.push_back(tmp[i]); + } + } + + // Plugins can change the locale, revert it to default. + setlocale(LC_ALL, "C"); + return rv; +} + +void +RealTimePluginFactory::enumerateAllPlugins(std::vector &list) +{ + RealTimePluginFactory *factory; + + // Query DSSI plugins before LADSPA ones. + // This is to provide for the interesting possibility of plugins + // providing either DSSI or LADSPA versions of themselves, + // returning both versions if the LADSPA identifiers are queried + // first but only the DSSI version if the DSSI identifiers are + // queried first. + + factory = instance("dssi"); + if (factory) factory->enumeratePlugins(list); + + factory = instance("ladspa"); + if (factory) factory->enumeratePlugins(list); + + // Plugins can change the locale, revert it to default. + setlocale(LC_ALL, "C"); +} + diff -r 000000000000 -r fc9323a41f5a plugin/RealTimePluginFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/RealTimePluginFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,107 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _REALTIME_PLUGIN_FACTORY_H_ +#define _REALTIME_PLUGIN_FACTORY_H_ + +#include +#include + +class RealTimePluginInstance; + +class RealTimePluginDescriptor +{ +public: + std::string name; + std::string label; + std::string maker; + std::string copyright; + std::string category; + bool isSynth; + unsigned int parameterCount; + unsigned int audioInputPortCount; + unsigned int audioOutputPortCount; + unsigned int controlOutputPortCount; + std::vector controlOutputPortNames; +}; + +class RealTimePluginFactory +{ +public: + virtual ~RealTimePluginFactory(); + + static RealTimePluginFactory *instance(QString pluginType); + static RealTimePluginFactory *instanceFor(QString identifier); + static std::vector getAllPluginIdentifiers(); + static void enumerateAllPlugins(std::vector &); + + static void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; } + + /** + * Look up the plugin path and find the plugins in it. Called + * automatically after construction of a factory. + */ + virtual void discoverPlugins() = 0; + + /** + * Return a reference to a list of all plugin identifiers that can + * be created by this factory. + */ + virtual const std::vector &getPluginIdentifiers() const = 0; + + /** + * Append to the given list descriptions of all the available + * plugins and their ports. This is in a standard format, see + * the LADSPA implementation for details. + */ + virtual void enumeratePlugins(std::vector &list) = 0; + + /** + * Get some basic information about a plugin (rapidly). + */ + virtual const RealTimePluginDescriptor *getPluginDescriptor(QString identifier) const = 0; + + /** + * Instantiate a plugin. + */ + virtual RealTimePluginInstance *instantiatePlugin(QString identifier, + int clientId, + int position, + unsigned int sampleRate, + unsigned int blockSize, + unsigned int channels) = 0; + + /** + * Get category metadata about a plugin (without instantiating it). + */ + virtual QString getPluginCategory(QString identifier) = 0; + +protected: + RealTimePluginFactory() { } + + // for call by RealTimePluginInstance dtor + virtual void releasePlugin(RealTimePluginInstance *, QString identifier) = 0; + friend class RealTimePluginInstance; + + static int m_sampleRate; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/RealTimePluginInstance.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/RealTimePluginInstance.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,39 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include "RealTimePluginInstance.h" +#include "RealTimePluginFactory.h" + +#include "PluginIdentifier.h" + +#include + + +RealTimePluginInstance::~RealTimePluginInstance() +{ +// std::cerr << "RealTimePluginInstance::~RealTimePluginInstance" << std::endl; + + if (m_factory) { +// std::cerr << "Asking factory to release " << m_identifier.toStdString() << std::endl; + + m_factory->releasePlugin(this, m_identifier); + } +} + diff -r 000000000000 -r fc9323a41f5a plugin/RealTimePluginInstance.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/RealTimePluginInstance.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,153 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _REALTIME_PLUGIN_INSTANCE_H_ +#define _REALTIME_PLUGIN_INSTANCE_H_ + +#include "vamp-sdk/PluginBase.h" +#include "vamp-sdk/RealTime.h" + +#include +#include +#include +#include +#include + +class RealTimePluginFactory; + +/** + * RealTimePluginInstance is an interface that an audio process can + * use to refer to an instance of a plugin without needing to know + * what type of plugin it is. + * + * The audio code calls run() on an instance that has been passed to + * it, and assumes that the passing code has already initialised the + * plugin, connected its inputs and outputs and so on, and that there + * is an understanding in place about the sizes of the buffers in use + * by the plugin. All of this depends on the subclass implementation. + * + * The PluginInstance base class includes additional abstract methods + * which the subclass of RealTimePluginInstance must implement. + */ + +/* + * N.B. RealTimePluginInstance, RealTimePluginFactory and their + * subclasses are terrible code. They've been reused, cut and pasted + * and mangled too many times to fit too many different uses, and + * could do with a good tidy. + */ + +// These names are taken from LADSPA, but the values are not +// guaranteed to match + +namespace PortType { // ORable + static const int Input = 1; + static const int Output = 2; + static const int Control = 4; + static const int Audio = 8; +} + +namespace PortHint { // ORable + static const int NoHint = 0; + static const int Toggled = 1; + static const int Integer = 2; + static const int Logarithmic = 4; + static const int SampleRate = 8; +} + +class RealTimePluginInstance : public Vamp::PluginBase +{ +public: + typedef float sample_t; + + virtual ~RealTimePluginInstance(); + + virtual bool isOK() const = 0; + + virtual QString getPluginIdentifier() const = 0; + + /** + * Run for one block, starting at the given time. The start time + * may be of interest to synths etc that may have queued events + * waiting. Other plugins can ignore it. + */ + virtual void run(const Vamp::RealTime &blockStartTime) = 0; + + virtual size_t getBufferSize() const = 0; + + virtual size_t getAudioInputCount() const = 0; + virtual size_t getAudioOutputCount() const = 0; + + virtual sample_t **getAudioInputBuffers() = 0; + virtual sample_t **getAudioOutputBuffers() = 0; + + // Control inputs are known as parameters here + virtual size_t getControlOutputCount() const = 0; + virtual float getControlOutputValue(size_t n) const = 0; + +// virtual QStringList getPrograms() const { return QStringList(); } +// virtual QString getCurrentProgram() const { return QString(); } + virtual std::string getProgram(int /* bank */, int /* program */) const { return std::string(); } +// virtual unsigned long getProgram(QString /* name */) const { return 0; } // bank << 16 + program +// virtual void selectProgram(QString) { } + + virtual unsigned int getParameterCount() const = 0; + virtual void setParameterValue(unsigned int parameter, float value) = 0; + virtual float getParameterValue(unsigned int parameter) const = 0; + virtual float getParameterDefault(unsigned int parameter) const = 0; + + virtual std::string configure(std::string /* key */, std::string /* value */) { return std::string(); } + + virtual void sendEvent(const Vamp::RealTime & /* eventTime */, + const void * /* event */) { } + virtual void clearEvents() { } + + virtual bool isBypassed() const = 0; + virtual void setBypassed(bool value) = 0; + + // This should be called after setup, but while not actually playing. + virtual size_t getLatency() = 0; + + virtual void silence() = 0; + virtual void discardEvents() { } + virtual void setIdealChannelCount(size_t channels) = 0; // must also silence(); may also re-instantiate + + void setFactory(RealTimePluginFactory *f) { m_factory = f; } // ew + + virtual std::string getType() const { return "Real-Time Plugin"; } + + virtual std::map getConfigurePairs() { + return m_configurationData; + } + +protected: + RealTimePluginInstance(RealTimePluginFactory *factory, QString identifier) : + m_factory(factory), m_identifier(identifier) { } + + RealTimePluginFactory *m_factory; + QString m_identifier; + + std::map m_configurationData; + + friend class PluginFactory; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/README Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,169 @@ +libdssialsacompat 1.0.8a +======================== +libdssialsacompat is simply an extraction from and repackaging of +the code from alsa-lib 1.0.8, necessary to support DSSI on non-ALSA +systems. It is copyright (c)2005 under the GNU Lesser General +Public License, version 2.1 or later. See the enclosed file COPYING +for details. + +More information on DSSI can be found at: + + http://dssi.sourceforge.net/ + +Introduction +============ +The DSSI specification makes use of the ALSA snd_seq_event_t +structure for passing MIDI events. This has the advantage of making +it immediately familiar to developers familiar with ALSA, but the +disadvantage of making porting DSSI applications and plugins to +systems that lack ALSA more difficult. + +libdssialsacompat is intended to provide the snd_seq_event_t +declarations and handling code necessary to compile and use DSSI on +non-ALSA systems. It aims to allows compiling DSSI code with as +little change as possible, while not presenting itself in such a way +as to fool other autoconf-enabled code into thinking a system has +ALSA. + +libdssialsacompat is simply an extraction of the relevant +snd_seq_event_t declarations, and raw MIDI stream to snd_seq_event_t +encoder code, from alsa-lib version 1.0.8, packaged into a +convenient library. + +This library does NOT provide any sort of emulation of the ALSA +audio, MIDI, or sequencer devices. The only part of ALSA that is +required by the DSSI specification is the snd_seq_event_t definition +and handling, and that is all libdssialsacompat is intended to +replace. Other ALSA code should be ported to native facilities. + +Installation +============ +libdssialsacompat uses GNU autoconf and automake, so installation can +be a simple as `./configure && make && make install'. See the +enclosed file INSTALL for more information. + +The library itself is installed to /lib; for example, on Mac +OS X, the following files are installed: + + /lib/libdssialsacompat.0.0.0.dylib + /lib/libdssialsacompat.0.dylib + /lib/libdssialsacompat.a + /lib/libdssialsacompat.dylib + /lib/libdssialsacompat.la + +The header files are installed to /include/dssi/alsa: + + /include/dssi/alsa/asoundef.h + /include/dssi/alsa/asoundlib.h + /include/dssi/alsa/seq.h + /include/dssi/alsa/seq_event.h + /include/dssi/alsa/seq_midi_event.h + /include/dssi/alsa/sound/asequencer.h + +Note that they are NOT installed to /include/alsa, which +could make them visible to non-libdssialsacompat-aware autoconf +configure scripts, possibly fooling them into thinking the full ALSA +was available. + +Finally, a pkgconfig configuration file is installed: + + /lib/pkgconfig/libdssialsacompat.pc + +Use +=== +At its most basic, compiling with gcc and libdssialsacompat consists +of adding '-I/include/dssi -L/lib -ldssialsacompat' +to your gcc command line. Note that the '-I' directive will cause +code like to following: + + #include + #include + +to find the libdssialsacompat header files, even though they are not +installed in the usual location for ALSA headers. + +libdssialsacompat is easiest to use with autoconf/automake/pkgconfig- +enabled code. In which case, editing the configure.in or configure.ac +file and changing: + + PKG_CHECK_MODULES(ALSA, alsa) + +to: + + PKG_CHECK_MODULES(ALSA, alsa, , [PKG_CHECK_MODULES(ALSA, libdssialsacompat)]) + +then doing 'autoreconf', may be all that is needed to get the +snd_seq_event_t-using code to compile cleanly. Of course, if the +code uses other ALSA features, libdssialsacompat won't help with +them.... + +DSSI Installation +================= +Installation of DSSI itself (at least as recently as 2005/4/6 CVS) +must be done by hand, which goes something like this (assuming +you're running OS X 10.3 and want to install to /usr/local): + +$ tar xpzf dssi-0.9.tar.gz +$ cd dssi-0.9 +$ sudo mkdir -p /usr/local/include +$ sudo cp dssi/dssi.h /usr/local/include/ +$ sed s:.PREFIX.:/usr/local: dssi.pc >dssi.pc.new +$ sudo mkdir -p /usr/local/lib/pkgconfig +$ sudo mv dssi.pc.new /usr/local/lib/pkgconfig/dssi.pc + +(You may stop here if you're not interested in the example plugins.) + +$ cd examples +$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig +$ export PKG_CONFIG_PATH +$ make dssi_osc_send +$ sudo cp -p dssi_osc_send /usr/local/bin +$ gcc -Wall -fno-common -O2 `pkg-config libdssialsacompat dssi --cflags` \ + trivial_synth.c -c -o trivial_synth.o +$ gcc -Wall -fno-common -O2 -c -o message_buffer.o \ + ../message_buffer/message_buffer.c +$ gcc -bundle -flat_namespace -undefined suppress -o trivial_synth.so \ + trivial_synth.o message_buffer.o \ + `pkg-config libdssialsacompat dssi --libs` +$ sudo mkdir -p /usr/local/lib/dssi +$ sudo cp -p trivial_synth.so /usr/local/lib/dssi + +Building the rest of the DSSI distribution's plugins is left as an +exercise to the reader. Note that (as of 2005/4/6) jack-dssi-host +will not build without patching since it uses the ALSA sequencer. + +What Works (Or Doesn't) +======================= +libdssialsacompat 1.0.8a was tested on Mac OS X 10.3.8, using the +Apple Developer Tools, Gentoo-installed versions of pkgconfig and +liblo 0.18, hand-installed GTK+ 1.2 and LADSPA SDK, and JackOSX +0.6.1. Under this configuration, the following are known to work: + +- ghostess (from the 20050411 release, which includes a clumsy but + working CoreMIDI driver.) ghostess can be found at: + + http://home.jps.net/~musound/ + +- trivial_synth.so (DSSI 0.9 release) + +- fluidsynth-dssi.so and FluidSynth-DSSI_gtk (DSSI 0.9 release, + using a statically compiled libfluidsynth 1.0.3) + +- Xsynth-DSSI (CVS as of 2005/4/11) + +- hexter (CVS as of 2005/4/11, note that sys-ex patch editing isn't + supported on non-ALSA systems) + +The following problems are known to exist: + +- less_trivial_synth.so (DSSI 0.9) plays at the wrong pitch on + big-endian systems due the little-endian assumption of the + typedef union fixp in less_trivial_synth.c (line 69). Otherwise + works fine. + +- I have not tested any of the DSSI 0.9 Qt GUIs, or + trivial_sampler.so. + +- jack-dssi-host (DSSI 0.9) works as an OSC-driven host if you + comment out all the ALSA seqeuncer code. + diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/asoundef.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/asoundef.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,166 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +/** + * \file + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela + * \author Abramo Bagnara + * \author Takashi Iwai + * \date 1998-2001 + * + * Definitions of constants for the ALSA driver + */ +/* + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_ASOUNDEF_H +#define __ALSA_ASOUNDEF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup MIDI_Interface Constants for MIDI v1.0 + * Constants for MIDI v1.0. + * \{ + */ + +#define MIDI_CHANNELS 16 /**< Number of channels per port/cable. */ +#define MIDI_GM_DRUM_CHANNEL (10-1) /**< Channel number for GM drums. */ + +/** + * \defgroup MIDI_Commands MIDI Commands + * MIDI command codes. + * \{ + */ + +#define MIDI_CMD_NOTE_OFF 0x80 /**< note off */ +#define MIDI_CMD_NOTE_ON 0x90 /**< note on */ +#define MIDI_CMD_NOTE_PRESSURE 0xa0 /**< key pressure */ +#define MIDI_CMD_CONTROL 0xb0 /**< control change */ +#define MIDI_CMD_PGM_CHANGE 0xc0 /**< program change */ +#define MIDI_CMD_CHANNEL_PRESSURE 0xd0 /**< channel pressure */ +#define MIDI_CMD_BENDER 0xe0 /**< pitch bender */ + +#define MIDI_CMD_COMMON_SYSEX 0xf0 /**< sysex (system exclusive) begin */ +#define MIDI_CMD_COMMON_MTC_QUARTER 0xf1 /**< MTC quarter frame */ +#define MIDI_CMD_COMMON_SONG_POS 0xf2 /**< song position */ +#define MIDI_CMD_COMMON_SONG_SELECT 0xf3 /**< song select */ +#define MIDI_CMD_COMMON_TUNE_REQUEST 0xf6 /**< tune request */ +#define MIDI_CMD_COMMON_SYSEX_END 0xf7 /**< end of sysex */ +#define MIDI_CMD_COMMON_CLOCK 0xf8 /**< clock */ +#define MIDI_CMD_COMMON_START 0xfa /**< start */ +#define MIDI_CMD_COMMON_CONTINUE 0xfb /**< continue */ +#define MIDI_CMD_COMMON_STOP 0xfc /**< stop */ +#define MIDI_CMD_COMMON_SENSING 0xfe /**< active sensing */ +#define MIDI_CMD_COMMON_RESET 0xff /**< reset */ + +/** \} */ + +/** + * \defgroup MIDI_Controllers MIDI Controllers + * MIDI controller numbers. + * \{ + */ + +#define MIDI_CTL_MSB_BANK 0x00 /**< Bank selection */ +#define MIDI_CTL_MSB_MODWHEEL 0x01 /**< Modulation */ +#define MIDI_CTL_MSB_BREATH 0x02 /**< Breath */ +#define MIDI_CTL_MSB_FOOT 0x04 /**< Foot */ +#define MIDI_CTL_MSB_PORTAMENTO_TIME 0x05 /**< Portamento time */ +#define MIDI_CTL_MSB_DATA_ENTRY 0x06 /**< Data entry */ +#define MIDI_CTL_MSB_MAIN_VOLUME 0x07 /**< Main volume */ +#define MIDI_CTL_MSB_BALANCE 0x08 /**< Balance */ +#define MIDI_CTL_MSB_PAN 0x0a /**< Panpot */ +#define MIDI_CTL_MSB_EXPRESSION 0x0b /**< Expression */ +#define MIDI_CTL_MSB_EFFECT1 0x0c /**< Effect1 */ +#define MIDI_CTL_MSB_EFFECT2 0x0d /**< Effect2 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE1 0x10 /**< General purpose 1 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE2 0x11 /**< General purpose 2 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE3 0x12 /**< General purpose 3 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE4 0x13 /**< General purpose 4 */ +#define MIDI_CTL_LSB_BANK 0x20 /**< Bank selection */ +#define MIDI_CTL_LSB_MODWHEEL 0x21 /**< Modulation */ +#define MIDI_CTL_LSB_BREATH 0x22 /**< Breath */ +#define MIDI_CTL_LSB_FOOT 0x24 /**< Foot */ +#define MIDI_CTL_LSB_PORTAMENTO_TIME 0x25 /**< Portamento time */ +#define MIDI_CTL_LSB_DATA_ENTRY 0x26 /**< Data entry */ +#define MIDI_CTL_LSB_MAIN_VOLUME 0x27 /**< Main volume */ +#define MIDI_CTL_LSB_BALANCE 0x28 /**< Balance */ +#define MIDI_CTL_LSB_PAN 0x2a /**< Panpot */ +#define MIDI_CTL_LSB_EXPRESSION 0x2b /**< Expression */ +#define MIDI_CTL_LSB_EFFECT1 0x2c /**< Effect1 */ +#define MIDI_CTL_LSB_EFFECT2 0x2d /**< Effect2 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE1 0x30 /**< General purpose 1 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE2 0x31 /**< General purpose 2 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE3 0x32 /**< General purpose 3 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE4 0x33 /**< General purpose 4 */ +#define MIDI_CTL_SUSTAIN 0x40 /**< Sustain pedal */ +#define MIDI_CTL_PORTAMENTO 0x41 /**< Portamento */ +#define MIDI_CTL_SUSTENUTO 0x42 /**< Sostenuto */ +#define MIDI_CTL_SOFT_PEDAL 0x43 /**< Soft pedal */ +#define MIDI_CTL_LEGATO_FOOTSWITCH 0x44 /**< Legato foot switch */ +#define MIDI_CTL_HOLD2 0x45 /**< Hold2 */ +#define MIDI_CTL_SC1_SOUND_VARIATION 0x46 /**< SC1 Sound Variation */ +#define MIDI_CTL_SC2_TIMBRE 0x47 /**< SC2 Timbre */ +#define MIDI_CTL_SC3_RELEASE_TIME 0x48 /**< SC3 Release Time */ +#define MIDI_CTL_SC4_ATTACK_TIME 0x49 /**< SC4 Attack Time */ +#define MIDI_CTL_SC5_BRIGHTNESS 0x4a /**< SC5 Brightness */ +#define MIDI_CTL_SC6 0x4b /**< SC6 */ +#define MIDI_CTL_SC7 0x4c /**< SC7 */ +#define MIDI_CTL_SC8 0x4d /**< SC8 */ +#define MIDI_CTL_SC9 0x4e /**< SC9 */ +#define MIDI_CTL_SC10 0x4f /**< SC10 */ +#define MIDI_CTL_GENERAL_PURPOSE5 0x50 /**< General purpose 5 */ +#define MIDI_CTL_GENERAL_PURPOSE6 0x51 /**< General purpose 6 */ +#define MIDI_CTL_GENERAL_PURPOSE7 0x52 /**< General purpose 7 */ +#define MIDI_CTL_GENERAL_PURPOSE8 0x53 /**< General purpose 8 */ +#define MIDI_CTL_PORTAMENTO_CONTROL 0x54 /**< Portamento control */ +#define MIDI_CTL_E1_REVERB_DEPTH 0x5b /**< E1 Reverb Depth */ +#define MIDI_CTL_E2_TREMOLO_DEPTH 0x5c /**< E2 Tremolo Depth */ +#define MIDI_CTL_E3_CHORUS_DEPTH 0x5d /**< E3 Chorus Depth */ +#define MIDI_CTL_E4_DETUNE_DEPTH 0x5e /**< E4 Detune Depth */ +#define MIDI_CTL_E5_PHASER_DEPTH 0x5f /**< E5 Phaser Depth */ +#define MIDI_CTL_DATA_INCREMENT 0x60 /**< Data Increment */ +#define MIDI_CTL_DATA_DECREMENT 0x61 /**< Data Decrement */ +#define MIDI_CTL_NONREG_PARM_NUM_LSB 0x62 /**< Non-registered parameter number */ +#define MIDI_CTL_NONREG_PARM_NUM_MSB 0x63 /**< Non-registered parameter number */ +#define MIDI_CTL_REGIST_PARM_NUM_LSB 0x64 /**< Registered parameter number */ +#define MIDI_CTL_REGIST_PARM_NUM_MSB 0x65 /**< Registered parameter number */ +#define MIDI_CTL_ALL_SOUNDS_OFF 0x78 /**< All sounds off */ +#define MIDI_CTL_RESET_CONTROLLERS 0x79 /**< Reset Controllers */ +#define MIDI_CTL_LOCAL_CONTROL_SWITCH 0x7a /**< Local control switch */ +#define MIDI_CTL_ALL_NOTES_OFF 0x7b /**< All notes off */ +#define MIDI_CTL_OMNI_OFF 0x7c /**< Omni off */ +#define MIDI_CTL_OMNI_ON 0x7d /**< Omni on */ +#define MIDI_CTL_MONO1 0x7e /**< Mono1 */ +#define MIDI_CTL_MONO2 0x7f /**< Mono2 */ + +/** \} */ + +/** \} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_ASOUNDEF_H */ diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/asoundlib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/asoundlib.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,12 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +#include + +#include +#include +#include + diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/seq.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/seq.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,189 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +/** + * \file + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela + * \author Abramo Bagnara + * \author Takashi Iwai + * \date 1998-2001 + */ +/* + * Application interface library for the ALSA driver + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_SEQ_H +#define __ALSA_SEQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup SeqEvType Sequencer Event Type Checks + * Sequencer Event Type Checks + * \ingroup Sequencer + * \{ + */ + +/* event type macros */ +enum { + SND_SEQ_EVFLG_RESULT, + SND_SEQ_EVFLG_NOTE, + SND_SEQ_EVFLG_CONTROL, + SND_SEQ_EVFLG_QUEUE, + SND_SEQ_EVFLG_SYSTEM, + SND_SEQ_EVFLG_MESSAGE, + SND_SEQ_EVFLG_CONNECTION, + SND_SEQ_EVFLG_SAMPLE, + SND_SEQ_EVFLG_USERS, + SND_SEQ_EVFLG_INSTR, + SND_SEQ_EVFLG_QUOTE, + SND_SEQ_EVFLG_NONE, + SND_SEQ_EVFLG_RAW, + SND_SEQ_EVFLG_FIXED, + SND_SEQ_EVFLG_VARIABLE, + SND_SEQ_EVFLG_VARUSR +}; + +enum { + SND_SEQ_EVFLG_NOTE_ONEARG, + SND_SEQ_EVFLG_NOTE_TWOARG +}; + +enum { + SND_SEQ_EVFLG_QUEUE_NOARG, + SND_SEQ_EVFLG_QUEUE_TICK, + SND_SEQ_EVFLG_QUEUE_TIME, + SND_SEQ_EVFLG_QUEUE_VALUE +}; + +/** + * Exported event type table + * + * This table is referred by snd_seq_ev_is_xxx. + */ +extern const unsigned int snd_seq_event_types[]; + +#define _SND_SEQ_TYPE(x) (1<<(x)) /**< master type - 24bit */ +#define _SND_SEQ_TYPE_OPT(x) ((x)<<24) /**< optional type - 8bit */ + +/** check the event type */ +#define snd_seq_type_check(ev,x) (snd_seq_event_types[(ev)->type] & _SND_SEQ_TYPE(x)) + +/** event type check: result events */ +#define snd_seq_ev_is_result_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_RESULT) +/** event type check: note events */ +#define snd_seq_ev_is_note_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_NOTE) +/** event type check: control events */ +#define snd_seq_ev_is_control_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_CONTROL) +/** event type check: channel specific events */ +#define snd_seq_ev_is_channel_type(ev) \ + (snd_seq_event_types[(ev)->type] & (_SND_SEQ_TYPE(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_CONTROL))) + +/** event type check: queue control events */ +#define snd_seq_ev_is_queue_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_QUEUE) +/** event type check: system status messages */ +#define snd_seq_ev_is_message_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_MESSAGE) +/** event type check: system status messages */ +#define snd_seq_ev_is_subscribe_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_CONNECTION) +/** event type check: sample messages */ +#define snd_seq_ev_is_sample_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_SAMPLE) +/** event type check: user-defined messages */ +#define snd_seq_ev_is_user_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_USERS) +/** event type check: instrument layer events */ +#define snd_seq_ev_is_instr_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_INSTR) +/** event type check: fixed length events */ +#define snd_seq_ev_is_fixed_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_FIXED) +/** event type check: variable length events */ +#define snd_seq_ev_is_variable_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_VARIABLE) +/** event type check: user pointer events */ +#define snd_seq_ev_is_varusr_type(ev) \ + snd_seq_type_check(ev, SND_SEQ_EVFLG_VARUSR) +/** event type check: reserved for kernel */ +#define snd_seq_ev_is_reserved(ev) \ + (! snd_seq_event_types[(ev)->type]) + +/** + * macros to check event flags + */ +/** prior events */ +#define snd_seq_ev_is_prior(ev) \ + (((ev)->flags & SND_SEQ_PRIORITY_MASK) == SND_SEQ_PRIORITY_HIGH) + +/** get the data length type */ +#define snd_seq_ev_length_type(ev) \ + ((ev)->flags & SND_SEQ_EVENT_LENGTH_MASK) +/** fixed length events */ +#define snd_seq_ev_is_fixed(ev) \ + (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_FIXED) +/** variable length events */ +#define snd_seq_ev_is_variable(ev) \ + (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARIABLE) +/** variable length on user-space */ +#define snd_seq_ev_is_varusr(ev) \ + (snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARUSR) + +/** time-stamp type */ +#define snd_seq_ev_timestamp_type(ev) \ + ((ev)->flags & SND_SEQ_TIME_STAMP_MASK) +/** event is in tick time */ +#define snd_seq_ev_is_tick(ev) \ + (snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_TICK) +/** event is in real-time */ +#define snd_seq_ev_is_real(ev) \ + (snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_REAL) + +/** time-mode type */ +#define snd_seq_ev_timemode_type(ev) \ + ((ev)->flags & SND_SEQ_TIME_MODE_MASK) +/** scheduled in absolute time */ +#define snd_seq_ev_is_abstime(ev) \ + (snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_ABS) +/** scheduled in relative time */ +#define snd_seq_ev_is_reltime(ev) \ + (snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_REL) + +/** direct dispatched events */ +#define snd_seq_ev_is_direct(ev) \ + ((ev)->queue == SND_SEQ_QUEUE_DIRECT) + +/** \} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_SEQ_H */ + diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/seq_event.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/seq_event.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,463 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +/** + * \file + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela + * \author Abramo Bagnara + * \author Takashi Iwai + * \date 1998-2001 + * + * Application interface library for the ALSA driver + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_SEQ_EVENT_H +#define __ALSA_SEQ_EVENT_H + +/* If we're not using GNU C, elide __attribute__ */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +/** + * \defgroup SeqEvents Sequencer Event Definitions + * Sequencer Event Definitions + * \ingroup Sequencer + * \{ + */ + +/** + * Sequencer event data type + */ +typedef unsigned char snd_seq_event_type_t; + +/** Sequencer event type */ +enum snd_seq_event_type { + /** system status; event data type = #snd_seq_result_t */ + SND_SEQ_EVENT_SYSTEM = 0, + /** returned result status; event data type = #snd_seq_result_t */ + SND_SEQ_EVENT_RESULT, + + /** note on and off with duration; event data type = #snd_seq_ev_note_t */ + SND_SEQ_EVENT_NOTE = 5, + /** note on; event data type = #snd_seq_ev_note_t */ + SND_SEQ_EVENT_NOTEON, + /** note off; event data type = #snd_seq_ev_note_t */ + SND_SEQ_EVENT_NOTEOFF, + /** key pressure change (aftertouch); event data type = #snd_seq_ev_note_t */ + SND_SEQ_EVENT_KEYPRESS, + + /** controller; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_CONTROLLER = 10, + /** program change; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_PGMCHANGE, + /** channel pressure; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_CHANPRESS, + /** pitchwheel; event data type = #snd_seq_ev_ctrl_t; data is from -8192 to 8191) */ + SND_SEQ_EVENT_PITCHBEND, + /** 14 bit controller value; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_CONTROL14, + /** 14 bit NRPN; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_NONREGPARAM, + /** 14 bit RPN; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_REGPARAM, + + /** SPP with LSB and MSB values; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_SONGPOS = 20, + /** Song Select with song ID number; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_SONGSEL, + /** midi time code quarter frame; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_QFRAME, + /** SMF Time Signature event; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_TIMESIGN, + /** SMF Key Signature event; event data type = #snd_seq_ev_ctrl_t */ + SND_SEQ_EVENT_KEYSIGN, + + /** MIDI Real Time Start message; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_START = 30, + /** MIDI Real Time Continue message; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_CONTINUE, + /** MIDI Real Time Stop message; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_STOP, + /** Set tick queue position; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_SETPOS_TICK, + /** Set real-time queue position; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_SETPOS_TIME, + /** (SMF) Tempo event; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_TEMPO, + /** MIDI Real Time Clock message; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_CLOCK, + /** MIDI Real Time Tick message; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_TICK, + /** Queue timer skew; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_QUEUE_SKEW, + /** Sync position changed; event data type = #snd_seq_ev_queue_control_t */ + SND_SEQ_EVENT_SYNC_POS, + + /** Tune request; event data type = none */ + SND_SEQ_EVENT_TUNE_REQUEST = 40, + /** Reset to power-on state; event data type = none */ + SND_SEQ_EVENT_RESET, + /** Active sensing event; event data type = none */ + SND_SEQ_EVENT_SENSING, + + /** Echo-back event; event data type = any type */ + SND_SEQ_EVENT_ECHO = 50, + /** OSS emulation raw event; event data type = any type */ + SND_SEQ_EVENT_OSS, + + /** New client has connected; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_CLIENT_START = 60, + /** Client has left the system; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_CLIENT_EXIT, + /** Client status/info has changed; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_CLIENT_CHANGE, + /** New port was created; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_PORT_START, + /** Port was deleted from system; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_PORT_EXIT, + /** Port status/info has changed; event data type = #snd_seq_addr_t */ + SND_SEQ_EVENT_PORT_CHANGE, + + /** Ports connected; event data type = #snd_seq_connect_t */ + SND_SEQ_EVENT_PORT_SUBSCRIBED, + /** Ports disconnected; event data type = #snd_seq_connect_t */ + SND_SEQ_EVENT_PORT_UNSUBSCRIBED, + + /** Sample select; event data type = #snd_seq_ev_sample_control_t */ + SND_SEQ_EVENT_SAMPLE = 70, + /** Sample cluster select; event data type = #snd_seq_ev_sample_control_t */ + SND_SEQ_EVENT_SAMPLE_CLUSTER, + /** voice start */ + SND_SEQ_EVENT_SAMPLE_START, + /** voice stop */ + SND_SEQ_EVENT_SAMPLE_STOP, + /** playback frequency */ + SND_SEQ_EVENT_SAMPLE_FREQ, + /** volume and balance */ + SND_SEQ_EVENT_SAMPLE_VOLUME, + /** sample loop */ + SND_SEQ_EVENT_SAMPLE_LOOP, + /** sample position */ + SND_SEQ_EVENT_SAMPLE_POSITION, + /** private (hardware dependent) event */ + SND_SEQ_EVENT_SAMPLE_PRIVATE1, + + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR0 = 90, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR1, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR2, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR3, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR4, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR5, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR6, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR7, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR8, + /** user-defined event; event data type = any (fixed size) */ + SND_SEQ_EVENT_USR9, + + /** begin of instrument management */ + SND_SEQ_EVENT_INSTR_BEGIN = 100, + /** end of instrument management */ + SND_SEQ_EVENT_INSTR_END, + /** query instrument interface info */ + SND_SEQ_EVENT_INSTR_INFO, + /** result of instrument interface info */ + SND_SEQ_EVENT_INSTR_INFO_RESULT, + /** query instrument format info */ + SND_SEQ_EVENT_INSTR_FINFO, + /** result of instrument format info */ + SND_SEQ_EVENT_INSTR_FINFO_RESULT, + /** reset instrument instrument memory */ + SND_SEQ_EVENT_INSTR_RESET, + /** get instrument interface status */ + SND_SEQ_EVENT_INSTR_STATUS, + /** result of instrument interface status */ + SND_SEQ_EVENT_INSTR_STATUS_RESULT, + /** put an instrument to port */ + SND_SEQ_EVENT_INSTR_PUT, + /** get an instrument from port */ + SND_SEQ_EVENT_INSTR_GET, + /** result of instrument query */ + SND_SEQ_EVENT_INSTR_GET_RESULT, + /** free instrument(s) */ + SND_SEQ_EVENT_INSTR_FREE, + /** get instrument list */ + SND_SEQ_EVENT_INSTR_LIST, + /** result of instrument list */ + SND_SEQ_EVENT_INSTR_LIST_RESULT, + /** set cluster parameters */ + SND_SEQ_EVENT_INSTR_CLUSTER, + /** get cluster parameters */ + SND_SEQ_EVENT_INSTR_CLUSTER_GET, + /** result of cluster parameters */ + SND_SEQ_EVENT_INSTR_CLUSTER_RESULT, + /** instrument change */ + SND_SEQ_EVENT_INSTR_CHANGE, + + /** system exclusive data (variable length); event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_SYSEX = 130, + /** error event; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_BOUNCE, + /** reserved for user apps; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_USR_VAR0 = 135, + /** reserved for user apps; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_USR_VAR1, + /** reserved for user apps; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_USR_VAR2, + /** reserved for user apps; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_USR_VAR3, + /** reserved for user apps; event data type = #snd_seq_ev_ext_t */ + SND_SEQ_EVENT_USR_VAR4, + + /** NOP; ignored in any case */ + SND_SEQ_EVENT_NONE = 255 +}; + + +/** Sequencer event address */ +typedef struct snd_seq_addr { + unsigned char client; /**< Client id */ + unsigned char port; /**< Port id */ +} snd_seq_addr_t; + +/** Connection (subscription) between ports */ +typedef struct snd_seq_connect { + snd_seq_addr_t sender; /**< sender address */ + snd_seq_addr_t dest; /**< destination address */ +} snd_seq_connect_t; + + +/** Real-time data record */ +typedef struct snd_seq_real_time { + unsigned int tv_sec; /**< seconds */ + unsigned int tv_nsec; /**< nanoseconds */ +} snd_seq_real_time_t; + +/** (MIDI) Tick-time data record */ +typedef unsigned int snd_seq_tick_time_t; + +/** unioned time stamp */ +typedef union snd_seq_timestamp { + snd_seq_tick_time_t tick; /**< tick-time */ + struct snd_seq_real_time time; /**< real-time */ +} snd_seq_timestamp_t; + + +/** + * Event mode flags + * + * NOTE: only 8 bits available! + */ +#define SND_SEQ_TIME_STAMP_TICK (0<<0) /**< timestamp in clock ticks */ +#define SND_SEQ_TIME_STAMP_REAL (1<<0) /**< timestamp in real time */ +#define SND_SEQ_TIME_STAMP_MASK (1<<0) /**< mask for timestamp bits */ + +#define SND_SEQ_TIME_MODE_ABS (0<<1) /**< absolute timestamp */ +#define SND_SEQ_TIME_MODE_REL (1<<1) /**< relative to current time */ +#define SND_SEQ_TIME_MODE_MASK (1<<1) /**< mask for time mode bits */ + +#define SND_SEQ_EVENT_LENGTH_FIXED (0<<2) /**< fixed event size */ +#define SND_SEQ_EVENT_LENGTH_VARIABLE (1<<2) /**< variable event size */ +#define SND_SEQ_EVENT_LENGTH_VARUSR (2<<2) /**< variable event size - user memory space */ +#define SND_SEQ_EVENT_LENGTH_MASK (3<<2) /**< mask for event length bits */ + +#define SND_SEQ_PRIORITY_NORMAL (0<<4) /**< normal priority */ +#define SND_SEQ_PRIORITY_HIGH (1<<4) /**< event should be processed before others */ +#define SND_SEQ_PRIORITY_MASK (1<<4) /**< mask for priority bits */ + + +/** Note event */ +typedef struct snd_seq_ev_note { + unsigned char channel; /**< channel number */ + unsigned char note; /**< note */ + unsigned char velocity; /**< velocity */ + unsigned char off_velocity; /**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */ + unsigned int duration; /**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */ +} snd_seq_ev_note_t; + +/** Controller event */ +typedef struct snd_seq_ev_ctrl { + unsigned char channel; /**< channel number */ + unsigned char unused[3]; /**< reserved */ + unsigned int param; /**< control parameter */ + signed int value; /**< control value */ +} snd_seq_ev_ctrl_t; + +/** generic set of bytes (12x8 bit) */ +typedef struct snd_seq_ev_raw8 { + unsigned char d[12]; /**< 8 bit value */ +} snd_seq_ev_raw8_t; + +/** generic set of integers (3x32 bit) */ +typedef struct snd_seq_ev_raw32 { + unsigned int d[3]; /**< 32 bit value */ +} snd_seq_ev_raw32_t; + +/** external stored data */ +typedef struct snd_seq_ev_ext { + unsigned int len; /**< length of data */ + void *ptr; /**< pointer to data (note: can be 64-bit) */ +} __attribute__((packed)) snd_seq_ev_ext_t; + +/** Instrument cluster type */ +typedef unsigned int snd_seq_instr_cluster_t; + +/** Instrument type */ +typedef struct snd_seq_instr { + snd_seq_instr_cluster_t cluster; /**< cluster id */ + unsigned int std; /**< instrument standard id; the upper byte means a private instrument (owner - client id) */ + unsigned short bank; /**< instrument bank id */ + unsigned short prg; /**< instrument program id */ +} snd_seq_instr_t; + +/** sample number */ +typedef struct snd_seq_ev_sample { + unsigned int std; /**< sample standard id */ + unsigned short bank; /**< sample bank id */ + unsigned short prg; /**< sample program id */ +} snd_seq_ev_sample_t; + +/** sample cluster */ +typedef struct snd_seq_ev_cluster { + snd_seq_instr_cluster_t cluster; /**< cluster id */ +} snd_seq_ev_cluster_t; + +/** sample position */ +typedef unsigned int snd_seq_position_t; /**< playback position (in samples) * 16 */ + +/** sample stop mode */ +typedef enum snd_seq_stop_mode { + SND_SEQ_SAMPLE_STOP_IMMEDIATELY = 0, /**< terminate playing immediately */ + SND_SEQ_SAMPLE_STOP_VENVELOPE = 1, /**< finish volume envelope */ + SND_SEQ_SAMPLE_STOP_LOOP = 2 /**< terminate loop and finish wave */ +} snd_seq_stop_mode_t; + +/** sample frequency */ +typedef int snd_seq_frequency_t; /**< playback frequency in HZ * 16 */ + +/** sample volume control; if any value is set to -1 == do not change */ +typedef struct snd_seq_ev_volume { + signed short volume; /**< range: 0-16383 */ + signed short lr; /**< left-right balance; range: 0-16383 */ + signed short fr; /**< front-rear balance; range: 0-16383 */ + signed short du; /**< down-up balance; range: 0-16383 */ +} snd_seq_ev_volume_t; + +/** simple loop redefinition */ +typedef struct snd_seq_ev_loop { + unsigned int start; /**< loop start (in samples) * 16 */ + unsigned int end; /**< loop end (in samples) * 16 */ +} snd_seq_ev_loop_t; + +/** Sample control events */ +typedef struct snd_seq_ev_sample_control { + unsigned char channel; /**< channel */ + unsigned char unused[3]; /**< reserved */ + union { + snd_seq_ev_sample_t sample; /**< sample number */ + snd_seq_ev_cluster_t cluster; /**< cluster number */ + snd_seq_position_t position; /**< position */ + snd_seq_stop_mode_t stop_mode; /**< stop mode */ + snd_seq_frequency_t frequency; /**< frequency */ + snd_seq_ev_volume_t volume; /**< volume */ + snd_seq_ev_loop_t loop; /**< loop control */ + unsigned char raw8[8]; /**< raw 8-bit */ + } param; /**< control parameters */ +} snd_seq_ev_sample_control_t; + + + +/** INSTR_BEGIN event */ +typedef struct snd_seq_ev_instr_begin { + int timeout; /**< zero = forever, otherwise timeout in ms */ +} snd_seq_ev_instr_begin_t; + +/** Result events */ +typedef struct snd_seq_result { + int event; /**< processed event type */ + int result; /**< status */ +} snd_seq_result_t; + +/** Queue skew values */ +typedef struct snd_seq_queue_skew { + unsigned int value; /**< skew value */ + unsigned int base; /**< skew base */ +} snd_seq_queue_skew_t; + +/** queue timer control */ +typedef struct snd_seq_ev_queue_control { + unsigned char queue; /**< affected queue */ + unsigned char unused[3]; /**< reserved */ + union { + signed int value; /**< affected value (e.g. tempo) */ + snd_seq_timestamp_t time; /**< time */ + unsigned int position; /**< sync position */ + snd_seq_queue_skew_t skew; /**< queue skew */ + unsigned int d32[2]; /**< any data */ + unsigned char d8[8]; /**< any data */ + } param; /**< data value union */ +} snd_seq_ev_queue_control_t; + + +/** Sequencer event */ +typedef struct snd_seq_event { + snd_seq_event_type_t type; /**< event type */ + unsigned char flags; /**< event flags */ + unsigned char tag; /**< tag */ + + unsigned char queue; /**< schedule queue */ + snd_seq_timestamp_t time; /**< schedule time */ + + snd_seq_addr_t source; /**< source address */ + snd_seq_addr_t dest; /**< destination address */ + + union { + snd_seq_ev_note_t note; /**< note information */ + snd_seq_ev_ctrl_t control; /**< MIDI control information */ + snd_seq_ev_raw8_t raw8; /**< raw8 data */ + snd_seq_ev_raw32_t raw32; /**< raw32 data */ + snd_seq_ev_ext_t ext; /**< external data */ + snd_seq_ev_queue_control_t queue; /**< queue control */ + snd_seq_timestamp_t time; /**< timestamp */ + snd_seq_addr_t addr; /**< address */ + snd_seq_connect_t connect; /**< connect information */ + snd_seq_result_t result; /**< operation result code */ + snd_seq_ev_instr_begin_t instr_begin; /**< instrument */ + snd_seq_ev_sample_control_t sample; /**< sample control */ + } data; /**< event data... */ +} snd_seq_event_t; + + +/** \} */ + +#endif /* __ALSA_SEQ_EVENT_H */ + diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/seq_midi_event.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/seq_midi_event.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,71 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +/** + * \file + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela + * \author Abramo Bagnara + * \author Takashi Iwai + * \date 1998-2001 + * + * Application interface library for the ALSA driver + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_SEQ_MIDI_EVENT_H +#define __ALSA_SEQ_MIDI_EVENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup MIDI_Event Sequencer event <-> MIDI byte stream coder + * \ingroup Sequencer + * Sequencer event <-> MIDI byte stream coder + * \{ + */ + +/** container for sequencer midi event parsers */ +typedef struct snd_midi_event snd_midi_event_t; + +int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev); +/* int snd_midi_event_resize_buffer(snd_midi_event_t *dev, size_t bufsize); */ +void snd_midi_event_free(snd_midi_event_t *dev); +/* void snd_midi_event_init(snd_midi_event_t *dev); */ +void snd_midi_event_reset_encode(snd_midi_event_t *dev); +/* void snd_midi_event_reset_decode(snd_midi_event_t *dev); */ +/* void snd_midi_event_no_status(snd_midi_event_t *dev, int on); */ +/* encode from byte stream - return number of written bytes if success */ +long snd_midi_event_encode(snd_midi_event_t *dev, const unsigned char *buf, long count, snd_seq_event_t *ev); +int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev); +/* decode from event to bytes - return number of written bytes if success */ +/* long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, const snd_seq_event_t *ev); */ + +/** \} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_SEQ_MIDI_EVENT_H */ + diff -r 000000000000 -r fc9323a41f5a plugin/api/alsa/sound/asequencer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/alsa/sound/asequencer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,38 @@ +/* DSSI ALSA compatibility library + * + * This library provides for Mac OS X the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + */ + +/* + * Main header file for the ALSA sequencer + * Copyright (c) 1998-1999 by Frank van de Pol + * (c) 1998-1999 by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SOUND_ASEQUENCER_H +#define __SOUND_ASEQUENCER_H + +#define SNDRV_SEQ_EVENT_SYSEX 130 /* system exclusive data (variable length) */ + +#define SNDRV_SEQ_EVENT_LENGTH_FIXED (0<<2) /* fixed event size */ +#define SNDRV_SEQ_EVENT_LENGTH_VARIABLE (1<<2) /* variable event size */ + +#define SNDRV_SEQ_EVENT_LENGTH_MASK (3<<2) + +#endif /* __SOUND_ASEQUENCER_H */ diff -r 000000000000 -r fc9323a41f5a plugin/api/dssi.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/dssi.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,662 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */ + +/* dssi.h + + DSSI version 0.10 + Copyright (c) 2004,2005 Chris Cannam, Steve Harris and Sean Bolton + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +*/ + +#ifndef DSSI_INCLUDED +#define DSSI_INCLUDED + +#include "ladspa.h" +#include "alsa/seq_event.h" + +#define DSSI_VERSION "0.10" +#define DSSI_VERSION_MAJOR 0 +#define DSSI_VERSION_MINOR 10 + +#ifdef __cplusplus +extern "C" { +#endif + +/* + There is a need for an API that supports hosted MIDI soft synths + with GUIs in Linux audio applications. In time the GMPI initiative + should comprehensively address this need, but the requirement for + Linux applications to be able to support simple hosted synths is + here now, and GMPI is not. This proposal (the "DSSI Soft Synth + Interface" or DSSI, pronounced "dizzy") aims to provide a simple + solution in a way that we hope will prove complete and compelling + enough to support now, yet not so compelling as to supplant GMPI or + any other comprehensive future proposal. + + For simplicity and familiarity, this API is based as far as + possible on existing work -- the LADSPA plugin API for control + values and audio processing, and the ALSA sequencer event types for + MIDI event communication. The GUI part of the proposal is quite + new, but may also be applicable retroactively to LADSPA plugins + that do not otherwise support this synth interface. +*/ + +/* + A program wishing to use the DSSI v2 API should set the following + symbol to 2 before including this header. +*/ +#if (!defined DSSI_API_LEVEL) +#define DSSI_API_LEVEL 1 +#endif + +typedef struct _DSSI_Program_Descriptor { + + /** Bank number for this program. Note that DSSI does not support + MIDI-style separation of bank LSB and MSB values. There is no + restriction on the set of available banks: the numbers do not + need to be contiguous, there does not need to be a bank 0, etc. */ + unsigned long Bank; + + /** Program number (unique within its bank) for this program. + There is no restriction on the set of available programs: the + numbers do not need to be contiguous, there does not need to + be a program 0, etc. */ + unsigned long Program; + + /** Name of the program. */ + const char * Name; + +} DSSI_Program_Descriptor; + + +#define DSSI_TRANSPORT_VALID_STATE 0x01 +#define DSSI_TRANSPORT_VALID_BPM 0x02 +#define DSSI_TRANSPORT_VALID_BBT 0x10 +#define DSSI_TRANSPORT_VALID_TIME 0x20 + +#define DSSI_TRANSPORT_STATE_STOPPED 0 +#define DSSI_TRANSPORT_STATE_RUNNING 1 +#define DSSI_TRANSPORT_STATE_FREEWHEELING 2 +#define DSSI_TRANSPORT_STATE_OTHER 3 /* waiting for sync, ? */ + +typedef struct _DSSI_Transport_Info { + + /** The value of this field indicates which of the following + * transport information fields contain valid values. It is + * the logical OR of the DSSI_TRANSPORT_VALID_* bits defined + * above, and may be zero. */ + int Valid; + + + /** This field is valid when (Valid & DSSI_TRANSPORT_VALID_STATE) + * is true: + * + * ---- The current transport state, one of the DSSI_TRANSPORT_STATE_* + * values defined above. */ + int State; + + + /** This field is valid when (Valid & DSSI_TRANSPORT_VALID_BPM) + * is true: + * + * ---- The current tempo, in beats per minute. */ + double Beats_Per_Minute; + + + /** These six fields are valid when (Valid & DSSI_TRANSPORT_VALID_BBT) + * is true: + * + * ---- The bar number at the beginning of the current process cycle. */ + unsigned long Bar; + + /** ---- The beat within that Bar. */ + unsigned long Beat; + + /** ---- The tick within that Beat. */ + unsigned long Tick; + + /** ---- The (possibly fractional) tick count since transport 'start' + * and the beginning of the current Bar. */ + double Bar_Start_Tick; + + /** ---- The number of beats per bar. */ + float Beats_Per_Bar; + + /** ---- The number of ticks for each beat. */ + double Ticks_Per_Beat; + + /* [Sean says: I left out the 'beat_type' (time signature "denominator") + * field of the jack_position_t structure, because I think it's useless + * except to a notation program. Does anybody else feel like we need it?] + */ + + /** These two fields are valid when (Valid & DSSI_TRANSPORT_VALID_TIME) + * is true: + * + * ---- The transport time at the beginning of the current process + * cycle, in seconds. */ + double Current_Time; + + /** ---- The transport time at the beginning of the next process + cycle, unless repositioning occurs. */ + double Next_Time; + +} DSSI_Transport_Info; + +typedef struct _DSSI_Host_Descriptor DSSI_Host_Descriptor; /* below */ + +typedef struct _DSSI_Descriptor { + + /** + * DSSI_API_Version + * + * This member indicates the DSSI API level used by this plugin. + * All plugins must set this to 1 or 2. The version 1 API contains + * all DSSI_Descriptor fields through run_multiple_synths_adding(), + * while the version 2 API adds the receive_host_descriptor(). + */ + int DSSI_API_Version; + + /** + * LADSPA_Plugin + * + * A DSSI synth plugin consists of a LADSPA plugin plus an + * additional framework for controlling program settings and + * transmitting MIDI events. A plugin must fully implement the + * LADSPA descriptor fields as well as the required LADSPA + * functions including instantiate() and (de)activate(). It + * should also implement run(), with the same behaviour as if + * run_synth() (below) were called with no synth events. + * + * In order to instantiate a synth the host calls the LADSPA + * instantiate function, passing in this LADSPA_Descriptor + * pointer. The returned LADSPA_Handle is used as the argument + * for the DSSI functions below as well as for the LADSPA ones. + */ + const LADSPA_Descriptor *LADSPA_Plugin; + + /** + * configure() + * + * This member is a function pointer that sends a piece of + * configuration data to the plugin. The key argument specifies + * some aspect of the synth's configuration that is to be changed, + * and the value argument specifies a new value for it. A plugin + * that does not require this facility at all may set this member + * to NULL. + * + * This call is intended to set some session-scoped aspect of a + * plugin's behaviour, for example to tell the plugin to load + * sample data from a particular file. The plugin should act + * immediately on the request. The call should return NULL on + * success, or an error string that may be shown to the user. The + * host will free the returned value after use if it is non-NULL. + * + * Calls to configure() are not automated as timed events. + * Instead, a host should remember the last value associated with + * each key passed to configure() during a given session for a + * given plugin instance, and should call configure() with the + * correct value for each key the next time it instantiates the + * "same" plugin instance, for example on reloading a project in + * which the plugin was used before. Plugins should note that a + * host may typically instantiate a plugin multiple times with the + * same configuration values, and should share data between + * instances where practical. + * + * Calling configure() completely invalidates the program and bank + * information last obtained from the plugin. + * + * Reserved and special key prefixes + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The DSSI: prefix + * ---------------- + * Configure keys starting with DSSI: are reserved for particular + * purposes documented in the DSSI specification. At the moment, + * there is one such key: DSSI:PROJECT_DIRECTORY. A host may call + * configure() passing this key and a directory path value. This + * indicates to the plugin and its UI that a directory at that + * path exists and may be used for project-local data. Plugins + * may wish to use the project directory as a fallback location + * when looking for other file data, or as a base for relative + * paths in other configuration values. + * + * The GLOBAL: prefix + * ------------------ + * Configure keys starting with GLOBAL: may be used by the plugin + * and its UI for any purpose, but are treated specially by the + * host. When one of these keys is used in a configure OSC call + * from the plugin UI, the host makes the corresponding configure + * call (preserving the GLOBAL: prefix) not only to the target + * plugin but also to all other plugins in the same instance + * group, as well as their UIs. Note that if any instance + * returns non-NULL from configure to indicate error, the host + * may stop there (and the set of plugins on which configure has + * been called will thus depend on the host implementation). + * See also the configure OSC call documentation in RFC.txt. + */ + char *(*configure)(LADSPA_Handle Instance, + const char *Key, + const char *Value); + + #define DSSI_RESERVED_CONFIGURE_PREFIX "DSSI:" + #define DSSI_GLOBAL_CONFIGURE_PREFIX "GLOBAL:" + #define DSSI_PROJECT_DIRECTORY_KEY \ + DSSI_RESERVED_CONFIGURE_PREFIX "PROJECT_DIRECTORY" + + /** + * get_program() + * + * This member is a function pointer that provides a description + * of a program (named preset sound) available on this synth. A + * plugin that does not support programs at all should set this + * member to NULL. + * + * The Index argument is an index into the plugin's list of + * programs, not a program number as represented by the Program + * field of the DSSI_Program_Descriptor. (This distinction is + * needed to support synths that use non-contiguous program or + * bank numbers.) + * + * This function returns a DSSI_Program_Descriptor pointer that is + * guaranteed to be valid only until the next call to get_program, + * deactivate, or configure, on the same plugin instance. This + * function must return NULL if passed an Index argument out of + * range, so that the host can use it to query the number of + * programs as well as their properties. + */ + const DSSI_Program_Descriptor *(*get_program)(LADSPA_Handle Instance, + unsigned long Index); + + /** + * select_program() + * + * This member is a function pointer that selects a new program + * for this synth. The program change should take effect + * immediately at the start of the next run_synth() call. (This + * means that a host providing the capability of changing programs + * between any two notes on a track must vary the block size so as + * to place the program change at the right place. A host that + * wanted to avoid this would probably just instantiate a plugin + * for each program.) + * + * A plugin that does not support programs at all should set this + * member NULL. Plugins should ignore a select_program() call + * with an invalid bank or program. + * + * A plugin is not required to select any particular default + * program on activate(): it's the host's duty to set a program + * explicitly. The current program is invalidated by any call to + * configure(). + * + * A plugin is permitted to re-write the values of its input + * control ports when select_program is called. The host should + * re-read the input control port values and update its own + * records appropriately. (This is the only circumstance in + * which a DSSI plugin is allowed to modify its own input ports.) + */ + void (*select_program)(LADSPA_Handle Instance, + unsigned long Bank, + unsigned long Program); + + /** + * get_midi_controller_for_port() + * + * This member is a function pointer that returns the MIDI + * controller number or NRPN that should be mapped to the given + * input control port. If the given port should not have any MIDI + * controller mapped to it, the function should return DSSI_NONE. + * The behaviour of this function is undefined if the given port + * number does not correspond to an input control port. A plugin + * that does not want MIDI controllers mapped to ports at all may + * set this member NULL. + * + * Correct values can be got using the macros DSSI_CC(num) and + * DSSI_NRPN(num) as appropriate, and values can be combined using + * bitwise OR: e.g. DSSI_CC(23) | DSSI_NRPN(1069) means the port + * should respond to CC #23 and NRPN #1069. + * + * The host is responsible for doing proper scaling from MIDI + * controller and NRPN value ranges to port ranges according to + * the plugin's LADSPA port hints. Hosts should not deliver + * through run_synth any MIDI controller events that have already + * been mapped to control port values. + * + * A plugin should not attempt to request mappings from + * controllers 0 or 32 (MIDI Bank Select MSB and LSB). + */ + int (*get_midi_controller_for_port)(LADSPA_Handle Instance, + unsigned long Port); + + /** + * run_synth() + * + * This member is a function pointer that runs a synth for a + * block. This is identical in function to the LADSPA run() + * function, except that it also supplies events to the synth. + * + * A plugin may provide this function, run_multiple_synths() (see + * below), both, or neither (if it is not in fact a synth). A + * plugin that does not provide this function must set this member + * to NULL. Authors of synth plugins are encouraged to provide + * this function if at all possible. + * + * The Events pointer points to a block of EventCount ALSA + * sequencer events, which is used to communicate MIDI and related + * events to the synth. Each event is timestamped relative to the + * start of the block, (mis)using the ALSA "tick time" field as a + * frame count. The host is responsible for ensuring that events + * with differing timestamps are already ordered by time. + * + * See also the notes on activation, port connection etc in + * ladpsa.h, in the context of the LADSPA run() function. + * + * Note Events + * ~~~~~~~~~~~ + * There are two minor requirements aimed at making the plugin + * writer's life as simple as possible: + * + * 1. A host must never send events of type SND_SEQ_EVENT_NOTE. + * Notes should always be sent as separate SND_SEQ_EVENT_NOTE_ON + * and NOTE_OFF events. A plugin should discard any one-point + * NOTE events it sees. + * + * 2. A host must not attempt to switch notes off by sending + * zero-velocity NOTE_ON events. It should always send true + * NOTE_OFFs. It is the host's responsibility to remap events in + * cases where an external MIDI source has sent it zero-velocity + * NOTE_ONs. + * + * Bank and Program Events + * ~~~~~~~~~~~~~~~~~~~~~~~ + * Hosts must map MIDI Bank Select MSB and LSB (0 and 32) + * controllers and MIDI Program Change events onto the banks and + * programs specified by the plugin, using the DSSI select_program + * call. No host should ever deliver a program change or bank + * select controller to a plugin via run_synth. + */ + void (*run_synth)(LADSPA_Handle Instance, + unsigned long SampleCount, + snd_seq_event_t *Events, + unsigned long EventCount); + + /** + * run_synth_adding() + * + * This member is a function pointer that runs an instance of a + * synth for a block, adding its outputs to the values already + * present at the output ports. This is provided for symmetry + * with LADSPA run_adding(), and is equally optional. A plugin + * that does not provide it must set this member to NULL. + */ + void (*run_synth_adding)(LADSPA_Handle Instance, + unsigned long SampleCount, + snd_seq_event_t *Events, + unsigned long EventCount); + + /** + * run_multiple_synths() + * + * This member is a function pointer that runs multiple synth + * instances for a block. This is very similar to run_synth(), + * except that Instances, Events, and EventCounts each point to + * arrays that hold the LADSPA handles, event buffers, and + * event counts for each of InstanceCount instances. That is, + * Instances points to an array of InstanceCount pointers to + * DSSI plugin instantiations, Events points to an array of + * pointers to each instantiation's respective event list, and + * EventCounts points to an array containing each instantiation's + * respective event count. + * + * A host using this function must guarantee that ALL active + * instances of the plugin are represented in each call to the + * function -- that is, a host may not call run_multiple_synths() + * for some instances of a given plugin and then call run_synth() + * as well for others. 'All .. instances of the plugin' means + * every instance sharing the same LADSPA label and shared object + * (*.so) file (rather than every instance sharing the same *.so). + * 'Active' means any instance for which activate() has been called + * but deactivate() has not. + * + * A plugin may provide this function, run_synths() (see above), + * both, or neither (if it not in fact a synth). A plugin that + * does not provide this function must set this member to NULL. + * Plugin authors implementing run_multiple_synths are strongly + * encouraged to implement run_synth as well if at all possible, + * to aid simplistic hosts, even where it would be less efficient + * to use it. + */ + void (*run_multiple_synths)(unsigned long InstanceCount, + LADSPA_Handle *Instances, + unsigned long SampleCount, + snd_seq_event_t **Events, + unsigned long *EventCounts); + + /** + * run_multiple_synths_adding() + * + * This member is a function pointer that runs multiple synth + * instances for a block, adding each synth's outputs to the + * values already present at the output ports. This is provided + * for symmetry with both the DSSI run_multiple_synths() and LADSPA + * run_adding() functions, and is equally optional. A plugin + * that does not provide it must set this member to NULL. + */ + void (*run_multiple_synths_adding)(unsigned long InstanceCount, + LADSPA_Handle *Instances, + unsigned long SampleCount, + snd_seq_event_t **Events, + unsigned long *EventCounts); + +#if (DSSI_API_LEVEL > 1) + + /** + * receive_host_descriptor() + * + * This member is a function pointer by which a host may provide + * a plugin with a pointer to its DSSI_Host_Descriptor. Hosts + * which provide host descriptor support must call this function + * once per plugin shared object file, before any calls to + * instantiate(). + * + * NOTE: This field was added in version 2 of the DSSI API. Hosts + * supporting version 2 must not access this field in a plugin + * whose DSSI_API_Version is 1, and plugins supporting version 2 + * should behave reasonably under hosts (of any version) which do + * not implement this function. A version 2 plugin that does not + * provide this function must set this member to NULL. + */ + void (*receive_host_descriptor)(const DSSI_Host_Descriptor *Descriptor); + +#endif + +} DSSI_Descriptor; + +struct _DSSI_Host_Descriptor { + + /** + * DSSI_API_Version + * + * This member indicates the DSSI API level used by this host. + * All hosts must set this to 2. Hopefully, we'll get this right + * the first time, and this will never be needed. + */ + int DSSI_API_Version; + + /** + * request_transport_information() + * + * This member is a function pointer by which a plugin instance may + * request that a host begin providing transport information (if + * Request is non-zero), or notify the host that it no longer needs + * transport information (if Request is zero). Upon receiving a + * non-zero request, the host should return a pointer to a + * DSSI_Transport_Info structure if it is able to provide transport + * information, or NULL otherwise. + * + * Once a plugin instance has received a non-null transport + * information pointer, it may read from the structure at any time + * within the execution of an audio class function (see doc/RFC.txt). + * It should not consider the structure contents to be meaningful + * while within a instantiation or control class function. Also, + * since the validity of fields within the structure may change + * between each new invocation of an audio class function, a plugin + * instance must check the Valid field of the structure accordingly + * before using the structure's other contents. + * + * A host which does not support this function must set this member + * to NULL. + */ + DSSI_Transport_Info * + (*request_transport_information)(LADSPA_Handle Instance, + int Request); + + /** + * request_midi_send() + * + * This member is a function pointer that allows a plugin to + * request the ability to send MIDI events to the host. + * + * While the interpretation of plugin-generated MIDI events is + * host implementation specific, a mechanism exists by which a + * plugin may declare to the host the number of destination + * 'ports' and MIDI channels it can expect will be used in the + * plugin-generated events. Plugins which generate unchannelized + * MIDI should supply zero for both Ports and Channels, otherwise + * they should supply the maximum numbers for Ports and Channels + * they expect to use. + * + * A plugin instance must call this function during instantiate(). + * [Sean says: this restriction seems reasonable to me, since + * the host may need to create output ports, etc., and instantiate() + * seems like a good place to do such things. I'm sure I haven't + * fully thought through all the details, though....] + * + * The host should return a non-zero value if it is able to + * provide MIDI send for the plugin instance, otherwise it should + * return zero, and the plugin instance may not subsequently call + * midi_send(). + * + * A host which does not support the MIDI send function must set + * both this member and (*midi_send)() below to NULL. + */ + int (*request_midi_send)(LADSPA_Handle Instance, + unsigned char Ports, + unsigned char Channels); + + /** + * midi_send() + * + * This member is a function pointer by which a plugin actually + * sends MIDI events to the host (provided it has received a non- + * zero return from request_midi_send()). As in the run_synth() + * functions, the Event pointer points to a block of EventCount + * ALSA sequencer events. The dest.port and data.*.channel fields + * of each event are used to specify destination port and channel, + * respectively, when the plugin is supplying channelized events. + * + * A plugin may only call this function from within the execution + * of the audio class run_*() or select_program() functions. When + * called from a run_*() functions, the events are timestamped + * relative to the start of the block, (mis)using the ALSA "tick + * time" field as a frame count. The plugin is responsible for + * ensuring that events with differing timestamps are already + * ordered by time, and that timestamps across multiple calls to + * midi_send() from within the same run_*() invocation are + * monotonic. When midi_send() is called from within + * select_program(), the timestamps are ignored, and the events + * are considered to originate at the same frame time as the + * select_program() call, if such a timing can be considered + * meaningful. + * + * The memory pointed to by Event belongs to the plugin, and it is + * the host's responsibility to copy the events as needed before + * returning from the midi_send() call. + * + * A host which does not support the MIDI send function must set + * both this member and (*request_midi_send)() above to NULL. + */ + void (*midi_send)(LADSPA_Handle Instance, + snd_seq_event_t *Event, + unsigned long EventCount); + + /** + * . . . additional fields could follow here, possibly supporting: + * + * - a facility by which a plugin instance may request from a + * host a non-realtime thread in which to do off-line + * rendering, I/O, etc., thus (hopefully) avoiding the + * crashes that seem to occur when plugins create their own + * threads. I got this idea after noticing that ZynAddSubFX + * achieves its gorgeous textures while remaining very + * responsive by doing a lot of non-real-time rendering. + * Several other uses for it have been mentioned on the DSSI + * list; I forget what. + * + * - per-voice audio output + */ + + int (*request_non_rt_thread)(LADSPA_Handle Instance, + void (*RunFunction)(LADSPA_Handle Instance)); +}; + +/** + * DSSI supports a plugin discovery method similar to that of LADSPA: + * + * - DSSI hosts may wish to locate DSSI plugin shared object files by + * searching the paths contained in the DSSI_PATH and LADSPA_PATH + * environment variables, if they are present. Both are expected + * to be colon-separated lists of directories to be searched (in + * order), and DSSI_PATH should be searched first if both variables + * are set. + * + * - Each shared object file containing DSSI plugins must include a + * function dssi_descriptor(), with the following function prototype + * and C-style linkage. Hosts may enumerate the plugin types + * available in the shared object file by repeatedly calling + * this function with successive Index values (beginning from 0), + * until a return value of NULL indicates no more plugin types are + * available. Each non-NULL return is the DSSI_Descriptor + * of a distinct plugin type. + */ + +const DSSI_Descriptor *dssi_descriptor(unsigned long Index); + +typedef const DSSI_Descriptor *(*DSSI_Descriptor_Function)(unsigned long Index); + +/* + * Macros to specify particular MIDI controllers in return values from + * get_midi_controller_for_port() + */ + +#define DSSI_CC_BITS 0x20000000 +#define DSSI_NRPN_BITS 0x40000000 + +#define DSSI_NONE -1 +#define DSSI_CONTROLLER_IS_SET(n) (DSSI_NONE != (n)) + +#define DSSI_CC(n) (DSSI_CC_BITS | (n)) +#define DSSI_IS_CC(n) (DSSI_CC_BITS & (n)) +#define DSSI_CC_NUMBER(n) ((n) & 0x7f) + +#define DSSI_NRPN(n) (DSSI_NRPN_BITS | ((n) << 7)) +#define DSSI_IS_NRPN(n) (DSSI_NRPN_BITS & (n)) +#define DSSI_NRPN_NUMBER(n) (((n) >> 7) & 0x3fff) + +#ifdef __cplusplus +} +#endif + +#endif /* DSSI_INCLUDED */ diff -r 000000000000 -r fc9323a41f5a plugin/api/dssi_alsa_compat.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/dssi_alsa_compat.c Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,811 @@ +/* DSSI ALSA compatibility library + * + * This library provides for non-ALSA systems the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + * + * See ./alsa/README for more information. + */ + +#include +#include + +#include "alsa/asoundef.h" +#include "alsa/sound/asequencer.h" +#include "alsa/seq.h" +#include "alsa/seq_event.h" +#include "alsa/seq_midi_event.h" + +/** + * \file seq/seq_event.c + * \brief Sequencer Event Types + * \author Takashi Iwai + * \date 2001 + */ + +#define FIXED_EV(x) (_SND_SEQ_TYPE(SND_SEQ_EVFLG_FIXED) | _SND_SEQ_TYPE(x)) + +/** Event types conversion array */ +const unsigned int snd_seq_event_types[256] = { + + FIXED_EV(SND_SEQ_EVFLG_RESULT), // SND_SEQ_EVENT_SYSTEM = 0 + FIXED_EV(SND_SEQ_EVFLG_RESULT), // SND_SEQ_EVENT_RESULT + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_NOTE_TWOARG), // SND_SEQ_EVENT_NOTE = 5 + FIXED_EV(SND_SEQ_EVFLG_NOTE), // SND_SEQ_EVENT_NOTEON + FIXED_EV(SND_SEQ_EVFLG_NOTE), // SND_SEQ_EVENT_NOTEOFF + FIXED_EV(SND_SEQ_EVFLG_NOTE), // SND_SEQ_EVENT_KEYPRESS + 0, + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_CONTROLLER = 10 + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_PGMCHANGE + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_CHANPRESS + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_PITCHBEND + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_CONTROL14 + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_NONREGPARAM + FIXED_EV(SND_SEQ_EVFLG_CONTROL),// SND_SEQ_EVENT_REGPARAM + 0, + 0, + 0, + 0, // = 20 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_QUEUE), // SND_SEQ_EVENT_START = 30 + FIXED_EV(SND_SEQ_EVFLG_QUEUE), // SND_SEQ_EVENT_CONTINUE + FIXED_EV(SND_SEQ_EVFLG_QUEUE), // SND_SEQ_EVENT_STOP + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TICK), // SND_SEQ_EVENT_SETPOS_TICK + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TIME), // SND_SEQ_EVENT_SETPOS_TIME + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),// SND_SEQ_EVENT_TEMPO + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),// SND_SEQ_EVENT_CLOCK + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),// SND_SEQ_EVENT_TICK + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),// SND_SEQ_EVENT_QUEUE_SKEW + FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),// SND_SEQ_EVENT_SYNC_POS + FIXED_EV(SND_SEQ_EVFLG_NONE), // SND_SEQ_EVENT_TUNE_REQUEST = 40 + FIXED_EV(SND_SEQ_EVFLG_NONE), // SND_SEQ_EVENT_RESET + FIXED_EV(SND_SEQ_EVFLG_NONE), // SND_SEQ_EVENT_SENSING + 0, + 0, + 0, + 0, + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_SYSTEM), // SND_SEQ_EVENT_ECHO = 50 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_SYSTEM), // SND_SEQ_EVENT_OSS + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_CLIENT_START = 60 + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_CLIENT_EXIT + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_CLIENT_CHANGE + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_PORT_START + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_PORT_EXIT + FIXED_EV(SND_SEQ_EVFLG_MESSAGE), // SND_SEQ_EVENT_PORT_CHANGE + FIXED_EV(SND_SEQ_EVFLG_CONNECTION), // SND_SEQ_EVENT_PORT_SUBSCRIBED + FIXED_EV(SND_SEQ_EVFLG_CONNECTION), // SND_SEQ_EVENT_PORT_UNSUBSCRIBED + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE = 70 + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_CLUSTER + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_START + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_STOP + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_FREQ + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_VOLUME + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_LOOP + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_POSITION + FIXED_EV(SND_SEQ_EVFLG_SAMPLE), // SND_SEQ_EVENT_SAMPLE_PRIVATE1 + 0, + 0, // = 80 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR0 = 90, + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR1 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR2 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR3 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR4 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR5 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR6 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR7 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR8 + FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), // SND_SEQ_EVENT_USR9 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_BEGIN = 100 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_END + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_INFO + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_INFO_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_FINFO + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_FINFO_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_RESET + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_STATUS + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_STATUS_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_PUT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_GET + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_GET_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_FREE + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_LIST + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_LIST_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_CLUSTER + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_CLUSTER_GET + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_CLUSTER_RESULT + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_END + _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), // SND_SEQ_EVENT_INSTR_CHANGE + 0, // = 120 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE), // SND_SEQ_EVENT_SYSEX = 130 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE), // SND_SEQ_EVENT_BOUNCE + 0, + 0, + 0, + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),// SND_SEQ_EVENT_USR_VAR0 = 135 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),// SND_SEQ_EVENT_USR_VAR1 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),// SND_SEQ_EVENT_USR_VAR2 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),// SND_SEQ_EVENT_USR_VAR3 + _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),// SND_SEQ_EVENT_USR_VAR4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 140 -> 149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 150 -> 159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160 -> 169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 170 -> 179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 180 -> 189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 190 -> 199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 200 -> 209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 210 -> 219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 220 -> 229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 230 -> 239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 240 -> 249 + 0, // = 250 + 0, + 0, + 0, + 0, + FIXED_EV(SND_SEQ_EVFLG_NONE), // SND_SEQ_EVENT_NONE = 255 +}; + +/*const unsigned int snd_seq_event_types[256] = { + [SND_SEQ_EVENT_SYSTEM ... SND_SEQ_EVENT_RESULT] + = FIXED_EV(SND_SEQ_EVFLG_RESULT), + [SND_SEQ_EVENT_NOTE] + = FIXED_EV(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_NOTE_TWOARG), + [SND_SEQ_EVENT_NOTEON ... SND_SEQ_EVENT_KEYPRESS] + = FIXED_EV(SND_SEQ_EVFLG_NOTE), + [SND_SEQ_EVENT_CONTROLLER ... SND_SEQ_EVENT_REGPARAM] + = FIXED_EV(SND_SEQ_EVFLG_CONTROL), + [SND_SEQ_EVENT_START ... SND_SEQ_EVENT_STOP] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE), + [SND_SEQ_EVENT_SETPOS_TICK] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TICK), + [SND_SEQ_EVENT_SETPOS_TIME] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TIME), + [SND_SEQ_EVENT_TEMPO ... SND_SEQ_EVENT_SYNC_POS] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE), + [SND_SEQ_EVENT_TUNE_REQUEST ... SND_SEQ_EVENT_SENSING] + = FIXED_EV(SND_SEQ_EVFLG_NONE), + [SND_SEQ_EVENT_ECHO ... SND_SEQ_EVENT_OSS] + = FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_SYSTEM), + [SND_SEQ_EVENT_CLIENT_START ... SND_SEQ_EVENT_PORT_CHANGE] + = FIXED_EV(SND_SEQ_EVFLG_MESSAGE), + [SND_SEQ_EVENT_PORT_SUBSCRIBED ... SND_SEQ_EVENT_PORT_UNSUBSCRIBED] + = FIXED_EV(SND_SEQ_EVFLG_CONNECTION), + [SND_SEQ_EVENT_SAMPLE ... SND_SEQ_EVENT_SAMPLE_PRIVATE1] + = FIXED_EV(SND_SEQ_EVFLG_SAMPLE), + [SND_SEQ_EVENT_USR0 ... SND_SEQ_EVENT_USR9] + = FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), + [SND_SEQ_EVENT_INSTR_BEGIN ... SND_SEQ_EVENT_INSTR_CHANGE] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), + [SND_SEQ_EVENT_SYSEX ... SND_SEQ_EVENT_BOUNCE] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE), + [SND_SEQ_EVENT_USR_VAR0 ... SND_SEQ_EVENT_USR_VAR4] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS), + [SND_SEQ_EVENT_NONE] + = FIXED_EV(SND_SEQ_EVFLG_NONE), +};*/ + +/** + * \file seq/seq_midi_event.c + * \brief MIDI byte <-> sequencer event coder + * \author Takashi Iwai + * \author Jaroslav Kysela + * \date 2000-2001 + */ + +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99,2000 Takashi Iwai , + * Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* midi status */ +struct snd_midi_event { + size_t qlen; /* queue length */ + size_t read; /* chars read */ + int type; /* current event type */ + unsigned char lastcmd; + unsigned char nostat; + size_t bufsize; + unsigned char *buf; /* input buffer */ +}; + + +/* queue type */ +/* from 0 to 7 are normal commands (note off, on, etc.) */ +#define ST_NOTEOFF 0 +#define ST_NOTEON 1 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* status event types */ +typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev); +typedef void (*event_decode_t)(const snd_seq_event_t *ev, unsigned char *buf); + +/* + * prototypes + */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void note_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list_t { + int event; + int qlen; + event_encode_t encode; + event_decode_t decode; +} status_event[] = { + /* 0x80 - 0xf0 */ + {SND_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SND_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SND_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SND_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SND_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SND_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SND_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf0 */ + /* 0xf0 - 0xff */ + {SND_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SND_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SND_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SND_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf4 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf5 */ + {SND_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf7 */ + {SND_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf9 */ + {SND_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SND_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SND_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xfd */ + {SND_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SND_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +#ifdef UNNEEDED_BY_DSSI +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev); +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev); + +static struct extra_event_list_t { + int event; + int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev); +} extra_event[] = { + {SND_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SND_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SND_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +#define numberof(ary) (sizeof(ary)/sizeof(ary[0])) +#endif /* UNNEEDED_BY_DSSI */ + +/** + * \brief Initialize MIDI event parser + * \param bufsize buffer size for MIDI message + * \param rdev allocated MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Allocates and initializes MIDI event parser. + */ +int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev) +{ + snd_midi_event_t *dev; + + *rdev = NULL; + dev = (snd_midi_event_t *)calloc(1, sizeof(snd_midi_event_t)); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = malloc(bufsize); + if (dev->buf == NULL) { + free(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + *rdev = dev; + return 0; +} + +/** + * \brief Free MIDI event parser + * \param rdev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Frees MIDI event parser. + */ +void snd_midi_event_free(snd_midi_event_t *dev) +{ + if (dev != NULL) { + if (dev->buf) + free(dev->buf); + free(dev); + } +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Enable/disable MIDI command merging + * \param dev MIDI event parser + * \param on 0 - enable MIDI command merging, 1 - always pass the command + * + * Enable/disable MIDI command merging + */ +void snd_midi_event_no_status(snd_midi_event_t *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} +#endif /* UNNEEDED_BY_DSSI */ + +/* + * initialize record + */ +//inline static void reset_encode(snd_midi_event_t *dev) +static void reset_encode(snd_midi_event_t *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = 0; +} + +/** + * \brief Reset MIDI encode parser + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Resets MIDI encode parser + */ +void snd_midi_event_reset_encode(snd_midi_event_t *dev) +{ + reset_encode(dev); +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Reset MIDI decode parser + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Resets MIDI decode parser + */ +void snd_midi_event_reset_decode(snd_midi_event_t *dev) +{ + dev->lastcmd = 0xff; +} + +/** + * \brief Initializes MIDI parsers + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Initializes MIDI parsers (both encode and decode) + */ +void snd_midi_event_init(snd_midi_event_t *dev) +{ + snd_midi_event_reset_encode(dev); + snd_midi_event_reset_decode(dev); +} + +/** + * \brief Resize MIDI message (event) buffer + * \param dev MIDI event parser + * \param bufsize new requested buffer size + * \return 0 on success otherwise a negative error code + * + * Resizes MIDI message (event) buffer. + */ +int snd_midi_event_resize_buffer(snd_midi_event_t *dev, size_t bufsize) +{ + unsigned char *new_buf, *old_buf; + + if (bufsize == dev->bufsize) + return 0; + new_buf = malloc(bufsize); + if (new_buf == NULL) + return -ENOMEM; + old_buf = dev->buf; + dev->buf = new_buf; + dev->bufsize = bufsize; + reset_encode(dev); + if (old_buf) + free(old_buf); + return 0; +} +#endif /* UNNEEDED_BY_DSSI */ + +/** + * \brief Read bytes and encode to sequencer event if finished + * \param dev MIDI event parser + * \param buf MIDI byte stream + * \param count count of bytes of MIDI byte stream to encode + * \param ev Result - sequencer event + * \return count of encoded bytes otherwise a negative error code + * + * Read bytes and encode to sequencer event if finished. + * If complete sequencer event is available, ev->type is not + * equal to #SND_SEQ_EVENT_NONE. + */ +long snd_midi_event_encode(snd_midi_event_t *dev, const unsigned char *buf, long count, snd_seq_event_t *ev) +{ + long result = 0; + int rc; + + ev->type = SND_SEQ_EVENT_NONE; + + while (count-- > 0) { + rc = snd_midi_event_encode_byte(dev, *buf++, ev); + result++; + if (rc < 0) + return rc; + else if (rc > 0) + return result; + } + + return result; +} + +/** + * \brief Read one byte and encode to sequencer event if finished + * \param dev MIDI event parser + * \param c a byte of MIDI stream + * \param ev Result - sequencer event + * \return 1 - sequencer event is completed, 0 - next byte is required for completion, otherwise a negative error code + * + * Read byte and encode to sequencer event if finished. + */ +int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev) +{ + int rc = 0; + + c &= 0xff; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return 1; + } + + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* new command */ + dev->read = 1; + if (c & 0x80) { + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* special events */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->qlen = status_event[dev->type].qlen; + } else { + /* process this byte as argument */ + dev->buf[dev->read++] = c; + dev->qlen = status_event[dev->type].qlen - 1; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + rc = 1; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = 1; + } + } + + return rc; +} + +/* encode note event */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Decode sequencer event to MIDI byte stream + * \param dev MIDI event parser + * \param buf Result - MIDI byte stream + * \param count Available bytes in MIDI byte stream + * \param ev Event to decode + * \return count of decoded bytes otherwise a negative error code + * + * Decode sequencer event to MIDI byte stream. + */ +long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, const snd_seq_event_t *ev) +{ + int cmd; + long qlen; + unsigned int type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < numberof(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < numberof(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + qlen = ev->data.ext.len; + if (count < qlen) + return -ENOMEM; + switch (ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) { + case SNDRV_SEQ_EVENT_LENGTH_FIXED: + return -EINVAL; /* invalid event */ + } + memcpy(buf, ev->data.ext.ptr, qlen); + return qlen; + } else { + unsigned char xbuf[4]; + + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} +#endif /* UNNEEDED_BY_DSSI */ + +/* decode note event */ +static void note_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +#ifdef UNNEEDED_BY_DSSI +/* decode 14bit control */ +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 32) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 32; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev) +{ + unsigned char cmd; + char *cbytes; + static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = ev->data.control.param & 0x007f; + bytes[1] = (ev->data.control.param & 0x3f80) >> 7; + bytes[2] = ev->data.control.value & 0x007f; + bytes[3] = (ev->data.control.value & 0x3f80) >> 7; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SND_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} +#endif /* UNNEEDED_BY_DSSI */ diff -r 000000000000 -r fc9323a41f5a plugin/api/dssi_alsa_compat.c.old --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/dssi_alsa_compat.c.old Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,649 @@ +/* DSSI ALSA compatibility library + * + * This library provides for non-ALSA systems the ALSA snd_seq_event_t handling + * necessary to compile and run DSSI. It was extracted from alsa-lib 1.0.8. + * + * See ./alsa/README for more information. + */ + +#include +#include + +#include "alsa/asoundef.h" +#include "alsa/sound/asequencer.h" +#include "alsa/seq.h" +#include "alsa/seq_event.h" +#include "alsa/seq_midi_event.h" + +/** + * \file seq/seq_event.c + * \brief Sequencer Event Types + * \author Takashi Iwai + * \date 2001 + */ + +#define FIXED_EV(x) (_SND_SEQ_TYPE(SND_SEQ_EVFLG_FIXED) | _SND_SEQ_TYPE(x)) + +/** Event types conversion array */ +const unsigned int snd_seq_event_types[256] = { + [SND_SEQ_EVENT_SYSTEM ... SND_SEQ_EVENT_RESULT] + = FIXED_EV(SND_SEQ_EVFLG_RESULT), + [SND_SEQ_EVENT_NOTE] + = FIXED_EV(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_NOTE_TWOARG), + [SND_SEQ_EVENT_NOTEON ... SND_SEQ_EVENT_KEYPRESS] + = FIXED_EV(SND_SEQ_EVFLG_NOTE), + [SND_SEQ_EVENT_CONTROLLER ... SND_SEQ_EVENT_REGPARAM] + = FIXED_EV(SND_SEQ_EVFLG_CONTROL), + [SND_SEQ_EVENT_START ... SND_SEQ_EVENT_STOP] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE), + [SND_SEQ_EVENT_SETPOS_TICK] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TICK), + [SND_SEQ_EVENT_SETPOS_TIME] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TIME), + [SND_SEQ_EVENT_TEMPO ... SND_SEQ_EVENT_SYNC_POS] + = FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE), + [SND_SEQ_EVENT_TUNE_REQUEST ... SND_SEQ_EVENT_SENSING] + = FIXED_EV(SND_SEQ_EVFLG_NONE), + [SND_SEQ_EVENT_ECHO ... SND_SEQ_EVENT_OSS] + = FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_SYSTEM), + [SND_SEQ_EVENT_CLIENT_START ... SND_SEQ_EVENT_PORT_CHANGE] + = FIXED_EV(SND_SEQ_EVFLG_MESSAGE), + [SND_SEQ_EVENT_PORT_SUBSCRIBED ... SND_SEQ_EVENT_PORT_UNSUBSCRIBED] + = FIXED_EV(SND_SEQ_EVFLG_CONNECTION), + [SND_SEQ_EVENT_SAMPLE ... SND_SEQ_EVENT_SAMPLE_PRIVATE1] + = FIXED_EV(SND_SEQ_EVFLG_SAMPLE), + [SND_SEQ_EVENT_USR0 ... SND_SEQ_EVENT_USR9] + = FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS), + [SND_SEQ_EVENT_INSTR_BEGIN ... SND_SEQ_EVENT_INSTR_CHANGE] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_INSTR) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARUSR), + [SND_SEQ_EVENT_SYSEX ... SND_SEQ_EVENT_BOUNCE] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE), + [SND_SEQ_EVENT_USR_VAR0 ... SND_SEQ_EVENT_USR_VAR4] + = _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS), + [SND_SEQ_EVENT_NONE] + = FIXED_EV(SND_SEQ_EVFLG_NONE), +}; + +/** + * \file seq/seq_midi_event.c + * \brief MIDI byte <-> sequencer event coder + * \author Takashi Iwai + * \author Jaroslav Kysela + * \date 2000-2001 + */ + +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99,2000 Takashi Iwai , + * Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* midi status */ +struct snd_midi_event { + size_t qlen; /* queue length */ + size_t read; /* chars read */ + int type; /* current event type */ + unsigned char lastcmd; + unsigned char nostat; + size_t bufsize; + unsigned char *buf; /* input buffer */ +}; + + +/* queue type */ +/* from 0 to 7 are normal commands (note off, on, etc.) */ +#define ST_NOTEOFF 0 +#define ST_NOTEON 1 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* status event types */ +typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev); +typedef void (*event_decode_t)(const snd_seq_event_t *ev, unsigned char *buf); + +/* + * prototypes + */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void note_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf); +static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list_t { + int event; + int qlen; + event_encode_t encode; + event_decode_t decode; +} status_event[] = { + /* 0x80 - 0xf0 */ + {SND_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SND_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SND_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SND_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SND_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SND_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SND_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf0 */ + /* 0xf0 - 0xff */ + {SND_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SND_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SND_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SND_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf4 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf5 */ + {SND_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf7 */ + {SND_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf9 */ + {SND_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SND_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SND_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SND_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xfd */ + {SND_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SND_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +#ifdef UNNEEDED_BY_DSSI +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev); +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev); + +static struct extra_event_list_t { + int event; + int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev); +} extra_event[] = { + {SND_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SND_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SND_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +#define numberof(ary) (sizeof(ary)/sizeof(ary[0])) +#endif /* UNNEEDED_BY_DSSI */ + +/** + * \brief Initialize MIDI event parser + * \param bufsize buffer size for MIDI message + * \param rdev allocated MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Allocates and initializes MIDI event parser. + */ +int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev) +{ + snd_midi_event_t *dev; + + *rdev = NULL; + dev = (snd_midi_event_t *)calloc(1, sizeof(snd_midi_event_t)); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = malloc(bufsize); + if (dev->buf == NULL) { + free(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + *rdev = dev; + return 0; +} + +/** + * \brief Free MIDI event parser + * \param rdev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Frees MIDI event parser. + */ +void snd_midi_event_free(snd_midi_event_t *dev) +{ + if (dev != NULL) { + if (dev->buf) + free(dev->buf); + free(dev); + } +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Enable/disable MIDI command merging + * \param dev MIDI event parser + * \param on 0 - enable MIDI command merging, 1 - always pass the command + * + * Enable/disable MIDI command merging + */ +void snd_midi_event_no_status(snd_midi_event_t *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} +#endif /* UNNEEDED_BY_DSSI */ + +/* + * initialize record + */ +inline static void reset_encode(snd_midi_event_t *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = 0; +} + +/** + * \brief Reset MIDI encode parser + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Resets MIDI encode parser + */ +void snd_midi_event_reset_encode(snd_midi_event_t *dev) +{ + reset_encode(dev); +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Reset MIDI decode parser + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Resets MIDI decode parser + */ +void snd_midi_event_reset_decode(snd_midi_event_t *dev) +{ + dev->lastcmd = 0xff; +} + +/** + * \brief Initializes MIDI parsers + * \param dev MIDI event parser + * \return 0 on success otherwise a negative error code + * + * Initializes MIDI parsers (both encode and decode) + */ +void snd_midi_event_init(snd_midi_event_t *dev) +{ + snd_midi_event_reset_encode(dev); + snd_midi_event_reset_decode(dev); +} + +/** + * \brief Resize MIDI message (event) buffer + * \param dev MIDI event parser + * \param bufsize new requested buffer size + * \return 0 on success otherwise a negative error code + * + * Resizes MIDI message (event) buffer. + */ +int snd_midi_event_resize_buffer(snd_midi_event_t *dev, size_t bufsize) +{ + unsigned char *new_buf, *old_buf; + + if (bufsize == dev->bufsize) + return 0; + new_buf = malloc(bufsize); + if (new_buf == NULL) + return -ENOMEM; + old_buf = dev->buf; + dev->buf = new_buf; + dev->bufsize = bufsize; + reset_encode(dev); + if (old_buf) + free(old_buf); + return 0; +} +#endif /* UNNEEDED_BY_DSSI */ + +/** + * \brief Read bytes and encode to sequencer event if finished + * \param dev MIDI event parser + * \param buf MIDI byte stream + * \param count count of bytes of MIDI byte stream to encode + * \param ev Result - sequencer event + * \return count of encoded bytes otherwise a negative error code + * + * Read bytes and encode to sequencer event if finished. + * If complete sequencer event is available, ev->type is not + * equal to #SND_SEQ_EVENT_NONE. + */ +long snd_midi_event_encode(snd_midi_event_t *dev, const unsigned char *buf, long count, snd_seq_event_t *ev) +{ + long result = 0; + int rc; + + ev->type = SND_SEQ_EVENT_NONE; + + while (count-- > 0) { + rc = snd_midi_event_encode_byte(dev, *buf++, ev); + result++; + if (rc < 0) + return rc; + else if (rc > 0) + return result; + } + + return result; +} + +/** + * \brief Read one byte and encode to sequencer event if finished + * \param dev MIDI event parser + * \param c a byte of MIDI stream + * \param ev Result - sequencer event + * \return 1 - sequencer event is completed, 0 - next byte is required for completion, otherwise a negative error code + * + * Read byte and encode to sequencer event if finished. + */ +int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev) +{ + int rc = 0; + + c &= 0xff; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return 1; + } + + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* new command */ + dev->read = 1; + if (c & 0x80) { + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* special events */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->qlen = status_event[dev->type].qlen; + } else { + /* process this byte as argument */ + dev->buf[dev->read++] = c; + dev->qlen = status_event[dev->type].qlen - 1; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + rc = 1; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = 1; + } + } + + return rc; +} + +/* encode note event */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +#ifdef UNNEEDED_BY_DSSI +/** + * \brief Decode sequencer event to MIDI byte stream + * \param dev MIDI event parser + * \param buf Result - MIDI byte stream + * \param count Available bytes in MIDI byte stream + * \param ev Event to decode + * \return count of decoded bytes otherwise a negative error code + * + * Decode sequencer event to MIDI byte stream. + */ +long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, const snd_seq_event_t *ev) +{ + int cmd; + long qlen; + unsigned int type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < numberof(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < numberof(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + qlen = ev->data.ext.len; + if (count < qlen) + return -ENOMEM; + switch (ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) { + case SNDRV_SEQ_EVENT_LENGTH_FIXED: + return -EINVAL; /* invalid event */ + } + memcpy(buf, ev->data.ext.ptr, qlen); + return qlen; + } else { + unsigned char xbuf[4]; + + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} +#endif /* UNNEEDED_BY_DSSI */ + +/* decode note event */ +static void note_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +#ifdef UNNEEDED_BY_DSSI +/* decode 14bit control */ +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 32) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 32; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev) +{ + unsigned char cmd; + char *cbytes; + static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = ev->data.control.param & 0x007f; + bytes[1] = (ev->data.control.param & 0x3f80) >> 7; + bytes[2] = ev->data.control.value & 0x007f; + bytes[3] = (ev->data.control.value & 0x3f80) >> 7; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SND_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} +#endif /* UNNEEDED_BY_DSSI */ diff -r 000000000000 -r fc9323a41f5a plugin/api/ladspa.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/ladspa.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,603 @@ +/* ladspa.h + + Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. + Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, + Stefan Westerfeld. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. */ + +#ifndef LADSPA_INCLUDED +#define LADSPA_INCLUDED + +#define LADSPA_VERSION "1.1" +#define LADSPA_VERSION_MAJOR 1 +#define LADSPA_VERSION_MINOR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************/ + +/* Overview: + + There is a large number of synthesis packages in use or development + on the Linux platform at this time. This API (`The Linux Audio + Developer's Simple Plugin API') attempts to give programmers the + ability to write simple `plugin' audio processors in C/C++ and link + them dynamically (`plug') into a range of these packages (`hosts'). + It should be possible for any host and any plugin to communicate + completely through this interface. + + This API is deliberately short and simple. To achieve compatibility + with a range of promising Linux sound synthesis packages it + attempts to find the `greatest common divisor' in their logical + behaviour. Having said this, certain limiting decisions are + implicit, notably the use of a fixed type (LADSPA_Data) for all + data transfer and absence of a parameterised `initialisation' + phase. See below for the LADSPA_Data typedef. + + Plugins are expected to distinguish between control and audio + data. Plugins have `ports' that are inputs or outputs for audio or + control data and each plugin is `run' for a `block' corresponding + to a short time interval measured in samples. Audio data is + communicated using arrays of LADSPA_Data, allowing a block of audio + to be processed by the plugin in a single pass. Control data is + communicated using single LADSPA_Data values. Control data has a + single value at the start of a call to the `run()' or `run_adding()' + function, and may be considered to remain this value for its + duration. The plugin may assume that all its input and output ports + have been connected to the relevant data location (see the + `connect_port()' function below) before it is asked to run. + + Plugins will reside in shared object files suitable for dynamic + linking by dlopen() and family. The file will provide a number of + `plugin types' that can be used to instantiate actual plugins + (sometimes known as `plugin instances') that can be connected + together to perform tasks. + + This API contains very limited error-handling. */ + +/*****************************************************************************/ + +/* Fundamental data type passed in and out of plugin. This data type + is used to communicate audio samples and control values. It is + assumed that the plugin will work sensibly given any numeric input + value although it may have a preferred range (see hints below). + + For audio it is generally assumed that 1.0f is the `0dB' reference + amplitude and is a `normal' signal level. */ + +typedef float LADSPA_Data; + +/*****************************************************************************/ + +/* Special Plugin Properties: + + Optional features of the plugin type are encapsulated in the + LADSPA_Properties type. This is assembled by ORing individual + properties together. */ + +typedef int LADSPA_Properties; + +/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a + real-time dependency (e.g. listens to a MIDI device) and so its + output must not be cached or subject to significant latency. */ +#define LADSPA_PROPERTY_REALTIME 0x1 + +/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin + may cease to work correctly if the host elects to use the same data + location for both input and output (see connect_port()). This + should be avoided as enabling this flag makes it impossible for + hosts to use the plugin to process audio `in-place.' */ +#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2 + +/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin + is capable of running not only in a conventional host but also in a + `hard real-time' environment. To qualify for this the plugin must + satisfy all of the following: + + (1) The plugin must not use malloc(), free() or other heap memory + management within its run() or run_adding() functions. All new + memory used in run() must be managed via the stack. These + restrictions only apply to the run() function. + + (2) The plugin will not attempt to make use of any library + functions with the exceptions of functions in the ANSI standard C + and C maths libraries, which the host is expected to provide. + + (3) The plugin will not access files, devices, pipes, sockets, IPC + or any other mechanism that might result in process or thread + blocking. + + (4) The plugin will take an amount of time to execute a run() or + run_adding() call approximately of form (A+B*SampleCount) where A + and B depend on the machine and host in use. This amount of time + may not depend on input signals or plugin state. The host is left + the responsibility to perform timings to estimate upper bounds for + A and B. */ +#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4 + +#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME) +#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN) +#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE) + +/*****************************************************************************/ + +/* Plugin Ports: + + Plugins have `ports' that are inputs or outputs for audio or + data. Ports can communicate arrays of LADSPA_Data (for audio + inputs/outputs) or single LADSPA_Data values (for control + input/outputs). This information is encapsulated in the + LADSPA_PortDescriptor type which is assembled by ORing individual + properties together. + + Note that a port must be an input or an output port but not both + and that a port must be a control or audio port but not both. */ + +typedef int LADSPA_PortDescriptor; + +/* Property LADSPA_PORT_INPUT indicates that the port is an input. */ +#define LADSPA_PORT_INPUT 0x1 + +/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */ +#define LADSPA_PORT_OUTPUT 0x2 + +/* Property LADSPA_PORT_CONTROL indicates that the port is a control + port. */ +#define LADSPA_PORT_CONTROL 0x4 + +/* Property LADSPA_PORT_AUDIO indicates that the port is a audio + port. */ +#define LADSPA_PORT_AUDIO 0x8 + +#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT) +#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT) +#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL) +#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO) + +/*****************************************************************************/ + +/* Plugin Port Range Hints: + + The host may wish to provide a representation of data entering or + leaving a plugin (e.g. to generate a GUI automatically). To make + this more meaningful, the plugin should provide `hints' to the host + describing the usual values taken by the data. + + Note that these are only hints. The host may ignore them and the + plugin must not assume that data supplied to it is meaningful. If + the plugin receives invalid input data it is expected to continue + to run without failure and, where possible, produce a sensible + output (e.g. a high-pass filter given a negative cutoff frequency + might switch to an all-pass mode). + + Hints are meaningful for all input and output ports but hints for + input control ports are expected to be particularly useful. + + More hint information is encapsulated in the + LADSPA_PortRangeHintDescriptor type which is assembled by ORing + individual hint types together. Hints may require further + LowerBound and UpperBound information. + + All the hint information for a particular port is aggregated in the + LADSPA_PortRangeHint structure. */ + +typedef int LADSPA_PortRangeHintDescriptor; + +/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) lower + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of LowerBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_BELOW 0x1 + +/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) upper + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of UpperBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_ABOVE 0x2 + +/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be + considered a Boolean toggle. Data less than or equal to zero should + be considered `off' or `false,' and data above zero should be + considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in + conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or + LADSPA_HINT_DEFAULT_1. */ +#define LADSPA_HINT_TOGGLED 0x4 + +/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified + should be interpreted as multiples of the sample rate. For + instance, a frequency range from 0Hz to the Nyquist frequency (half + the sample rate) could be requested by this hint in conjunction + with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds + at all must support this hint to retain meaning. */ +#define LADSPA_HINT_SAMPLE_RATE 0x8 + +/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the + user will find it more intuitive to view values using a logarithmic + scale. This is particularly useful for frequencies and gains. */ +#define LADSPA_HINT_LOGARITHMIC 0x10 + +/* Hint LADSPA_HINT_INTEGER indicates that a user interface would + probably wish to provide a stepped control taking only integer + values. Any bounds set should be slightly wider than the actual + integer range required to avoid floating point rounding errors. For + instance, the integer set {0,1,2,3} might be described as [-0.1, + 3.1]. */ +#define LADSPA_HINT_INTEGER 0x20 + +/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' + value for the port that is sensible as a default. For instance, + this value is suitable for use as an initial value in a user + interface or as a value the host might assign to a control port + when the user has not provided one. Defaults are encoded using a + mask so only one default may be specified for a port. Some of the + hints make use of lower and upper bounds, in which case the + relevant bound or bounds must be available and + LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting + default must be rounded if LADSPA_HINT_INTEGER is present. Default + values were introduced in LADSPA v1.1. */ +#define LADSPA_HINT_DEFAULT_MASK 0x3C0 + +/* This default values indicates that no default is provided. */ +#define LADSPA_HINT_DEFAULT_NONE 0x0 + +/* This default hint indicates that the suggested lower bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MINIMUM 0x40 + +/* This default hint indicates that a low value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper + * 0.25). */ +#define LADSPA_HINT_DEFAULT_LOW 0x80 + +/* This default hint indicates that a middle value between the + suggested lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * + 0.5). */ +#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 + +/* This default hint indicates that a high value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper + * 0.75). */ +#define LADSPA_HINT_DEFAULT_HIGH 0x100 + +/* This default hint indicates that the suggested upper bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 + +/* This default hint indicates that the number 0 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_0 0x200 + +/* This default hint indicates that the number 1 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_1 0x240 + +/* This default hint indicates that the number 100 should be used. */ +#define LADSPA_HINT_DEFAULT_100 0x280 + +/* This default hint indicates that the Hz frequency of `concert A' + should be used. This will be 440 unless the host uses an unusual + tuning convention, in which case it may be within a few Hz. */ +#define LADSPA_HINT_DEFAULT_440 0x2C0 + +#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) +#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) +#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) +#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) +#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) +#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) + +#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) +#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MINIMUM) +#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_LOW) +#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MIDDLE) +#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_HIGH) +#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MAXIMUM) +#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_0) +#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_1) +#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_100) +#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_440) + +typedef struct _LADSPA_PortRangeHint { + + /* Hints about the port. */ + LADSPA_PortRangeHintDescriptor HintDescriptor; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data LowerBound; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data UpperBound; + +} LADSPA_PortRangeHint; + +/*****************************************************************************/ + +/* Plugin Handles: + + This plugin handle indicates a particular instance of the plugin + concerned. It is valid to compare this to NULL (0 for C++) but + otherwise the host should not attempt to interpret it. The plugin + may use it to reference internal instance data. */ + +typedef void * LADSPA_Handle; + +/*****************************************************************************/ + +/* Descriptor for a Type of Plugin: + + This structure is used to describe a plugin type. It provides a + number of functions to examine the type, instantiate it, link it to + buffers and workspaces and to run it. */ + +typedef struct _LADSPA_Descriptor { + + /* This numeric identifier indicates the plugin type + uniquely. Plugin programmers may reserve ranges of IDs from a + central body to avoid clashes. Hosts may assume that IDs are + below 0x1000000. */ + unsigned long UniqueID; + + /* This identifier can be used as a unique, case-sensitive + identifier for the plugin type within the plugin file. Plugin + types should be identified by file and label rather than by index + or plugin name, which may be changed in new plugin + versions. Labels must not contain white-space characters. */ + const char * Label; + + /* This indicates a number of properties of the plugin. */ + LADSPA_Properties Properties; + + /* This member points to the null-terminated name of the plugin + (e.g. "Sine Oscillator"). */ + const char * Name; + + /* This member points to the null-terminated string indicating the + maker of the plugin. This can be an empty string but not NULL. */ + const char * Maker; + + /* This member points to the null-terminated string indicating any + copyright applying to the plugin. If no Copyright applies the + string "None" should be used. */ + const char * Copyright; + + /* This indicates the number of ports (input AND output) present on + the plugin. */ + unsigned long PortCount; + + /* This member indicates an array of port descriptors. Valid indices + vary from 0 to PortCount-1. */ + const LADSPA_PortDescriptor * PortDescriptors; + + /* This member indicates an array of null-terminated strings + describing ports (e.g. "Frequency (Hz)"). Valid indices vary from + 0 to PortCount-1. */ + const char * const * PortNames; + + /* This member indicates an array of range hints for each port (see + above). Valid indices vary from 0 to PortCount-1. */ + const LADSPA_PortRangeHint * PortRangeHints; + + /* This may be used by the plugin developer to pass any custom + implementation data into an instantiate call. It must not be used + or interpreted by the host. It is expected that most plugin + writers will not use this facility as LADSPA_Handle should be + used to hold instance data. */ + void * ImplementationData; + + /* This member is a function pointer that instantiates a plugin. A + handle is returned indicating the new plugin instance. The + instantiation function accepts a sample rate as a parameter. The + plugin descriptor from which this instantiate function was found + must also be passed. This function must return NULL if + instantiation fails. + + Note that instance initialisation should generally occur in + activate() rather than here. */ + LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor, + unsigned long SampleRate); + + /* This member is a function pointer that connects a port on an + instantiated plugin to a memory location at which a block of data + for the port will be read/written. The data location is expected + to be an array of LADSPA_Data for audio ports or a single + LADSPA_Data value for control ports. Memory issues will be + managed by the host. The plugin must read/write the data at these + locations every time run() or run_adding() is called and the data + present at the time of this connection call should not be + considered meaningful. + + connect_port() may be called more than once for a plugin instance + to allow the host to change the buffers that the plugin is + reading or writing. These calls may be made before or after + activate() or deactivate() calls. + + connect_port() must be called at least once for each port before + run() or run_adding() is called. When working with blocks of + LADSPA_Data the plugin should pay careful attention to the block + size passed to the run function as the block allocated may only + just be large enough to contain the block of samples. + + Plugin writers should be aware that the host may elect to use the + same buffer for more than one port and even use the same buffer + for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN). + However, overlapped buffers or use of a single buffer for both + audio and control data may result in unexpected behaviour. */ + void (*connect_port)(LADSPA_Handle Instance, + unsigned long Port, + LADSPA_Data * DataLocation); + + /* This member is a function pointer that initialises a plugin + instance and activates it for use. This is separated from + instantiate() to aid real-time support and so that hosts can + reinitialise a plugin instance by calling deactivate() and then + activate(). In this case the plugin instance must reset all state + information dependent on the history of the plugin instance + except for any data locations provided by connect_port() and any + gain set by set_run_adding_gain(). If there is nothing for + activate() to do then the plugin writer may provide a NULL rather + than an empty function. + + When present, hosts must call this function once before run() (or + run_adding()) is called for the first time. This call should be + made as close to the run() call as possible and indicates to + real-time plugins that they are now live. Plugins should not rely + on a prompt call to run() after activate(). activate() may not be + called again unless deactivate() is called first. Note that + connect_port() may be called before or after a call to + activate(). */ + void (*activate)(LADSPA_Handle Instance); + + /* This method is a function pointer that runs an instance of a + plugin for a block. Two parameters are required: the first is a + handle to the particular instance to be run and the second + indicates the block size (in samples) for which the plugin + instance may run. + + Note that if an activate() function exists then it must be called + before run() or run_adding(). If deactivate() is called for a + plugin instance then the plugin instance may not be reused until + activate() has been called again. + + If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE + then there are various things that the plugin should not do + within the run() or run_adding() functions (see above). */ + void (*run)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that runs an instance of a + plugin for a block. This has identical behaviour to run() except + in the way data is output from the plugin. When run() is used, + values are written directly to the memory areas associated with + the output ports. However when run_adding() is called, values + must be added to the values already present in the memory + areas. Furthermore, output values written must be scaled by the + current gain set by set_run_adding_gain() (see below) before + addition. + + run_adding() is optional. When it is not provided by a plugin, + this function pointer must be set to NULL. When it is provided, + the function set_run_adding_gain() must be provided also. */ + void (*run_adding)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that sets the output gain for + use when run_adding() is called (see above). If this function is + never called the gain is assumed to default to 1. Gain + information should be retained when activate() or deactivate() + are called. + + This function should be provided by the plugin if and only if the + run_adding() function is provided. When it is absent this + function pointer must be set to NULL. */ + void (*set_run_adding_gain)(LADSPA_Handle Instance, + LADSPA_Data Gain); + + /* This is the counterpart to activate() (see above). If there is + nothing for deactivate() to do then the plugin writer may provide + a NULL rather than an empty function. + + Hosts must deactivate all activated units after they have been + run() (or run_adding()) for the last time. This call should be + made as close to the last run() call as possible and indicates to + real-time plugins that they are no longer live. Plugins should + not rely on prompt deactivation. Note that connect_port() may be + called before or after a call to deactivate(). + + Deactivation is not similar to pausing as the plugin instance + will be reinitialised when activate() is called to reuse it. */ + void (*deactivate)(LADSPA_Handle Instance); + + /* Once an instance of a plugin has been finished with it can be + deleted using the following function. The instance handle passed + ceases to be valid after this call. + + If activate() was called for a plugin instance then a + corresponding call to deactivate() must be made before cleanup() + is called. */ + void (*cleanup)(LADSPA_Handle Instance); + +} LADSPA_Descriptor; + +/**********************************************************************/ + +/* Accessing a Plugin: */ + +/* The exact mechanism by which plugins are loaded is host-dependent, + however all most hosts will need to know is the name of shared + object file containing the plugin types. To allow multiple hosts to + share plugin types, hosts may wish to check for environment + variable LADSPA_PATH. If present, this should contain a + colon-separated path indicating directories that should be searched + (in order) when loading plugin types. + + A plugin programmer must include a function called + "ladspa_descriptor" with the following function prototype within + the shared object file. This function will have C-style linkage (if + you are using C++ this is taken care of by the `extern "C"' clause + at the top of the file). + + A host will find the plugin shared object file by one means or + another, find the ladspa_descriptor() function, call it, and + proceed from there. + + Plugin types are accessed by index (not ID) using values from 0 + upwards. Out of range indexes must result in this function + returning NULL, so the plugin count can be determined by checking + for the least index that results in NULL being returned. */ + +const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); + +/* Datatype corresponding to the ladspa_descriptor() function. */ +typedef const LADSPA_Descriptor * +(*LADSPA_Descriptor_Function)(unsigned long Index); + +/**********************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* LADSPA_INCLUDED */ + +/* EOF */ diff -r 000000000000 -r fc9323a41f5a plugin/api/new_dssi_transport_patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/api/new_dssi_transport_patch Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,245 @@ +diff -r dssi-CVS-20051012=0.9.1/dssi/dssi.h _dssi-transport-mine-new/dssi/dssi.h +5,6c5,6 +< DSSI version 0.9 +< Copyright (c) 2004 Chris Cannam, Steve Harris and Sean Bolton +--- +> DSSI version 0.10 +> Copyright (c) 2004,2005 Chris Cannam, Steve Harris and Sean Bolton +30c30 +< #define DSSI_VERSION "0.9" +--- +> #define DSSI_VERSION "0.10" +32c32 +< #define DSSI_VERSION_MINOR 9 +--- +> #define DSSI_VERSION_MINOR 10 +76a77,152 +> #define DSSI_TRANSPORT_VALID_STATE 0x01 +> #define DSSI_TRANSPORT_VALID_BPM 0x02 +> #define DSSI_TRANSPORT_VALID_BBT 0x10 +> #define DSSI_TRANSPORT_VALID_TIME 0x20 +> +> #define DSSI_TRANSPORT_STATE_STOPPED 0 +> #define DSSI_TRANSPORT_STATE_RUNNING 1 +> #define DSSI_TRANSPORT_STATE_FREEWHEELING 2 +> #define DSSI_TRANSPORT_STATE_OTHER 3 /* waiting for sync, ? */ +> +> typedef struct _DSSI_Transport_Info { +> +> /** The value of this field indicates which of the following +> * transport information fields contain valid values. It is +> * the logical OR of the DSSI_TRANSPORT_VALID_* bits defined +> * above, and may be zero. */ +> int Valid; +> +> +> /** This field is valid when (Valid & DSSI_TRANSPORT_VALID_STATE) +> * is true: +> * +> * ---- The current transport state, one of the DSSI_TRANSPORT_STATE_* +> * values defined above. */ +> int State; +> +> +> /** This field is valid when (Valid & DSSI_TRANSPORT_VALID_BPM) +> * is true: +> * +> * ---- The current tempo, in beats per minute. */ +> double Beats_Per_Minute; +> +> +> /** These six fields are valid when (Valid & DSSI_TRANSPORT_VALID_BBT) +> * is true: +> * +> * ---- The bar number at the beginning of the current process cycle. */ +> unsigned long Bar; +> +> * ---- The beat within that Bar. */ +> unsigned long Beat; +> +> /** ---- The tick within that Beat. */ +> unsigned long Tick; +> +> /** ---- The (possibly fractional) tick count since transport 'start' +> * and the beginning of the current Bar. */ +> double Bar_Start_Tick; +> +> /** ---- The number of beats per bar. */ +> float Beats_Per_Bar; +> +> /** ---- The number of ticks for each beat. */ +> double Ticks_Per_Beat; +> +> /* [Sean says: I left out the 'beat_type' (time signature "denominator") +> * field of the jack_position_t structure, because I think it's useless +> * except to a notation program. Does anybody else feel like we need it?] +> +> +> /** These two fields are valid when (Valid & DSSI_TRANSPORT_VALID_TIME) +> * is true: +> * +> * ---- The transport time at the beginning of the current process +> * cycle, in seconds. */ +> double Current_Time; +> +> /** ---- The transport time at the beginning of the next process +> cycle, unless repositioning occurs. */ +> double Next_Time; +> +> } DSSI_Transport_Info; +> +> typedef struct _DSSI_Host_Descriptor DSSI_Host_Descriptor; /* below */ +> +83,84c159,161 +< * If we're lucky, this will never be needed. For now all plugins +< * must set it to 1. +--- +> * All plugins must set this to 1 or 2. The version 1 API contains +> * all DSSI_Descriptor fields through run_multiple_synths_adding(), +> * while the version 2 API adds the receive_host_descriptor(). +376a454,472 +> +> /** +> * receive_host_descriptor() +> * +> * This member is a function pointer by which a host may provide +> * a plugin with a pointer to its DSSI_Host_Descriptor. Hosts +> * which provide host descriptor support must call this function +> * once per plugin shared object file, before any calls to +> * instantiate(). +> * +> * NOTE: This field was added in version 2 of the DSSI API. Hosts +> * supporting version 2 must not access this field in a plugin +> * whose DSSI_API_Version is 1, and plugins supporting version 2 +> * should behave reasonably under hosts (of any version) which do +> * not implement this function. A version 2 plugin that does not +> * provide this function must set this member to NULL. +> */ +> void (*receive_host_descriptor)(DSSI_Host_Descriptor *Descriptor); +> +377a474,598 +> +> struct _DSSI_Host_Descriptor { +> +> /** +> * DSSI_API_Version +> * +> * This member indicates the DSSI API level used by this host. +> * All hosts must set this to 2. Hopefully, we'll get this right +> * the first time, and this will never be needed. +> */ +> int DSSI_API_Version; +> +> /** +> * request_tranport_information() +> * +> * This member is a function pointer by which a plugin instance may +> * request that a host begin providing transport information (if +> * Request is non-zero), or notify the host that it no longer needs +> * transport information (if Request is zero). Upon receiving a +> * non-zero request, the host should return a pointer to a +> * DSSI_Transport_Info structure if it is able to provide transport +> * information, or NULL otherwise. +> * +> * Once a plugin instance has received a non-null transport +> * information pointer, it may read from the structure at any time +> * within the execution of an audio class function (see doc/RFC.txt). +> * It should not consider the structure contents to be meaningful +> * while within a instantiation or control class function. Also, +> * since the validity of fields within the structure may change +> * between each new invocation of an audio class function, a plugin +> * instance must check the Valid field of the structure accordingly +> * before using the structure's other contents. +> * +> * A host which does not support this function must set this member +> * to NULL. +> */ +> DSSI_Transport_Info * +> (*request_transport_information)(LADSPA_Handle Instance, +> int Request); +> +> /** +> * request_midi_send() +> * +> * This member is a function pointer that allows a plugin to +> * request the ability to send MIDI events to the host. +> * +> * While the interpretation of plugin-generated MIDI events is +> * host implementation specific, a mechanism exists by which a +> * plugin may declare to the host the number of destination +> * 'ports' and MIDI channels it can expect will be used in the +> * plugin-generated events. Plugins which generate unchannelized +> * MIDI should supply zero for both Ports and Channels, otherwise +> * they should supply the maximum numbers for Ports and Channels +> * they expect to use. +> * +> * A plugin instance must call this function during instantiate(). +> * [Sean says: this restriction seems reasonable to me, since +> * the host may need to create output ports, etc., and instantiate() +> * seems like a good place to do such things. I'm sure I haven't +> * fully thought through all the details, though....] +> * +> * The host should return a non-zero value if it is able to +> * provide MIDI send for the plugin instance, otherwise it should +> * return zero, and the plugin instance may not subsequently call +> * midi_send(). +> * +> * A host which does not support the MIDI send function must set +> * both this member and (*midi_send)() below to NULL. +> */ +> int (*request_midi_send)(LADSPA_Handle Instance, +> unsigned char Ports, +> unsigned char Channels); +> +> /** +> * midi_send() +> * +> * This member is a function pointer by which a plugin actually +> * sends MIDI events to the host (provided it has received a non- +> * zero return from request_midi_send()). As in the run_synth() +> * functions, the Event pointer points to a block of EventCount +> * ALSA sequencer events. The dest.port and data.*.channel fields +> * of each event are used to specify destination port and channel, +> * respectively, when the plugin is supplying channelized events. +> * +> * A plugin may only call this function from within the execution +> * of the audio class run_*() or select_program() functions. When +> * called from a run_*() functions, the events are timestamped +> * relative to the start of the block, (mis)using the ALSA "tick +> * time" field as a frame count. The plugin is responsible for +> * ensuring that events with differing timestamps are already +> * ordered by time, and that timestamps across multiple calls to +> * midi_send() from within the same run_*() invocation are +> * monotonic. When midi_send() is called from within +> * select_program(), the timestamps are ignored, and the events +> * are considered to originate at the same frame time as the +> * select_program() call, if such a timing can be considered +> * meaningful. +> * +> * The memory pointed to by Event belongs to the plugin, and it is +> * the host's responsibility to copy the events as needed before +> * returning from the midi_send() call. +> * +> * A host which does not support the MIDI send function must set +> * both this member and (*request_midi_send)() above to NULL. +> */ +> void (*midi_send)(LADSPA_Handle Instance, +> snd_seq_event_t *Event, +> unsigned long EventCount); +> +> /** +> * . . . additional fields could follow here, possibly supporting: +> * +> * - a facility by which a plugin instance may request from a +> * host a non-realtime thread in which to do off-line +> * rendering, I/O, etc., thus (hopefully) avoiding the +> * crashes that seem to occur when plugins create their own +> * threads. I got this idea after noticing that ZynAddSubFX +> * achieves its gorgeous textures while remaining very +> * responsive by doing a lot of non-real-time rendering. +> * Several other uses for it have been mentioned on the DSSI +> * list; I forget what. +> * +> * - per-voice audio output +> */ +> }; diff -r 000000000000 -r fc9323a41f5a plugin/plugin.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/plugin.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,48 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = vamp vamp-hostsdk lrdf raptor +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += xml + +TARGET = svplugin + +# Doesn't work with this library, which contains C99 as well as C++ +PRECOMPILED_HEADER = + +DEPENDPATH += . .. api plugins api/alsa api/alsa/sound +INCLUDEPATH += . .. api api/alsa plugins api/alsa/sound +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += DSSIPluginFactory.h \ + DSSIPluginInstance.h \ + FeatureExtractionPluginFactory.h \ + LADSPAPluginFactory.h \ + LADSPAPluginInstance.h \ + PluginIdentifier.h \ + PluginXml.h \ + RealTimePluginFactory.h \ + RealTimePluginInstance.h \ + api/dssi.h \ + api/ladspa.h \ + plugins/SamplePlayer.h \ + api/alsa/asoundef.h \ + api/alsa/asoundlib.h \ + api/alsa/seq.h \ + api/alsa/seq_event.h \ + api/alsa/seq_midi_event.h \ + api/alsa/sound/asequencer.h +SOURCES += DSSIPluginFactory.cpp \ + DSSIPluginInstance.cpp \ + FeatureExtractionPluginFactory.cpp \ + LADSPAPluginFactory.cpp \ + LADSPAPluginInstance.cpp \ + PluginIdentifier.cpp \ + PluginXml.cpp \ + RealTimePluginFactory.cpp \ + RealTimePluginInstance.cpp \ + api/dssi_alsa_compat.c \ + plugins/SamplePlayer.cpp diff -r 000000000000 -r fc9323a41f5a plugin/plugins/SamplePlayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/plugins/SamplePlayer.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,598 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + Based on trivial_sampler from the DSSI distribution + (by Chris Cannam, public domain). +*/ + +#include "SamplePlayer.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "system/System.h" + +const char *const +SamplePlayer::portNames[PortCount] = +{ + "Output", + "Tuned (on/off)", + "Base Pitch (MIDI)", + "Tuning of A (Hz)", + "Sustain (on/off)", + "Release time (s)" +}; + +const LADSPA_PortDescriptor +SamplePlayer::ports[PortCount] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL +}; + +const LADSPA_PortRangeHint +SamplePlayer::hints[PortCount] = +{ + { 0, 0, 0 }, + { LADSPA_HINT_DEFAULT_MAXIMUM | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, + { LADSPA_HINT_DEFAULT_MIDDLE | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 120 }, + { LADSPA_HINT_DEFAULT_440 | LADSPA_HINT_LOGARITHMIC | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 400, 499 }, + { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_INTEGER | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0, 1 }, + { LADSPA_HINT_DEFAULT_MINIMUM | LADSPA_HINT_LOGARITHMIC | + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, 0.001, 2.0 } +}; + +const LADSPA_Properties +SamplePlayer::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; + +const LADSPA_Descriptor +SamplePlayer::ladspaDescriptor = +{ + 0, // "Unique" ID + "sample_player", // Label + properties, + "Library Sample Player", // Name + "Chris Cannam", // Maker + "GPL", // Copyright + PortCount, + ports, + portNames, + hints, + 0, // Implementation data + instantiate, + connectPort, + activate, + run, + 0, // Run adding + 0, // Set run adding gain + deactivate, + cleanup +}; + +const DSSI_Descriptor +SamplePlayer::dssiDescriptor = +{ + 2, // DSSI API version + &ladspaDescriptor, + configure, + getProgram, + selectProgram, + getMidiController, + runSynth, + 0, // Run synth adding + 0, // Run multiple synths + 0, // Run multiple synths adding + receiveHostDescriptor +}; + +const DSSI_Host_Descriptor * +SamplePlayer::hostDescriptor = 0; + + +const DSSI_Descriptor * +SamplePlayer::getDescriptor(unsigned long index) +{ + if (index == 0) return &dssiDescriptor; + return 0; +} + +SamplePlayer::SamplePlayer(int sampleRate) : + m_output(0), + m_retune(0), + m_basePitch(0), + m_concertA(0), + m_sustain(0), + m_release(0), + m_sampleData(0), + m_sampleCount(0), + m_sampleRate(sampleRate), + m_sampleNo(0), + m_sampleDir("samples"), + m_sampleSearchComplete(false), + m_pendingProgramChange(-1) +{ +} + +SamplePlayer::~SamplePlayer() +{ + if (m_sampleData) free(m_sampleData); +} + +LADSPA_Handle +SamplePlayer::instantiate(const LADSPA_Descriptor *, unsigned long rate) +{ + if (!hostDescriptor || !hostDescriptor->request_non_rt_thread) { + std::cerr << "SamplePlayer::instantiate: Host does not provide request_non_rt_thread, not instantiating" << std::endl; + return 0; + } + + SamplePlayer *player = new SamplePlayer(rate); + + if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) { + std::cerr << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << std::endl; + delete player; + return 0; + } + + return player; +} + +void +SamplePlayer::connectPort(LADSPA_Handle handle, + unsigned long port, LADSPA_Data *location) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + float **ports[PortCount] = { + &player->m_output, + &player->m_retune, + &player->m_basePitch, + &player->m_concertA, + &player->m_sustain, + &player->m_release + }; + + *ports[port] = (float *)location; +} + +void +SamplePlayer::activate(LADSPA_Handle handle) +{ + SamplePlayer *player = (SamplePlayer *)handle; + QMutexLocker locker(&player->m_mutex); + + player->m_sampleNo = 0; + + for (size_t i = 0; i < Polyphony; ++i) { + player->m_ons[i] = -1; + player->m_offs[i] = -1; + player->m_velocities[i] = 0; + } +} + +void +SamplePlayer::run(LADSPA_Handle handle, unsigned long samples) +{ + runSynth(handle, samples, 0, 0); +} + +void +SamplePlayer::deactivate(LADSPA_Handle handle) +{ + activate(handle); // both functions just reset the plugin +} + +void +SamplePlayer::cleanup(LADSPA_Handle handle) +{ + delete (SamplePlayer *)handle; +} + +char * +SamplePlayer::configure(LADSPA_Handle handle, const char *key, const char *value) +{ + if (key && !strcmp(key, "sampledir")) { + + SamplePlayer *player = (SamplePlayer *)handle; + + QMutexLocker locker(&player->m_mutex); + + if (QFileInfo(value).exists() && + QFileInfo(value).isDir()) { + + player->m_sampleDir = value; + + if (player->m_sampleSearchComplete) { + player->m_sampleSearchComplete = false; + player->searchSamples(); + } + + return 0; + + } else { + char *buffer = (char *)malloc(strlen(value) + 80); + sprintf(buffer, "Sample directory \"%s\" does not exist, leaving unchanged", value); + return buffer; + } + } + + return strdup("Unknown configure key"); +} + +const DSSI_Program_Descriptor * +SamplePlayer::getProgram(LADSPA_Handle handle, unsigned long program) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + if (!player->m_sampleSearchComplete) { + QMutexLocker locker(&player->m_mutex); + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + } + if (program >= player->m_samples.size()) return 0; + + static DSSI_Program_Descriptor descriptor; + static char name[60]; + + strncpy(name, player->m_samples[program].first.toLocal8Bit().data(), 60); + name[59] = '\0'; + + descriptor.Bank = 0; + descriptor.Program = program; + descriptor.Name = name; + + return &descriptor; +} + +void +SamplePlayer::selectProgram(LADSPA_Handle handle, + unsigned long, + unsigned long program) +{ + SamplePlayer *player = (SamplePlayer *)handle; + player->m_pendingProgramChange = program; +} + +int +SamplePlayer::getMidiController(LADSPA_Handle, unsigned long port) +{ + int controllers[PortCount] = { + DSSI_NONE, + DSSI_CC(12), + DSSI_CC(13), + DSSI_CC(64), + DSSI_CC(72) + }; + + return controllers[port]; +} + +void +SamplePlayer::runSynth(LADSPA_Handle handle, unsigned long samples, + snd_seq_event_t *events, unsigned long eventCount) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + player->runImpl(samples, events, eventCount); +} + +void +SamplePlayer::receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor) +{ + hostDescriptor = descriptor; +} + +void +SamplePlayer::workThreadCallback(LADSPA_Handle handle) +{ + SamplePlayer *player = (SamplePlayer *)handle; + + if (player->m_pendingProgramChange >= 0) { + +// std::cerr << "SamplePlayer::workThreadCallback: pending program change " << player->m_pendingProgramChange << std::endl; + + player->m_mutex.lock(); + + int program = player->m_pendingProgramChange; + player->m_pendingProgramChange = -1; + + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + + if (program < int(player->m_samples.size())) { + QString path = player->m_samples[program].second; + QString programName = player->m_samples[program].first; + if (programName != player->m_program) { + player->m_program = programName; + player->m_mutex.unlock(); + player->loadSampleData(path); + } else { + player->m_mutex.unlock(); + } + } + } + + if (!player->m_sampleSearchComplete) { + + QMutexLocker locker(&player->m_mutex); + + if (!player->m_sampleSearchComplete) { + player->searchSamples(); + } + } +} + +void +SamplePlayer::searchSamples() +{ + if (m_sampleSearchComplete) return; + + m_samples.clear(); + + std::cerr << "SamplePlayer::searchSamples: Directory is \"" + << m_sampleDir.toLocal8Bit().data() << "\"" << std::endl; + + QDir dir(m_sampleDir, "*.wav"); + + for (unsigned int i = 0; i < dir.count(); ++i) { + QFileInfo file(dir.filePath(dir[i])); + if (file.isReadable()) { + m_samples.push_back(std::pair + (file.baseName(), file.filePath())); +// std::cerr << "Found: " << dir[i].toLocal8Bit().data() << std::endl; + } + } + + m_sampleSearchComplete = true; +} + +void +SamplePlayer::loadSampleData(QString path) +{ + SF_INFO info; + SNDFILE *file; + size_t samples = 0; + float *tmpFrames, *tmpSamples, *tmpResamples, *tmpOld; + size_t i; + + info.format = 0; + file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); + if (!file) { + std::cerr << "SamplePlayer::loadSampleData: Failed to open file " + << path.toLocal8Bit().data() << ": " + << sf_strerror(file) << std::endl; + return; + } + + samples = info.frames; + tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float)); + if (!tmpFrames) return; + + sf_readf_float(file, tmpFrames, info.frames); + sf_close(file); + + tmpResamples = 0; + + if (info.samplerate != m_sampleRate) { + + double ratio = (double)m_sampleRate / (double)info.samplerate; + size_t target = (size_t)(info.frames * ratio); + SRC_DATA data; + + tmpResamples = (float *)malloc(target * info.channels * sizeof(float)); + if (!tmpResamples) { + free(tmpFrames); + return; + } + + memset(tmpResamples, 0, target * info.channels * sizeof(float)); + + data.data_in = tmpFrames; + data.data_out = tmpResamples; + data.input_frames = info.frames; + data.output_frames = target; + data.src_ratio = ratio; + + if (!src_simple(&data, SRC_SINC_BEST_QUALITY, info.channels)) { + free(tmpFrames); + tmpFrames = tmpResamples; + samples = target; + } else { + free(tmpResamples); + } + } + + /* add an extra sample for linear interpolation */ + tmpSamples = (float *)malloc((samples + 1) * sizeof(float)); + if (!tmpSamples) { + free(tmpFrames); + return; + } + + for (i = 0; i < samples; ++i) { + int j; + tmpSamples[i] = 0.0f; + for (j = 0; j < info.channels; ++j) { + tmpSamples[i] += tmpFrames[i * info.channels + j]; + } + } + + free(tmpFrames); + + /* add an extra sample for linear interpolation */ + tmpSamples[samples] = 0.0f; + + QMutexLocker locker(&m_mutex); + + tmpOld = m_sampleData; + m_sampleData = tmpSamples; + m_sampleCount = samples; + + for (i = 0; i < Polyphony; ++i) { + m_ons[i] = -1; + m_offs[i] = -1; + m_velocities[i] = 0; + } + + if (tmpOld) free(tmpOld); + + printf("%s: loaded %s (%ld samples from original %ld channels resampled from %ld frames at %ld Hz)\n", "sampler", path.toLocal8Bit().data(), (long)samples, (long)info.channels, (long)info.frames, (long)info.samplerate); +} + +void +SamplePlayer::runImpl(unsigned long sampleCount, + snd_seq_event_t *events, + unsigned long eventCount) +{ + unsigned long pos; + unsigned long count; + unsigned long event_pos; + int i; + + memset(m_output, 0, sampleCount * sizeof(float)); + + if (!m_mutex.tryLock()) return; + + if (!m_sampleData || !m_sampleCount) { + m_sampleNo += sampleCount; + m_mutex.unlock(); + return; + } + + for (pos = 0, event_pos = 0; pos < sampleCount; ) { + + while (event_pos < eventCount + && pos >= events[event_pos].time.tick) { + + if (events[event_pos].type == SND_SEQ_EVENT_NOTEON) { + snd_seq_ev_note_t n = events[event_pos].data.note; + if (n.velocity > 0) { + m_ons[n.note] = + m_sampleNo + events[event_pos].time.tick; + m_offs[n.note] = -1; + m_velocities[n.note] = n.velocity; + } else { + if (!m_sustain || (*m_sustain < 0.001)) { + m_offs[n.note] = + m_sampleNo + events[event_pos].time.tick; + } + } + } else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF && + (!m_sustain || (*m_sustain < 0.001))) { + snd_seq_ev_note_t n = events[event_pos].data.note; + m_offs[n.note] = + m_sampleNo + events[event_pos].time.tick; + } + + ++event_pos; + } + + count = sampleCount - pos; + if (event_pos < eventCount && + events[event_pos].time.tick < sampleCount) { + count = events[event_pos].time.tick - pos; + } + + for (i = 0; i < Polyphony; ++i) { + if (m_ons[i] >= 0) { + addSample(i, pos, count); + } + } + + pos += count; + } + + m_sampleNo += sampleCount; + m_mutex.unlock(); +} + +void +SamplePlayer::addSample(int n, unsigned long pos, unsigned long count) +{ + float ratio = 1.f; + float gain = 1.f; + unsigned long i, s; + + if (m_retune && *m_retune) { + if (m_concertA) { + ratio *= *m_concertA / 440.f; + } + if (m_basePitch && n != *m_basePitch) { + ratio *= powf(1.059463094f, n - *m_basePitch); + } + } + + if (long(pos + m_sampleNo) < m_ons[n]) return; + + gain = (float)m_velocities[n] / 127.0f; + + for (i = 0, s = pos + m_sampleNo - m_ons[n]; + i < count; + ++i, ++s) { + + float lgain = gain; + float rs = s * ratio; + unsigned long rsi = lrintf(floor(rs)); + + if (rsi >= m_sampleCount) { + m_ons[n] = -1; + break; + } + + if (m_offs[n] >= 0 && + long(pos + i + m_sampleNo) > m_offs[n]) { + + unsigned long dist = + pos + i + m_sampleNo - m_offs[n]; + + unsigned long releaseFrames = 200; + if (m_release) { + releaseFrames = long(*m_release * m_sampleRate + 0.0001); + } + + if (dist > releaseFrames) { + m_ons[n] = -1; + break; + } else { + lgain = lgain * (float)(releaseFrames - dist) / + (float)releaseFrames; + } + } + + float sample = m_sampleData[rsi] + + ((m_sampleData[rsi + 1] - + m_sampleData[rsi]) * + (rs - (float)rsi)); + + m_output[pos + i] += lgain * sample; + } +} diff -r 000000000000 -r fc9323a41f5a plugin/plugins/SamplePlayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/plugins/SamplePlayer.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,106 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SAMPLE_PLAYER_H_ +#define _SAMPLE_PLAYER_H_ + +#define DSSI_API_LEVEL 2 + +#include +#include +#include + +#include +#include +#include + +class SamplePlayer +{ +public: + static const DSSI_Descriptor *getDescriptor(unsigned long index); + +private: + SamplePlayer(int sampleRate); + ~SamplePlayer(); + + enum { + OutputPort = 0, + RetunePort = 1, + BasePitchPort = 2, + ConcertAPort = 3, + SustainPort = 4, + ReleasePort = 5, + PortCount = 6 + }; + + enum { + Polyphony = 128 + }; + + static const char *const portNames[PortCount]; + static const LADSPA_PortDescriptor ports[PortCount]; + static const LADSPA_PortRangeHint hints[PortCount]; + static const LADSPA_Properties properties; + static const LADSPA_Descriptor ladspaDescriptor; + static const DSSI_Descriptor dssiDescriptor; + static const DSSI_Host_Descriptor *hostDescriptor; + + static LADSPA_Handle instantiate(const LADSPA_Descriptor *, unsigned long); + static void connectPort(LADSPA_Handle, unsigned long, LADSPA_Data *); + static void activate(LADSPA_Handle); + static void run(LADSPA_Handle, unsigned long); + static void deactivate(LADSPA_Handle); + static void cleanup(LADSPA_Handle); + static char *configure(LADSPA_Handle, const char *, const char *); + static const DSSI_Program_Descriptor *getProgram(LADSPA_Handle, unsigned long); + static void selectProgram(LADSPA_Handle, unsigned long, unsigned long); + static int getMidiController(LADSPA_Handle, unsigned long); + static void runSynth(LADSPA_Handle, unsigned long, + snd_seq_event_t *, unsigned long); + static void receiveHostDescriptor(const DSSI_Host_Descriptor *descriptor); + static void workThreadCallback(LADSPA_Handle); + + void searchSamples(); + void loadSampleData(QString path); + void runImpl(unsigned long, snd_seq_event_t *, unsigned long); + void addSample(int, unsigned long, unsigned long); + + float *m_output; + float *m_retune; + float *m_basePitch; + float *m_concertA; + float *m_sustain; + float *m_release; + + float *m_sampleData; + size_t m_sampleCount; + int m_sampleRate; + + long m_ons[Polyphony]; + long m_offs[Polyphony]; + int m_velocities[Polyphony]; + long m_sampleNo; + + QString m_sampleDir; + QString m_program; + std::vector > m_samples; // program name, path + bool m_sampleSearchComplete; + int m_pendingProgramChange; + + QMutex m_mutex; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a plugin/svplugin.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/svplugin.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a sonic-visualiser.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sonic-visualiser.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,7 @@ + +TEMPLATE = subdirs + +SUBDIRS = base data layer plugin view widgets system sv + +TRANSLATIONS += i18n/sonic-visualiser_ru.ts + diff -r 000000000000 -r fc9323a41f5a sound-access.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sound-access.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,7 @@ + +TEMPLATE = subdirs + +SUBDIRS = base data layer plugin view widgets system sv + +TRANSLATIONS += i18n/sound-access_ru.ts + diff -r 000000000000 -r fc9323a41f5a sound_access.sln --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sound_access.sln Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,89 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svbase", "base\svbase.vcproj", "{88D50F52-2DFD-43D9-BDCE-BFDD2174D460}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svdata", "data\svdata.vcproj", "{7FA04762-1A96-4830-8549-A2A5FBCC95E7}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svlayer", "layer\svlayer.vcproj", "{4D07397E-2110-4EDA-8BA3-17E344B50385}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svplugin", "plugin\svplugin.vcproj", "{D4262F2A-F989-4406-ADBD-6E72E5849C43}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svsystem", "system\svsystem.vcproj", "{59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sound-access", "sv\sound_access.vcproj", "{D98C1DD4-008F-34B2-A980-0998ECF8427E}" + ProjectSection(ProjectDependencies) = postProject + {88D50F52-2DFD-43D9-BDCE-BFDD2174D460} = {88D50F52-2DFD-43D9-BDCE-BFDD2174D460} + {7FA04762-1A96-4830-8549-A2A5FBCC95E7} = {7FA04762-1A96-4830-8549-A2A5FBCC95E7} + {4D07397E-2110-4EDA-8BA3-17E344B50385} = {4D07397E-2110-4EDA-8BA3-17E344B50385} + {D4262F2A-F989-4406-ADBD-6E72E5849C43} = {D4262F2A-F989-4406-ADBD-6E72E5849C43} + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + {ED66805F-C344-4BA0-8CE7-CD903593D3E9} = {ED66805F-C344-4BA0-8CE7-CD903593D3E9} + {FB828812-61D9-42F1-B9BD-89E56A134879} = {FB828812-61D9-42F1-B9BD-89E56A134879} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svview", "view\svview.vcproj", "{ED66805F-C344-4BA0-8CE7-CD903593D3E9}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svwidgets", "widgets\svwidgets.vcproj", "{FB828812-61D9-42F1-B9BD-89E56A134879}" + ProjectSection(ProjectDependencies) = postProject + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} = {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {88D50F52-2DFD-43D9-BDCE-BFDD2174D460}.Debug|Win32.ActiveCfg = Debug|Win32 + {88D50F52-2DFD-43D9-BDCE-BFDD2174D460}.Debug|Win32.Build.0 = Debug|Win32 + {88D50F52-2DFD-43D9-BDCE-BFDD2174D460}.Release|Win32.ActiveCfg = Release|Win32 + {88D50F52-2DFD-43D9-BDCE-BFDD2174D460}.Release|Win32.Build.0 = Release|Win32 + {7FA04762-1A96-4830-8549-A2A5FBCC95E7}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FA04762-1A96-4830-8549-A2A5FBCC95E7}.Debug|Win32.Build.0 = Debug|Win32 + {7FA04762-1A96-4830-8549-A2A5FBCC95E7}.Release|Win32.ActiveCfg = Release|Win32 + {7FA04762-1A96-4830-8549-A2A5FBCC95E7}.Release|Win32.Build.0 = Release|Win32 + {4D07397E-2110-4EDA-8BA3-17E344B50385}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D07397E-2110-4EDA-8BA3-17E344B50385}.Debug|Win32.Build.0 = Debug|Win32 + {4D07397E-2110-4EDA-8BA3-17E344B50385}.Release|Win32.ActiveCfg = Release|Win32 + {4D07397E-2110-4EDA-8BA3-17E344B50385}.Release|Win32.Build.0 = Release|Win32 + {D4262F2A-F989-4406-ADBD-6E72E5849C43}.Debug|Win32.ActiveCfg = Debug|Win32 + {D4262F2A-F989-4406-ADBD-6E72E5849C43}.Debug|Win32.Build.0 = Debug|Win32 + {D4262F2A-F989-4406-ADBD-6E72E5849C43}.Release|Win32.ActiveCfg = Release|Win32 + {D4262F2A-F989-4406-ADBD-6E72E5849C43}.Release|Win32.Build.0 = Release|Win32 + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE}.Debug|Win32.ActiveCfg = Debug|Win32 + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE}.Debug|Win32.Build.0 = Debug|Win32 + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE}.Release|Win32.ActiveCfg = Release|Win32 + {59E68DF5-9474-4CE3-AAD5-8A3BC196B5CE}.Release|Win32.Build.0 = Release|Win32 + {D98C1DD4-008F-34B2-A980-0998ECF8427E}.Debug|Win32.ActiveCfg = Debug|Win32 + {D98C1DD4-008F-34B2-A980-0998ECF8427E}.Debug|Win32.Build.0 = Debug|Win32 + {D98C1DD4-008F-34B2-A980-0998ECF8427E}.Release|Win32.ActiveCfg = Release|Win32 + {D98C1DD4-008F-34B2-A980-0998ECF8427E}.Release|Win32.Build.0 = Release|Win32 + {ED66805F-C344-4BA0-8CE7-CD903593D3E9}.Debug|Win32.ActiveCfg = Debug|Win32 + {ED66805F-C344-4BA0-8CE7-CD903593D3E9}.Debug|Win32.Build.0 = Debug|Win32 + {ED66805F-C344-4BA0-8CE7-CD903593D3E9}.Release|Win32.ActiveCfg = Release|Win32 + {ED66805F-C344-4BA0-8CE7-CD903593D3E9}.Release|Win32.Build.0 = Release|Win32 + {FB828812-61D9-42F1-B9BD-89E56A134879}.Debug|Win32.ActiveCfg = Debug|Win32 + {FB828812-61D9-42F1-B9BD-89E56A134879}.Debug|Win32.Build.0 = Debug|Win32 + {FB828812-61D9-42F1-B9BD-89E56A134879}.Release|Win32.ActiveCfg = Release|Win32 + {FB828812-61D9-42F1-B9BD-89E56A134879}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff -r 000000000000 -r fc9323a41f5a sv.prf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv.prf Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,175 @@ + +### +### BEGIN CONFIGURABLE STUFF +### + +CONFIG += release precompile_header + +# Whizzy optimization flags here. +# (Don't use -ffast-math -- it does make things faster, but it prevents isnan and +# isinf from working, and we need those.) +# +linux-g++:QMAKE_CXXFLAGS_RELEASE += -DNDEBUG -DNO_TIMING -O2 -march=pentium3 -mfpmath=sse +# QMAKE_CXXFLAGS_RELEASE += -O3 -march=pentium4 -mfpmath=sse -msse -msse2 -ffast-math -fomit-frame-pointer +# QMAKE_CXXFLAGS_RELEASE += -O3 -march=athlon-mp -mfpmath=sse -fomit-frame-pointer + +# To do a static build with gcc on Linux +# +#linux-g++:LIBS += -Wl,-Bstatic +#linux-g++:DEFINES += BUILD_STATIC + +#PRECOMPILED_HEADER = /work/sonic-visualiser/pch.h + + +# These are testable on platforms with pkg-config. If you don't have +# pkg-config, edit the "If you don't have pkg-config" block below (see +# comments). +# +PKGCONFIG_PACKAGES = vamp vamp-hostsdk oggz fishsound mad fftw3f sndfile samplerate lrdf raptor jack liblo + +# No pkg-config test for the bzip2 library. This library is required. +# If you don't have it, install it. +# +DEFINES += HAVE_BZ2 +LIBS += -lbz2 + +# No pkg-config test for PortAudio. If you don't have it, comment these out. +# We support PortAudio v18 and v19; the default is v19. If you want to use +# v18, see below. +# +DEFINES += HAVE_PORTAUDIO +LIBS += -lportaudio +# +# If you want to use PortAudio v18, uncomment this line (as well as +# HAVE_PORTAUDIO above): +# +#DEFINES += HAVE_PORTAUDIO_V18 + + +!system(pkg-config --atleast-pkgconfig-version=0) { + + # If you don't have pkg-config, comment out (or install) any of the + # following that you lack. If you have pkg-config, you should be + # able to ignore all this provided the right symbols are defined + # in PKGCONFIG_PACKAGES above. + # + DEFINES += HAVE_JACK # Optional -- an audio playback option + DEFINES += HAVE_OGGZ # Optional -- to import .ogg files + DEFINES += HAVE_FISHSOUND # Optional -- to import .ogg files + DEFINES += HAVE_MAD # Optional -- to import .mp3 files + DEFINES += HAVE_FFTW3F # Optional -- but SV will be slower without it + # + LIBS += -ljack + LIBS += -loggz -lfishsound + LIBS += -lmad + + # These ones are mandatory. + # If you don't have them, you'll have to find them. + # + DEFINES += HAVE_VAMP # Required -- for analysis plugins + DEFINES += HAVE_VAMP_HOSTSDK # Required -- for analysis plugins + DEFINES += HAVE_SNDFILE # Required -- to import and export .wav files + DEFINES += HAVE_SAMPLERATE # Required -- for resampling + # + INCLUDEPATH += ../../vamp-plugin-sdk + LIBPATH += ../../vamp-plugin-sdk/vamp-sdk + # + LIBS += -lvamp-hostsdk -lfftw3f -lsndfile -lsamplerate +} + +### +### END CONFIGURABLE STUFF +### + + +system(pkg-config --atleast-pkgconfig-version=0) { + + # If you have pkg-config, this block should locate all packages + # for you provided they have .pc files and are listed in + # PKGCONFIG_PACKAGES. + # + for(PKG, PKGCONFIG_PACKAGES) { + contains(SV_UNIT_PACKAGES, $$PKG) { + system(pkg-config --exists $$PKG) { + VERSION = $$system(pkg-config --modversion $$PKG) + PACKAGE_SYMBOL = $$system(echo $$PKG | tr '[a-z-]' '[A-Z_]') + VERSION_SYMBOL = $$PACKAGE_SYMBOL'_VERSION' + DEFINES += HAVE_$$PACKAGE_SYMBOL + QMAKE_CXXFLAGS += -D"'"$$VERSION_SYMBOL='"'$$VERSION'"'"'" + QMAKE_CXXFLAGS += $$system(pkg-config --cflags $$PKG) + LIBS += $$system(pkg-config --libs $$PKG) + message("Using pkg-config package $$PKG with version $$VERSION") + } else { + message("WARNING: Failed to find pkg-config package $$PKG") + } + } + } +} + +contains(SV_UNIT_PACKAGES, portaudio) { + contains(DEFINES, HAVE_PORTAUDIO) { + message("Including PortAudio support for audio playback") + } else { + message("WARNING: PortAudio audio playback support will not be included") + } +} + +contains(SV_UNIT_PACKAGES, jack) { + contains(DEFINES, HAVE_JACK) { + message("Including JACK support for audio playback") + } else { + !win32:message("WARNING: JACK audio playback support will not be included") + !contains(DEFINES, HAVE_PORTAUDIO) { + message("WARNING: No audio playback support is configured!") + } + } +} + +contains(SV_UNIT_PACKAGES, oggz) { + contains(DEFINES, HAVE_OGGZ) { + contains(DEFINES, HAVE_FISHSOUND) { + message("Including .ogg file import") + } else { + message("WARNING: .ogg file import will not be included") + } + } else { + message("WARNING: .ogg file import will not be included") + } +} + +contains(SV_UNIT_PACKAGES, mad) { + contains(DEFINES, HAVE_MAD) { + message("Including .mp3 file import") + } else { + message("WARNING: .mp3 file import will not be included") + } +} + +contains(SV_UNIT_PACKAGES, fftw3f) { + contains(DEFINES, HAVE_FFTW3F) { + message("Using FFTW3f") + } else { + message("WARNING: FFTW3f not available, using slower FFT implementation") + } +} + +contains(SV_UNIT_PACKAGES, vamp):!contains(DEFINES, HAVE_VAMP):error("Vamp plugin API required") +contains(SV_UNIT_PACKAGES, vamp-hostsdk):!contains(DEFINES, HAVE_VAMP_HOSTSDK):error("Vamp plugin host SDK required") +contains(SV_UNIT_PACKAGES, bz2):!contains(DEFINES, HAVE_BZ2):error("bzip2 library required") +contains(SV_UNIT_PACKAGES, sndfile):!contains(DEFINES, HAVE_SNDFILE):error("sndfile library required") +contains(SV_UNIT_PACKAGES, samplerate):!contains(DEFINES, HAVE_SAMPLERATE):error("libsamplerate required") + +VERSION_CFLAGS += -D"'"SVNREV='"'$$system(svnversion -n .)'"'"'" + +QMAKE_CXXFLAGS_DEBUG += -DBUILD_DEBUG $$VERSION_CFLAGS +QMAKE_CXXFLAGS_RELEASE += -DBUILD_RELEASE $$VERSION_CFLAGS + +linux-g++ { + contains(DEFINES, BUILD_STATIC) { + LIBS += -lFLAC -ldl + } +} + +# Restore dynamic linkage, in case we went static earlier +linux-g++:LIBS += -Wl,-Bdynamic + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCallbackPlaySource.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCallbackPlaySource.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1465 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioCallbackPlaySource.h" + +#include "AudioGenerator.h" + +#include "data/model/Model.h" +#include "view/ViewManager.h" +#include "base/PlayParameterRepository.h" +#include "base/Preferences.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/WaveFileModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "plugin/RealTimePluginInstance.h" +#include "PhaseVocoderTimeStretcher.h" + +#include +#include + +//#define DEBUG_AUDIO_PLAY_SOURCE 1 +//#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1 + +const size_t AudioCallbackPlaySource::m_ringBufferSize = 131071; + +AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManager *manager) : + m_viewManager(manager), + m_audioGenerator(new AudioGenerator()), + m_readBuffers(0), + m_writeBuffers(0), + m_readBufferFill(0), + m_writeBufferFill(0), + m_bufferScavenger(1), + m_sourceChannelCount(0), + m_blockSize(1024), + m_sourceSampleRate(0), + m_targetSampleRate(0), + m_playLatency(0), + m_playing(false), + m_exiting(false), + m_lastModelEndFrame(0), + m_outputLeft(0.0), + m_outputRight(0.0), + m_auditioningPlugin(0), + m_auditioningPluginBypassed(false), + m_timeStretcher(0), + m_fillThread(0), + m_converter(0), + m_crapConverter(0), + m_resampleQuality(Preferences::getInstance()->getResampleQuality()) +{ + m_viewManager->setAudioPlaySource(this); + + connect(m_viewManager, SIGNAL(selectionChanged()), + this, SLOT(selectionChanged())); + connect(m_viewManager, SIGNAL(playLoopModeChanged()), + this, SLOT(playLoopModeChanged())); + connect(m_viewManager, SIGNAL(playSelectionModeChanged()), + this, SLOT(playSelectionModeChanged())); + + connect(PlayParameterRepository::getInstance(), + SIGNAL(playParametersChanged(PlayParameters *)), + this, SLOT(playParametersChanged(PlayParameters *))); + + connect(Preferences::getInstance(), + SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); +} + +AudioCallbackPlaySource::~AudioCallbackPlaySource() +{ + m_exiting = true; + + if (m_fillThread) { + m_condition.wakeAll(); + m_fillThread->wait(); + delete m_fillThread; + } + + clearModels(); + + if (m_readBuffers != m_writeBuffers) { + delete m_readBuffers; + } + + delete m_writeBuffers; + + delete m_audioGenerator; + + m_bufferScavenger.scavenge(true); + m_pluginScavenger.scavenge(true); + m_timeStretcherScavenger.scavenge(true); +} + +void +AudioCallbackPlaySource::addModel(Model *model) +{ + if (m_models.find(model) != m_models.end()) return; + + bool canPlay = m_audioGenerator->addModel(model); + + m_mutex.lock(); + + m_models.insert(model); + if (model->getEndFrame() > m_lastModelEndFrame) { + m_lastModelEndFrame = model->getEndFrame(); + } + + bool buffersChanged = false, srChanged = false; + + size_t modelChannels = 1; + DenseTimeValueModel *dtvm = dynamic_cast(model); + if (dtvm) modelChannels = dtvm->getChannelCount(); + if (modelChannels > m_sourceChannelCount) { + m_sourceChannelCount = modelChannels; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Adding model with " << modelChannels << " channels " << std::endl; +#endif + + if (m_sourceSampleRate == 0) { + + m_sourceSampleRate = model->getSampleRate(); + srChanged = true; + + } else if (model->getSampleRate() != m_sourceSampleRate) { + + // If this is a dense time-value model and we have no other, we + // can just switch to this model's sample rate + + if (dtvm) { + + bool conflicting = false; + + for (std::set::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { + // Only wave file models can be considered conflicting -- + // writable wave file models are derived and we shouldn't + // take their rates into account. Also, don't give any + // particular weight to a file that's already playing at + // the wrong rate anyway + WaveFileModel *wfm = dynamic_cast(*i); + if (wfm && wfm != dtvm && + wfm->getSampleRate() != model->getSampleRate() && + wfm->getSampleRate() == m_sourceSampleRate) { + std::cerr << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << *i << " found" << std::endl; + conflicting = true; + break; + } + } + + if (conflicting) { + + std::cerr << "AudioCallbackPlaySource::addModel: ERROR: " + << "New model sample rate does not match" << std::endl + << "existing model(s) (new " << model->getSampleRate() + << " vs " << m_sourceSampleRate + << "), playback will be wrong" + << std::endl; + + emit sampleRateMismatch(model->getSampleRate(), + m_sourceSampleRate, + false); + } else { + m_sourceSampleRate = model->getSampleRate(); + srChanged = true; + } + } + } + + if (!m_writeBuffers || (m_writeBuffers->size() < getTargetChannelCount())) { + clearRingBuffers(true, getTargetChannelCount()); + buffersChanged = true; + } else { + if (canPlay) clearRingBuffers(true); + } + + if (buffersChanged || srChanged) { + if (m_converter) { + src_delete(m_converter); + src_delete(m_crapConverter); + m_converter = 0; + m_crapConverter = 0; + } + } + + m_mutex.unlock(); + + m_audioGenerator->setTargetChannelCount(getTargetChannelCount()); + + if (!m_fillThread) { + m_fillThread = new FillThread(*this); + m_fillThread->start(); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s) -- emitting modelReplaced" << std::endl; +#endif + + if (buffersChanged || srChanged) { + emit modelReplaced(); + } + + m_condition.wakeAll(); +} + +void +AudioCallbackPlaySource::removeModel(Model *model) +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySource::removeModel(" << model << ")" << std::endl; +#endif + + m_models.erase(model); + + if (m_models.empty()) { + if (m_converter) { + src_delete(m_converter); + src_delete(m_crapConverter); + m_converter = 0; + m_crapConverter = 0; + } + m_sourceSampleRate = 0; + } + + size_t lastEnd = 0; + for (std::set::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { +// std::cout << "AudioCallbackPlaySource::removeModel(" << model << "): checking end frame on model " << *i << std::endl; + if ((*i)->getEndFrame() > lastEnd) lastEnd = (*i)->getEndFrame(); +// std::cout << "(done, lastEnd now " << lastEnd << ")" << std::endl; + } + m_lastModelEndFrame = lastEnd; + + m_mutex.unlock(); + + m_audioGenerator->removeModel(model); + + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::clearModels() +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySource::clearModels()" << std::endl; +#endif + + m_models.clear(); + + if (m_converter) { + src_delete(m_converter); + src_delete(m_crapConverter); + m_converter = 0; + m_crapConverter = 0; + } + + m_lastModelEndFrame = 0; + + m_sourceSampleRate = 0; + + m_mutex.unlock(); + + m_audioGenerator->clearModels(); +} + +void +AudioCallbackPlaySource::clearRingBuffers(bool haveLock, size_t count) +{ + if (!haveLock) m_mutex.lock(); + + if (count == 0) { + if (m_writeBuffers) count = m_writeBuffers->size(); + } + + size_t sf = m_readBufferFill; + RingBuffer *rb = getReadRingBuffer(0); + if (rb) { + //!!! This is incorrect if we're in a non-contiguous selection + //Same goes for all related code (subtracting the read space + //from the fill frame to try to establish where the effective + //pre-resample/timestretch read pointer is) + size_t rs = rb->getReadSpace(); + if (rs < sf) sf -= rs; + else sf = 0; + } + m_writeBufferFill = sf; + + if (m_readBuffers != m_writeBuffers) { + delete m_writeBuffers; + } + + m_writeBuffers = new RingBufferVector; + + for (size_t i = 0; i < count; ++i) { + m_writeBuffers->push_back(new RingBuffer(m_ringBufferSize)); + } + +// std::cout << "AudioCallbackPlaySource::clearRingBuffers: Created " +// << count << " write buffers" << std::endl; + + if (!haveLock) { + m_mutex.unlock(); + } +} + +void +AudioCallbackPlaySource::play(size_t startFrame) +{ + if (m_viewManager->getPlaySelectionMode() && + !m_viewManager->getSelections().empty()) { + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + MultiSelection::SelectionList::iterator i = selections.begin(); + if (i != selections.end()) { + if (startFrame < i->getStartFrame()) { + startFrame = i->getStartFrame(); + } else { + MultiSelection::SelectionList::iterator j = selections.end(); + --j; + if (startFrame >= j->getEndFrame()) { + startFrame = i->getStartFrame(); + } + } + } + } else { + if (startFrame >= m_lastModelEndFrame) { + startFrame = 0; + } + } + + // The fill thread will automatically empty its buffers before + // starting again if we have not so far been playing, but not if + // we're just re-seeking. + + m_mutex.lock(); + if (m_playing) { + m_readBufferFill = m_writeBufferFill = startFrame; + if (m_readBuffers) { + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *rb = getReadRingBuffer(c); + if (rb) rb->reset(); + } + } + if (m_converter) src_reset(m_converter); + if (m_crapConverter) src_reset(m_crapConverter); + } else { + if (m_converter) src_reset(m_converter); + if (m_crapConverter) src_reset(m_crapConverter); + m_readBufferFill = m_writeBufferFill = startFrame; + } + m_mutex.unlock(); + + m_audioGenerator->reset(); + + bool changed = !m_playing; + m_playing = true; + m_condition.wakeAll(); + if (changed) emit playStatusChanged(m_playing); +} + +void +AudioCallbackPlaySource::stop() +{ + bool changed = m_playing; + m_playing = false; + m_condition.wakeAll(); + if (changed) emit playStatusChanged(m_playing); +} + +void +AudioCallbackPlaySource::selectionChanged() +{ + if (m_viewManager->getPlaySelectionMode()) { + clearRingBuffers(); + } +} + +void +AudioCallbackPlaySource::playLoopModeChanged() +{ + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::playSelectionModeChanged() +{ + if (!m_viewManager->getSelections().empty()) { + clearRingBuffers(); + } +} + +void +AudioCallbackPlaySource::playParametersChanged(PlayParameters *) +{ + clearRingBuffers(); +} + +void +AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n) +{ + if (n == "Resample Quality") { + setResampleQuality(Preferences::getInstance()->getResampleQuality()); + } +} + +void +AudioCallbackPlaySource::audioProcessingOverload() +{ + RealTimePluginInstance *ap = m_auditioningPlugin; + if (ap && m_playing && !m_auditioningPluginBypassed) { + m_auditioningPluginBypassed = true; + emit audioOverloadPluginDisabled(); + } +} + +void +AudioCallbackPlaySource::setTargetBlockSize(size_t size) +{ +// std::cout << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl; + assert(size < m_ringBufferSize); + m_blockSize = size; +} + +size_t +AudioCallbackPlaySource::getTargetBlockSize() const +{ +// std::cout << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << std::endl; + return m_blockSize; +} + +void +AudioCallbackPlaySource::setTargetPlayLatency(size_t latency) +{ + m_playLatency = latency; +} + +size_t +AudioCallbackPlaySource::getTargetPlayLatency() const +{ + return m_playLatency; +} + +size_t +AudioCallbackPlaySource::getCurrentPlayingFrame() +{ + bool resample = false; + double ratio = 1.0; + + if (getSourceSampleRate() != getTargetSampleRate()) { + resample = true; + ratio = double(getSourceSampleRate()) / double(getTargetSampleRate()); + } + + size_t readSpace = 0; + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + size_t spaceHere = rb->getReadSpace(); + if (c == 0 || spaceHere < readSpace) readSpace = spaceHere; + } + } + + if (resample) { + readSpace = size_t(readSpace * ratio + 0.1); + } + + size_t latency = m_playLatency; + if (resample) latency = size_t(m_playLatency * ratio + 0.1); + + PhaseVocoderTimeStretcher *timeStretcher = m_timeStretcher; + if (timeStretcher) { + latency += timeStretcher->getProcessingLatency(); + } + + latency += readSpace; + size_t bufferedFrame = m_readBufferFill; + + bool looping = m_viewManager->getPlayLoopMode(); + bool constrained = (m_viewManager->getPlaySelectionMode() && + !m_viewManager->getSelections().empty()); + + size_t framePlaying = bufferedFrame; + + if (looping && !constrained) { + while (framePlaying < latency) framePlaying += m_lastModelEndFrame; + } + + if (framePlaying > latency) framePlaying -= latency; + else framePlaying = 0; + + if (!constrained) { + if (!looping && framePlaying > m_lastModelEndFrame) { + framePlaying = m_lastModelEndFrame; + stop(); + } + return framePlaying; + } + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + MultiSelection::SelectionList::const_iterator i; + +// i = selections.begin(); +// size_t rangeStart = i->getStartFrame(); + + i = selections.end(); + --i; + size_t rangeEnd = i->getEndFrame(); + + for (i = selections.begin(); i != selections.end(); ++i) { + if (i->contains(bufferedFrame)) break; + } + + size_t f = bufferedFrame; + +// std::cout << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << ", rangeEnd=" << rangeEnd << std::endl; + + if (i == selections.end()) { + --i; + if (i->getEndFrame() + latency < f) { +// std::cout << "framePlaying = " << framePlaying << ", rangeEnd = " << rangeEnd << std::endl; + + if (!looping && (framePlaying > rangeEnd)) { +// std::cout << "STOPPING" << std::endl; + stop(); + return rangeEnd; + } else { + return framePlaying; + } + } else { +// std::cout << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl; + latency -= (f - i->getEndFrame()); + f = i->getEndFrame(); + } + } + +// std::cout << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl; + + while (latency > 0) { + size_t offset = f - i->getStartFrame(); + if (offset >= latency) { + if (f > latency) { + framePlaying = f - latency; + } else { + framePlaying = 0; + } + break; + } else { + if (i == selections.begin()) { + if (looping) { + i = selections.end(); + } + } + latency -= offset; + --i; + f = i->getEndFrame(); + } + } + + return framePlaying; +} + +void +AudioCallbackPlaySource::setOutputLevels(float left, float right) +{ + m_outputLeft = left; + m_outputRight = right; +} + +bool +AudioCallbackPlaySource::getOutputLevels(float &left, float &right) +{ + left = m_outputLeft; + right = m_outputRight; + return true; +} + +void +AudioCallbackPlaySource::setTargetSampleRate(size_t sr) +{ + m_targetSampleRate = sr; + initialiseConverter(); +} + +void +AudioCallbackPlaySource::initialiseConverter() +{ + m_mutex.lock(); + + if (m_converter) { + src_delete(m_converter); + src_delete(m_crapConverter); + m_converter = 0; + m_crapConverter = 0; + } + + if (getSourceSampleRate() != getTargetSampleRate()) { + + int err = 0; + + m_converter = src_new(m_resampleQuality == 2 ? SRC_SINC_BEST_QUALITY : + m_resampleQuality == 1 ? SRC_SINC_MEDIUM_QUALITY : + m_resampleQuality == 0 ? SRC_SINC_FASTEST : + SRC_SINC_MEDIUM_QUALITY, + getTargetChannelCount(), &err); + + if (m_converter) { + m_crapConverter = src_new(SRC_LINEAR, + getTargetChannelCount(), + &err); + } + + if (!m_converter || !m_crapConverter) { + std::cerr + << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " + << src_strerror(err) << std::endl; + + if (m_converter) { + src_delete(m_converter); + m_converter = 0; + } + + if (m_crapConverter) { + src_delete(m_crapConverter); + m_crapConverter = 0; + } + + m_mutex.unlock(); + + emit sampleRateMismatch(getSourceSampleRate(), + getTargetSampleRate(), + false); + } else { + + m_mutex.unlock(); + + emit sampleRateMismatch(getSourceSampleRate(), + getTargetSampleRate(), + true); + } + } else { + m_mutex.unlock(); + } +} + +void +AudioCallbackPlaySource::setResampleQuality(int q) +{ + if (q == m_resampleQuality) return; + m_resampleQuality = q; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cerr << "AudioCallbackPlaySource::setResampleQuality: setting to " + << m_resampleQuality << std::endl; +#endif + + initialiseConverter(); +} + +void +AudioCallbackPlaySource::setAuditioningPlugin(RealTimePluginInstance *plugin) +{ + RealTimePluginInstance *formerPlugin = m_auditioningPlugin; + m_auditioningPlugin = plugin; + m_auditioningPluginBypassed = false; + if (formerPlugin) m_pluginScavenger.claim(formerPlugin); +} + +size_t +AudioCallbackPlaySource::getTargetSampleRate() const +{ + if (m_targetSampleRate) return m_targetSampleRate; + else return getSourceSampleRate(); +} + +size_t +AudioCallbackPlaySource::getSourceChannelCount() const +{ + return m_sourceChannelCount; +} + +size_t +AudioCallbackPlaySource::getTargetChannelCount() const +{ + if (m_sourceChannelCount < 2) return 2; + return m_sourceChannelCount; +} + +size_t +AudioCallbackPlaySource::getSourceSampleRate() const +{ + return m_sourceSampleRate; +} + +void +AudioCallbackPlaySource::setTimeStretch(float factor, bool sharpen, bool mono) +{ + // Avoid locks -- create, assign, mark old one for scavenging + // later (as a call to getSourceSamples may still be using it) + + PhaseVocoderTimeStretcher *existingStretcher = m_timeStretcher; + + size_t channels = getTargetChannelCount(); + if (mono) channels = 1; + + if (existingStretcher && + existingStretcher->getRatio() == factor && + existingStretcher->getSharpening() == sharpen && + existingStretcher->getChannelCount() == channels) { + return; + } + + if (factor != 1) { + + if (existingStretcher && + existingStretcher->getSharpening() == sharpen && + existingStretcher->getChannelCount() == channels) { + existingStretcher->setRatio(factor); + return; + } + + PhaseVocoderTimeStretcher *newStretcher = new PhaseVocoderTimeStretcher + (getTargetSampleRate(), + channels, + factor, + sharpen, + getTargetBlockSize()); + + m_timeStretcher = newStretcher; + + } else { + m_timeStretcher = 0; + } + + if (existingStretcher) { + m_timeStretcherScavenger.claim(existingStretcher); + } +} + +size_t +AudioCallbackPlaySource::getSourceSamples(size_t count, float **buffer) +{ + if (!m_playing) { + for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { + for (size_t i = 0; i < count; ++i) { + buffer[ch][i] = 0.0; + } + } + return 0; + } + + // Ensure that all buffers have at least the amount of data we + // need -- else reduce the size of our requests correspondingly + + for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { + + RingBuffer *rb = getReadRingBuffer(ch); + + if (!rb) { + std::cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " + << "No ring buffer available for channel " << ch + << ", returning no data here" << std::endl; + count = 0; + break; + } + + size_t rs = rb->getReadSpace(); + if (rs < count) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " + << "Ring buffer for channel " << ch << " has only " + << rs << " (of " << count << ") samples available, " + << "reducing request size" << std::endl; +#endif + count = rs; + } + } + + if (count == 0) return 0; + + PhaseVocoderTimeStretcher *ts = m_timeStretcher; + + if (!ts || ts->getRatio() == 1) { + + size_t got = 0; + + for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { + + RingBuffer *rb = getReadRingBuffer(ch); + + if (rb) { + + // this is marginally more likely to leave our channels in + // sync after a processing failure than just passing "count": + size_t request = count; + if (ch > 0) request = got; + + got = rb->read(buffer[ch], request); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " (of " << count << ") samples on channel " << ch << ", signalling for more (possibly)" << std::endl; +#endif + } + + for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { + for (size_t i = got; i < count; ++i) { + buffer[ch][i] = 0.0; + } + } + } + + applyAuditioningEffect(count, buffer); + + m_condition.wakeAll(); + return got; + } + + float ratio = ts->getRatio(); + +// std::cout << "ratio = " << ratio << std::endl; + + size_t channels = getTargetChannelCount(); + bool mix = (channels > 1 && ts->getChannelCount() == 1); + + size_t available; + + int warned = 0; + + // We want output blocks of e.g. 1024 (probably fixed, certainly + // bounded). We can provide input blocks of any size (unbounded) + // at the timestretcher's request. The input block for a given + // output is approx output / ratio, but we can't predict it + // exactly, for an adaptive timestretcher. The stretcher will + // need some additional buffer space. See the time stretcher code + // and comments. + + while ((available = ts->getAvailableOutputSamples()) < count) { + + size_t reqd = lrintf((count - available) / ratio); + reqd = max(reqd, ts->getRequiredInputSamples()); + if (reqd == 0) reqd = 1; + + //float *ib[channels]; + float **ib = (float**) malloc(channels*sizeof(float*)); + + size_t got = reqd; + + if (mix) { + for (size_t c = 0; c < channels; ++c) { + if (c == 0) ib[c] = new float[reqd]; //!!! fix -- this is a rt function + else ib[c] = 0; + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + size_t gotHere; + if (c > 0) gotHere = rb->readAdding(ib[0], got); + else gotHere = rb->read(ib[0], got); + if (gotHere < got) got = gotHere; + } + } + } else { + for (size_t c = 0; c < channels; ++c) { + ib[c] = new float[reqd]; //!!! fix -- this is a rt function + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + size_t gotHere = rb->read(ib[c], got); + if (gotHere < got) got = gotHere; + } + } + } + + if (got < reqd) { + std::cerr << "WARNING: Read underrun in playback (" + << got << " < " << reqd << ")" << std::endl; + } + + ts->putInput(ib, got); + + for (size_t c = 0; c < channels; ++c) { + delete[] ib[c]; + } + + if (got == 0) break; + + if (ts->getAvailableOutputSamples() == available) { + std::cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << std::endl; + if (++warned == 5) break; + } + } + + ts->getOutput(buffer, count); + + if (mix) { + for (size_t c = 1; c < channels; ++c) { + for (size_t i = 0; i < count; ++i) { + buffer[c][i] = buffer[0][i] / channels; + } + } + for (size_t i = 0; i < count; ++i) { + buffer[0][i] /= channels; + } + } + + applyAuditioningEffect(count, buffer); + + m_condition.wakeAll(); + + return count; +} + +void +AudioCallbackPlaySource::applyAuditioningEffect(size_t count, float **buffers) +{ + if (m_auditioningPluginBypassed) return; + RealTimePluginInstance *plugin = m_auditioningPlugin; + if (!plugin) return; + + if (plugin->getAudioInputCount() != getTargetChannelCount()) { +// std::cerr << "plugin input count " << plugin->getAudioInputCount() +// << " != our channel count " << getTargetChannelCount() +// << std::endl; + return; + } + if (plugin->getAudioOutputCount() != getTargetChannelCount()) { +// std::cerr << "plugin output count " << plugin->getAudioOutputCount() +// << " != our channel count " << getTargetChannelCount() +// << std::endl; + return; + } + if (plugin->getBufferSize() != count) { +// std::cerr << "plugin buffer size " << plugin->getBufferSize() +// << " != our block size " << count +// << std::endl; + return; + } + + float **ib = plugin->getAudioInputBuffers(); + float **ob = plugin->getAudioOutputBuffers(); + + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + for (size_t i = 0; i < count; ++i) { + ib[c][i] = buffers[c][i]; + } + } + + plugin->run(Vamp::RealTime::zeroTime); + + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + for (size_t i = 0; i < count; ++i) { + buffers[c][i] = ob[c][i]; + } + } +} + +// Called from fill thread, m_playing true, mutex held +bool +AudioCallbackPlaySource::fillBuffers() +{ + static float *tmp = 0; + static size_t tmpSize = 0; + + size_t space = 0; + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + size_t spaceHere = wb->getWriteSpace(); + if (c == 0 || spaceHere < space) space = spaceHere; + } + } + + if (space == 0) return false; + + size_t f = m_writeBufferFill; + + bool readWriteEqual = (m_readBuffers == m_writeBuffers); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl; +#endif + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "buffered to " << f << " already" << std::endl; +#endif + + bool resample = (getSourceSampleRate() != getTargetSampleRate()); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << std::endl; +#endif + + size_t channels = getTargetChannelCount(); + + size_t orig = space; + size_t got = 0; + + static float **bufferPtrs = 0; + static size_t bufferPtrCount = 0; + + if (bufferPtrCount < channels) { + if (bufferPtrs) delete[] bufferPtrs; + bufferPtrs = new float *[channels]; + bufferPtrCount = channels; + } + + size_t generatorBlockSize = m_audioGenerator->getBlockSize(); + + if (resample && !m_converter) { + static bool warned = false; + if (!warned) { + std::cerr << "WARNING: sample rates differ, but no converter available!" << std::endl; + warned = true; + } + } + + if (resample && m_converter) { + + double ratio = + double(getTargetSampleRate()) / double(getSourceSampleRate()); + orig = size_t(orig / ratio + 0.1); + + // orig must be a multiple of generatorBlockSize + orig = (orig / generatorBlockSize) * generatorBlockSize; + if (orig == 0) return false; + + size_t work = max(orig, space); + + // We only allocate one buffer, but we use it in two halves. + // We place the non-interleaved values in the second half of + // the buffer (orig samples for channel 0, orig samples for + // channel 1 etc), and then interleave them into the first + // half of the buffer. Then we resample back into the second + // half (interleaved) and de-interleave the results back to + // the start of the buffer for insertion into the ringbuffers. + // What a faff -- especially as we've already de-interleaved + // the audio data from the source file elsewhere before we + // even reach this point. + + if (tmpSize < channels * work * 2) { + delete[] tmp; + tmp = new float[channels * work * 2]; + tmpSize = channels * work * 2; + } + + float *nonintlv = tmp + channels * work; + float *intlv = tmp; + float *srcout = tmp + channels * work; + + for (size_t c = 0; c < channels; ++c) { + for (size_t i = 0; i < orig; ++i) { + nonintlv[channels * i + c] = 0.0f; + } + } + + for (size_t c = 0; c < channels; ++c) { + bufferPtrs[c] = nonintlv + c * orig; + } + + got = mixModels(f, orig, bufferPtrs); + + // and interleave into first half + for (size_t c = 0; c < channels; ++c) { + for (size_t i = 0; i < got; ++i) { + float sample = nonintlv[c * got + i]; + intlv[channels * i + c] = sample; + } + } + + SRC_DATA data; + data.data_in = intlv; + data.data_out = srcout; + data.input_frames = got; + data.output_frames = work; + data.src_ratio = ratio; + data.end_of_input = 0; + + int err = 0; + + if (m_timeStretcher && m_timeStretcher->getRatio() < 0.4) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Using crappy converter" << std::endl; +#endif + src_process(m_crapConverter, &data); + } else { + src_process(m_converter, &data); + } + + size_t toCopy = size_t(got * ratio + 0.1); + + if (err) { + std::cerr + << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " + << src_strerror(err) << std::endl; + //!!! Then what? + } else { + got = data.input_frames_used; + toCopy = data.output_frames_gen; +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Resampled " << got << " frames to " << toCopy << " frames" << std::endl; +#endif + } + + for (size_t c = 0; c < channels; ++c) { + for (size_t i = 0; i < toCopy; ++i) { + tmp[i] = srcout[channels * i + c]; + } + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) wb->write(tmp, toCopy); + } + + m_writeBufferFill = f; + if (readWriteEqual) m_readBufferFill = f; + + } else { + + // space must be a multiple of generatorBlockSize + space = (space / generatorBlockSize) * generatorBlockSize; + if (space == 0) return false; + + if (tmpSize < channels * space) { + delete[] tmp; + tmp = new float[channels * space]; + tmpSize = channels * space; + } + + for (size_t c = 0; c < channels; ++c) { + + bufferPtrs[c] = tmp + c * space; + + for (size_t i = 0; i < space; ++i) { + tmp[c * space + i] = 0.0f; + } + } + + size_t got = mixModels(f, space, bufferPtrs); + + for (size_t c = 0; c < channels; ++c) { + + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + size_t actual = wb->write(bufferPtrs[c], got); +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Wrote " << actual << " samples for ch " << c << ", now " + << wb->getReadSpace() << " to read" + << std::endl; +#endif + if (actual < got) { + std::cerr << "WARNING: Buffer overrun in channel " << c + << ": wrote " << actual << " of " << got + << " samples" << std::endl; + } + } + } + + m_writeBufferFill = f; + if (readWriteEqual) m_readBufferFill = f; + + //!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples + } + + return true; +} + +size_t +AudioCallbackPlaySource::mixModels(size_t &frame, size_t count, float **buffers) +{ + size_t processed = 0; + size_t chunkStart = frame; + size_t chunkSize = count; + size_t selectionSize = 0; + size_t nextChunkStart = chunkStart + chunkSize; + + bool looping = m_viewManager->getPlayLoopMode(); + bool constrained = (m_viewManager->getPlaySelectionMode() && + !m_viewManager->getSelections().empty()); + + static float **chunkBufferPtrs = 0; + static size_t chunkBufferPtrCount = 0; + size_t channels = getTargetChannelCount(); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << std::endl; +#endif + + if (chunkBufferPtrCount < channels) { + if (chunkBufferPtrs) delete[] chunkBufferPtrs; + chunkBufferPtrs = new float *[channels]; + chunkBufferPtrCount = channels; + } + + for (size_t c = 0; c < channels; ++c) { + chunkBufferPtrs[c] = buffers[c]; + } + + while (processed < count) { + + chunkSize = count - processed; + nextChunkStart = chunkStart + chunkSize; + selectionSize = 0; + + size_t fadeIn = 0, fadeOut = 0; + + if (constrained) { + + Selection selection = + m_viewManager->getContainingSelection(chunkStart, true); + + if (selection.isEmpty()) { + if (looping) { + selection = *m_viewManager->getSelections().begin(); + chunkStart = selection.getStartFrame(); + fadeIn = 50; + } + } + + if (selection.isEmpty()) { + + chunkSize = 0; + nextChunkStart = chunkStart; + + } else { + + selectionSize = + selection.getEndFrame() - + selection.getStartFrame(); + + if (chunkStart < selection.getStartFrame()) { + chunkStart = selection.getStartFrame(); + fadeIn = 50; + } + + nextChunkStart = chunkStart + chunkSize; + + if (nextChunkStart >= selection.getEndFrame()) { + nextChunkStart = selection.getEndFrame(); + fadeOut = 50; + } + + chunkSize = nextChunkStart - chunkStart; + } + + } else if (looping && m_lastModelEndFrame > 0) { + + if (chunkStart >= m_lastModelEndFrame) { + chunkStart = 0; + } + if (chunkSize > m_lastModelEndFrame - chunkStart) { + chunkSize = m_lastModelEndFrame - chunkStart; + } + nextChunkStart = chunkStart + chunkSize; + } + +// std::cout << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << std::endl; + + if (!chunkSize) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Ending selection playback at " << nextChunkStart << std::endl; +#endif + // We need to maintain full buffers so that the other + // thread can tell where it's got to in the playback -- so + // return the full amount here + frame = frame + count; + return count; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << std::endl; +#endif + + size_t got = 0; + + if (selectionSize < 100) { + fadeIn = 0; + fadeOut = 0; + } else if (selectionSize < 300) { + if (fadeIn > 0) fadeIn = 10; + if (fadeOut > 0) fadeOut = 10; + } + + if (fadeIn > 0) { + if (processed * 2 < fadeIn) { + fadeIn = processed * 2; + } + } + + if (fadeOut > 0) { + if ((count - processed - chunkSize) * 2 < fadeOut) { + fadeOut = (count - processed - chunkSize) * 2; + } + } + + for (std::set::iterator mi = m_models.begin(); + mi != m_models.end(); ++mi) { + + got = m_audioGenerator->mixModel(*mi, chunkStart, + chunkSize, chunkBufferPtrs, + fadeIn, fadeOut); + } + + for (size_t c = 0; c < channels; ++c) { + chunkBufferPtrs[c] += chunkSize; + } + + processed += chunkSize; + chunkStart = nextChunkStart; + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "Returning selection playback " << processed << " frames to " << nextChunkStart << std::endl; +#endif + + frame = nextChunkStart; + return processed; +} + +void +AudioCallbackPlaySource::unifyRingBuffers() +{ + if (m_readBuffers == m_writeBuffers) return; + + // only unify if there will be something to read + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + if (wb->getReadSpace() < m_blockSize * 2) { + if ((m_writeBufferFill + m_blockSize * 2) < + m_lastModelEndFrame) { + // OK, we don't have enough and there's more to + // read -- don't unify until we can do better + return; + } + } + break; + } + } + + size_t rf = m_readBufferFill; + RingBuffer *rb = getReadRingBuffer(0); + if (rb) { + size_t rs = rb->getReadSpace(); + //!!! incorrect when in non-contiguous selection, see comments elsewhere +// std::cout << "rs = " << rs << std::endl; + if (rs < rf) rf -= rs; + else rf = 0; + } + + //std::cout << "m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << std::endl; + + size_t wf = m_writeBufferFill; + size_t skip = 0; + for (size_t c = 0; c < getTargetChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + if (c == 0) { + + size_t wrs = wb->getReadSpace(); +// std::cout << "wrs = " << wrs << std::endl; + + if (wrs < wf) wf -= wrs; + else wf = 0; +// std::cout << "wf = " << wf << std::endl; + + if (wf < rf) skip = rf - wf; + if (skip == 0) break; + } + +// std::cout << "skipping " << skip << std::endl; + wb->skip(skip); + } + } + + m_bufferScavenger.claim(m_readBuffers); + m_readBuffers = m_writeBuffers; + m_readBufferFill = m_writeBufferFill; +// std::cout << "unified" << std::endl; +} + +void +AudioCallbackPlaySource::FillThread::run() +{ + AudioCallbackPlaySource &s(m_source); + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySourceFillThread starting" << std::endl; +#endif + + s.m_mutex.lock(); + + bool previouslyPlaying = s.m_playing; + bool work = false; + + while (!s.m_exiting) { + + s.unifyRingBuffers(); + s.m_bufferScavenger.scavenge(); + s.m_pluginScavenger.scavenge(); + s.m_timeStretcherScavenger.scavenge(); + + if (work && s.m_playing && s.getSourceSampleRate()) { + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySourceFillThread: not waiting" << std::endl; +#endif + + s.m_mutex.unlock(); + s.m_mutex.lock(); + + } else { + + float ms = 100; + if (s.getSourceSampleRate() > 0) { + ms = float(m_ringBufferSize) / float(s.getSourceSampleRate()) * 1000.0; + } + + if (s.m_playing) ms /= 10; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + if (!s.m_playing) std::cout << std::endl; + std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << std::endl; +#endif + + s.m_condition.wait(&s.m_mutex, size_t(ms)); + } + +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySourceFillThread: awoken" << std::endl; +#endif + + work = false; + + if (!s.getSourceSampleRate()) continue; + + bool playing = s.m_playing; + + if (playing && !previouslyPlaying) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl; +#endif + for (size_t c = 0; c < s.getTargetChannelCount(); ++c) { + RingBuffer *rb = s.getReadRingBuffer(c); + if (rb) rb->reset(); + } + } + previouslyPlaying = playing; + + work = s.fillBuffers(); + } + + s.m_mutex.unlock(); +} + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCallbackPlaySource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCallbackPlaySource.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,332 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_CALLBACK_PLAY_SOURCE_H_ +#define _AUDIO_CALLBACK_PLAY_SOURCE_H_ + +#include "base/RingBuffer.h" +#include "base/AudioPlaySource.h" +#include "base/PropertyContainer.h" +#include "base/Scavenger.h" + +#include +#include +#include + +#include "base/Thread.h" + +#include + +#include +#include + +class Model; +class ViewManager; +class AudioGenerator; +class PlayParameters; +class PhaseVocoderTimeStretcher; +class RealTimePluginInstance; + +/** + * AudioCallbackPlaySource manages audio data supply to callback-based + * audio APIs such as JACK or CoreAudio. It maintains one ring buffer + * per channel, filled during playback by a non-realtime thread, and + * provides a method for a realtime thread to pick up the latest + * available sample data from these buffers. + */ +class AudioCallbackPlaySource : public virtual QObject, + public AudioPlaySource +{ + Q_OBJECT + +public: + AudioCallbackPlaySource(ViewManager *); + virtual ~AudioCallbackPlaySource(); + + /** + * Add a data model to be played from. The source can mix + * playback from a number of sources including dense and sparse + * models. The models must match in sample rate, but they don't + * have to have identical numbers of channels. + */ + virtual void addModel(Model *model); + + /** + * Remove a model. + */ + virtual void removeModel(Model *model); + + /** + * Remove all models. (Silence will ensue.) + */ + virtual void clearModels(); + + /** + * Start making data available in the ring buffers for playback, + * from the given frame. If playback is already under way, reseek + * to the given frame and continue. + */ + virtual void play(size_t startFrame); + + /** + * Stop playback and ensure that no more data is returned. + */ + virtual void stop(); + + /** + * Return whether playback is currently supposed to be happening. + */ + virtual bool isPlaying() const { return m_playing; } + + /** + * Return the frame number that is currently expected to be coming + * out of the speakers. (i.e. compensating for playback latency.) + */ + virtual size_t getCurrentPlayingFrame(); + + /** + * Return the frame at which playback is expected to end (if not looping). + */ + virtual size_t getPlayEndFrame() { return m_lastModelEndFrame; } + + /** + * Set the block size of the target audio device. This should + * be called by the target class. + */ + void setTargetBlockSize(size_t); + + /** + * Get the block size of the target audio device. + */ + size_t getTargetBlockSize() const; + + /** + * Set the playback latency of the target audio device, in frames + * at the target sample rate. This is the difference between the + * frame currently "leaving the speakers" and the last frame (or + * highest last frame across all channels) requested via + * getSamples(). The default is zero. + */ + void setTargetPlayLatency(size_t); + + /** + * Get the playback latency of the target audio device. + */ + size_t getTargetPlayLatency() const; + + /** + * Specify that the target audio device has a fixed sample rate + * (i.e. cannot accommodate arbitrary sample rates based on the + * source). If the target sets this to something other than the + * source sample rate, this class will resample automatically to + * fit. + */ + void setTargetSampleRate(size_t); + + /** + * Return the sample rate set by the target audio device (or the + * source sample rate if the target hasn't set one). + */ + virtual size_t getTargetSampleRate() const; + + /** + * Set the current output levels for metering (for call from the + * target) + */ + void setOutputLevels(float left, float right); + + /** + * Return the current (or thereabouts) output levels in the range + * 0.0 -> 1.0, for metering purposes. + */ + virtual bool getOutputLevels(float &left, float &right); + + /** + * Get the number of channels of audio that in the source models. + * This may safely be called from a realtime thread. Returns 0 if + * there is no source yet available. + */ + size_t getSourceChannelCount() const; + + /** + * Get the number of channels of audio that will be provided + * to the play target. This may be more than the source channel + * count: for example, a mono source will provide 2 channels + * after pan. + * This may safely be called from a realtime thread. Returns 0 if + * there is no source yet available. + */ + size_t getTargetChannelCount() const; + + /** + * Get the actual sample rate of the source material. This may + * safely be called from a realtime thread. Returns 0 if there is + * no source yet available. + */ + virtual size_t getSourceSampleRate() const; + + /** + * Get "count" samples (at the target sample rate) of the mixed + * audio data, in all channels. This may safely be called from a + * realtime thread. + */ + size_t getSourceSamples(size_t count, float **buffer); + + /** + * Set the time stretcher factor (i.e. playback speed). Also + * specify whether the time stretcher will be variable rate + * (sharpening transients), and whether time stretching will be + * carried out on data mixed down to mono for speed. + */ + void setTimeStretch(float factor, bool sharpen, bool mono); + + /** + * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is + * highest quality. + */ + void setResampleQuality(int q); + + /** + * Set a single real-time plugin as a processing effect for + * auditioning during playback. + * + * The plugin must have been initialised with + * getTargetChannelCount() channels and a getTargetBlockSize() + * sample frame processing block size. + * + * This playback source takes ownership of the plugin, which will + * be deleted at some point after the following call to + * setAuditioningPlugin (depending on real-time constraints). + * + * Pass a null pointer to remove the current auditioning plugin, + * if any. + */ + void setAuditioningPlugin(RealTimePluginInstance *plugin); + +signals: + void modelReplaced(); + + void playStatusChanged(bool isPlaying); + + void sampleRateMismatch(size_t requested, size_t available, bool willResample); + + void audioOverloadPluginDisabled(); + +public slots: + void audioProcessingOverload(); + +protected slots: + void selectionChanged(); + void playLoopModeChanged(); + void playSelectionModeChanged(); + void playParametersChanged(PlayParameters *); + void preferenceChanged(PropertyContainer::PropertyName); + +protected: + ViewManager *m_viewManager; + AudioGenerator *m_audioGenerator; + + class RingBufferVector : public std::vector *> { + public: + virtual ~RingBufferVector() { + while (!empty()) { + delete *begin(); + erase(begin()); + } + } + }; + + std::set m_models; + RingBufferVector *m_readBuffers; + RingBufferVector *m_writeBuffers; + size_t m_readBufferFill; + size_t m_writeBufferFill; + Scavenger m_bufferScavenger; + size_t m_sourceChannelCount; + size_t m_blockSize; + size_t m_sourceSampleRate; + size_t m_targetSampleRate; + size_t m_playLatency; + bool m_playing; + bool m_exiting; + size_t m_lastModelEndFrame; + static const size_t m_ringBufferSize; + float m_outputLeft; + float m_outputRight; + RealTimePluginInstance *m_auditioningPlugin; + bool m_auditioningPluginBypassed; + Scavenger m_pluginScavenger; + + RingBuffer *getWriteRingBuffer(size_t c) { + if (m_writeBuffers && c < m_writeBuffers->size()) { + return (*m_writeBuffers)[c]; + } else { + return 0; + } + } + + RingBuffer *getReadRingBuffer(size_t c) { + RingBufferVector *rb = m_readBuffers; + if (rb && c < rb->size()) { + return (*rb)[c]; + } else { + return 0; + } + } + + void clearRingBuffers(bool haveLock = false, size_t count = 0); + void unifyRingBuffers(); + + PhaseVocoderTimeStretcher *m_timeStretcher; + Scavenger m_timeStretcherScavenger; + + // Called from fill thread, m_playing true, mutex held + // Return true if work done + bool fillBuffers(); + + // Called from fillBuffers. Return the number of frames written, + // which will be count or fewer. Return in the frame argument the + // new buffered frame position (which may be earlier than the + // frame argument passed in, in the case of looping). + size_t mixModels(size_t &frame, size_t count, float **buffers); + + // Called from getSourceSamples. + void applyAuditioningEffect(size_t count, float **buffers); + + class FillThread : public Thread + { + public: + FillThread(AudioCallbackPlaySource &source) : + Thread(Thread::NonRTThread), + m_source(source) { } + + virtual void run(); + + protected: + AudioCallbackPlaySource &m_source; + }; + + QMutex m_mutex; + QWaitCondition m_condition; + FillThread *m_fillThread; + SRC_STATE *m_converter; + SRC_STATE *m_crapConverter; // for use when playing very fast + int m_resampleQuality; + void initialiseConverter(); +}; + +#endif + + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCallbackPlayTarget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCallbackPlayTarget.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,40 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioCallbackPlayTarget.h" +#include "AudioCallbackPlaySource.h" + +#include + +AudioCallbackPlayTarget::AudioCallbackPlayTarget(AudioCallbackPlaySource *source) : + m_source(source), + m_outputGain(1.0) +{ + if (m_source) { + connect(m_source, SIGNAL(modelReplaced()), + this, SLOT(sourceModelReplaced())); + } +} + +AudioCallbackPlayTarget::~AudioCallbackPlayTarget() +{ +} + +void +AudioCallbackPlayTarget::setOutputGain(float gain) +{ + m_outputGain = gain; +} + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCallbackPlayTarget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCallbackPlayTarget.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_CALLBACK_PLAY_TARGET_H_ +#define _AUDIO_CALLBACK_PLAY_TARGET_H_ + +#include + +class AudioCallbackPlaySource; + +class AudioCallbackPlayTarget : public QObject +{ + Q_OBJECT + +public: + AudioCallbackPlayTarget(AudioCallbackPlaySource *source); + virtual ~AudioCallbackPlayTarget(); + + virtual bool isOK() const = 0; + + float getOutputGain() const { + return m_outputGain; + } + +public slots: + /** + * Set the playback gain (0.0 = silence, 1.0 = levels unmodified) + */ + virtual void setOutputGain(float gain); + + /** + * The main source model (providing the playback sample rate) has + * been changed. The target should query the source's sample + * rate, set its output sample rate accordingly, and call back on + * the source's setTargetSampleRate to indicate what sample rate + * it succeeded in setting at the output. If this differs from + * the model rate, the source will resample. + */ + virtual void sourceModelReplaced() = 0; + +protected: + AudioCallbackPlaySource *m_source; + float m_outputGain; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCoreAudioTarget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCoreAudioTarget.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,22 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_COREAUDIO + +#include "AudioCoreAudioTarget.h" + + + +#endif diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioCoreAudioTarget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioCoreAudioTarget.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,64 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_CORE_AUDIO_TARGET_H_ +#define _AUDIO_CORE_AUDIO_TARGET_H_ + +#ifdef HAVE_COREAUDIO + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "AudioCallbackPlayTarget.h" + +class AudioCallbackPlaySource; + +class AudioCoreAudioTarget : public AudioCallbackPlayTarget +{ + Q_OBJECT + +public: + AudioCoreAudioTarget(AudioCallbackPlaySource *source); + ~AudioCoreAudioTarget(); + + virtual bool isOK() const; + +public slots: + virtual void sourceModelReplaced(); + +protected: + OSStatus process(void *data, + AudioUnitRenderActionFlags *flags, + const AudioTimeStamp *timestamp, + unsigned int inbus, + unsigned int inframes, + AudioBufferList *ioData); + + int m_bufferSize; + int m_sampleRate; + int m_latency; +}; + +#endif /* HAVE_COREAUDIO */ + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioGenerator.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioGenerator.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,769 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioGenerator.h" + +#include "base/TempDirectory.h" +#include "base/PlayParameters.h" +#include "base/PlayParameterRepository.h" +#include "base/Pitch.h" +#include "base/Exceptions.h" + +#include "data/model/NoteModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/SparseOneDimensionalModel.h" + +#include "plugin/RealTimePluginFactory.h" +#include "plugin/RealTimePluginInstance.h" +#include "plugin/PluginIdentifier.h" +#include "plugin/PluginXml.h" +#include "plugin/api/alsa/seq_event.h" + +#include +#include + +#include +#include + +const size_t +AudioGenerator::m_pluginBlockSize = 2048; + +QString +AudioGenerator::m_sampleDir = ""; + +//#define DEBUG_AUDIO_GENERATOR 1 + +AudioGenerator::AudioGenerator() : + m_sourceSampleRate(0), + m_targetChannelCount(1) +{ + connect(PlayParameterRepository::getInstance(), + SIGNAL(playPluginIdChanged(const Model *, QString)), + this, + SLOT(playPluginIdChanged(const Model *, QString))); + + connect(PlayParameterRepository::getInstance(), + SIGNAL(playPluginConfigurationChanged(const Model *, QString)), + this, + SLOT(playPluginConfigurationChanged(const Model *, QString))); +} + +AudioGenerator::~AudioGenerator() +{ +} + +bool +AudioGenerator::canPlay(const Model *model) +{ + if (dynamic_cast(model) || + dynamic_cast(model) || + dynamic_cast(model)) { + return true; + } else { + return false; + } +} + +bool +AudioGenerator::addModel(Model *model) +{ + if (m_sourceSampleRate == 0) { + + m_sourceSampleRate = model->getSampleRate(); + + } else { + + DenseTimeValueModel *dtvm = + dynamic_cast(model); + + if (dtvm) { + m_sourceSampleRate = model->getSampleRate(); + return true; + } + } + + RealTimePluginInstance *plugin = loadPluginFor(model); + if (plugin) { + QMutexLocker locker(&m_mutex); + m_synthMap[model] = plugin; + return true; + } + + return false; +} + +void +AudioGenerator::playPluginIdChanged(const Model *model, QString) +{ + if (m_synthMap.find(model) == m_synthMap.end()) return; + + RealTimePluginInstance *plugin = loadPluginFor(model); + if (plugin) { + QMutexLocker locker(&m_mutex); + delete m_synthMap[model]; + m_synthMap[model] = plugin; + } +} + +void +AudioGenerator::playPluginConfigurationChanged(const Model *model, + QString configurationXml) +{ +// std::cerr << "AudioGenerator::playPluginConfigurationChanged" << std::endl; + + if (m_synthMap.find(model) == m_synthMap.end()) { + std::cerr << "AudioGenerator::playPluginConfigurationChanged: We don't know about this plugin" << std::endl; + return; + } + + RealTimePluginInstance *plugin = m_synthMap[model]; + if (plugin) { + PluginXml(plugin).setParametersFromXml(configurationXml); + } +} + +QString +AudioGenerator::getDefaultPlayPluginId(const Model *model) +{ + const SparseOneDimensionalModel *sodm = + dynamic_cast(model); + if (sodm) { + return QString("dssi:%1:sample_player"). + arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME); + } + + const NoteModel *nm = dynamic_cast(model); + if (nm) { + return QString("dssi:%1:sample_player"). + arg(PluginIdentifier::BUILTIN_PLUGIN_SONAME); + } + + return ""; +} + +QString +AudioGenerator::getDefaultPlayPluginConfiguration(const Model *model) +{ + QString program = ""; + + const SparseOneDimensionalModel *sodm = + dynamic_cast(model); + if (sodm) { + program = "tap"; + } + + const NoteModel *nm = dynamic_cast(model); + if (nm) { + program = "piano"; + } + + if (program == "") return ""; + + return + QString("") + .arg(XmlExportable::encodeEntities + (QString("sampledir=%1") + .arg(PluginXml::encodeConfigurationChars(getSampleDir())))) + .arg(XmlExportable::encodeEntities(program)); +} + +QString +AudioGenerator::getSampleDir() +{ + if (m_sampleDir != "") return m_sampleDir; + + try { + m_sampleDir = TempDirectory::getInstance()->getSubDirectoryPath("samples"); + } catch (DirectoryCreationFailed f) { + std::cerr << "WARNING: AudioGenerator::getSampleDir: Failed to create " + << "temporary sample directory" << std::endl; + m_sampleDir = ""; + return ""; + } + + QDir sampleResourceDir(":/samples", "*.wav"); + + for (unsigned int i = 0; i < sampleResourceDir.count(); ++i) { + + QString fileName(sampleResourceDir[i]); + QFile file(sampleResourceDir.filePath(fileName)); + + if (!file.copy(QDir(m_sampleDir).filePath(fileName))) { + std::cerr << "WARNING: AudioGenerator::getSampleDir: " + << "Unable to copy " << fileName.toStdString() + << " into temporary directory \"" + << m_sampleDir.toStdString() << "\"" << std::endl; + } + } + + return m_sampleDir; +} + +void +AudioGenerator::setSampleDir(RealTimePluginInstance *plugin) +{ + plugin->configure("sampledir", getSampleDir().toStdString()); +} + +RealTimePluginInstance * +AudioGenerator::loadPluginFor(const Model *model) +{ + QString pluginId, configurationXml; + + PlayParameters *parameters = + PlayParameterRepository::getInstance()->getPlayParameters(model); + if (parameters) { + pluginId = parameters->getPlayPluginId(); + configurationXml = parameters->getPlayPluginConfiguration(); + } + + if (pluginId == "") { + pluginId = getDefaultPlayPluginId(model); + configurationXml = getDefaultPlayPluginConfiguration(model); + } + + if (pluginId == "") return 0; + + RealTimePluginInstance *plugin = loadPlugin(pluginId, ""); + if (!plugin) return 0; + + if (configurationXml != "") { + PluginXml(plugin).setParametersFromXml(configurationXml); + } + + if (parameters) { + parameters->setPlayPluginId(pluginId); + parameters->setPlayPluginConfiguration(configurationXml); + } + + return plugin; +} + +RealTimePluginInstance * +AudioGenerator::loadPlugin(QString pluginId, QString program) +{ + RealTimePluginFactory *factory = + RealTimePluginFactory::instanceFor(pluginId); + + if (!factory) { + std::cerr << "Failed to get plugin factory" << std::endl; + return false; + } + + RealTimePluginInstance *instance = + factory->instantiatePlugin + (pluginId, 0, 0, m_sourceSampleRate, m_pluginBlockSize, m_targetChannelCount); + + if (!instance) { + std::cerr << "Failed to instantiate plugin " << pluginId.toStdString() << std::endl; + return 0; + } + + setSampleDir(instance); + + for (unsigned int i = 0; i < instance->getParameterCount(); ++i) { + instance->setParameterValue(i, instance->getParameterDefault(i)); + } + std::string defaultProgram = instance->getProgram(0, 0); + if (defaultProgram != "") { +// std::cerr << "first selecting default program " << defaultProgram << std::endl; + instance->selectProgram(defaultProgram); + } + if (program != "") { +// std::cerr << "now selecting desired program " << program.toStdString() << std::endl; + instance->selectProgram(program.toStdString()); + } + instance->setIdealChannelCount(m_targetChannelCount); // reset! + + return instance; +} + +void +AudioGenerator::removeModel(Model *model) +{ + SparseOneDimensionalModel *sodm = + dynamic_cast(model); + if (!sodm) return; // nothing to do + + QMutexLocker locker(&m_mutex); + + if (m_synthMap.find(sodm) == m_synthMap.end()) return; + + RealTimePluginInstance *instance = m_synthMap[sodm]; + m_synthMap.erase(sodm); + delete instance; +} + +void +AudioGenerator::clearModels() +{ + QMutexLocker locker(&m_mutex); + while (!m_synthMap.empty()) { + RealTimePluginInstance *instance = m_synthMap.begin()->second; + m_synthMap.erase(m_synthMap.begin()); + delete instance; + } +} + +void +AudioGenerator::reset() +{ + QMutexLocker locker(&m_mutex); + for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { + if (i->second) { + i->second->silence(); + i->second->discardEvents(); + } + } + + m_noteOffs.clear(); +} + +void +AudioGenerator::setTargetChannelCount(size_t targetChannelCount) +{ + if (m_targetChannelCount == targetChannelCount) return; + +// std::cerr << "AudioGenerator::setTargetChannelCount(" << targetChannelCount << ")" << std::endl; + + QMutexLocker locker(&m_mutex); + m_targetChannelCount = targetChannelCount; + + for (PluginMap::iterator i = m_synthMap.begin(); i != m_synthMap.end(); ++i) { + if (i->second) i->second->setIdealChannelCount(targetChannelCount); + } +} + +size_t +AudioGenerator::getBlockSize() const +{ + return m_pluginBlockSize; +} + +size_t +AudioGenerator::mixModel(Model *model, size_t startFrame, size_t frameCount, + float **buffer, size_t fadeIn, size_t fadeOut) +{ + if (m_sourceSampleRate == 0) { + std::cerr << "WARNING: AudioGenerator::mixModel: No base source sample rate available" << std::endl; + return frameCount; + } + + QMutexLocker locker(&m_mutex); + + PlayParameters *parameters = + PlayParameterRepository::getInstance()->getPlayParameters(model); + if (!parameters) return frameCount; + + bool playing = !parameters->isPlayMuted(); + if (!playing) { +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "AudioGenerator::mixModel(" << model << "): muted" << std::endl; +#endif + return frameCount; + } + + float gain = parameters->getPlayGain(); + float pan = parameters->getPlayPan(); + + DenseTimeValueModel *dtvm = dynamic_cast(model); + if (dtvm) { + return mixDenseTimeValueModel(dtvm, startFrame, frameCount, + buffer, gain, pan, fadeIn, fadeOut); + } + + SparseOneDimensionalModel *sodm = dynamic_cast + (model); + if (sodm) { + return mixSparseOneDimensionalModel(sodm, startFrame, frameCount, + buffer, gain, pan, fadeIn, fadeOut); + } + + NoteModel *nm = dynamic_cast(model); + if (nm) { + return mixNoteModel(nm, startFrame, frameCount, + buffer, gain, pan, fadeIn, fadeOut); + } + + return frameCount; +} + +size_t +AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm, + size_t startFrame, size_t frames, + float **buffer, float gain, float pan, + size_t fadeIn, size_t fadeOut) +{ + static float *channelBuffer = 0; + static size_t channelBufSiz = 0; + + size_t totalFrames = frames + fadeIn/2 + fadeOut/2; + + if (channelBufSiz < totalFrames) { + delete[] channelBuffer; + channelBuffer = new float[totalFrames]; + channelBufSiz = totalFrames; + } + + size_t got = 0; + size_t prevChannel = 999; + + for (size_t c = 0; c < m_targetChannelCount; ++c) { + + size_t sourceChannel = (c % dtvm->getChannelCount()); + +// std::cerr << "mixing channel " << c << " from source channel " << sourceChannel << std::endl; + + float channelGain = gain; + if (pan != 0.0) { + if (c == 0) { + if (pan > 0.0) channelGain *= 1.0 - pan; + } else { + if (pan < 0.0) channelGain *= pan + 1.0; + } + } + + if (prevChannel != sourceChannel) { + if (startFrame >= fadeIn/2) { + got = dtvm->getValues + (sourceChannel, + startFrame - fadeIn/2, startFrame + frames + fadeOut/2, + channelBuffer); + } else { + size_t missing = fadeIn/2 - startFrame; + got = dtvm->getValues + (sourceChannel, + 0, startFrame + frames + fadeOut/2, + channelBuffer + missing); + } + } + prevChannel = sourceChannel; + + for (size_t i = 0; i < fadeIn/2; ++i) { + float *back = buffer[c]; + back -= fadeIn/2; + back[i] += (channelGain * channelBuffer[i] * i) / fadeIn; + } + + for (size_t i = 0; i < frames + fadeOut/2; ++i) { + float mult = channelGain; + if (i < fadeIn/2) { + mult = (mult * i) / fadeIn; + } + if (i > frames - fadeOut/2) { + mult = (mult * ((frames + fadeOut/2) - i)) / fadeOut; + } + buffer[c][i] += mult * channelBuffer[i]; + } + } + + return got; +} + +size_t +AudioGenerator::mixSparseOneDimensionalModel(SparseOneDimensionalModel *sodm, + size_t startFrame, size_t frames, + float **buffer, float gain, float pan, + size_t /* fadeIn */, + size_t /* fadeOut */) +{ + RealTimePluginInstance *plugin = m_synthMap[sodm]; + if (!plugin) return 0; + + size_t latency = plugin->getLatency(); + size_t blocks = frames / m_pluginBlockSize; + + //!!! hang on -- the fact that the audio callback play source's + //buffer is a multiple of the plugin's buffer size doesn't mean + //that we always get called for a multiple of it here (because it + //also depends on the JACK block size). how should we ensure that + //all models write the same amount in to the mix, and that we + //always have a multiple of the plugin buffer size? I guess this + //class has to be queryable for the plugin buffer size & the + //callback play source has to use that as a multiple for all the + //calls to mixModel + + size_t got = blocks * m_pluginBlockSize; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [sparse]: frames " << frames + << ", blocks " << blocks << std::endl; +#endif + + snd_seq_event_t onEv; + onEv.type = SND_SEQ_EVENT_NOTEON; + onEv.data.note.channel = 0; + onEv.data.note.note = 64; + onEv.data.note.velocity = 127; + + snd_seq_event_t offEv; + offEv.type = SND_SEQ_EVENT_NOTEOFF; + offEv.data.note.channel = 0; + offEv.data.note.velocity = 0; + + NoteOffSet ¬eOffs = m_noteOffs[sodm]; + + for (size_t i = 0; i < blocks; ++i) { + + size_t reqStart = startFrame + i * m_pluginBlockSize; + + SparseOneDimensionalModel::PointList points = + sodm->getPoints(reqStart + latency, + reqStart + latency + m_pluginBlockSize); + + Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime + (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); + + for (SparseOneDimensionalModel::PointList::iterator pli = + points.begin(); pli != points.end(); ++pli) { + + size_t pliFrame = pli->frame; + + if (pliFrame >= latency) pliFrame -= latency; + + if (pliFrame < reqStart || + pliFrame >= reqStart + m_pluginBlockSize) continue; + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= pliFrame) { + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (noteOffs.begin()->frame, m_sourceSampleRate); + + offEv.data.note.note = noteOffs.begin()->pitch; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "mixModel [sparse]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; +#endif + + plugin->sendEvent(eventTime, &offEv); + noteOffs.erase(noteOffs.begin()); + } + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (pliFrame, m_sourceSampleRate); + + plugin->sendEvent(eventTime, &onEv); + +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [sparse]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl; +#endif + + size_t duration = 7000; // frames [for now] + NoteOff noff; + noff.pitch = onEv.data.note.note; + noff.frame = pliFrame + duration; + noteOffs.insert(noff); + } + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= + startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (noteOffs.begin()->frame, m_sourceSampleRate); + + offEv.data.note.note = noteOffs.begin()->pitch; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "mixModel [sparse]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; +#endif + + plugin->sendEvent(eventTime, &offEv); + noteOffs.erase(noteOffs.begin()); + } + + plugin->run(blockTime); + float **outs = plugin->getAudioOutputBuffers(); + + for (size_t c = 0; c < m_targetChannelCount; ++c) { +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [sparse]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl; +#endif + + size_t sourceChannel = (c % plugin->getAudioOutputCount()); + + float channelGain = gain; + if (pan != 0.0) { + if (c == 0) { + if (pan > 0.0) channelGain *= 1.0 - pan; + } else { + if (pan < 0.0) channelGain *= pan + 1.0; + } + } + + for (size_t j = 0; j < m_pluginBlockSize; ++j) { + buffer[c][i * m_pluginBlockSize + j] += + channelGain * outs[sourceChannel][j]; + } + } + } + + return got; +} + + +//!!! mucho duplication with above -- refactor +size_t +AudioGenerator::mixNoteModel(NoteModel *nm, + size_t startFrame, size_t frames, + float **buffer, float gain, float pan, + size_t /* fadeIn */, + size_t /* fadeOut */) +{ + RealTimePluginInstance *plugin = m_synthMap[nm]; + if (!plugin) return 0; + + size_t latency = plugin->getLatency(); + size_t blocks = frames / m_pluginBlockSize; + + //!!! hang on -- the fact that the audio callback play source's + //buffer is a multiple of the plugin's buffer size doesn't mean + //that we always get called for a multiple of it here (because it + //also depends on the JACK block size). how should we ensure that + //all models write the same amount in to the mix, and that we + //always have a multiple of the plugin buffer size? I guess this + //class has to be queryable for the plugin buffer size & the + //callback play source has to use that as a multiple for all the + //calls to mixModel + + size_t got = blocks * m_pluginBlockSize; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [note]: frames " << frames + << ", blocks " << blocks << std::endl; +#endif + + snd_seq_event_t onEv; + onEv.type = SND_SEQ_EVENT_NOTEON; + onEv.data.note.channel = 0; + onEv.data.note.note = 64; + onEv.data.note.velocity = 127; + + snd_seq_event_t offEv; + offEv.type = SND_SEQ_EVENT_NOTEOFF; + offEv.data.note.channel = 0; + offEv.data.note.velocity = 0; + + NoteOffSet ¬eOffs = m_noteOffs[nm]; + + for (size_t i = 0; i < blocks; ++i) { + + size_t reqStart = startFrame + i * m_pluginBlockSize; + + NoteModel::PointList points = + nm->getPoints(reqStart + latency, + reqStart + latency + m_pluginBlockSize); + + Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime + (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); + + for (NoteModel::PointList::iterator pli = + points.begin(); pli != points.end(); ++pli) { + + size_t pliFrame = pli->frame; + + if (pliFrame >= latency) pliFrame -= latency; + + if (pliFrame < reqStart || + pliFrame >= reqStart + m_pluginBlockSize) continue; + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= pliFrame) { + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (noteOffs.begin()->frame, m_sourceSampleRate); + + offEv.data.note.note = noteOffs.begin()->pitch; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "mixModel [note]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; +#endif + + plugin->sendEvent(eventTime, &offEv); + noteOffs.erase(noteOffs.begin()); + } + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (pliFrame, m_sourceSampleRate); + + if (nm->getScaleUnits() == "Hz") { + onEv.data.note.note = Pitch::getPitchForFrequency(pli->value); + } else { + onEv.data.note.note = lrintf(pli->value); + } + + plugin->sendEvent(eventTime, &onEv); + +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [note]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl; +#endif + + size_t duration = pli->duration; + if (duration == 0 || duration == 1) { + duration = m_sourceSampleRate / 20; + } + NoteOff noff; + noff.pitch = onEv.data.note.note; + noff.frame = pliFrame + duration; + noteOffs.insert(noff); + } + + while (noteOffs.begin() != noteOffs.end() && + noteOffs.begin()->frame <= + startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { + + Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime + (noteOffs.begin()->frame, m_sourceSampleRate); + + offEv.data.note.note = noteOffs.begin()->pitch; + +#ifdef DEBUG_AUDIO_GENERATOR + std::cerr << "mixModel [note]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << std::endl; +#endif + + plugin->sendEvent(eventTime, &offEv); + noteOffs.erase(noteOffs.begin()); + } + + plugin->run(blockTime); + float **outs = plugin->getAudioOutputBuffers(); + + for (size_t c = 0; c < m_targetChannelCount; ++c) { +#ifdef DEBUG_AUDIO_GENERATOR + std::cout << "mixModel [note]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl; +#endif + + size_t sourceChannel = (c % plugin->getAudioOutputCount()); + + float channelGain = gain; + if (pan != 0.0) { + if (c == 0) { + if (pan > 0.0) channelGain *= 1.0 - pan; + } else { + if (pan < 0.0) channelGain *= pan + 1.0; + } + } + + for (size_t j = 0; j < m_pluginBlockSize; ++j) { + buffer[c][i * m_pluginBlockSize + j] += + channelGain * outs[sourceChannel][j]; + } + } + } + + return got; +} + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioGenerator.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioGenerator.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,144 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_GENERATOR_H_ +#define _AUDIO_GENERATOR_H_ + +class Model; +class NoteModel; +class DenseTimeValueModel; +class SparseOneDimensionalModel; +class RealTimePluginInstance; + +#include +#include + +#include +#include + +class AudioGenerator : public QObject +{ + Q_OBJECT + +public: + AudioGenerator(); + virtual ~AudioGenerator(); + + /** + * Return true if the given model is of a type that we generally + * know how to play. This doesn't guarantee that a specific + * AudioGenerator will actually produce sounds for it (for + * example, it may turn out that a vital plugin is missing). + */ + static bool canPlay(const Model *model); + + static QString getDefaultPlayPluginId(const Model *model); + static QString getDefaultPlayPluginConfiguration(const Model *model); + + /** + * Add a data model to be played from and initialise any necessary + * audio generation code. Returns true if the model will be + * played. (The return value test here is stricter than that for + * canPlay, above.) The model will be added regardless of the + * return value. + */ + virtual bool addModel(Model *model); + + /** + * Remove a model. + */ + virtual void removeModel(Model *model); + + /** + * Remove all models. + */ + virtual void clearModels(); + + /** + * Reset playback, clearing plugins and the like. + */ + virtual void reset(); + + /** + * Set the target channel count. The buffer parameter to mixModel + * must always point to at least this number of arrays. + */ + virtual void setTargetChannelCount(size_t channelCount); + + /** + * Return the internal processing block size. The frameCount + * argument to all mixModel calls must be a multiple of this + * value. + */ + virtual size_t getBlockSize() const; + + /** + * Mix a single model into an output buffer. + */ + virtual size_t mixModel(Model *model, size_t startFrame, size_t frameCount, + float **buffer, size_t fadeIn = 0, size_t fadeOut = 0); + +protected slots: + void playPluginIdChanged(const Model *, QString); + void playPluginConfigurationChanged(const Model *, QString); + +protected: + size_t m_sourceSampleRate; + size_t m_targetChannelCount; + + struct NoteOff { + + int pitch; + size_t frame; + + struct Comparator { + bool operator()(const NoteOff &n1, const NoteOff &n2) const { + return n1.frame < n2.frame; + } + }; + }; + + typedef std::map PluginMap; + + typedef std::set NoteOffSet; + typedef std::map NoteOffMap; + + QMutex m_mutex; + PluginMap m_synthMap; + NoteOffMap m_noteOffs; + static QString m_sampleDir; + + virtual RealTimePluginInstance *loadPluginFor(const Model *model); + virtual RealTimePluginInstance *loadPlugin(QString id, QString program); + static QString getSampleDir(); + static void setSampleDir(RealTimePluginInstance *plugin); + + virtual size_t mixDenseTimeValueModel + (DenseTimeValueModel *model, size_t startFrame, size_t frameCount, + float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); + + virtual size_t mixSparseOneDimensionalModel + (SparseOneDimensionalModel *model, size_t startFrame, size_t frameCount, + float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); + + virtual size_t mixNoteModel + (NoteModel *model, size_t startFrame, size_t frameCount, + float **buffer, float gain, float pan, size_t fadeIn, size_t fadeOut); + + static const size_t m_pluginBlockSize; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioJACKTarget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioJACKTarget.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,400 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_JACK + +#include "AudioJACKTarget.h" +#include "AudioCallbackPlaySource.h" + +#include +#include + +//#define DEBUG_AUDIO_JACK_TARGET 1 + +#ifdef BUILD_STATIC +#ifdef Q_OS_LINUX + +// Some lunacy to enable JACK support in static builds. JACK isn't +// supposed to be linked statically, because it depends on a +// consistent shared memory layout between client library and daemon, +// so it's very fragile in the face of version mismatches. +// +// Therefore for static builds on Linux we avoid linking against JACK +// at all during the build, instead using dlopen and runtime symbol +// lookup to switch on JACK support at runtime. The following big +// mess (down to the #endifs) is the code that implements this. + +static void *symbol(const char *name) +{ + static bool attempted = false; + static void *library = 0; + static std::map symbols; + if (symbols.find(name) != symbols.end()) return symbols[name]; + if (!library) { + if (!attempted) { + library = ::dlopen("libjack.so.1", RTLD_NOW); + if (!library) library = ::dlopen("libjack.so.0", RTLD_NOW); + if (!library) library = ::dlopen("libjack.so", RTLD_NOW); + if (!library) { + std::cerr << "WARNING: AudioJACKTarget: Failed to load JACK library: " + << ::dlerror() << " (tried .so, .so.0, .so.1)" + << std::endl; + } + attempted = true; + } + if (!library) return 0; + } + void *symbol = ::dlsym(library, name); + if (!symbol) { + std::cerr << "WARNING: AudioJACKTarget: Failed to locate symbol " + << name << ": " << ::dlerror() << std::endl; + } + symbols[name] = symbol; + return symbol; +} + +static int dynamic_jack_set_process_callback(jack_client_t *client, + JackProcessCallback process_callback, + void *arg) +{ + typedef int (*func)(jack_client_t *client, + JackProcessCallback process_callback, + void *arg); + void *s = symbol("jack_set_process_callback"); + if (!s) return 1; + func f = (func)s; + return f(client, process_callback, arg); +} + +static int dynamic_jack_set_xrun_callback(jack_client_t *client, + JackXRunCallback xrun_callback, + void *arg) +{ + typedef int (*func)(jack_client_t *client, + JackXRunCallback xrun_callback, + void *arg); + void *s = symbol("jack_set_xrun_callback"); + if (!s) return 1; + func f = (func)s; + return f(client, xrun_callback, arg); +} + +static const char **dynamic_jack_get_ports(jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags) +{ + typedef const char **(*func)(jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags); + void *s = symbol("jack_get_ports"); + if (!s) return 0; + func f = (func)s; + return f(client, port_name_pattern, type_name_pattern, flags); +} + +static jack_port_t *dynamic_jack_port_register(jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size) +{ + typedef jack_port_t *(*func)(jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size); + void *s = symbol("jack_port_register"); + if (!s) return 0; + func f = (func)s; + return f(client, port_name, port_type, flags, buffer_size); +} + +static int dynamic_jack_connect(jack_client_t *client, + const char *source, + const char *dest) +{ + typedef int (*func)(jack_client_t *client, + const char *source, + const char *dest); + void *s = symbol("jack_connect"); + if (!s) return 1; + func f = (func)s; + return f(client, source, dest); +} + +static void *dynamic_jack_port_get_buffer(jack_port_t *port, + jack_nframes_t sz) +{ + typedef void *(*func)(jack_port_t *, jack_nframes_t); + void *s = symbol("jack_port_get_buffer"); + if (!s) return 0; + func f = (func)s; + return f(port, sz); +} + +static int dynamic_jack_port_unregister(jack_client_t *client, + jack_port_t *port) +{ + typedef int(*func)(jack_client_t *, jack_port_t *); + void *s = symbol("jack_port_unregister"); + if (!s) return 0; + func f = (func)s; + return f(client, port); +} + +#define dynamic1(rv, name, argtype, failval) \ + static rv dynamic_##name(argtype arg) { \ + typedef rv (*func) (argtype); \ + void *s = symbol(#name); \ + if (!s) return failval; \ + func f = (func) s; \ + return f(arg); \ + } + +dynamic1(jack_client_t *, jack_client_new, const char *, 0); +dynamic1(jack_nframes_t, jack_get_buffer_size, jack_client_t *, 0); +dynamic1(jack_nframes_t, jack_get_sample_rate, jack_client_t *, 0); +dynamic1(int, jack_activate, jack_client_t *, 1); +dynamic1(int, jack_deactivate, jack_client_t *, 1); +dynamic1(int, jack_client_close, jack_client_t *, 1); +dynamic1(jack_nframes_t, jack_port_get_latency, jack_port_t *, 0); +dynamic1(const char *, jack_port_name, const jack_port_t *, 0); + +#define jack_client_new dynamic_jack_client_new +#define jack_get_buffer_size dynamic_jack_get_buffer_size +#define jack_get_sample_rate dynamic_jack_get_sample_rate +#define jack_set_process_callback dynamic_jack_set_process_callback +#define jack_set_xrun_callback dynamic_jack_set_xrun_callback +#define jack_activate dynamic_jack_activate +#define jack_deactivate dynamic_jack_deactivate +#define jack_client_close dynamic_jack_client_close +#define jack_get_ports dynamic_jack_get_ports +#define jack_port_register dynamic_jack_port_register +#define jack_port_unregister dynamic_jack_port_unregister +#define jack_port_get_latency dynamic_jack_port_get_latency +#define jack_port_name dynamic_jack_port_name +#define jack_connect dynamic_jack_connect +#define jack_port_get_buffer dynamic_jack_port_get_buffer + +#endif +#endif + +AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) : + AudioCallbackPlayTarget(source), + m_client(0), + m_bufferSize(0), + m_sampleRate(0) +{ + char name[100]; + strcpy(name, "Sonic Visualiser"); + m_client = jack_client_new(name); + + if (!m_client) { + sprintf(name, "Sonic Visualiser (%d)", (int)getpid()); + m_client = jack_client_new(name); + if (!m_client) { + std::cerr + << "ERROR: AudioJACKTarget: Failed to connect to JACK server" + << std::endl; + } + } + + if (!m_client) return; + + m_bufferSize = jack_get_buffer_size(m_client); + m_sampleRate = jack_get_sample_rate(m_client); + + jack_set_xrun_callback(m_client, xrunStatic, this); + jack_set_process_callback(m_client, processStatic, this); + + if (jack_activate(m_client)) { + std::cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client" + << std::endl; + } + + if (m_source) { + sourceModelReplaced(); + } +} + +AudioJACKTarget::~AudioJACKTarget() +{ + if (m_client) { + jack_deactivate(m_client); + jack_client_close(m_client); + } +} + +bool +AudioJACKTarget::isOK() const +{ + return (m_client != 0); +} + +int +AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg) +{ + return ((AudioJACKTarget *)arg)->process(nframes); +} + +int +AudioJACKTarget::xrunStatic(void *arg) +{ + return ((AudioJACKTarget *)arg)->xrun(); +} + +void +AudioJACKTarget::sourceModelReplaced() +{ + m_mutex.lock(); + + m_source->setTargetBlockSize(m_bufferSize); + m_source->setTargetSampleRate(m_sampleRate); + + size_t channels = m_source->getSourceChannelCount(); + + // Because we offer pan, we always want at least 2 channels + if (channels < 2) channels = 2; + + if (channels == m_outputs.size() || !m_client) { + m_mutex.unlock(); + return; + } + + const char **ports = + jack_get_ports(m_client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + size_t physicalPortCount = 0; + while (ports[physicalPortCount]) ++physicalPortCount; + +#ifdef DEBUG_AUDIO_JACK_TARGET + std::cerr << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << std::endl; +#endif + + while (m_outputs.size() < channels) { + + char name[20]; + jack_port_t *port; + + sprintf(name, "out %d", m_outputs.size() + 1); + + port = jack_port_register(m_client, + name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, + 0); + + if (!port) { + std::cerr + << "ERROR: AudioJACKTarget: Failed to create JACK output port " + << m_outputs.size() << std::endl; + } else { + m_source->setTargetPlayLatency(jack_port_get_latency(port)); + } + + if (m_outputs.size() < physicalPortCount) { + jack_connect(m_client, jack_port_name(port), ports[m_outputs.size()]); + } + + m_outputs.push_back(port); + } + + while (m_outputs.size() > channels) { + std::vector::iterator itr = m_outputs.end(); + --itr; + jack_port_t *port = *itr; + if (port) jack_port_unregister(m_client, port); + m_outputs.erase(itr); + } + + m_mutex.unlock(); +} + +int +AudioJACKTarget::process(jack_nframes_t nframes) +{ + if (!m_mutex.tryLock()) { + return 0; + } + + if (m_outputs.empty()) { + m_mutex.unlock(); + return 0; + } + +#ifdef DEBUG_AUDIO_JACK_TARGET + std::cout << "AudioJACKTarget::process(" << nframes << "): have a source" << std::endl; +#endif + +#ifdef DEBUG_AUDIO_JACK_TARGET + if (m_bufferSize != nframes) { + std::cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << std::endl; + } +#endif + + float **buffers = (float **)alloca(m_outputs.size() * sizeof(float *)); + + for (size_t ch = 0; ch < m_outputs.size(); ++ch) { + buffers[ch] = (float *)jack_port_get_buffer(m_outputs[ch], nframes); + } + + size_t received = 0; + + if (m_source) { + received = m_source->getSourceSamples(nframes, buffers); + } + + for (size_t ch = 0; ch < m_outputs.size(); ++ch) { + for (size_t i = received; i < nframes; ++i) { + buffers[ch][i] = 0.0; + } + } + + float peakLeft = 0.0, peakRight = 0.0; + + for (size_t ch = 0; ch < m_outputs.size(); ++ch) { + + float peak = 0.0; + + for (size_t i = 0; i < nframes; ++i) { + buffers[ch][i] *= m_outputGain; + float sample = fabsf(buffers[ch][i]); + if (sample > peak) peak = sample; + } + + if (ch == 0) peakLeft = peak; + if (ch > 0 || m_outputs.size() == 1) peakRight = peak; + } + + if (m_source) { + m_source->setOutputLevels(peakLeft, peakRight); + } + + m_mutex.unlock(); + return 0; +} + +int +AudioJACKTarget::xrun() +{ + std::cerr << "AudioJACKTarget: xrun!" << std::endl; + if (m_source) m_source->audioProcessingOverload(); + return 0; +} + +#endif /* HAVE_JACK */ + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioJACKTarget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioJACKTarget.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,60 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_JACK_TARGET_H_ +#define _AUDIO_JACK_TARGET_H_ + +#ifdef HAVE_JACK + +#include +#include + +#include "AudioCallbackPlayTarget.h" + +#include + +class AudioCallbackPlaySource; + +class AudioJACKTarget : public AudioCallbackPlayTarget +{ + Q_OBJECT + +public: + AudioJACKTarget(AudioCallbackPlaySource *source); + virtual ~AudioJACKTarget(); + + virtual bool isOK() const; + +public slots: + virtual void sourceModelReplaced(); + +protected: + int process(jack_nframes_t nframes); + int xrun(); + + static int processStatic(jack_nframes_t, void *); + static int xrunStatic(void *); + + jack_client_t *m_client; + std::vector m_outputs; + jack_nframes_t m_bufferSize; + jack_nframes_t m_sampleRate; + QMutex m_mutex; +}; + +#endif /* HAVE_JACK */ + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioPortAudioTarget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioPortAudioTarget.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,254 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_PORTAUDIO + +#include "AudioPortAudioTarget.h" +#include "AudioCallbackPlaySource.h" + +#include +#include +#include + +//#define DEBUG_AUDIO_PORT_AUDIO_TARGET 1 + +AudioPortAudioTarget::AudioPortAudioTarget(AudioCallbackPlaySource *source) : + AudioCallbackPlayTarget(source), + m_stream(0), + m_bufferSize(0), + m_sampleRate(0), + m_latency(0) +{ + PaError err; + +#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET +#ifdef HAVE_PORTAUDIO_V18 + std::cerr << "AudioPortAudioTarget: Initialising for PortAudio v18" << std::endl; +#else + std::cerr << "AudioPortAudioTarget: Initialising for PortAudio v19" << std::endl; +#endif +#endif + + err = Pa_Initialize(); + if (err != paNoError) { + std::cerr << "ERROR: AudioPortAudioTarget: Failed to initialize PortAudio: " << Pa_GetErrorText(err) << std::endl; + return; + } + + m_bufferSize = 1024; + m_sampleRate = 44100; + if (m_source && (m_source->getSourceSampleRate() != 0)) { + m_sampleRate = m_source->getSourceSampleRate(); + } + +#ifdef HAVE_PORTAUDIO_V18 + m_latency = Pa_GetMinNumBuffers(m_bufferSize, m_sampleRate) * m_bufferSize; +#endif + +#ifdef HAVE_PORTAUDIO_V18 + err = Pa_OpenDefaultStream(&m_stream, 0, 2, paFloat32, + m_sampleRate, m_bufferSize, 0, + processStatic, this); +#else + err = Pa_OpenDefaultStream(&m_stream, 0, 2, paFloat32, + m_sampleRate, m_bufferSize, + processStatic, this); +#endif + + if (err != paNoError) { + std::cerr << "ERROR: AudioPortAudioTarget: Failed to open PortAudio stream: " << Pa_GetErrorText(err) << std::endl; + m_stream = 0; + Pa_Terminate(); + return; + } + +#ifndef HAVE_PORTAUDIO_V18 + const PaStreamInfo *info = Pa_GetStreamInfo(m_stream); + m_latency = int(info->outputLatency * m_sampleRate + 0.001); +#endif + + std::cerr << "PortAudio latency = " << m_latency << " frames" << std::endl; + + err = Pa_StartStream(m_stream); + + if (err != paNoError) { + std::cerr << "ERROR: AudioPortAudioTarget: Failed to start PortAudio stream: " << Pa_GetErrorText(err) << std::endl; + Pa_CloseStream(m_stream); + m_stream = 0; + Pa_Terminate(); + return; + } + + if (m_source) { + std::cerr << "AudioPortAudioTarget: block size " << m_bufferSize << std::endl; + m_source->setTargetBlockSize(m_bufferSize); + m_source->setTargetSampleRate(m_sampleRate); + m_source->setTargetPlayLatency(m_latency); + } + +#ifdef DEBUG_PORT_AUDIO_TARGET + std::cerr << "AudioPortAudioTarget: initialised OK" << std::endl; +#endif +} + +AudioPortAudioTarget::~AudioPortAudioTarget() +{ + if (m_stream) { + PaError err; + err = Pa_CloseStream(m_stream); + if (err != paNoError) { + std::cerr << "ERROR: AudioPortAudioTarget: Failed to close PortAudio stream: " << Pa_GetErrorText(err) << std::endl; + } + err = Pa_Terminate(); + if (err != paNoError) { + std::cerr << "ERROR: AudioPortAudioTarget: Failed to terminate PortAudio: " << Pa_GetErrorText(err) << std::endl; + } + } +} + +bool +AudioPortAudioTarget::isOK() const +{ + return (m_stream != 0); +} + +#ifdef HAVE_PORTAUDIO_V18 +int +AudioPortAudioTarget::processStatic(void *input, void *output, + unsigned long nframes, + PaTimestamp outTime, void *data) +{ + return ((AudioPortAudioTarget *)data)->process(input, output, + nframes, outTime); +} +#else +int +AudioPortAudioTarget::processStatic(const void *input, void *output, + unsigned long nframes, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags flags, void *data) +{ + return ((AudioPortAudioTarget *)data)->process(input, output, + nframes, timeInfo, + flags); +} +#endif + +void +AudioPortAudioTarget::sourceModelReplaced() +{ + m_source->setTargetSampleRate(m_sampleRate); +} + +#ifdef HAVE_PORTAUDIO_V18 +int +AudioPortAudioTarget::process(void *inputBuffer, void *outputBuffer, + unsigned long nframes, + PaTimestamp) +#else +int +AudioPortAudioTarget::process(const void *, void *outputBuffer, + unsigned long nframes, + const PaStreamCallbackTimeInfo *, + PaStreamCallbackFlags) +#endif +{ +#ifdef DEBUG_AUDIO_PORT_AUDIO_TARGET + std::cout << "AudioPortAudioTarget::process(" << nframes << ")" << std::endl; +#endif + + if (!m_source) return 0; + + float *output = (float *)outputBuffer; + + assert(nframes <= m_bufferSize); + + static float **tmpbuf = 0; + static size_t tmpbufch = 0; + static size_t tmpbufsz = 0; + + size_t sourceChannels = m_source->getSourceChannelCount(); + + // Because we offer pan, we always want at least 2 channels + if (sourceChannels < 2) sourceChannels = 2; + + if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < m_bufferSize) { + + if (tmpbuf) { + for (size_t i = 0; i < tmpbufch; ++i) { + delete[] tmpbuf[i]; + } + delete[] tmpbuf; + } + + tmpbufch = sourceChannels; + tmpbufsz = m_bufferSize; + tmpbuf = new float *[tmpbufch]; + + for (size_t i = 0; i < tmpbufch; ++i) { + tmpbuf[i] = new float[tmpbufsz]; + } + } + + size_t received = m_source->getSourceSamples(nframes, tmpbuf); + + float peakLeft = 0.0, peakRight = 0.0; + + for (size_t ch = 0; ch < 2; ++ch) { + + float peak = 0.0; + + if (ch < sourceChannels) { + + // PortAudio samples are interleaved + for (size_t i = 0; i < nframes; ++i) { + if (i < received) { + output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; + float sample = fabsf(output[i * 2 + ch]); + if (sample > peak) peak = sample; + } else { + output[i * 2 + ch] = 0; + } + } + + } else if (ch == 1 && sourceChannels == 1) { + + for (size_t i = 0; i < nframes; ++i) { + if (i < received) { + output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; + float sample = fabsf(output[i * 2 + ch]); + if (sample > peak) peak = sample; + } else { + output[i * 2 + ch] = 0; + } + } + + } else { + for (size_t i = 0; i < nframes; ++i) { + output[i * 2 + ch] = 0; + } + } + + if (ch == 0) peakLeft = peak; + if (ch > 0 || sourceChannels == 1) peakRight = peak; + } + + m_source->setOutputLevels(peakLeft, peakRight); + + return 0; +} + +#endif /* HAVE_PORTAUDIO */ + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioPortAudioTarget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioPortAudioTarget.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_PORT_AUDIO_TARGET_H_ +#define _AUDIO_PORT_AUDIO_TARGET_H_ + +#ifdef HAVE_PORTAUDIO + +// This code can be compiled for either PortAudio v18 or v19. +// PortAudio v19 is the default. If you want to use v18, define +// the preprocessor symbol HAVE_PORTAUDIO_v18. + +#include +#include + +#include "AudioCallbackPlayTarget.h" + +class AudioCallbackPlaySource; + +class AudioPortAudioTarget : public AudioCallbackPlayTarget +{ + Q_OBJECT + +public: + AudioPortAudioTarget(AudioCallbackPlaySource *source); + virtual ~AudioPortAudioTarget(); + + virtual bool isOK() const; + +public slots: + virtual void sourceModelReplaced(); + +protected: +#ifdef HAVE_PORTAUDIO_V18 + + int process(void *input, void *output, unsigned long frames, + PaTimestamp outTime); + + static int processStatic(void *, void *, unsigned long, + PaTimestamp, void *); + + PortAudioStream *m_stream; + +#else + + int process(const void *input, void *output, unsigned long frames, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags); + + static int processStatic(const void *, void *, unsigned long, + const PaStreamCallbackTimeInfo *, + PaStreamCallbackFlags, void *); + + PaStream *m_stream; + +#endif + + int m_bufferSize; + int m_sampleRate; + int m_latency; +}; + +#endif /* HAVE_PORTAUDIO */ + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioTargetFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioTargetFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "AudioTargetFactory.h" + +#include "AudioJACKTarget.h" +#include "AudioCoreAudioTarget.h" +#include "AudioPortAudioTarget.h" + +#include + +AudioCallbackPlayTarget * +AudioTargetFactory::createCallbackTarget(AudioCallbackPlaySource *source) +{ + AudioCallbackPlayTarget *target = 0; + +#ifdef HAVE_JACK + target = new AudioJACKTarget(source); + if (target->isOK()) return target; + else { + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open JACK target" << std::endl; + delete target; + } +#endif + +#ifdef HAVE_COREAUDIO + target = new AudioCoreAudioTarget(source); + if (target->isOK()) return target; + else { + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open CoreAudio target" << std::endl; + delete target; + } +#endif + +#ifdef HAVE_DIRECTSOUND + target = new AudioDirectSoundTarget(source); + if (target->isOK()) return target; + else { + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open DirectSound target" << std::endl; + delete target; + } +#endif + +#ifdef HAVE_PORTAUDIO + target = new AudioPortAudioTarget(source); + if (target->isOK()) return target; + else { + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open PortAudio target" << std::endl; + delete target; + } +#endif + + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: No suitable targets available" << std::endl; + return 0; +} + + diff -r 000000000000 -r fc9323a41f5a sv/audioio/AudioTargetFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/AudioTargetFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,29 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_TARGET_FACTORY_H_ +#define _AUDIO_TARGET_FACTORY_H_ + +class AudioCallbackPlaySource; +class AudioCallbackPlayTarget; + +class AudioTargetFactory +{ +public: + static AudioCallbackPlayTarget *createCallbackTarget(AudioCallbackPlaySource *); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/audioio/PhaseVocoderTimeStretcher.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/PhaseVocoderTimeStretcher.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,626 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PhaseVocoderTimeStretcher.h" + +#include +#include + +#include + +//#define DEBUG_PHASE_VOCODER_TIME_STRETCHER 1 + +PhaseVocoderTimeStretcher::PhaseVocoderTimeStretcher(size_t sampleRate, + size_t channels, + float ratio, + bool sharpen, + size_t maxOutputBlockSize) : + m_sampleRate(sampleRate), + m_channels(channels), + m_maxOutputBlockSize(maxOutputBlockSize), + m_ratio(ratio), + m_sharpen(sharpen), + m_totalCount(0), + m_transientCount(0), + m_n2sum(0), + m_mutex(new QMutex()) +{ + initialise(); +} + +PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher() +{ + std::cerr << "PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher" << std::endl; + + cleanup(); + + delete m_mutex; +} + +void +PhaseVocoderTimeStretcher::initialise() +{ + std::cerr << "PhaseVocoderTimeStretcher::initialise" << std::endl; + + calculateParameters(); + + m_analysisWindow = new Window(HanningWindow, m_wlen); + m_synthesisWindow = new Window(HanningWindow, m_wlen); + + m_prevPhase = new float *[m_channels]; + m_prevAdjustedPhase = new float *[m_channels]; + + m_prevTransientMag = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); + m_prevTransientScore = 0; + m_prevTransient = false; + + m_tempbuf = (float *)fftf_malloc(sizeof(float) * m_wlen); + + m_time = new float *[m_channels]; + m_freq = new fftf_complex *[m_channels]; + m_plan = new fftf_plan[m_channels]; + m_iplan = new fftf_plan[m_channels]; + + m_inbuf = new RingBuffer *[m_channels]; + m_outbuf = new RingBuffer *[m_channels]; + m_mashbuf = new float *[m_channels]; + + m_modulationbuf = (float *)fftf_malloc(sizeof(float) * m_wlen); + + for (size_t c = 0; c < m_channels; ++c) { + + m_prevPhase[c] = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); + m_prevAdjustedPhase[c] = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); + + m_time[c] = (float *)fftf_malloc(sizeof(float) * m_wlen); + m_freq[c] = (fftf_complex *)fftf_malloc(sizeof(fftf_complex) * + (m_wlen / 2 + 1)); + + m_plan[c] = fftf_plan_dft_r2c_1d(m_wlen, m_time[c], m_freq[c], FFTW_ESTIMATE); + m_iplan[c] = fftf_plan_dft_c2r_1d(m_wlen, m_freq[c], m_time[c], FFTW_ESTIMATE); + + m_outbuf[c] = new RingBuffer + ((m_maxOutputBlockSize + m_wlen) * 2); + m_inbuf[c] = new RingBuffer + (lrintf(m_outbuf[c]->getSize() / m_ratio) + m_wlen); + + std::cerr << "making inbuf size " << m_inbuf[c]->getSize() << " (outbuf size is " << m_outbuf[c]->getSize() << ", ratio " << m_ratio << ")" << std::endl; + + + m_mashbuf[c] = (float *)fftf_malloc(sizeof(float) * m_wlen); + + for (size_t i = 0; i < m_wlen; ++i) { + m_mashbuf[c][i] = 0.0; + } + + for (size_t i = 0; i <= m_wlen/2; ++i) { + m_prevPhase[c][i] = 0.0; + m_prevAdjustedPhase[c][i] = 0.0; + } + } + + for (size_t i = 0; i < m_wlen; ++i) { + m_modulationbuf[i] = 0.0; + } + + for (size_t i = 0; i <= m_wlen/2; ++i) { + m_prevTransientMag[i] = 0.0; + } +} + +void +PhaseVocoderTimeStretcher::calculateParameters() +{ + std::cerr << "PhaseVocoderTimeStretcher::calculateParameters" << std::endl; + + m_wlen = 1024; + + //!!! In transient sharpening mode, we need to pick the window + //length so as to be more or less fixed in audio duration (i.e. we + //need to exploit the sample rate) + + //!!! have to work out the relationship between wlen and transient + //threshold + + if (m_ratio < 1) { + if (m_ratio < 0.4) { + m_n1 = 1024; + m_wlen = 2048; + } else if (m_ratio < 0.8) { + m_n1 = 512; + } else { + m_n1 = 256; + } + if (shouldSharpen()) { + m_wlen = 2048; + } + m_n2 = lrintf(m_n1 * m_ratio); + } else { + if (m_ratio > 2) { + m_n2 = 512; + m_wlen = 4096; + } else if (m_ratio > 1.6) { + m_n2 = 384; + m_wlen = 2048; + } else { + m_n2 = 256; + } + if (shouldSharpen()) { + if (m_wlen < 2048) m_wlen = 2048; + } + m_n1 = lrintf(m_n2 / m_ratio); + if (m_n1 == 0) { + m_n1 = 1; + m_n2 = lrintf(m_ratio); + } + } + + m_transientThreshold = lrintf(m_wlen / 4.5); + + m_totalCount = 0; + m_transientCount = 0; + m_n2sum = 0; + + + std::cerr << "PhaseVocoderTimeStretcher: channels = " << m_channels + << ", ratio = " << m_ratio + << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = " + << m_wlen << ", max = " << m_maxOutputBlockSize << std::endl; +// << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl; +} + +void +PhaseVocoderTimeStretcher::cleanup() +{ + std::cerr << "PhaseVocoderTimeStretcher::cleanup" << std::endl; + + for (size_t c = 0; c < m_channels; ++c) { + + fftf_destroy_plan(m_plan[c]); + fftf_destroy_plan(m_iplan[c]); + + fftf_free(m_time[c]); + fftf_free(m_freq[c]); + + fftf_free(m_mashbuf[c]); + fftf_free(m_prevPhase[c]); + fftf_free(m_prevAdjustedPhase[c]); + + delete m_inbuf[c]; + delete m_outbuf[c]; + } + + fftf_free(m_tempbuf); + fftf_free(m_modulationbuf); + fftf_free(m_prevTransientMag); + + delete[] m_prevPhase; + delete[] m_prevAdjustedPhase; + delete[] m_inbuf; + delete[] m_outbuf; + delete[] m_mashbuf; + delete[] m_time; + delete[] m_freq; + delete[] m_plan; + delete[] m_iplan; + + delete m_analysisWindow; + delete m_synthesisWindow; +} + +void +PhaseVocoderTimeStretcher::setRatio(float ratio) +{ + QMutexLocker locker(m_mutex); + + size_t formerWlen = m_wlen; + m_ratio = ratio; + + std::cerr << "PhaseVocoderTimeStretcher::setRatio: new ratio " << ratio + << std::endl; + + calculateParameters(); + + if (m_wlen == formerWlen) { + + // This is the only container whose size depends on m_ratio + + RingBuffer **newin = new RingBuffer *[m_channels]; + + size_t formerSize = m_inbuf[0]->getSize(); + size_t newSize = lrintf(m_outbuf[0]->getSize() / m_ratio) + m_wlen; + + std::cerr << "resizing inbuf from " << formerSize << " to " + << newSize << " (outbuf size is " << m_outbuf[0]->getSize() << ", ratio " << m_ratio << ")" << std::endl; + + if (formerSize != newSize) { + + size_t ready = m_inbuf[0]->getReadSpace(); + + for (size_t c = 0; c < m_channels; ++c) { + newin[c] = new RingBuffer(newSize); + } + + if (ready > 0) { + + size_t copy = min(ready, newSize); + float *tmp = new float[ready]; + + for (size_t c = 0; c < m_channels; ++c) { + m_inbuf[c]->read(tmp, ready); + newin[c]->write(tmp + ready - copy, copy); + } + + delete[] tmp; + } + + for (size_t c = 0; c < m_channels; ++c) { + delete m_inbuf[c]; + } + + delete[] m_inbuf; + m_inbuf = newin; + } + + } else { + + std::cerr << "wlen changed" << std::endl; + cleanup(); + initialise(); + } +} + +size_t +PhaseVocoderTimeStretcher::getProcessingLatency() const +{ + return getWindowSize() - getInputIncrement(); +} + +size_t +PhaseVocoderTimeStretcher::getRequiredInputSamples() const +{ + QMutexLocker locker(m_mutex); + + if (m_inbuf[0]->getReadSpace() >= m_wlen) return 0; + return m_wlen - m_inbuf[0]->getReadSpace(); +} + +void +PhaseVocoderTimeStretcher::putInput(float **input, size_t samples) +{ + QMutexLocker locker(m_mutex); + + // We need to add samples from input to our internal buffer. When + // we have m_windowSize samples in the buffer, we can process it, + // move the samples back by m_n1 and write the output onto our + // internal output buffer. If we have (samples * ratio) samples + // in that, we can write m_n2 of them back to output and return + // (otherwise we have to write zeroes). + + // When we process, we write m_wlen to our fixed output buffer + // (m_mashbuf). We then pull out the first m_n2 samples from that + // buffer, push them into the output ring buffer, and shift + // m_mashbuf left by that amount. + + // The processing latency is then m_wlen - m_n2. + + size_t consumed = 0; + + while (consumed < samples) { + + size_t writable = m_inbuf[0]->getWriteSpace(); + writable = min(writable, samples - consumed); + + if (writable == 0) { +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "WARNING: PhaseVocoderTimeStretcher::putInput: writable == 0 (inbuf has " << m_inbuf[0]->getReadSpace() << " samples available for reading, space for " << m_inbuf[0]->getWriteSpace() << " more)" << std::endl; +#endif + if (m_inbuf[0]->getReadSpace() < m_wlen || + m_outbuf[0]->getWriteSpace() < m_n2) { + std::cerr << "WARNING: PhaseVocoderTimeStretcher::putInput: Inbuf has " << m_inbuf[0]->getReadSpace() << ", outbuf has space for " << m_outbuf[0]->getWriteSpace() << " (n2 = " << m_n2 << ", wlen = " << m_wlen << "), won't be able to process" << std::endl; + break; + } + } else { + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "writing " << writable << " from index " << consumed << " to inbuf, consumed will be " << consumed + writable << std::endl; +#endif + + for (size_t c = 0; c < m_channels; ++c) { + m_inbuf[c]->write(input[c] + consumed, writable); + } + consumed += writable; + } + + while (m_inbuf[0]->getReadSpace() >= m_wlen && + m_outbuf[0]->getWriteSpace() >= m_n2) { + + // We know we have at least m_wlen samples available + // in m_inbuf. We need to peek m_wlen of them for + // processing, and then read m_n1 to advance the read + // pointer. + + for (size_t c = 0; c < m_channels; ++c) { + + size_t got = m_inbuf[c]->peek(m_tempbuf, m_wlen); + assert(got == m_wlen); + + analyseBlock(c, m_tempbuf); + } + + bool transient = false; + if (shouldSharpen()) transient = isTransient(); + + size_t n2 = m_n2; + + if (transient) { + n2 = m_n1; + } + + ++m_totalCount; + if (transient) ++m_transientCount; + m_n2sum += n2; + +// std::cerr << "ratio for last 10: " < 50 && m_transientCount < m_totalCount) { + + int fixed = lrintf(m_transientCount * m_n1); + + int idealTotal = lrintf(m_totalCount * m_n1 * m_ratio); + int idealSquashy = idealTotal - fixed; + + int squashyCount = m_totalCount - m_transientCount; + + n2 = lrintf(idealSquashy / squashyCount); + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + if (n2 != m_n2) { + std::cerr << m_n2 << " -> " << n2 << std::endl; + } +#endif + } + + for (size_t c = 0; c < m_channels; ++c) { + + synthesiseBlock(c, m_mashbuf[c], + c == 0 ? m_modulationbuf : 0, + m_prevTransient ? m_n1 : m_n2); + + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "writing first " << m_n2 << " from mashbuf, skipping " << m_n1 << " on inbuf " << std::endl; +#endif + m_inbuf[c]->skip(m_n1); + + for (size_t i = 0; i < n2; ++i) { + if (m_modulationbuf[i] > 0.f) { + m_mashbuf[c][i] /= m_modulationbuf[i]; + } + } + + m_outbuf[c]->write(m_mashbuf[c], n2); + + for (size_t i = 0; i < m_wlen - n2; ++i) { + m_mashbuf[c][i] = m_mashbuf[c][i + n2]; + } + + for (size_t i = m_wlen - n2; i < m_wlen; ++i) { + m_mashbuf[c][i] = 0.0f; + } + } + + m_prevTransient = transient; + + for (size_t i = 0; i < m_wlen - n2; ++i) { + m_modulationbuf[i] = m_modulationbuf[i + n2]; + } + + for (size_t i = m_wlen - n2; i < m_wlen; ++i) { + m_modulationbuf[i] = 0.0f; + } + + if (!transient) m_n2 = n2; + } + + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "loop ended: inbuf read space " << m_inbuf[0]->getReadSpace() << ", outbuf write space " << m_outbuf[0]->getWriteSpace() << std::endl; +#endif + } + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "PhaseVocoderTimeStretcher::putInput returning" << std::endl; +#endif + +// std::cerr << "ratio: nominal: " << getRatio() << " actual: " +// << m_total2 << "/" << m_total1 << " = " << float(m_total2) / float(m_total1) << " ideal: " << m_ratio << std::endl; +} + +size_t +PhaseVocoderTimeStretcher::getAvailableOutputSamples() const +{ + QMutexLocker locker(m_mutex); + + return m_outbuf[0]->getReadSpace(); +} + +void +PhaseVocoderTimeStretcher::getOutput(float **output, size_t samples) +{ + QMutexLocker locker(m_mutex); + + if (m_outbuf[0]->getReadSpace() < samples) { + std::cerr << "WARNING: PhaseVocoderTimeStretcher::getOutput: not enough data (yet?) (" << m_outbuf[0]->getReadSpace() << " < " << samples << ")" << std::endl; + size_t fill = samples - m_outbuf[0]->getReadSpace(); + for (size_t c = 0; c < m_channels; ++c) { + for (size_t i = 0; i < fill; ++i) { + output[c][i] = 0.0; + } + m_outbuf[c]->read(output[c] + fill, m_outbuf[c]->getReadSpace()); + } + } else { +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "enough data - writing " << samples << " from outbuf" << std::endl; +#endif + for (size_t c = 0; c < m_channels; ++c) { + m_outbuf[c]->read(output[c], samples); + } + } + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "PhaseVocoderTimeStretcher::getOutput returning" << std::endl; +#endif +} + +void +PhaseVocoderTimeStretcher::analyseBlock(size_t c, float *buf) +{ + size_t i; + + // buf contains m_wlen samples + +#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER + std::cerr << "PhaseVocoderTimeStretcher::analyseBlock (channel " << c << ")" << std::endl; +#endif + + m_analysisWindow->cut(buf); + + for (i = 0; i < m_wlen/2; ++i) { + float temp = buf[i]; + buf[i] = buf[i + m_wlen/2]; + buf[i + m_wlen/2] = temp; + } + + for (i = 0; i < m_wlen; ++i) { + m_time[c][i] = buf[i]; + } + + fftf_execute(m_plan[c]); // m_time -> m_freq +} + +bool +PhaseVocoderTimeStretcher::isTransient() +{ + int count = 0; + + for (size_t i = 0; i <= m_wlen/2; ++i) { + + float real = 0.f, imag = 0.f; + + for (size_t c = 0; c < m_channels; ++c) { + real += m_freq[c][i][0]; + imag += m_freq[c][i][1]; + } + + float sqrmag = (real * real + imag * imag); + + if (m_prevTransientMag[i] > 0.f) { + float diff = 10.f * log10f(sqrmag / m_prevTransientMag[i]); + if (diff > 3.f) ++count; + } + + m_prevTransientMag[i] = sqrmag; + } + + bool isTransient = false; + +// if (count > m_transientThreshold && +// count > m_prevTransientScore * 1.2) { + if (count > m_prevTransientScore && + count > m_transientThreshold && + count - m_prevTransientScore > int(m_wlen) / 20) { + isTransient = true; + + + std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ")" << std::endl; +// } else { +// std::cerr << " !transient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ")" << std::endl; + } + + m_prevTransientScore = count; + + return isTransient; +} + +void +PhaseVocoderTimeStretcher::synthesiseBlock(size_t c, + float *out, + float *modulation, + size_t lastStep) +{ + bool unchanged = (lastStep == m_n1); + + for (size_t i = 0; i <= m_wlen/2; ++i) { + + float phase = princargf(atan2f(m_freq[c][i][1], m_freq[c][i][0])); + float adjustedPhase = phase; + + if (!unchanged) { + + float omega = (2 * M_PI * m_n1 * i) / m_wlen; + + float expectedPhase = m_prevPhase[c][i] + omega; + + float phaseError = princargf(phase - expectedPhase); + + float phaseIncrement = (omega + phaseError) / m_n1; + + adjustedPhase = m_prevAdjustedPhase[c][i] + + lastStep * phaseIncrement; + + float mag = sqrtf(m_freq[c][i][0] * m_freq[c][i][0] + + m_freq[c][i][1] * m_freq[c][i][1]); + + float real = mag * cosf(adjustedPhase); + float imag = mag * sinf(adjustedPhase); + m_freq[c][i][0] = real; + m_freq[c][i][1] = imag; + } + + m_prevPhase[c][i] = phase; + m_prevAdjustedPhase[c][i] = adjustedPhase; + } + + fftf_execute(m_iplan[c]); // m_freq -> m_time, inverse fft + + for (size_t i = 0; i < m_wlen/2; ++i) { + float temp = m_time[c][i]; + m_time[c][i] = m_time[c][i + m_wlen/2]; + m_time[c][i + m_wlen/2] = temp; + } + + for (size_t i = 0; i < m_wlen; ++i) { + m_time[c][i] = m_time[c][i] / m_wlen; + } + + m_synthesisWindow->cut(m_time[c]); + + for (size_t i = 0; i < m_wlen; ++i) { + out[i] += m_time[c][i]; + } + + if (modulation) { + + float area = m_analysisWindow->getArea(); + + for (size_t i = 0; i < m_wlen; ++i) { + float val = m_synthesisWindow->getValue(i); + modulation[i] += val * area; + } + } +} + + diff -r 000000000000 -r fc9323a41f5a sv/audioio/PhaseVocoderTimeStretcher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/PhaseVocoderTimeStretcher.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,187 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PHASE_VOCODER_TIME_STRETCHER_H_ +#define _PHASE_VOCODER_TIME_STRETCHER_H_ + +#include "base/Window.h" +#include "base/RingBuffer.h" + +#include "data/fft/FFTapi.h" + +#include + +/** + * A time stretcher that alters the performance speed of audio, + * preserving pitch. + * + * This is based on the straightforward phase vocoder with phase + * unwrapping (as in e.g. the DAFX book pp275-), with optional + * percussive transient detection to avoid smearing percussive notes + * and resynchronise phases, and adding a stream API for real-time + * use. Principles and methods from Chris Duxbury, AES 2002 and 2004 + * thesis; Emmanuel Ravelli, DAFX 2005; Dan Barry, ISSC 2005 on + * percussion detection; code by Chris Cannam. + */ + +class PhaseVocoderTimeStretcher +{ +public: + PhaseVocoderTimeStretcher(size_t sampleRate, + size_t channels, + float ratio, + bool sharpen, + size_t maxOutputBlockSize); + virtual ~PhaseVocoderTimeStretcher(); + + /** + * Return the number of samples that would need to be added via + * putInput in order to provoke the time stretcher into doing some + * time stretching and making more output samples available. + * This will be an estimate, if transient sharpening is on; the + * caller may need to do the put/get/test cycle more than once. + */ + size_t getRequiredInputSamples() const; + + /** + * Put (and possibly process) a given number of input samples. + * Number should usually equal the value returned from + * getRequiredInputSamples(). + */ + void putInput(float **input, size_t samples); + + /** + * Get the number of processed samples ready for reading. + */ + size_t getAvailableOutputSamples() const; + + /** + * Get some processed samples. + */ + void getOutput(float **output, size_t samples); + + //!!! and reset? + + /** + * Change the time stretch ratio. + */ + void setRatio(float ratio); + + /** + * Get the hop size for input. + */ + size_t getInputIncrement() const { return m_n1; } + + /** + * Get the hop size for output. + */ + size_t getOutputIncrement() const { return m_n2; } + + /** + * Get the window size for FFT processing. + */ + size_t getWindowSize() const { return m_wlen; } + + /** + * Get the stretch ratio. + */ + float getRatio() const { return float(m_n2) / float(m_n1); } + + /** + * Return whether this time stretcher will attempt to sharpen transients. + */ + bool getSharpening() const { return m_sharpen; } + + /** + * Return the number of channels for this time stretcher. + */ + size_t getChannelCount() const { return m_channels; } + + /** + * Get the latency added by the time stretcher, in sample frames. + * This will be exact if transient sharpening is off, or approximate + * if it is on. + */ + size_t getProcessingLatency() const; + +protected: + /** + * Process a single phase vocoder frame from "in" into + * m_freq[channel]. + */ + void analyseBlock(size_t channel, float *in); // into m_freq[channel] + + /** + * Examine m_freq[0..m_channels-1] and return whether a percussive + * transient is found. + */ + bool isTransient(); + + /** + * Resynthesise from m_freq[channel] adding in to "out", + * adjusting phases on the basis of a prior step size of lastStep. + * Also add the window shape in to the modulation array (if + * present) -- for use in ensuring the output has the correct + * magnitude afterwards. + */ + void synthesiseBlock(size_t channel, float *out, float *modulation, + size_t lastStep); + + void initialise(); + void calculateParameters(); + void cleanup(); + + bool shouldSharpen() { + return m_sharpen && (m_ratio > 0.25); + } + + size_t m_sampleRate; + size_t m_channels; + size_t m_maxOutputBlockSize; + float m_ratio; + bool m_sharpen; + size_t m_n1; + size_t m_n2; + size_t m_wlen; + Window *m_analysisWindow; + Window *m_synthesisWindow; + + int m_totalCount; + int m_transientCount; + int m_n2sum; + + float **m_prevPhase; + float **m_prevAdjustedPhase; + + float *m_prevTransientMag; + int m_prevTransientScore; + int m_transientThreshold; + bool m_prevTransient; + + float *m_tempbuf; + float **m_time; + fftf_complex **m_freq; + fftf_plan *m_plan; + fftf_plan *m_iplan; + + RingBuffer **m_inbuf; + RingBuffer **m_outbuf; + float **m_mashbuf; + float *m_modulationbuf; + + QMutex *m_mutex; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/audioio/PlaySpeedRangeMapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/PlaySpeedRangeMapper.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,135 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PlaySpeedRangeMapper.h" + +#include +#include + +#include "system/System.h" + +PlaySpeedRangeMapper::PlaySpeedRangeMapper(int minpos, int maxpos) : + m_minpos(minpos), + m_maxpos(maxpos) +{ +} + +int +PlaySpeedRangeMapper::getPositionForValue(float value) const +{ + // value is percent + float factor = getFactorForValue(value); + int position = getPositionForFactor(factor); + return position; +} + +int +PlaySpeedRangeMapper::getPositionForFactor(float factor) const +{ + bool slow = (factor > 1.0); + + if (!slow) factor = 1.0 / factor; + + int half = (m_maxpos + m_minpos) / 2; + + factor = sqrtf((factor - 1.0) * 1000.f); + int position = lrintf(((factor * (half - m_minpos)) / 100.0) + m_minpos); + + if (slow) { + position = half - position; + } else { + position = position + half; + } + +// std::cerr << "value = " << value << " slow = " << slow << " factor = " << factor << " position = " << position << std::endl; + + return position; +} + +float +PlaySpeedRangeMapper::getValueForPosition(int position) const +{ + float factor = getFactorForPosition(position); + float pc = getValueForFactor(factor); + return pc; +} + +float +PlaySpeedRangeMapper::getValueForFactor(float factor) const +{ + float pc; + if (factor < 1.0) pc = ((1.0 / factor) - 1.0) * 100.0; + else pc = (1.0 - factor) * 100.0; +// std::cerr << "position = " << position << " percent = " << pc << std::endl; + return pc; +} + +float +PlaySpeedRangeMapper::getFactorForValue(float value) const +{ + // value is percent + + float factor; + + if (value <= 0) { + factor = 1.0 - (value / 100.0); + } else { + factor = 1.0 / (1.0 + (value / 100.0)); + } + +// std::cerr << "value = " << value << " factor = " << factor << std::endl; + return factor; +} + +float +PlaySpeedRangeMapper::getFactorForPosition(int position) const +{ + bool slow = false; + + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; + + int half = (m_maxpos + m_minpos) / 2; + + if (position < half) { + slow = true; + position = half - position; + } else { + position = position - half; + } + + // position is between min and half (inclusive) + + float factor; + + if (position == m_minpos) { + factor = 1.0; + } else { + factor = ((position - m_minpos) * 100.0) / (half - m_minpos); + factor = 1.0 + (factor * factor) / 1000.f; + } + + if (!slow) factor = 1.0 / factor; + +// std::cerr << "position = " << position << " slow = " << slow << " factor = " << factor << std::endl; + + return factor; +} + +QString +PlaySpeedRangeMapper::getUnit() const +{ + return "%"; +} diff -r 000000000000 -r fc9323a41f5a sv/audioio/PlaySpeedRangeMapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/audioio/PlaySpeedRangeMapper.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,43 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLAY_SPEED_RANGE_MAPPER_H_ +#define _PLAY_SPEED_RANGE_MAPPER_H_ + +#include "base/RangeMapper.h" + +class PlaySpeedRangeMapper : public RangeMapper +{ +public: + PlaySpeedRangeMapper(int minpos, int maxpos); + + virtual int getPositionForValue(float value) const; + virtual float getValueForPosition(int position) const; + + int getPositionForFactor(float factor) const; + float getValueForFactor(float factor) const; + + float getFactorForPosition(int position) const; + float getFactorForValue(float value) const; + + virtual QString getUnit() const; + +protected: + int m_minpos; + int m_maxpos; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a sv/document/Document.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/document/Document.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,863 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Document.h" + +#include "data/model/WaveFileModel.h" +#include "data/model/WritableWaveFileModel.h" +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "layer/Layer.h" +#include "base/CommandHistory.h" +#include "base/Command.h" +#include "view/View.h" +#include "base/PlayParameterRepository.h" +#include "base/PlayParameters.h" +#include "transform/TransformFactory.h" +#include +#include + +//!!! still need to handle command history, documentRestored/documentModified + +Document::Document() : + m_mainModel(0) +{ + connect(this, SIGNAL(modelAboutToBeDeleted(Model *)), + TransformFactory::getInstance(), + SLOT(modelAboutToBeDeleted(Model *))); +} + +Document::~Document() +{ + //!!! Document should really own the command history. atm we + //still refer to it in various places that don't have access to + //the document, be nice to fix that + +// std::cerr << "\n\nDocument::~Document: about to clear command history" << std::endl; + CommandHistory::getInstance()->clear(); + +// std::cerr << "Document::~Document: about to delete layers" << std::endl; + while (!m_layers.empty()) { + deleteLayer(*m_layers.begin(), true); + } + + if (!m_models.empty()) { + std::cerr << "Document::~Document: WARNING: " + << m_models.size() << " model(s) still remain -- " + << "should have been garbage collected when deleting layers" + << std::endl; + while (!m_models.empty()) { + if (m_models.begin()->first == m_mainModel) { + // just in case! + std::cerr << "Document::~Document: WARNING: Main model is also" + << " in models list!" << std::endl; + } else { + emit modelAboutToBeDeleted(m_models.begin()->first); + delete m_models.begin()->first; + } + m_models.erase(m_models.begin()); + } + } + +// std::cerr << "Document::~Document: About to get rid of main model" +// << std::endl; + emit modelAboutToBeDeleted(m_mainModel); + emit mainModelChanged(0); + delete m_mainModel; + +} + +Layer * +Document::createLayer(LayerFactory::LayerType type) +{ + Layer *newLayer = LayerFactory::getInstance()->createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName(newLayer->objectName())); + + m_layers.insert(newLayer); + emit layerAdded(newLayer); + + return newLayer; +} + +Layer * +Document::createMainModelLayer(LayerFactory::LayerType type) +{ + Layer *newLayer = createLayer(type); + if (!newLayer) return 0; + setModel(newLayer, m_mainModel); + return newLayer; +} + +Layer * +Document::createImportedLayer(Model *model) +{ + LayerFactory::LayerTypeSet types = + LayerFactory::getInstance()->getValidLayerTypes(model); + + if (types.empty()) { + std::cerr << "WARNING: Document::importLayer: no valid display layer for model" << std::endl; + return 0; + } + + //!!! for now, just use the first suitable layer type + LayerFactory::LayerType type = *types.begin(); + + Layer *newLayer = LayerFactory::getInstance()->createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName(newLayer->objectName())); + + addImportedModel(model); + setModel(newLayer, model); + + //!!! and all channels + setChannel(newLayer, -1); + + m_layers.insert(newLayer); + emit layerAdded(newLayer); + return newLayer; +} + +Layer * +Document::createEmptyLayer(LayerFactory::LayerType type) +{ + Model *newModel = + LayerFactory::getInstance()->createEmptyModel(type, m_mainModel); + if (!newModel) return 0; + + Layer *newLayer = createLayer(type); + if (!newLayer) { + delete newModel; + return 0; + } + + addImportedModel(newModel); + setModel(newLayer, newModel); + + return newLayer; +} + +Layer * +Document::createDerivedLayer(LayerFactory::LayerType type, + TransformId transform) +{ + Layer *newLayer = createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName(transform))); + + return newLayer; +} + +Layer * +Document::createDerivedLayer(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Model *newModel = addDerivedModel(transform, inputModel, + context, configurationXml); + if (!newModel) { + // error already printed to stderr by addDerivedModel + emit modelGenerationFailed(transform); + return 0; + } + + LayerFactory::LayerTypeSet types = + LayerFactory::getInstance()->getValidLayerTypes(newModel); + + if (types.empty()) { + std::cerr << "WARNING: Document::createLayerForTransform: no valid display layer for output of transform " << transform.toStdString() << std::endl; + delete newModel; + return 0; + } + + //!!! for now, just use the first suitable layer type + + Layer *newLayer = createLayer(*types.begin()); + setModel(newLayer, newModel); + + //!!! We need to clone the model when adding the layer, so that it + //can be edited without affecting other layers that are based on + //the same model. Unfortunately we can't just clone it now, + //because it probably hasn't been completed yet -- the transform + //runs in the background. Maybe the transform has to handle + //cloning and cacheing models itself. + // + // Once we do clone models here, of course, we'll have to avoid + // leaking them too. + // + // We want the user to be able to add a model to a second layer + // _while it's still being calculated in the first_ and have it + // work quickly. That means we need to put the same physical + // model pointer in both layers, so they can't actually be cloned. + + if (newLayer) { + newLayer->setObjectName(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName(transform))); + } + + emit layerAdded(newLayer); + return newLayer; +} + +void +Document::setMainModel(WaveFileModel *model) +{ + Model *oldMainModel = m_mainModel; + m_mainModel = model; + + emit modelAdded(m_mainModel); + + std::vector obsoleteLayers; + std::set failedTransforms; + + // We need to ensure that no layer is left using oldMainModel or + // any of the old derived models as its model. Either replace the + // model, or delete the layer for each layer that is currently + // using one of these. Carry out this replacement before we + // delete any of the models. + + for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + Layer *layer = *i; + Model *model = layer->getModel(); + +// std::cerr << "Document::setMainModel: inspecting model " +// << (model ? model->objectName().toStdString() : "(null)") << " in layer " +// << layer->objectName().toStdString() << std::endl; + + if (model == oldMainModel) { +// std::cerr << "... it uses the old main model, replacing" << std::endl; + LayerFactory::getInstance()->setModel(layer, m_mainModel); + continue; + } + + if (m_models.find(model) == m_models.end()) { + std::cerr << "WARNING: Document::setMainModel: Unknown model " + << model << " in layer " << layer << std::endl; + // get rid of this hideous degenerate + obsoleteLayers.push_back(layer); + continue; + } + + if (m_models[model].source == oldMainModel) { + +// std::cerr << "... it uses a model derived from the old main model, regenerating" << std::endl; + + // This model was derived from the previous main + // model: regenerate it. + + TransformId transform = m_models[model].transform; + PluginTransform::ExecutionContext context = m_models[model].context; + + Model *replacementModel = + addDerivedModel(transform, + m_mainModel, + context, + m_models[model].configurationXml); + + if (!replacementModel) { + std::cerr << "WARNING: Document::setMainModel: Failed to regenerate model for transform \"" + << transform.toStdString() << "\"" << " in layer " << layer << std::endl; + if (failedTransforms.find(transform) == failedTransforms.end()) { + emit modelRegenerationFailed(layer->objectName(), + transform); + failedTransforms.insert(transform); + } + obsoleteLayers.push_back(layer); + } else { + std::cerr << "Replacing model " << model << " (type " + << typeid(*model).name() << ") with model " + << replacementModel << " (type " + << typeid(*replacementModel).name() << ") in layer " + << layer << " (name " << layer->objectName().toStdString() << ")" + << std::endl; + RangeSummarisableTimeValueModel *rm = + dynamic_cast(replacementModel); + if (rm) { + std::cerr << "new model has " << rm->getChannelCount() << " channels " << std::endl; + } else { + std::cerr << "new model is not a RangeSummarisableTimeValueModel!" << std::endl; + } + setModel(layer, replacementModel); + } + } + } + + for (size_t k = 0; k < obsoleteLayers.size(); ++k) { + deleteLayer(obsoleteLayers[k], true); + } + + emit mainModelChanged(m_mainModel); + + // we already emitted modelAboutToBeDeleted for this + delete oldMainModel; +} + +void +Document::addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + Model *outputModelToAdd, + QString configurationXml) +{ + if (m_models.find(outputModelToAdd) != m_models.end()) { + std::cerr << "WARNING: Document::addDerivedModel: Model already added" + << std::endl; + return; + } + +// std::cerr << "Document::addDerivedModel: source is " << inputModel << " \"" << inputModel->objectName().toStdString() << "\"" << std::endl; + + ModelRecord rec; + rec.source = inputModel; + rec.transform = transform; + rec.context = context; + rec.configurationXml = configurationXml; + rec.refcount = 0; + + m_models[outputModelToAdd] = rec; + + emit modelAdded(outputModelToAdd); +} + + +void +Document::addImportedModel(Model *model) +{ + if (m_models.find(model) != m_models.end()) { + std::cerr << "WARNING: Document::addImportedModel: Model already added" + << std::endl; + return; + } + + ModelRecord rec; + rec.source = 0; + rec.transform = ""; + rec.refcount = 0; + + m_models[model] = rec; + + emit modelAdded(model); +} + +Model * +Document::addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Model *model = 0; + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + if (i->second.transform == transform && + i->second.source == inputModel && + i->second.context == context && + i->second.configurationXml == configurationXml) { + return i->first; + } + } + + model = TransformFactory::getInstance()->transform + (transform, inputModel, context, configurationXml); + + if (!model) { + std::cerr << "WARNING: Document::addDerivedModel: no output model for transform " << transform.toStdString() << std::endl; + } else { + addDerivedModel(transform, inputModel, context, model, configurationXml); + } + + return model; +} + +void +Document::releaseModel(Model *model) // Will _not_ release main model! +{ + if (model == 0) { + return; + } + + if (model == m_mainModel) { + return; + } + + bool toDelete = false; + + if (m_models.find(model) != m_models.end()) { + + if (m_models[model].refcount == 0) { + std::cerr << "WARNING: Document::releaseModel: model " << model + << " reference count is zero already!" << std::endl; + } else { + if (--m_models[model].refcount == 0) { + toDelete = true; + } + } + } else { + std::cerr << "WARNING: Document::releaseModel: Unfound model " + << model << std::endl; + toDelete = true; + } + + if (toDelete) { + + int sourceCount = 0; + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + if (i->second.source == model) { + ++sourceCount; + i->second.source = 0; + } + } + + if (sourceCount > 0) { + std::cerr << "Document::releaseModel: Deleting model " + << model << " even though it is source for " + << sourceCount << " other derived model(s) -- resetting " + << "their source fields appropriately" << std::endl; + } + + emit modelAboutToBeDeleted(model); + m_models.erase(model); + delete model; + } +} + +void +Document::deleteLayer(Layer *layer, bool force) +{ + if (m_layerViewMap.find(layer) != m_layerViewMap.end() && + m_layerViewMap[layer].size() > 0) { + + std::cerr << "WARNING: Document::deleteLayer: Layer " + << layer << " [" << layer->objectName().toStdString() << "]" + << " is still used in " << m_layerViewMap[layer].size() + << " views!" << std::endl; + + if (force) { + + std::cerr << "(force flag set -- deleting from all views)" << std::endl; + + for (std::set::iterator j = m_layerViewMap[layer].begin(); + j != m_layerViewMap[layer].end(); ++j) { + // don't use removeLayerFromView, as it issues a command + layer->setLayerDormant(*j, true); + (*j)->removeLayer(layer); + } + + m_layerViewMap.erase(layer); + + } else { + return; + } + } + + if (m_layers.find(layer) == m_layers.end()) { + std::cerr << "Document::deleteLayer: Layer " + << layer << " does not exist, or has already been deleted " + << "(this may not be as serious as it sounds)" << std::endl; + return; + } + + m_layers.erase(layer); + + releaseModel(layer->getModel()); + emit layerRemoved(layer); + emit layerAboutToBeDeleted(layer); + delete layer; +} + +void +Document::setModel(Layer *layer, Model *model) +{ + if (model && + model != m_mainModel && + m_models.find(model) == m_models.end()) { + std::cerr << "ERROR: Document::setModel: Layer " << layer + << " (\"" << layer->objectName().toStdString() + << "\") wants to use unregistered model " << model + << ": register the layer's model before setting it!" + << std::endl; + return; + } + + Model *previousModel = layer->getModel(); + + if (previousModel == model) { + std::cerr << "WARNING: Document::setModel: Layer " << layer << " (\"" + << layer->objectName().toStdString() + << "\") is already set to model " + << model << " (\"" + << (model ? model->objectName().toStdString() : "(null)") + << "\")" << std::endl; + return; + } + + if (model && model != m_mainModel) { + m_models[model].refcount ++; + } + + LayerFactory::getInstance()->setModel(layer, model); + + if (previousModel) { + releaseModel(previousModel); + } +} + +void +Document::setChannel(Layer *layer, int channel) +{ + LayerFactory::getInstance()->setChannel(layer, channel); +} + +void +Document::addLayerToView(View *view, Layer *layer) +{ + Model *model = layer->getModel(); + if (!model) { +// std::cerr << "Document::addLayerToView: Layer (\"" +// << layer->objectName().toStdString() +// << "\") with no model being added to view: " +// << "normally you want to set the model first" << std::endl; + } else { + if (model != m_mainModel && + m_models.find(model) == m_models.end()) { + std::cerr << "ERROR: Document::addLayerToView: Layer " << layer + << " has unregistered model " << model + << " -- register the layer's model before adding the layer!" << std::endl; + return; + } + } + + CommandHistory::getInstance()->addCommand + (new Document::AddLayerCommand(this, view, layer)); +} + +void +Document::removeLayerFromView(View *view, Layer *layer) +{ + CommandHistory::getInstance()->addCommand + (new Document::RemoveLayerCommand(this, view, layer)); +} + +void +Document::addToLayerViewMap(Layer *layer, View *view) +{ + bool firstView = (m_layerViewMap.find(layer) == m_layerViewMap.end() || + m_layerViewMap[layer].empty()); + + if (m_layerViewMap[layer].find(view) != + m_layerViewMap[layer].end()) { + std::cerr << "WARNING: Document::addToLayerViewMap:" + << " Layer " << layer << " -> view " << view << " already in" + << " layer view map -- internal inconsistency" << std::endl; + } + + m_layerViewMap[layer].insert(view); + + if (firstView) emit layerInAView(layer, true); +} + +void +Document::removeFromLayerViewMap(Layer *layer, View *view) +{ + if (m_layerViewMap[layer].find(view) == + m_layerViewMap[layer].end()) { + std::cerr << "WARNING: Document::removeFromLayerViewMap:" + << " Layer " << layer << " -> view " << view << " not in" + << " layer view map -- internal inconsistency" << std::endl; + } + + m_layerViewMap[layer].erase(view); + + if (m_layerViewMap[layer].empty()) { + m_layerViewMap.erase(layer); + emit layerInAView(layer, false); + } +} + +QString +Document::getUniqueLayerName(QString candidate) +{ + for (int count = 1; ; ++count) { + + QString adjusted = + (count > 1 ? QString("%1 <%2>").arg(candidate).arg(count) : + candidate); + + bool duplicate = false; + + for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if ((*i)->objectName() == adjusted) { + duplicate = true; + break; + } + } + + if (!duplicate) return adjusted; + } +} + +std::vector +Document::getTransformInputModels() +{ + std::vector models; + + if (!m_mainModel) return models; + + models.push_back(m_mainModel); + + //!!! This will pick up all models, including those that aren't visible... + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + + Model *model = i->first; + if (!model || model == m_mainModel) continue; + DenseTimeValueModel *dtvm = dynamic_cast(model); + + if (dtvm) { + models.push_back(dtvm); + } + } + + return models; +} + +Document::AddLayerCommand::AddLayerCommand(Document *d, + View *view, + Layer *layer) : + m_d(d), + m_view(view), + m_layer(layer), + m_name(qApp->translate("AddLayerCommand", "Add %1 Layer").arg(layer->objectName())), + m_added(false) +{ +} + +Document::AddLayerCommand::~AddLayerCommand() +{ +// std::cerr << "Document::AddLayerCommand::~AddLayerCommand" << std::endl; + if (!m_added) { + m_d->deleteLayer(m_layer); + } +} + +void +Document::AddLayerCommand::execute() +{ + for (int i = 0; i < m_view->getLayerCount(); ++i) { + if (m_view->getLayer(i) == m_layer) { + // already there + m_layer->setLayerDormant(m_view, false); + m_added = true; + return; + } + } + + m_view->addLayer(m_layer); + m_layer->setLayerDormant(m_view, false); + + m_d->addToLayerViewMap(m_layer, m_view); + m_added = true; +} + +void +Document::AddLayerCommand::unexecute() +{ + m_view->removeLayer(m_layer); + m_layer->setLayerDormant(m_view, true); + + m_d->removeFromLayerViewMap(m_layer, m_view); + m_added = false; +} + +Document::RemoveLayerCommand::RemoveLayerCommand(Document *d, + View *view, + Layer *layer) : + m_d(d), + m_view(view), + m_layer(layer), + m_name(qApp->translate("RemoveLayerCommand", "Delete %1 Layer").arg(layer->objectName())), + m_added(true) +{ +} + +Document::RemoveLayerCommand::~RemoveLayerCommand() +{ +// std::cerr << "Document::RemoveLayerCommand::~RemoveLayerCommand" << std::endl; + if (!m_added) { + m_d->deleteLayer(m_layer); + } +} + +void +Document::RemoveLayerCommand::execute() +{ + bool have = false; + for (int i = 0; i < m_view->getLayerCount(); ++i) { + if (m_view->getLayer(i) == m_layer) { + have = true; + break; + } + } + + if (!have) { // not there! + m_layer->setLayerDormant(m_view, true); + m_added = false; + return; + } + + m_view->removeLayer(m_layer); + m_layer->setLayerDormant(m_view, true); + + m_d->removeFromLayerViewMap(m_layer, m_view); + m_added = false; +} + +void +Document::RemoveLayerCommand::unexecute() +{ + m_view->addLayer(m_layer); + m_layer->setLayerDormant(m_view, false); + + m_d->addToLayerViewMap(m_layer, m_view); + m_added = true; +} + +void +Document::toXml(QTextStream &out, QString indent, QString extraAttributes) const +{ + out << indent + QString("\n") + .arg(extraAttributes == "" ? "" : " ").arg(extraAttributes); + + if (m_mainModel) { + m_mainModel->toXml(out, indent + " ", "mainModel=\"true\""); + } + + // Models that are not used in a layer that is in a view should + // not be written. Get our list of required models first. + + std::set used; + + for (LayerViewMap::const_iterator i = m_layerViewMap.begin(); + i != m_layerViewMap.end(); ++i) { + + if (i->first && !i->second.empty() && i->first->getModel()) { + used.insert(i->first->getModel()); + } + } + + for (ModelMap::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { + + const Model *model = i->first; + const ModelRecord &rec = i->second; + + if (used.find(model) == used.end()) continue; + + // We need an intelligent way to determine which models need + // to be streamed (i.e. have been edited, or are small) and + // which should not be (i.e. remain as generated by a + // transform, and are large). + // + // At the moment we can get away with deciding not to stream + // dense 3d models or writable wave file models, provided they + // were generated from a transform, because at the moment there + // is no way to edit those model types so it should be safe to + // regenerate them. That won't always work in future though. + // It would be particularly nice to be able to ask the user, + // as well as making an intelligent guess. + + bool writeModel = true; + bool haveDerivation = false; + + if (rec.source && rec.transform != "") { + haveDerivation = true; + } + + if (haveDerivation) { + if (dynamic_cast(model)) { + writeModel = false; + } else if (dynamic_cast(model)) { + writeModel = false; + } + } + + if (writeModel) { + i->first->toXml(out, indent + " "); + } + + if (haveDerivation) { + + //!!! stream the rest of the execution context in both directions (i.e. not just channel) + + out << indent; + out << QString(" first)) + .arg(rec.context.channel) + .arg(rec.context.domain) + .arg(rec.context.stepSize) + .arg(rec.context.blockSize) + .arg(int(rec.context.windowType)) + .arg(XmlExportable::encodeEntities(rec.transform)); + + if (rec.configurationXml != "") { + out << ">\n " + indent + rec.configurationXml + + "\n" + indent + " \n"; + } else { + out << "/>\n"; + } + } + + //!!! We should probably own the PlayParameterRepository + PlayParameters *playParameters = + PlayParameterRepository::getInstance()->getPlayParameters(i->first); + if (playParameters) { + playParameters->toXml + (out, indent + " ", + QString("model=\"%1\"") + .arg(XmlExportable::getObjectExportId(i->first))); + } + } + + for (LayerSet::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + (*i)->toXml(out, indent + " "); + } + + out << indent + "\n"; +} + +QString +Document::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + { + QTextStream out(&s); + toXml(out, indent, extraAttributes); + } + + return s; +} + diff -r 000000000000 -r fc9323a41f5a sv/document/Document.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/document/Document.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,308 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DOCUMENT_H_ +#define _DOCUMENT_H_ + +#include "layer/LayerFactory.h" +#include "transform/Transform.h" +#include "transform/PluginTransform.h" +#include "base/Command.h" + +#include +#include + +class Model; +class Layer; +class View; +class WaveFileModel; + +/** + * A Sonic Visualiser document consists of a set of data models, and + * also the visualisation layers used to display them. Changes to the + * layers and their layout need to be stored and managed in much the + * same way as changes to the underlying data. + * + * The document manages: + * + * - A main data Model, which provides the underlying sample rate and + * such like. This must be a WaveFileModel. + * + * - Any number of imported Model objects, which contain data without any + * requirement to remember where the data came from or how to + * regenerate it. + * + * - Any number of Model objects that were generated by a Transform + * such as FeatureExtractionPluginTransform. For these, we also + * record the source model and the name of the transform used to + * generate the model so that we can regenerate it (potentially + * from a different source) on demand. + * + * - A flat list of Layer objects. Elsewhere, the GUI may distribute these + * across any number of View widgets. A layer may be viewable on more + * than one view at once, in principle. A layer refers to one model, + * but the same model can be in use in more than one layer. + * + * The document does *not* manage the existence or structure of Pane + * and other view widgets. However, it does provide convenience + * methods for reference-counted command-based management of the + * association between layers and views (addLayerToView, + * removeLayerFromView). + */ + +class Document : public QObject, + public XmlExportable +{ + Q_OBJECT + +public: + Document(); + virtual ~Document(); + + /** + * Create and return a new layer of the given type, associated + * with no model. The caller may set any model on this layer, but + * the model must also be registered with the document via the + * add-model methods below. + */ + Layer *createLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer of the given type, associated + * with the current main model (if appropriate to the layer type). + */ + Layer *createMainModelLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer associated with the given model, + * and register the model as an imported model. + */ + Layer *createImportedLayer(Model *); + + /** + * Create and return a new layer of the given type, with an + * appropriate empty model. If the given type is not one for + * which an empty model can meaningfully be created, return 0. + */ + Layer *createEmptyLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer of the given type, associated + * with the given transform name. This method does not run the + * transform itself, nor create a model. The caller can safely + * add a model to the layer later, but note that all models used + * by a transform layer _must_ be registered with the document + * using addDerivedModel below. + */ + Layer *createDerivedLayer(LayerFactory::LayerType, TransformId); + + /** + * Create and return a suitable layer for the given transform, + * running the transform and associating the resulting model with + * the new layer. + */ + Layer *createDerivedLayer(TransformId, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml); + + /** + * Set the main model (the source for playback sample rate, etc) + * to the given wave file model. This will regenerate any derived + * models that were based on the previous main model. + */ + void setMainModel(WaveFileModel *); + + /** + * Get the main model (the source for playback sample rate, etc). + */ + WaveFileModel *getMainModel() { return m_mainModel; } + + /** + * Get the main model (the source for playback sample rate, etc). + */ + const WaveFileModel *getMainModel() const { return m_mainModel; } + + std::vector getTransformInputModels(); + + /** + * Add a derived model associated with the given transform, + * running the transform and returning the resulting model. + */ + Model *addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml); + + /** + * Add a derived model associated with the given transform. This + * is necessary to register any derived model that was not created + * by the document using createDerivedModel or createDerivedLayer. + */ + void addDerivedModel(TransformId, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + Model *outputModelToAdd, + QString configurationXml); + + /** + * Add an imported (non-derived, non-main) model. This is + * necessary to register any imported model that is associated + * with a layer. + */ + void addImportedModel(Model *); + + /** + * Associate the given model with the given layer. The model must + * have already been registered using one of the addXXModel + * methods above. + */ + void setModel(Layer *, Model *); + + /** + * Set the given layer to use the given channel of its model (-1 + * means all available channels). + */ + void setChannel(Layer *, int); + + /** + * Add the given layer to the given view. If the layer is + * intended to show a particular model, the model should normally + * be set using setModel before this method is called. + */ + void addLayerToView(View *, Layer *); + + /** + * Remove the given layer from the given view. + */ + void removeLayerFromView(View *, Layer *); + + void toXml(QTextStream &, QString indent, QString extraAttributes) const; + QString toXmlString(QString indent, QString extraAttributes) const; + +signals: + void layerAdded(Layer *); + void layerRemoved(Layer *); + void layerAboutToBeDeleted(Layer *); + + // Emitted when a layer is first added to a view, or when it is + // last removed from a view + void layerInAView(Layer *, bool); + + void modelAdded(Model *); + void mainModelChanged(WaveFileModel *); // emitted after modelAdded + void modelAboutToBeDeleted(Model *); + + void modelGenerationFailed(QString transformName); + void modelRegenerationFailed(QString layerName, QString transformName); + +protected: + void releaseModel(Model *model); + + /** + * Delete the given layer, and also its associated model if no + * longer used by any other layer. In general, this should be the + * only method used to delete layers -- doing so directly is a bit + * of a social gaffe. + */ + void deleteLayer(Layer *, bool force = false); + + /* + * Every model that is in use by a layer in the document must be + * found in either m_mainModel, m_derivedModels or + * m_importedModels. We own and control the lifespan of all of + * these models. + */ + + /** + * The model that provides the underlying sample rate, etc. This + * model is not reference counted for layers, and is not freed + * unless it is replaced or the document is deleted. + */ + WaveFileModel *m_mainModel; + + struct ModelRecord + { + // Information associated with a non-main model. If this + // model is derived from another, then source will be non-NULL + // and the transform name will be set appropriately. If the + // transform name is set but source is NULL, then there was a + // transform involved but the (target) model has been modified + // since being generated from it. + const Model *source; + TransformId transform; + PluginTransform::ExecutionContext context; + QString configurationXml; + + // Count of the number of layers using this model. + int refcount; + }; + + typedef std::map ModelMap; + ModelMap m_models; + + class AddLayerCommand : public Command + { + public: + AddLayerCommand(Document *d, View *view, Layer *layer); + virtual ~AddLayerCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const { return m_name; } + + protected: + Document *m_d; + View *m_view; // I don't own this + Layer *m_layer; // Document owns this, but I determine its lifespans + QString m_name; + bool m_added; + }; + + class RemoveLayerCommand : public Command + { + public: + RemoveLayerCommand(Document *d, View *view, Layer *layer); + virtual ~RemoveLayerCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const { return m_name; } + + protected: + Document *m_d; + View *m_view; // I don't own this + Layer *m_layer; // Document owns this, but I determine its lifespan + QString m_name; + bool m_added; + }; + + typedef std::map > LayerViewMap; + LayerViewMap m_layerViewMap; + + void addToLayerViewMap(Layer *, View *); + void removeFromLayerViewMap(Layer *, View *); + + QString getUniqueLayerName(QString candidate); + + /** + * And these are the layers. We also control the lifespans of + * these (usually through the commands used to add and remove them). + */ + typedef std::set LayerSet; + LayerSet m_layers; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/document/SVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/document/SVFileReader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1070 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SVFileReader.h" + +#include "layer/Layer.h" +#include "view/View.h" +#include "base/PlayParameters.h" +#include "base/PlayParameterRepository.h" + +#include "data/fileio/AudioFileReaderFactory.h" +#include "data/fileio/FileFinder.h" +#include "data/fileio/RemoteFile.h" + +#include "data/model/WaveFileModel.h" +#include "data/model/EditableDenseThreeDimensionalModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/NoteModel.h" +#include "data/model/TextModel.h" + +#include "view/Pane.h" + +#include "Document.h" + +#include +#include +#include + +#include + +SVFileReader::SVFileReader(Document *document, + SVFileReaderPaneCallback &callback, + QString location) : + m_document(document), + m_paneCallback(callback), + m_location(location), + m_currentPane(0), + m_currentDataset(0), + m_currentDerivedModel(0), + m_currentDerivedModelId(-1), + m_currentPlayParameters(0), + m_datasetSeparator(" "), + m_inRow(false), + m_rowNumber(0), + m_ok(false) +{ +} + +void +SVFileReader::parse(const QString &xmlData) +{ + QXmlInputSource inputSource; + inputSource.setData(xmlData); + parse(inputSource); +} + +void +SVFileReader::parse(QXmlInputSource &inputSource) +{ + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + m_ok = reader.parse(inputSource); +} + +bool +SVFileReader::isOK() +{ + return m_ok; +} + +SVFileReader::~SVFileReader() +{ + if (!m_awaitingDatasets.empty()) { + std::cerr << "WARNING: SV-XML: File ended with " + << m_awaitingDatasets.size() << " unfilled model dataset(s)" + << std::endl; + } + + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + if (!unaddedModels.empty()) { + std::cerr << "WARNING: SV-XML: File contained " + << unaddedModels.size() << " unused models" + << std::endl; + while (!unaddedModels.empty()) { + delete *unaddedModels.begin(); + unaddedModels.erase(unaddedModels.begin()); + } + } +} + +bool +SVFileReader::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString name = qName.toLower(); + + bool ok = false; + + // Valid element names: + // + // sv + // data + // dataset + // display + // derivation + // playparameters + // layer + // model + // point + // row + // view + // window + + if (name == "sv") { + + // nothing needed + ok = true; + + } else if (name == "data") { + + // nothing needed + m_inData = true; + ok = true; + + } else if (name == "display") { + + // nothing needed + ok = true; + + } else if (name == "window") { + + ok = readWindow(attributes); + + } else if (name == "model") { + + ok = readModel(attributes); + + } else if (name == "dataset") { + + ok = readDatasetStart(attributes); + + } else if (name == "bin") { + + ok = addBinToDataset(attributes); + + } else if (name == "point") { + + ok = addPointToDataset(attributes); + + } else if (name == "row") { + + ok = addRowToDataset(attributes); + + } else if (name == "layer") { + + addUnaddedModels(); // all models must be specified before first layer + ok = readLayer(attributes); + + } else if (name == "view") { + + m_inView = true; + ok = readView(attributes); + + } else if (name == "derivation") { + + ok = readDerivation(attributes); + + } else if (name == "playparameters") { + + ok = readPlayParameters(attributes); + + } else if (name == "plugin") { + + ok = readPlugin(attributes); + + } else if (name == "selections") { + + m_inSelections = true; + ok = true; + + } else if (name == "selection") { + + ok = readSelection(attributes); + } + + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to completely process element \"" + << name.toLocal8Bit().data() << "\"" << std::endl; + } + + return true; +} + +bool +SVFileReader::characters(const QString &text) +{ + bool ok = false; + + if (m_inRow) { + ok = readRowData(text); + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to read row data content for row " << m_rowNumber << std::endl; + } + } + + return true; +} + +bool +SVFileReader::endElement(const QString &, const QString &, + const QString &qName) +{ + QString name = qName.toLower(); + + if (name == "dataset") { + + if (m_currentDataset) { + + bool foundInAwaiting = false; + + for (std::map::iterator i = m_awaitingDatasets.begin(); + i != m_awaitingDatasets.end(); ++i) { + if (m_models[i->second] == m_currentDataset) { + m_awaitingDatasets.erase(i); + foundInAwaiting = true; + break; + } + } + + if (!foundInAwaiting) { + std::cerr << "WARNING: SV-XML: Dataset precedes model, or no model uses dataset" << std::endl; + } + } + + m_currentDataset = 0; + + } else if (name == "data") { + + addUnaddedModels(); + m_inData = false; + + } else if (name == "derivation") { + + if (!m_currentDerivedModel) { + if (m_currentDerivedModel < 0) { + std::cerr << "WARNING: SV-XML: Bad derivation output model id " + << m_currentDerivedModelId << std::endl; + } else if (m_models[m_currentDerivedModelId]) { + std::cerr << "WARNING: SV-XML: Derivation has existing model " + << m_currentDerivedModelId + << " as target, not regenerating" << std::endl; + } else { + m_currentDerivedModel = m_models[m_currentDerivedModelId] = + m_document->addDerivedModel(m_currentTransform, + m_currentTransformSource, + m_currentTransformContext, + m_currentTransformConfiguration); + } + } else { + m_document->addDerivedModel(m_currentTransform, + m_currentTransformSource, + m_currentTransformContext, + m_currentDerivedModel, + m_currentTransformConfiguration); + } + + m_addedModels.insert(m_currentDerivedModel); + m_currentDerivedModel = 0; + m_currentDerivedModelId = -1; + m_currentTransform = ""; + m_currentTransformConfiguration = ""; + + } else if (name == "row") { + m_inRow = false; + } else if (name == "view") { + m_inView = false; + } else if (name == "selections") { + m_inSelections = false; + } else if (name == "playparameters") { + m_currentPlayParameters = 0; + } + + return true; +} + +bool +SVFileReader::error(const QXmlParseException &exception) +{ + m_errorString = + QString("ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::error(exception); +} + +bool +SVFileReader::fatalError(const QXmlParseException &exception) +{ + m_errorString = + QString("FATAL ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::fatalError(exception); +} + + +#define READ_MANDATORY(TYPE, NAME, CONVERSION) \ + TYPE NAME = attributes.value(#NAME).trimmed().CONVERSION(&ok); \ + if (!ok) { \ + std::cerr << "WARNING: SV-XML: Missing or invalid mandatory " #TYPE " attribute \"" #NAME "\"" << std::endl; \ + return false; \ + } + +bool +SVFileReader::readWindow(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, width, toInt); + READ_MANDATORY(int, height, toInt); + + m_paneCallback.setWindowSize(width, height); + return true; +} + +void +SVFileReader::addUnaddedModels() +{ + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + for (std::set::iterator i = unaddedModels.begin(); + i != unaddedModels.end(); ++i) { + m_document->addImportedModel(*i); + m_addedModels.insert(*i); + } +} + +bool +SVFileReader::readModel(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + + if (m_models.find(id) != m_models.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate model id " << id + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + READ_MANDATORY(int, sampleRate, toInt); + + QString type = attributes.value("type").trimmed(); + bool mainModel = (attributes.value("mainModel").trimmed() == "true"); + + if (type == "wavefile") { + + WaveFileModel *model = 0; + FileFinder *ff = FileFinder::getInstance(); + QString originalPath = attributes.value("file"); + QString path = ff->find(FileFinder::AudioFile, + originalPath, m_location); + QUrl url(path); + + if (RemoteFile::canHandleScheme(url)) { + + RemoteFile rf(url); + rf.wait(); + + if (rf.isOK()) { + + model = new WaveFileModel(rf.getLocalFilename(), path); + if (!model->isOK()) { + delete model; + model = 0; + //!!! and delete local file? + } + } + } else { + + model = new WaveFileModel(path); + if (!model->isOK()) { + delete model; + model = 0; + } + } + + if (!model) return false; + + m_models[id] = model; + if (mainModel) { + m_document->setMainModel(model); + m_addedModels.insert(model); + } + // Derived models will be added when their derivation + // is found. + + return true; + + } else if (type == "dense") { + + READ_MANDATORY(int, dimensions, toInt); + + // Currently the only dense model we support here is the dense + // 3d model. Dense time-value models are always file-backed + // waveform data, at this point, and they come in as wavefile + // models. + + if (dimensions == 3) { + + READ_MANDATORY(int, windowSize, toInt); + READ_MANDATORY(int, yBinCount, toInt); + + EditableDenseThreeDimensionalModel *model = + new EditableDenseThreeDimensionalModel + (sampleRate, windowSize, yBinCount); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + if (ok) model->setMinimumLevel(minimum); + + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + if (ok) model->setMaximumLevel(maximum); + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + m_models[id] = model; + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected dense model dimension (" + << dimensions << ")" << std::endl; + } + } else if (type == "sparse") { + + READ_MANDATORY(int, dimensions, toInt); + + if (dimensions == 1) { + + READ_MANDATORY(int, resolution, toInt); + + SparseOneDimensionalModel *model = new SparseOneDimensionalModel + (sampleRate, resolution); + m_models[id] = model; + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else if (dimensions == 2 || dimensions == 3) { + + READ_MANDATORY(int, resolution, toInt); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + float valueQuantization = + attributes.value("valueQuantization").trimmed().toFloat(&ok); + + bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true"); + + QString units = attributes.value("units"); + + if (dimensions == 2) { + if (attributes.value("subtype") == "text") { + TextModel *model = new TextModel + (sampleRate, resolution, notifyOnAdd); + m_models[id] = model; + } else { + SparseTimeValueModel *model = new SparseTimeValueModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setScaleUnits(units); + m_models[id] = model; + } + } else { + NoteModel *model = new NoteModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setValueQuantization(valueQuantization); + model->setScaleUnits(units); + m_models[id] = model; + } + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected sparse model dimension (" + << dimensions << ")" << std::endl; + } + } else { + + std::cerr << "WARNING: SV-XML: Unexpected model type \"" + << type.toLocal8Bit().data() << "\" for model id " << id << std::endl; + } + + return false; +} + +bool +SVFileReader::readView(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + m_currentPane = 0; + + if (type != "pane") { + std::cerr << "WARNING: SV-XML: Unexpected view type \"" + << type.toLocal8Bit().data() << "\"" << std::endl; + return false; + } + + m_currentPane = m_paneCallback.addPane(); + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: Internal error: Failed to add pane!" + << std::endl; + return false; + } + + bool ok = false; + + View *view = m_currentPane; + + // The view properties first + + READ_MANDATORY(size_t, centre, toUInt); + READ_MANDATORY(size_t, zoom, toUInt); + READ_MANDATORY(int, followPan, toInt); + READ_MANDATORY(int, followZoom, toInt); + QString tracking = attributes.value("tracking"); + + // Specify the follow modes before we set the actual values + view->setFollowGlobalPan(followPan); + view->setFollowGlobalZoom(followZoom); + view->setPlaybackFollow(tracking == "scroll" ? PlaybackScrollContinuous : + tracking == "page" ? PlaybackScrollPage + : PlaybackIgnore); + + // Then set these values + view->setCentreFrame(centre); + view->setZoomLevel(zoom); + + // And pane properties + READ_MANDATORY(int, centreLineVisible, toInt); + m_currentPane->setCentreLineVisible(centreLineVisible); + + int height = attributes.value("height").toInt(&ok); + if (ok) { + m_currentPane->resize(m_currentPane->width(), height); + } + + return true; +} + +bool +SVFileReader::readLayer(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + + int id; + bool ok = false; + id = attributes.value("id").trimmed().toInt(&ok); + + if (!ok) { + std::cerr << "WARNING: SV-XML: No layer id for layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + Layer *layer = 0; + bool isNewLayer = false; + + // Layers are expected to be defined in layer elements in the data + // section, and referred to in layer elements in the view + // sections. So if we're in the data section, we expect this + // layer not to exist already; if we're in the view section, we + // expect it to exist. + + if (m_inData) { + + if (m_layers.find(id) != m_layers.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate layer id " << id + << " in data section" << std::endl; + return false; + } + + layer = m_layers[id] = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + + } else { + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: No current pane for layer " << id + << " in view section" << std::endl; + return false; + } + + if (m_layers.find(id) != m_layers.end()) { + + layer = m_layers[id]; + + } else { + std::cerr << "WARNING: SV-XML: Layer id " << id + << " in view section has not been defined -- defining it here" + << std::endl; + + layer = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + } + } + + if (!layer) { + std::cerr << "WARNING: SV-XML: Failed to add layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + if (isNewLayer) { + + QString name = attributes.value("name"); + layer->setObjectName(name); + + int modelId; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (modelOk) { + if (m_models.find(modelId) != m_models.end()) { + Model *model = m_models[modelId]; + m_document->setModel(layer, model); + } else { + std::cerr << "WARNING: SV-XML: Unknown model id " << modelId + << " in layer definition" << std::endl; + } + } + + layer->setProperties(attributes); + } + + if (!m_inData && m_currentPane) { + + QString visible = attributes.value("visible"); + bool dormant = (visible == "false"); + + // We need to do this both before and after adding the layer + // to the view -- we need it to be dormant if appropriate + // before it's actually added to the view so that any property + // box gets the right state when it's added, but the add layer + // command sets dormant to false because it assumes it may be + // restoring a previously dormant layer, so we need to set it + // again afterwards too. Hm + layer->setLayerDormant(m_currentPane, dormant); + + m_document->addLayerToView(m_currentPane, layer); + + layer->setLayerDormant(m_currentPane, dormant); + } + + return true; +} + +bool +SVFileReader::readDatasetStart(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + READ_MANDATORY(int, dimensions, toInt); + + if (m_awaitingDatasets.find(id) == m_awaitingDatasets.end()) { + std::cerr << "WARNING: SV-XML: Unwanted dataset " << id << std::endl; + return false; + } + + int modelId = m_awaitingDatasets[id]; + + Model *model = 0; + if (m_models.find(modelId) != m_models.end()) { + model = m_models[modelId]; + } else { + std::cerr << "WARNING: SV-XML: Internal error: Unknown model " << modelId + << " expecting dataset " << id << std::endl; + return false; + } + + bool good = false; + + switch (dimensions) { + case 1: + if (dynamic_cast(model)) good = true; + break; + + case 2: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; + break; + + case 3: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) { + m_datasetSeparator = attributes.value("separator"); + good = true; + } + break; + } + + if (!good) { + std::cerr << "WARNING: SV-XML: Model id " << modelId << " has wrong number of dimensions for " << dimensions << "-D dataset " << id << std::endl; + m_currentDataset = 0; + return false; + } + + m_currentDataset = model; + return true; +} + +bool +SVFileReader::addPointToDataset(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, frame, toInt); + + SparseOneDimensionalModel *sodm = dynamic_cast + (m_currentDataset); + + if (sodm) { + QString label = attributes.value("label"); + sodm->addPoint(SparseOneDimensionalModel::Point(frame, label)); + return true; + } + + SparseTimeValueModel *stvm = dynamic_cast + (m_currentDataset); + + if (stvm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + stvm->addPoint(SparseTimeValueModel::Point(frame, value, label)); + return ok; + } + + NoteModel *nm = dynamic_cast(m_currentDataset); + + if (nm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + size_t duration = 0; + duration = attributes.value("duration").trimmed().toUInt(&ok); + QString label = attributes.value("label"); + nm->addPoint(NoteModel::Point(frame, value, duration, label)); + return ok; + } + + TextModel *tm = dynamic_cast(m_currentDataset); + + if (tm) { + float height = 0.0; + height = attributes.value("height").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + tm->addPoint(TextModel::Point(frame, height, label)); + return ok; + } + + std::cerr << "WARNING: SV-XML: Point element found in non-point dataset" << std::endl; + + return false; +} + +bool +SVFileReader::addBinToDataset(const QXmlAttributes &attributes) +{ + EditableDenseThreeDimensionalModel *dtdm = + dynamic_cast + (m_currentDataset); + + if (dtdm) { + + bool ok = false; + int n = attributes.value("number").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid bin number" + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + dtdm->setBinName(n, name); + return true; + } + + std::cerr << "WARNING: SV-XML: Bin definition found in incompatible dataset" << std::endl; + + return false; +} + + +bool +SVFileReader::addRowToDataset(const QXmlAttributes &attributes) +{ + m_inRow = false; + + bool ok = false; + m_rowNumber = attributes.value("n").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid row number" + << std::endl; + return false; + } + + m_inRow = true; + +// std::cerr << "SV-XML: In row " << m_rowNumber << std::endl; + + return true; +} + +bool +SVFileReader::readRowData(const QString &text) +{ + EditableDenseThreeDimensionalModel *dtdm = + dynamic_cast + (m_currentDataset); + + bool warned = false; + + if (dtdm) { + QStringList data = text.split(m_datasetSeparator); + + DenseThreeDimensionalModel::Column values; + + for (QStringList::iterator i = data.begin(); i != data.end(); ++i) { + + if (values.size() == dtdm->getHeight()) { + if (!warned) { + std::cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row " + << m_rowNumber << std::endl; + warned = true; + } + } + + bool ok; + float value = i->toFloat(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Bad floating-point value " + << i->toLocal8Bit().data() + << " in row data" << std::endl; + } else { + values.push_back(value); + } + } + + dtdm->setColumn(m_rowNumber, values); + return true; + } + + std::cerr << "WARNING: SV-XML: Row data found in non-row dataset" << std::endl; + + return false; +} + +bool +SVFileReader::readDerivation(const QXmlAttributes &attributes) +{ + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for derivation" << std::endl; + return false; + } + + QString transform = attributes.value("transform"); + + if (m_models.find(modelId) != m_models.end()) { + m_currentDerivedModel = m_models[modelId]; + } else { + // we'll regenerate the model when the derivation element ends + m_currentDerivedModel = 0; + } + + m_currentDerivedModelId = modelId; + + int sourceId = 0; + bool sourceOk = false; + sourceId = attributes.value("source").trimmed().toInt(&sourceOk); + + if (sourceOk && m_models[sourceId]) { + m_currentTransformSource = m_models[sourceId]; + } else { + m_currentTransformSource = m_document->getMainModel(); + } + + m_currentTransform = transform; + m_currentTransformConfiguration = ""; + + m_currentTransformContext = PluginTransform::ExecutionContext(); + + bool ok = false; + int channel = attributes.value("channel").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.channel = channel; + + int domain = attributes.value("domain").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.domain = Vamp::Plugin::InputDomain(domain); + + int stepSize = attributes.value("stepSize").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.stepSize = stepSize; + + int blockSize = attributes.value("blockSize").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.blockSize = blockSize; + + int windowType = attributes.value("windowType").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.windowType = WindowType(windowType); + + return true; +} + +bool +SVFileReader::readPlayParameters(const QXmlAttributes &attributes) +{ + m_currentPlayParameters = 0; + + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for play parameters" << std::endl; + return false; + } + + if (m_models.find(modelId) != m_models.end()) { + + bool ok = false; + + PlayParameters *parameters = PlayParameterRepository::getInstance()-> + getPlayParameters(m_models[modelId]); + + if (!parameters) { + std::cerr << "WARNING: SV-XML: Play parameters for model " + << modelId + << " not found - has model been added to document?" + << std::endl; + return false; + } + + bool muted = (attributes.value("mute").trimmed() == "true"); + parameters->setPlayMuted(muted); + + float pan = attributes.value("pan").toFloat(&ok); + if (ok) parameters->setPlayPan(pan); + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) parameters->setPlayGain(gain); + + QString pluginId = attributes.value("pluginId"); + if (pluginId != "") parameters->setPlayPluginId(pluginId); + + m_currentPlayParameters = parameters; + +// std::cerr << "Current play parameters for model: " << m_models[modelId] << ": " << m_currentPlayParameters << std::endl; + + } else { + + std::cerr << "WARNING: SV-XML: Unknown model " << modelId + << " for play parameters" << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlugin(const QXmlAttributes &attributes) +{ + if (m_currentDerivedModelId < 0 && !m_currentPlayParameters) { + std::cerr << "WARNING: SV-XML: Plugin found outside derivation or play parameters" << std::endl; + return false; + } + + QString configurationXml = "setPlayPluginConfiguration(configurationXml); + } else { + m_currentTransformConfiguration += configurationXml; + } + + return true; +} + +bool +SVFileReader::readSelection(const QXmlAttributes &attributes) +{ + bool ok; + + READ_MANDATORY(int, start, toInt); + READ_MANDATORY(int, end, toInt); + + m_paneCallback.addSelection(start, end); + + return true; +} + +SVFileReaderPaneCallback::~SVFileReaderPaneCallback() +{ +} + diff -r 000000000000 -r fc9323a41f5a sv/document/SVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/document/SVFileReader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,227 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SV_FILE_READER_H_ +#define _SV_FILE_READER_H_ + +#include "layer/LayerFactory.h" +#include "transform/Transform.h" +#include "transform/PluginTransform.h" + +#include + +#include + +class Pane; +class Model; +class Document; +class PlayParameters; + +class SVFileReaderPaneCallback +{ +public: + virtual ~SVFileReaderPaneCallback(); + virtual Pane *addPane() = 0; + virtual void setWindowSize(int width, int height) = 0; + virtual void addSelection(int start, int end) = 0; +}; + +/** + SVFileReader loads Sonic Visualiser XML files. (The SV file + format is bzipped XML.) + + Some notes about the SV XML format follow. We're very lazy with + our XML: there's no schema or DTD, and we depend heavily on + elements being in a particular order. + +\verbatim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\endverbatim + */ + + +class SVFileReader : public QXmlDefaultHandler +{ +public: + SVFileReader(Document *document, + SVFileReaderPaneCallback &callback, + QString location = ""); // for audio file locate mechanism + virtual ~SVFileReader(); + + void parse(const QString &xmlData); + void parse(QXmlInputSource &source); + + bool isOK(); + QString getErrorString() const { return m_errorString; } + + // For loading a single layer onto an existing pane + void setCurrentPane(Pane *pane) { m_currentPane = pane; } + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, + const QString &qName, + const QXmlAttributes& atts); + + virtual bool characters(const QString &); + + virtual bool endElement(const QString &namespaceURI, + const QString &localName, + const QString &qName); + + bool error(const QXmlParseException &exception); + bool fatalError(const QXmlParseException &exception); + +protected: + bool readWindow(const QXmlAttributes &); + bool readModel(const QXmlAttributes &); + bool readView(const QXmlAttributes &); + bool readLayer(const QXmlAttributes &); + bool readDatasetStart(const QXmlAttributes &); + bool addBinToDataset(const QXmlAttributes &); + bool addPointToDataset(const QXmlAttributes &); + bool addRowToDataset(const QXmlAttributes &); + bool readRowData(const QString &); + bool readDerivation(const QXmlAttributes &); + bool readPlayParameters(const QXmlAttributes &); + bool readPlugin(const QXmlAttributes &); + bool readSelection(const QXmlAttributes &); + void addUnaddedModels(); + + Document *m_document; + SVFileReaderPaneCallback &m_paneCallback; + QString m_location; + Pane *m_currentPane; + std::map m_layers; + std::map m_models; + std::set m_addedModels; + std::map m_awaitingDatasets; // map dataset id -> model id + Model *m_currentDataset; + Model *m_currentDerivedModel; + int m_currentDerivedModelId; + PlayParameters *m_currentPlayParameters; + QString m_currentTransform; + Model *m_currentTransformSource; + PluginTransform::ExecutionContext m_currentTransformContext; + QString m_currentTransformConfiguration; + QString m_datasetSeparator; + bool m_inRow; + bool m_inView; + bool m_inData; + bool m_inSelections; + int m_rowNumber; + QString m_errorString; + bool m_ok; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/i18n/sound-access_ru.qm Binary file sv/i18n/sound-access_ru.qm has changed diff -r 000000000000 -r fc9323a41f5a sv/i18n/sound-access_ru.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/i18n/sound-access_ru.ts Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,5318 @@ + + + + AddLayerCommand + + + Add %1 Layer + Добавить Ñлой %1 + + + + AudioDial + + + Enter new value + Введите новое значение + + + + %1: %2%3 + %1: %2%3 + + + + %2%3 + %2%3 + + + + New value for %1, from %2 to %3 %4: + Ðовое значение Ð´Ð»Ñ %1, от %2 до %3 %4: + + + + New value for %1, from %2 to %3: + Ðовое значение Ð´Ð»Ñ %1, от %2 до %3: + + + + Enter a new value from %1 to %2 %3: + Введите новое значение от %1 до %2 %3: + + + + Enter a new value from %1 to %2: + Введите новое значение от %1 до %2: + + + + BZipFileDevice + + + File is already open + Этот файл уже открыт + + + + Append mode not supported + Режим Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² конец не поддерживаетÑÑ + + + + File access mode not specified + СпоÑоб доÑтупа к файлам не указан + + + + Read and write modes both specified + Режимы Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¸ запиÑи определены + + + + Failed to open file for writing + Ðе удалоÑÑŒ открыть файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + + + + Failed to open bzip2 stream for writing + Ðе удалоÑÑŒ открыть поток bzip2 Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + + + + Failed to open file for reading + Ðе удалоÑÑŒ открыть файл Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ + + + + Failed to open bzip2 stream for reading + Ðе удалоÑÑŒ открыть поток bzip2 Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ + + + + Internal error (open for neither read nor write) + ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° (не открываетÑÑ Ð½Ð¸ на чтение, ни на запиÑÑŒ) + + + + File not open + Файл не открыт + + + + bzip2 stream write close error + Ошибка Ð·Ð°ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñи в поток bzip2 + + + + bzip2 stream read close error + Ошибка Ð·Ð°ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÐ° bzip2 + + + + Internal error (close for neither read nor write) + ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° (не закрываетÑÑ Ð½Ð¸ чтение, ни запиÑÑŒ) + + + + bzip2 stream read error + Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÐ° bzip2 + + + + CSVFileWriter + + + Failed to open file %1 for writing + Ðе удалоÑÑŒ открыть файл %1 Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + + + + CSVFormatDialog + + + Select Data Format + Выберите формат данных + + + + +Please select the correct data format for this file. + + +Выберите правильный формат данных Ð´Ð»Ñ Ñтого файла. + + + + + Each row specifies: + ÐšÐ°Ð¶Ð´Ð°Ñ Ñтрока определÑет: + + + + A point in time + Точку во времени + + + + A value at a time + Значение во точке времени + + + + A set of values + Серию значений + + + + The first column contains: + Первый Ñтолбец Ñодержит: + + + + Time, in seconds + ВремÑ, в Ñекундах + + + + Time, in audio sample frames + ВремÑ, в выборках звуковых ÑÑмплов + + + + Data (rows are consecutive in time) + Данные (Ñтроки поÑледовательны во времени) + + + + Audio sample rate (Hz): + ЧаÑтота ÑÑÐ¼Ð¿Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð²ÑƒÐºÐ° (Гц): + + + + Frame increment between rows: + + + + + +Example data from file: + +Пример данных из файла: + + + + OK + ОК + + + + Cancel + Отменить + + + + Colour3DPlotLayer + + + Time:%1 - %2 +Bin:%3 +Value:%4 + ВремÑ:%1 - %2 +Bin:%3 +Значение:%4 + + + + Linear + Линейный + + + + dB + Дб + + + + <unknown> + <неизвеÑтно> + + + + Colour + Цвет + + + + Scale + МаÑштаб + + + + Normalize Columns + Ðормализовать Ñтолбцы + + + + Normalize Visible Area + Ðормализовать видимую облаÑть + + + + Log + ЛогарифмичеÑкий + + + + +/-1 + +/-1 + + + + ColourMapper + + + <unknown> + <неизвеÑтно> + + + + Default + По умолчанию + + + + White on Black + Белое на чёрном + + + + Black on White + Чёрное на белом + + + + Red on Blue + КраÑное на Ñинем + + + + Yellow on Black + Жёлтое на чёрном + + + + Blue on Black + Синее на чёрном + + + + Sunset + Закат + + + + Fruit Salad + Фруктовый Ñалат + + + + Banded + + + + + Highlight + + + + + CommandHistory + + + &Undo + &Отменить + + + + Ctrl+Z + Ctrl+Z + + + + Re&do + Ве&рнуть + + + + Ctrl+Shift+Z + Ctrl+Shift+Z + + + + Nothing to undo + Ðет отменÑемых дейÑтвий + + + + Nothing to redo + Ðет повторÑемых дейÑтвий + + + + &Undo %1 + &Отменить дейÑтвие «%1» + + + + Re&do %1 + Повто&рить дейÑтвие «%1» + + + + Undo the last editing operation + Отменить поÑледнее дейÑтвие правки + + + + Redo the last operation that was undone + Повторить поÑледнее дейÑтвие правки + + + + Document::AddLayerCommand + + + Add %1 Layer + Добавить %1 Ñлой + + + + Document::RemoveLayerCommand + + + Delete %1 Layer + Удалить %1 Ñлой + + + + FFTModel + + + %1 Hz + %1 Гц + + + + Fader + + + Level: Off + Уровень: выкл + + + + Level: %1%2.%3%4 dB + Уровень: %1%2.%3%4 Дб + + + + Enter new fader level + Ввведите новый уровень фейдера + + + + New fader level, from %1 to %2 dBFS: + Ðовый уровень фейдера, от %1 до %2 dBFS: + + + + FeatureExtractionPluginTransform + + + FFT cache failed + Ðе удалоÑÑŒ кÑшировать FFT + + + + Failed to create the FFT model for this transform. +There may be insufficient memory or disc space to continue. + Ðе удалоÑÑŒ Ñоздать модель FFT Ð´Ð»Ñ Ñтого преобразованиÑ. +ВероÑтно, не хватает памÑти или диÑкового проÑтранÑтва Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ. + + + + FileFinder + + + Select file + Выберите файл + + + + All files (*.*) + Ð’Ñе файлы (*.*) + + + + Select a session file + Выберите файл ÑеÑÑии + + + + Sonic Visualiser session files (*.sv) +All files (*.*) + Файлы ÑеÑÑий Sonic Visualiser (*.sv) +Ð’Ñе файлы (*.*) + + + + Audio files (%1) +All files (*.*) + Звуковые файлы (%1) +Ð’Ñе файлы (*.*) + + + + All supported files (%1) +Sonic Visualiser Layer XML files (*.svl) +Comma-separated data files (*.csv) +Space-separated .lab files (*.lab) +MIDI files (*.mid) +Text files (*.txt) +All files (*.*) + Ð’Ñе поддерживаемые типы файлов (%1) +XML-файлы Ñлоёв Sonic Visualiser (*.svl) +Разделённые запÑтой файлы данных (*.csv) +Разделённые пробелом файлы .lab (*.lab) +MIDI-файлы (*.mid) +ТекÑтовые файлы (*.txt) +Ð’Ñе файлы (*.*) + + + + All supported files (*.sv %1) +Sonic Visualiser session files (*.sv) +Audio files (%1) +All files (*.*) + Ð’Ñе поддерживаемые типы файлов (*.sv %1) +Файлы ÑеÑÑий Sonic Visualiser (*.sv) +Звуковые файлы (%1) +Ð’Ñе файлы (*.*) + + + + All supported files (*.sv %1 %2) +Sonic Visualiser session files (*.sv) +Audio files (%1) +Layer files (%2) +All files (*.*) + Ð’Ñе поддерживаемые типы файлов (*.sv %1 %2) +Файлы ÑеÑÑий Sonic Visualiser (*.sv) +Звуковые файлы (%1) +Файлы Ñлоёв (%2) +Ð’Ñе файлы (*.*) + + + + File does not exist + Файл не ÑущеÑтвует + + + + File "%1" does not exist + Файл "%1" не ÑущеÑтвует + + + + File is not readable + Файл нечитаем + + + + File "%1" can not be read + Файл "%1" не может быть прочитан + + + + Directory selected + Выбран каталог + + + + File "%1" is a directory + Файл "%1" ÑвлÑетÑÑ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð¾Ð¼ + + + + Non-file selected + Выбран не файл + + + + Path "%1" is not a file + Путь "%1" не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼ + + + + File is empty + Файл пуÑÑ‚ + + + + File "%1" is empty + Файл "%1" пуÑÑ‚ + + + + Select a file to export to + Выберите файл, в который ÑкÑпортировать + + + + WAV audio files (*.wav) +All files (*.*) + Звуковые файлы WAV (*.wav) +Ð’Ñе файлы (*.*) + + + + Sonic Visualiser Layer XML files (*.svl) +Comma-separated data files (*.csv) +Text files (*.txt) +All files (*.*) + XML-файлы Ñлоёв Sonic Visualiser (*.svl) +Разделённые запÑтой данные (*.csv) +ТекÑтовые файлы (*.txt) +Ð’Ñе файлы (*.*) + + + + File exists + Такой файл уже ÑущеÑтвует + + + + The file "%1" already exists. +Do you want to overwrite it? + Файл "%1" уже ÑущеÑтвует. +Ð’Ñ‹ хотите перезапиÑать его? + + + + Audio file "%1" could not be opened. +Do you want to locate it? + Ðе удалоÑÑŒ открыть звуковой файл "%1". +Ð’Ñ‹ хотите указать его раÑположение? + + + + File "%1" could not be opened. +Do you want to locate it? + Ðе удалоÑÑŒ открыть файл "%1". +Ð’Ñ‹ хотите указать его раÑположение? + + + + Failed to open file + Ðе удалоÑÑŒ открыть файл + + + + Locate file... + Указать файл... + + + + Use URL... + ИÑпользовать URL... + + + + Cancel + Отменить + + + + Use URL + ИÑпользовать URL + + + + Please enter the URL to use for this file: + Укажите URL, который будет иÑпользоватьÑÑ Ð´Ð»Ñ Ñтого файла: + + + + Failed to open location + Ðе удалоÑÑŒ открыть меÑтоположение + + + + URL "%1" could not be opened + Ðе удалоÑÑŒ открыть URL "%1" + + + + Portable Network Graphics files (*.png) +All files (*.*) + Файлы Portable Network Graphics (*.png) +Ð’Ñе файлы (*.*) + + + + ItemEditDialog + + + Timing + Тайминг + + + + Time: + ВремÑ: + + + + frames + выборок + + + + sec + Ñ + + + + usec + Ð¼Ñ + + + + Duration: + ДлительноÑть: + + + + Properties + СвойÑтва + + + + Value: + Значение: + + + + Text: + ТекÑÑ‚: + + + + OK + ОК + + + + Reset + СброÑить + + + + Cancel + Отменить + + + + Layer + + + Waveform + Ð’Ð¾Ð»Ð½Ð¾Ð²Ð°Ñ Ñ„Ð¾Ñ€Ð¼Ð° + + + + Spectrogram + Спектрограмма + + + + Ruler + Линейка + + + + Time Instants + Отметки времени + + + + Time Values + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð¸ + + + + Notes + Ðоты + + + + Text + ТекÑÑ‚ + + + + Colour 3D Plot + Цветной 3D-график + + + + Layer + Слой + + + + Spectrum + Спектр + + + + Time Slice + + + + + LayerTreeModel + + + Layer + Слой + + + + Model + Модель + + + + ListInputDialog + + + OK + ОК + + + + Cancel + Отменить + + + + MIDIFileReader + + + Wrong length for long data in MIDI stream (%1, should be %2) + + + + + Wrong length for int data in MIDI stream (%1, should be %2) + + + + + getMIDIByte called but no MIDI file open + + + + + End of MIDI file encountered while reading + + + + + Attempt to get more bytes than expected on Track + + + + + Attempt to read past MIDI file end + + + + + getMIDIBytes called but no MIDI file open + + + + + Attempt to get more bytes than available on Track (%1, only have %2) + + + + + getNumberFromMIDIBytes called but no MIDI file open + + + + + skipToNextTrack called but no MIDI file open + + + + + Invalid event code %1 found + + + + + Running status used for first event in track + + + + + No notes in MIDI file + Ð’ MIDI-файле нет нот + + + + MIDI file "%1" has no notes in any track + + + + + Merge all tracks + Объединить вÑе дорожки + + + + Merge all non-percussion tracks + Объединить вÑе неперкуÑÑионные дорожки + + + + - uses GM percussion channel + — иÑпользует канал перкуÑÑии GM + + + + Track %1 (%2)%3 + Дорожка %1 (%2)%3 + + + + Track %1 (untitled)%3 + Дорожка %1 (без имени)%3 + + + + Select track or tracks to import + Выберите одну или неÑколько дорожек Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° + + + + You can only import this file as a single annotation layer, +but the file contains more than one track, +or notes on more than one channel. + +Please select the track or merged tracks you wish to import: + + + + + %1 - vel %2 + + + + + MainWindow + + + Sonic Visualiser + Sonic Visualiser + + + + &Layer + С&лой + + + + &File + &Файл + + + + File Toolbar + Панель файлов + + + + &New Session + &Создать ÑеÑÑию + + + + Ctrl+N + Ctrl+N + + + + Clear the current Sonic Visualiser session and start a new one + ОчиÑтить текущую ÑеÑÑию Sonic Visualiser и начать новую + + + + &Open Session... + &Открыть ÑеÑÑию... + + + + Ctrl+O + Ctrl+O + + + + Open a previously saved Sonic Visualiser session file + Открыть файл Ñохранённой ранее ÑеÑÑии Sonic Visualiser + + + + &Open... + О&ткрыть... + + + + Open a session file, audio file, or layer + Открыть файл ÑеÑÑии, звуковой файл или Ñлой + + + + &Save Session + Сохр&анить ÑеÑÑию + + + + Ctrl+S + Ctrl+S + + + + Save the current session into a Sonic Visualiser session file + Сохранить в файл текущую ÑеÑÑию Sonic Visualiser + + + + Save Session &As... + Сохранить ÑеÑÑию &как... + + + + Save the current session into a new Sonic Visualiser session file + Сохранить текущую ÑеÑÑию Sonic Visualiser в файл Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼ именем + + + + &Import Audio File... + &Импортировать звуковой файл... + + + + Ctrl+I + Ctrl+I + + + + Import an existing audio file + Импортировать ÑущеÑтвующий звуковой файл + + + + Import Secondary Audio File... + Импортировать второй звуковой файл... + + + + Ctrl+Shift+I + Ctrl+Shift+I + + + + Import an extra audio file as a separate layer + Импортировать ещё один звуковой файл в отдельный Ñлой + + + + &Export Audio File... + Э&кÑпортировать звуковой файл... + + + + Export selection as an audio file + ЭкÑпортировать выделенное в звуковой файл + + + + Import Annotation &Layer... + И&мпортировать Ñлой аннотаций... + + + + Ctrl+L + Ctrl+L + + + + Import layer data from an existing file + Импортировать данные ÑÐ»Ð¾Ñ Ð¸Ð· ÑущеÑтвующего файла + + + + Export Annotation Layer... + ЭкÑпортировать Ñлой аннотаций... + + + + Export layer data to a file + ЭкÑпортировать данные ÑÐ»Ð¾Ñ Ð² файл + + + + &Quit + Ð’Ñ‹&йти + + + + Ctrl+Q + Ctrl+Q + + + + &Edit + &Правка + + + + Cu&t + &Вырезать + + + + Ctrl+X + Ctrl+X + + + + &Copy + С&копировать + + + + Ctrl+C + Ctrl+C + + + + &Paste + Ð’ÑÑ‚&авить + + + + Ctrl+V + Ctrl+V + + + + &Delete Selected Items + &Удалить выбранное + + + + Del + Del + + + + Select &All + Ð’&ыделить вÑÑ‘ + + + + Ctrl+A + Ctrl+A + + + + Select &Visible Range + Ð’Ñ‹&делить вÑÑ‘ видимое + + + + Ctrl+Shift+A + Ctrl+Shift+A + + + + Select to &Start + Выделить до &начала + + + + Shift+Left + Shift+Left + + + + Select to &End + Выделить до &конца + + + + Shift+Right + Shift+Right + + + + C&lear Selection + Сн&Ñть выделение + + + + Esc + Esc + + + + &Insert Instant at Playback Position + Ð’Ñтавить &отметку в точку воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + Enter + Enter + + + + &View + &Вид + + + + 0 + 0 + + + + 9 + 9 + + + + 8 + 8 + + + + Scroll &Left + Прокрутить в&лево + + + + Left + Влево + + + + Scroll the current pane to the left + Прокрутить активное окно влево + + + + Scroll &Right + Прокрутить в&право + + + + Right + Вправо + + + + Scroll the current pane to the right + Прокрутить активное окно вправо + + + + Ctrl+Left + Ctrl+Влево + + + + Scroll the current pane a big step to the left + Сделать большой шаг прокрутки влево + + + + Ctrl+Right + Ctrl+Вправо + + + + Scroll the current pane a big step to the right + Сделать большой шаг прокрутки вправо + + + + Zoom &In + При&близить + + + + Up + Вверх + + + + Increase the zoom level + Увеличить маÑштаб Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + Zoom &Out + &Отдалить + + + + Down + Вниз + + + + Decrease the zoom level + Уменьшить маÑштаб Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + Restore &Default Zoom + &ВоÑÑтановить обычный маÑштаб + + + + Zoom to &Fit + &УмеÑтить в окне + + + + Zoom to show the whole file + Увидеть веÑÑŒ файл + + + + &Pane + &Окно + + + + Add &New Pane + Добавить &новое окно + + + + Alt+N + Alt+н + + + + Add a new pane containing only a time ruler + Добавить новое окно, Ñодержащее только линейку времени + + + + Add New %1 Layer + Добавить новый Ñлой «%1» + + + + Add a new empty layer of type %1 + Добавить новый пуÑтой Ñлой типа «%1» + + + + Alt+T + + + + + Add &Waveform + Добавить &волновую форму + + + + Alt+W + Alt+в + + + + Add a new pane showing a waveform view + Добавить новое окно Ñ Ð²Ð¸Ð´Ð¾Ð¼ волновой формы + + + + Add a new layer showing a waveform view + Добавить новый Ñлой Ñ Ð²Ð¸Ð´Ð¾Ð¼ волновой формы + + + + Add &Spectrogram + Добавить &Ñпектограмму + + + + Alt+S + Alt+Ñ + + + + Add a new pane showing a dB spectrogram + Добавить новую панель Ñо Ñпектрограммой в Дб + + + + Add &Melodic Range Spectrogram + Добавить Ñпектограмму &мелодичеÑкого диапазона + + + + Alt+M + Alt+м + + + + Add &Peak Frequency Spectrogram + Добавить Ñпектограмму &пиковой чаÑтоты + + + + Alt+P + Alt+п + + + + Add a new pane showing a spectrogram set up for tracking frequencies + + + + + Add a new layer showing a spectrogram set up for tracking frequencies + + + + + &All Channels Mixed + Ð’Ñе &каналы Ñведены + + + + &All Channels + &Ð’Ñе каналы + + + + Channel &%1 + Канал &%1 + + + + &Delete Pane + У&далить окно + + + + Alt+D + Alt+д + + + + Delete the currently selected pane + Удалить активную панель + + + + Add &Time Ruler + Добавить линейку &времени + + + + Add a new layer showing a time ruler + Добавить новый Ñлой Ñ Ð»Ð¸Ð½ÐµÐ¹ÐºÐ¾Ð¹ + + + + Add &Existing Layer + Добавить &ÑущеÑтвующий Ñлой + + + + &Rename Layer... + &Переименовать Ñлой... + + + + Alt+R + Alt+п + + + + Rename the currently active layer + Переименовать активный Ñлой + + + + &Delete Layer + &Удалить Ñлой + + + + Alt+Shift+D + Alt+Shift+D + + + + Delete the currently active layer + Удалить активный Ñлой + + + + &Help + &Справка + + + + &Help Reference + &Справка по программе + + + + Open the Sonic Visualiser reference manual + Открыть Ñправку по Sonic Visualiser + + + + Sonic Visualiser on the &Web + Sonic Visualiser в &Интернете + + + + Open the Sonic Visualiser website + Открыть веб-Ñайт Sonic Visualiser + + + + &About Sonic Visualiser + &О Sonic Visualiser + + + + Show information about Sonic Visualiser + Показать информацию о Sonic Visualiser + + + + Transport Toolbar + Панель транÑпорта + + + + Rewind to Start + Перемотать в начало + + + + Home + Домой + + + + Rewind to the start + Перемотать в начало + + + + Rewind + Ðазад + + + + PageUp + PageUp + + + + Rewind to the previous time instant in the current layer + Перемотать до предыдущей отметки времени в текущем Ñлое + + + + Play / Pause + ВоÑпроизвеÑти / ПриоÑтановить + + + + Space + Пробел + + + + Start or stop playback from the current position + ЗапуÑтить или оÑтановить воÑпроизведение Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ позиции + + + + Fast Forward + Вперёд + + + + PageDown + PageDown + + + + Fast forward to the next time instant in the current layer + Перемотать до Ñледующей отметки времени в текущем Ñлое + + + + Fast Forward to End + Перемотать до конца + + + + End + Конец + + + + Fast-forward to the end + Перемотать до конца + + + + Play Mode Toolbar + Панель режима воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + Constrain Playback to Selection + Ограничить воÑпроизведение выделением + + + + s + Ñ + + + + Constrain playback to the selected area + Ограничить воÑпроизведение выделенной облаÑтью + + + + Loop Playback + ВоÑпроизведение в цикле + + + + l + l + + + + Loop playback + ВоÑпроизведение в цикле + + + + Edit Toolbar + Панель правки + + + + Tools Toolbar + Панель инÑтрументов + + + + Navigate + Перемещение + + + + 1 + 1 + + + + Select + Выделение + + + + 2 + 2 + + + + Edit + Правка + + + + 3 + 3 + + + + Draw + РиÑование + + + + 4 + 4 + + + + No audio file loaded. + Ðи один звуковой файл не загружен. + + + + %1Hz (resampling to %2Hz) + %1 Гц (реÑÑмплирование до %2 Гц) + + + + %1 (modified) + + + + + (modified) + + + + + Cut + + + + + Add Point + Добавить точку + + + + Add Points + Добавить точки + + + + Select an audio file + Выбрать звуковой файл + + + + Audio files (%1) +All files (*.*) + Звуковые файлы (%1) +Ð’Ñе файлы (*.*) + + + + Failed to open file + Ðе удалоÑÑŒ открыть файл + + + + Audio file "%1" could not be opened + Ðе удалоÑÑŒ открыть звуковой файл "%1" + + + + WAV audio files (*.wav) +All files (*.*) + Звуковые файлы WAV (*.wav) +Ð’Ñе файлы (*.*) + + + + Export the selected region only + ЭкÑпортировать только выделенную облаÑть + + + + Export the whole audio file + ЭкÑпортировать веÑÑŒ звуковой файл + + + + Select region to export + Выделите облаÑть Ð´Ð»Ñ ÑкÑпорта + + + + Which region from the original audio file do you want to export? + Какую облаÑть иÑходного звукового файла вы хотите ÑкÑпортировать? + + + + Export the selected regions into a single audio file + ЭкÑпортировать выделенные облаÑти в один звуковой файл + + + + Export the selected regions into separate files + ЭкÑпортировать выделенные облаÑти в разные звуковые файлы + + + + Multiple regions of the original audio file are selected. +What do you want to export? + Выбрано неÑколько облаÑтей иÑходного звукового файла. +Какую из них вы хотите ÑкÑпортировать? + + + + Fragment file %1 already exists, aborting + Файл фрагмента %1 уже ÑущеÑтвует, прерывание + + + + Failed to write file + Ðе удалоÑÑŒ запиÑать файл + + + + Select file + Выберите файл + + + + All supported files (%1) +Sonic Visualiser Layer XML files (*.svl) +Comma-separated data files (*.csv) +Space-separated .lab files (*.lab) +MIDI files (*.mid) +Text files (*.txt) +All files (*.*) + Ð’Ñе поддерживаемые типы файлов (%1) +XML-файлы Ñлоёв Sonic Visualiser (*.svl) +Разделённые запÑтой файлы данных (*.csv) +Разделённые пробелом файлы .lab (*.lab) +MIDI-файлы (*.mid) +ТекÑтовые файлы (*.txt) +Ð’Ñе файлы (*.*) + + + + File %1 could not be opened. + Ðе удалоÑÑŒ открыть файл %1. + + + + Sonic Visualiser Layer XML files (*.svl) +Comma-separated data files (*.csv) +Text files (*.txt) +All files (*.*) + XML-файлы Ñлоёв Sonic Visualiser (*.svl) +Разделённые запÑтой данные (*.csv) +ТекÑтовые файлы (*.txt) +Ð’Ñе файлы (*.*) + + + + Failed to open file %1 for writing + Ðе удалоÑÑŒ открыть файл %1 Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи + + + + Replace the existing main waveform + + + + + Load this file into a new waveform pane + Загрузить Ñтот файл в новое окно волновой формы + + + + Select target for import + + + + + Sonic Visualiser: %1 + Sonic Visualiser: %1 + + + + Sonic Visualiser: %1 [%2] + Sonic Visualiser: %1 [%2] + + + + Import "%1" + Импортировать "%1" + + + + Couldn't open audio device + Ðе удалоÑÑŒ открыть звуковое уÑтройÑтво + + + + Could not open an audio device for playback. +Audio playback will not be available during this session. + + Ðе удалоÑÑŒ открыть звуковое уÑтройÑтво Ð´Ð»Ñ Ð²Ð¾ÑпроизведениÑ. +Ð’ Ñтот раз воÑпроизведение будет недоÑтупно. + + + + + File "%1" does not exist or is not a readable file + Файл "%1" не ÑущеÑтвует, либо в него Ð½ÐµÐ»ÑŒÐ·Ñ Ð·Ð°Ð¿Ð¸Ñывать + + + + Session file "%1" could not be opened + Ðе удалоÑÑŒ открыть файл ÑеÑÑии "%1" + + + + All supported files (*.sv %1 %2) +Sonic Visualiser session files (*.sv) +Audio files (%1) +Layer files (%2) +All files (*.*) + All supported files (*.sv %1 %2) +Sonic Visualiser session files (*.sv) +Audio files (%1) +Layer files (%2) +All files (*.*) + + + + All supported files (*.sv %1) +Sonic Visualiser session files (*.sv) +Audio files (%1) +All files (*.*) + All supported files (*.sv %1) +Sonic Visualiser session files (*.sv) +Audio files (%1) +All files (*.*) + + + + Select a file to open + Выберите файл Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ + + + + File "%1" could not be opened + Ðе удалоÑÑŒ открыть файл "%1" + + + + SV XML file read error: +%1 + Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ XML-файла SV: +%1 + + + + Session modified + СеÑÑÐ¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð° + + + + The current session has been modified. +Do you want to save it? + Ð¢ÐµÐºÑƒÑ‰Ð°Ñ ÑеÑÑÐ¸Ñ Ð±Ñ‹Ð»Ð° изменена. +Ð’Ñ‹ хотите Ñохранить её? + + + + Failed to save file + Ðе удалоÑÑŒ Ñохранить файл + + + + Session file "%1" could not be saved. + Ðе удалоÑÑŒ Ñохранить файл ÑеÑÑии "%1". + + + + Directory selected + Выбран каталог + + + + File "%1" is a directory + Файл "%1" ÑвлÑетÑÑ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð¾Ð¼ + + + + File exists + Такой файл уже ÑущеÑтвует + + + + The file "%1" already exists. +Do you want to overwrite it? + Файл "%1" уже ÑущеÑтвует. +Ð’Ñ‹ хотите перезапиÑать его? + + + + Failed to write to file "%1": %2 + Ðе удалоÑÑŒ запиÑать файл "%1": %2 + + + + Delete Pane + Удалить окно + + + + Rename Layer + Переименовать Ñлой + + + + New name for this layer: + Ðовое Ð¸Ð¼Ñ Ñтого ÑлоÑ: + + + + Sample rate mismatch + ÐеÑоответÑтвие чаÑтоты диÑкретизации + + + + Failed to regenerate layer + Ðе удалоÑÑŒ повторно Ñоздать Ñлой + + + + http://www.sonicvisualiser.org/ + http://www.sonicvisualiser.org/ + + + + http://www.sonicvisualiser.org/doc/reference/en/ + http://www.sonicvisualiser.org/doc/reference/en/ + + + + Release %1 : Revision %2 + ВерÑÐ¸Ñ %1 : Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ %2 + + + + Release %1 + ВерÑÐ¸Ñ %1 + + + + Unreleased : Revision %1 + Ðе выпущено : Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ %1 + + + + <h3>About Sonic Visualiser</h3> + <h3>О программе Sonic Visualiser</h3> + + + + <p>%1 : %2 build</p> + <p>%1 : Ñборка %2</p> + + + + Debug + Отладка + + + + Release + ВерÑÐ¸Ñ + + + + <p>Statically linked + <p>СтатичеÑки Ñлинкована + + + + <br>With Qt (v%1) &copy; Trolltech AS + <br>С Qt (v%1) &copy; Trolltech AS + + + + <br>With JACK audio output (v%1) &copy; Paul Davis and Jack O'Quin + <br>С выводом в JACK (v%1) &copy; Paul Davis и Jack O'Quin + + + + <br>With PortAudio audio output &copy; Ross Bencina and Phil Burk + <br>С выводом в PortAudio &copy; Ross Bencina и Phil Burk + + + + <br>With Ogg file decoder (oggz v%1, fishsound v%2) &copy; CSIRO Australia + <br>С декодером Ogg (oggz v%1, fishsound v%2) &copy; CSIRO Australia + + + + <br>With MAD mp3 decoder (v%1) &copy; Underbit Technologies Inc + <br>С декодером MAD mp3 (v%1) &copy; Underbit Technologies Inc + + + + <br>With libsamplerate (v%1) &copy; Erik de Castro Lopo + <br>С libsamplerate (v%1) &copy; Erik de Castro Lopo + + + + <br>With libsndfile (v%1) &copy; Erik de Castro Lopo + <br>С libsndfile (v%1) &copy; Erik de Castro Lopo + + + + <br>With FFTW3 (v%1) &copy; Matteo Frigo and MIT + <br>С FFTW3 (v%1) &copy; Matteo Frigo и MIT + + + + <br>With Vamp plugin support (API v%1, SDK v%2) &copy; Chris Cannam + <br>С поддержкой раÑширений Vamp (API v%1, SDK v%2) &copy; Chris Cannam + + + + <br>With LADSPA plugin support (API v%1) &copy; Richard Furse, Paul Davis, Stefan Westerfeld + <br>С поддержкой раÑширений LADSPA (API v%1) &copy; Richard Furse, Paul Davis, Stefan Westerfeld + + + + <br>With DSSI plugin support (API v%1) &copy; Chris Cannam, Steve Harris, Sean Bolton + <br>С поддержкой раÑширений DSSI (API v%1) &copy; Chris Cannam, Steve Harris, Sean Bolton + + + + About Sonic Visualiser + О программе Sonic Visualiser + + + + Failed to generate layer + Ðе удалоÑÑŒ Ñоздать Ñлой + + + + Adjust the application preferences + Изменить параметры работы Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + + + + You already have an audio waveform loaded. +What would you like to do with the new audio file? + У Ð²Ð°Ñ ÑƒÐ¶Ðµ еÑть Ð·Ð°Ð³Ñ€ÑƒÐ¶ÐµÐ½Ð½Ð°Ñ Ð²Ð¾Ð»Ð½Ð¾Ð²Ð°Ñ Ñ„Ð¾Ñ€Ð¼Ð° звука. +Что вы хотите Ñделать Ñ Ð½Ð¾Ð²Ñ‹Ð¼ звуковым файлом? + + + + Sharpen percussive transients + + + + + Run time stretcher in mono only + ЗапуÑкать раÑÑ‚Ñгиватель времени только в монорежиме + + + + &Recent Files + Ð&едавние файлы + + + + &Preferences... + &Параметры... + + + + ; + ; + + + + Show &Zoom Wheels + Показывать &колёÑа маÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ + + + + Z + Z + + + + Show thumbwheels for zooming horizontally and vertically + Показывать колёÑа Ð´Ð»Ñ Ð¼Ð°ÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ горизонтали и вертикали + + + + %1 by Category + %1 по категории + + + + Unclassified + ÐеклаÑÑифицированные + + + + %1 by Maker + %1 по имени ÑÐ¾Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ + + + + Unknown + ÐеизвеÑтен + + + + %1 by Plugin Name + %1 названию раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ + + + + %1... + %1... + + + + Add Spectr&um + Добавить &график Ñпектральной функции + + + + Alt+U + Alt+г + + + + Add a new pane showing a frequency spectrum + Добавить новое окно Ñ Ñ‡Ð°Ñтотой Ñпектра + + + + Add a new layer showing a frequency spectrum + Добавить новый Ñлой Ñ Ñ‡Ð°Ñтотой Ñпектра + + + + Playback Speedup + УÑкорение воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + &Transform + Пр&ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ + + + + %1: %2 + %1: %2 + + + + &Recent Transforms + &Ðедавние Ð¿Ñ€ÐµÐ¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ + + + + [\(<].*$ + [\(<].*$ + + + + Audio processing overload + + + + + Audio effects plugin auditioning has been disabled +due to a processing overload. + + + + + Failed to generate a derived layer. + +The layer transform "%1" failed. + +This probably means that a plugin failed to initialise, perhaps because it +rejected the processing block size that was requested. + + + + + Failed to regenerate derived layer "%1". + +The layer transform "%2" failed to run. + +This probably means the layer used a plugin that is not currently available. + + + + + Show Property Bo&xes + Показывать панели Ñ&войÑтв + + + + X + + + + + Show the layer property boxes at the side of the main window + Показывать панели ÑвойÑтв Ñлоёв Ñбоку от оÑновного окна + + + + Add %1 Pane + Добавить окно %1 + + + + <br>With liblo Lite OSC library (v%1) &copy; Steve Harris + + + + + <p>The OSC URL for this instance is: "%1" + + + + + Abandon the current Sonic Visualiser session and start a new one + ОтказатьÑÑ Ð¾Ñ‚ текущей ÑеÑÑии Sonic Visualiser и начать новую + + + + Open Lo&cation... + Открыть &меÑтоположение... + + + + Ctrl+Shift+O + Ctrl+Shift+O + + + + Open or import a file from a remote URL + Открыть или импортировать файл Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ð¾Ð³Ð¾ узла + + + + Exit Sonic Visualiser + Выйти из Sonic Visualiser + + + + Cut the selection from the current layer to the clipboard + Вырезать выделение из активного ÑÐ»Ð¾Ñ Ð² буфер обмена + + + + Copy the selection from the current layer to the clipboard + Скопировать выделение из активного ÑÐ»Ð¾Ñ Ð² буфер обмена + + + + Paste from the clipboard to the current layer + Ð’Ñтавить Ñодержимое буфера обмена в активный Ñлой + + + + Delete the selection from the current layer + Удалить выделение из активного ÑÐ»Ð¾Ñ + + + + Select the whole duration of the current session + + + + + Select the time range corresponding to the current window width + + + + + Select from the start of the session to the current playback position + Выделить от начала ÑеÑÑии до текущей точки воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + Select from the current playback position to the end of the session + Выделить от текущей точки воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð´Ð¾ конца ÑеÑÑии + + + + Clear the selection + ОчиÑтить выделение + + + + Insert a new time instant at the current playback position, in a new layer if necessary + Ð’Ñтавить новую отметку времени в точку воÑпроизведениÑ, при необходимоÑти — в новый Ñлой + + + + Insert Instants at Selection &Boundaries + Ð’Ñтавить отметки времени по &краÑм Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + + + + Shift+Enter + Shift+Enter + + + + Insert new time instants at the start and end of the current selection, in a new layer if necessary + Ð’Ñтавить новые отметки времени в начале и конце активного выделениÑ, при необходимоÑти — в новом Ñлое + + + + &Jump Left + ПереÑкочить в&лево + + + + J&ump Right + П&ереÑкочить вправо + + + + Restore the zoom level to the default + ВоÑÑтановить обычный маÑштаб Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + Show &No Overlays + Ðе по&казывать Ð¿ÐµÑ€ÐµÐºÑ€Ñ‹Ñ‚Ð¸Ñ ÑовÑем + + + + Hide centre indicator, frame times, layer names and scale + СпрÑтать индикацию центра, времён выделениÑ, имена Ñлоёв и маÑштаб + + + + Show &Minimal Overlays + Показывать &минимум перекрытий + + + + Show centre indicator only + Показывать только индикатор центра + + + + Show &Standard Overlays + Показывать о&бычные Ð¿ÐµÑ€ÐµÐºÑ€Ñ‹Ñ‚Ð¸Ñ + + + + Show centre indicator, frame times and scale + Показывать индикатор центра, времена выделений, имена Ñлоёв и маÑштаб + + + + Show &All Overlays + Показывать в&Ñе Ð¿ÐµÑ€ÐµÐºÑ€Ñ‹Ñ‚Ð¸Ñ + + + + 7 + 7 + + + + Show all texts and scale + Показывать веÑÑŒ текÑÑ‚ и маÑштаб + + + + Show Status &Bar + Показывать ÑтатуÑную &Ñтроку + + + + Show context help information in the status bar at the bottom of the window + Показывать контекÑтную Ñправку в ÑтатуÑной Ñтроке, находÑщейÑÑ Ð²Ð½Ð¸Ð·Ñƒ окна программы + + + + Add a new pane showing a spectrogram + Добавить новое окно Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼ Ñпектрограммы + + + + Add a new layer showing a spectrogram + Добавить новый Ñлой Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼ Ñпектрограммы + + + + Add a new pane showing a spectrogram set up for an overview of note pitches + + + + + Add a new layer showing a spectrogram set up for an overview of note pitches + + + + + Delete the currently active pane + Удалить активное окно + + + + Add S&lice of Layer + Добавить &фрагмент ÑÐ»Ð¾Ñ + + + + Select ranges + + + + + Edit items in layer + + + + + Draw new items in layer + + + + + Open Location + Открыть меÑтоположение + + + + Please enter the URL of the location to open: + Введите URL открываемого меÑтоположениÑ: + + + + Failed to open location + Ðе удалоÑÑŒ открыть меÑтоположение + + + + URL "%1" could not be opened + Ðе удалоÑÑŒ открыть URL "%1" + + + + Unsupported scheme in URL + ÐÐµÐ¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ñхема URL + + + + The URL scheme "%1" is not supported + Схема URL "%1" не поддерживаетÑÑ + + + + File download failed + Ðе удалоÑÑŒ Ñкачать файл + + + + Failed to download URL "%1": %2 + Ðе удалоÑÑŒ Ñкачать URL "%1": %2 + + + + Playing: %1 of %2 (%3 remaining) + ВоÑпроизводитÑÑ: %1 из %2 (оÑталоÑÑŒ %3) + + + + Visible: %1 to %2 (duration %3) + Ð’Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð¾Ð±Ð»Ð°Ñть: от %1 до %2 (длительноÑть %3) + + + + Adjust the master playback level + Изменить общую громкоÑть воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + Adjust the master playback speed + Изменить общую ÑкороÑть воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + Toggle transient sharpening for playback time scaling + + + + + Toggle mono mode for playback time scaling + + + + + <p>Sonic Visualiser is a program for viewing and exploring audio data for<br>semantic music analysis and annotation.</p> + <p>Sonic Visualiser — программа Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра и иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð²ÑƒÐºÐ¾Ð²Ñ‹Ñ…<br>данных Ñ Ñ†ÐµÐ»ÑŒÑŽ ÑемантичеÑкого анализа и аннотации музыки.</p> + + + + <p>%1 : %2 configuration</p> + <p>%1 : ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ %2</p> + + + + <br>With JACK audio output &copy; Paul Davis and Jack O'Quin + <br>С выводом в JACK &copy; Paul Davis и Jack O'Quin + + + + <br>With Ogg file decoder &copy; CSIRO Australia + <br>С декодером Ogg &copy; CSIRO Australia + + + + <br>With MAD mp3 decoder &copy; Underbit Technologies Inc + <br>С декодером MAD mp3 &copy; Underbit Technologies Inc + + + + <br>With libsamplerate &copy; Erik de Castro Lopo + <br>С libsamplerate &copy; Erik de Castro Lopo + + + + <br>With libsndfile &copy; Erik de Castro Lopo + <br>С libsndfile &copy; Erik de Castro Lopo + + + + <br>With FFTW3 &copy; Matteo Frigo and MIT + <br>С FFTW3 &copy; Matteo Frigo и MIT + + + + <br>With Vamp plugin support (API v%1, host SDK v%2) &copy; Chris Cannam + <br>С поддержкой раÑширений Vamp (API v%1, host SDK v%2) &copy; Chris Cannam + + + + <br>With liblo Lite OSC library &copy; Steve Harris + <br>С liblo Lite OSC library &copy; Steve Harris + + + + Export Image File... + ЭкÑпортировать в файл изображениÑ... + + + + Export a single pane to an image file + ЭкÑпортировать вÑÑ‘ окно в файл Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + Export the whole pane (%1x%2 pixels) + ЭкÑпортировать вÑÑ‘ окно (%1x%2 пикÑелов) + + + + Export the visible area only (%1x%2 pixels) + ЭкÑпортировать только видимую чаÑть (%1x%2 пикÑелов) + + + + Export the selection extent (%1x%2 pixels) + ЭкÑпортировать выделение (%1x%2 пикÑелов) + + + + Export the selection extent + ЭкÑпортировать выделение + + + + Which region of the current pane do you want to export as an image? + Какую облаÑть текущего окна вы хотите ÑкÑпортировать как изображение? + + + + Note: the whole pane is too wide to be exported as a single image. + Примечание: вÑÑ‘ окно Ñлишком широко, чтобы Ñохранить его как изображение. + + + + Failed to save image file + Ðе удалоÑÑŒ Ñохранить файл Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + Failed to save image file %1 + Ðе удалоÑÑŒ Ñохранить файл Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ %1 + + + + Selection: %1 to %2 (duration %3) + Выделение: %1 до %2 (длительноÑть %3) + + + + The sample rate of this audio file (%1 Hz) does not match +the current playback rate (%2 Hz). + +The file will play at the wrong speed and pitch. + ЧаÑтота ÑÑÐ¼Ð¿Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого звукового файла (%1 Гц) не Ñовпадает +Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ чаÑтотой ÑÑÐ¼Ð¿Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ Ð²Ð¾ÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ (%2 Гц). + +Файл будет воÑпроизведён Ñ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾Ð¹ ÑкороÑтью и выÑотой тона. + + + + http://www.sonicvisualiser.org/doc/reference/1.0/en/ + http://www.sonicvisualiser.org/doc/reference/1.0/en/ + + + + MainWindow::AddPaneCommand + + + Add Pane + Добавить окно + + + + MainWindow::RemovePaneCommand + + + Remove Pane + Удалить окно + + + + NoteLayer + + + New Point + ÐÐ¾Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° + + + + Colour + Цвет + + + + Vertical Scale + Верт. маÑштаб + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Auto-Align + Ðвтовыравнивание + + + + Linear Scale + Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ ÑˆÐºÐ°Ð»Ð° + + + + Log Scale + Логарифм. шкала + + + + MIDI Note Range + Диапазон MIDI-нот + + + + <unknown> + <неизвеÑтно> + + + + In progress + Ð’ процеÑÑе + + + + No local points + Ðет локальных точек + + + + Time:%1 +Pitch:%2 +Duration:%3 +No label + ВремÑ:%1 +Ð’Ñ‹Ñота тона:%2 +ДлительноÑть:%3 +Без метки + + + + Time:%1 +Pitch:%2 +Duration:%3 +Label:%4 + ВремÑ:%1 +Ð’Ñ‹Ñота тона:%2 +ДлительноÑть:%3 +Метка:%4 + + + + Draw Point + ÐариÑовать точку + + + + Drag Point + Перетащить точку + + + + Edit Point + Изменить точку + + + + Relocate Point + ПеремеÑтить точку + + + + Change Point Value + Изменить значение точки + + + + Drag Selection + Перетащить выделение + + + + Resize Selection + Изменить размер Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + + + + Delete Selected Points + Удалить выбранные точки + + + + Paste + Ð’Ñтавить + + + + Scale Units + Единицы шкалы + + + + Scale + МаÑштаб + + + + Linear + Линейный + + + + Log + ЛогарифмичеÑкий + + + + MIDI Notes + Ðоты MIDI + + + + Overview + + + Overview + Обзор + + + + Click and drag to navigate; double-click to jump + Щёлкните и потащите Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ окну; щёлкните дважды Ð´Ð»Ñ Ñкачка + + + + Pane + + + Some lengthy prefix: + Ðекий длинный префикÑ: + + + + (R) + (R) + + + + (X) + (X) + + + + %1 / %2Hz%3 + %1 / %2 Гц%3 + + + + Drag Selection + Перетащить выделение + + + + Resize Selection + Изменить выделение + + + + Horizontal Zoom + Горизонтальное маÑштабирование + + + + Vertical Zoom + Вертикальное маÑштабирование + + + + Enter new range + Введите новый диапазон + + + + New vertical display range, from %1 to %2 %4: + + + + + Click and drag to navigate + Щёлкните и потащите Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ окну + + + + Click and drag to select a range; hold Shift to avoid snapping to items; hold Ctrl for multi-select; middle-click and drag to navigate + + + + + Click and drag to select a range; hold Ctrl for multi-select; middle-click and drag to navigate + + + + + Click and drag to move the selection boundary + Щёлкните и потащите Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð³Ñ€Ð°Ð½Ð¸Ñ† Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + + + + Click and drag to select a range; hold Shift to avoid snapping to items; middle-click to navigate + + + + + Click and drag to select a range; middle-click and drag to navigate + + + + + Click to add a new item in the active layer + Щёлкните Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ объекта на активный Ñлой + + + + Click and drag an item in the active layer to move it + Щёлкните объект и потащите его Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð² активном Ñлое + + + + Click and drag to move all items in the selected range + Щёлкните и потащите вÑе объекты в выделенной облаÑти + + + + Click and drag to adjust the visible range of the vertical scale + + + + + Click and drag to adjust the vertical zoom level + Щёлкните и потащите Ð´Ð»Ñ Ñмены ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¼Ð°ÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ вертикали + + + + Click and drag to adjust the horizontal zoom level + Щёлкните и потащите Ð´Ð»Ñ Ñмены ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¼Ð°ÑÑˆÑ‚Ð°Ð±Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ горизонтали + + + + Reset horizontal and vertical zoom levels to their defaults + + + + + PluginParameterBox + + + This plugin has no adjustable parameters. + У Ñтого раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½ÐµÑ‚ изменÑемых параметров. + + + + Program + Программа + + + + PluginParameterDialog + + + Plugin + РаÑширение + + + + Name: + ИмÑ: + + + + Type: + Тип: + + + + Maker: + Ðвтор: + + + + Copyright: + Копирайт: + + + + Version: + ВерÑиÑ: + + + + Plugin Parameters + Параметры раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ + + + + Channel mismatch + ÐеÑовпадение каналов + + + + This plugin requires at least %1 input channels, but only %2 %3 available. The plugin probably will not work correctly. + Этому раÑширению нужно Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ %1 вхдных каналовs, но лишь %2 %3 доÑтупны. ВероÑтно, раÑширение не Ñработает как должно. + + + + are + ÑвлÑÑŽÑ‚ÑÑ + + + + is + ÑвлÑетÑÑ + + + + Channels + Каналы + + + + This plugin accepts no more than %1 input channels, +but %2 are available. Only the first %3 will be used. + + + + + + This plugin only has a single channel input, +but the source has %1 channels. + + + + + Use mean of source channels + + + + + Use channel %1 only + ИÑпользовать только канал %1 + + + + OK + ОК + + + + Cancel + Отменить + + + + Output: + Выход: + + + + Processing + Идёт обработка + + + + Window size: + Размер оконной функции: + + + + Audio frames per block: + Выборок звука на блок: + + + + Window increment: + + + + + Window shape: + + + + + Advanced >> + Больше >> + + + + Advanced << + Меньше << + + + + Input Source + + + + + Preferences + + + Frequency of concert A + ЧаÑтота концерта A + + + + Property box layout + Внешний вид панели ÑвойÑтв + + + + Spectral analysis window shape + Форма оконной функции при Ñпектральном анализе + + + + Show boxes for all panes + Показывать панели Ð´Ð»Ñ Ð²Ñех окон + + + + Show box for current pane only + Показывать панель только Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ окна + + + + Rectangular + ПрÑÐ¼Ð¾ÑƒÐ³Ð¾Ð»ÑŒÐ½Ð°Ñ + + + + Triangular + Ð¢Ñ€ÐµÑƒÐ³Ð¾Ð»ÑŒÐ½Ð°Ñ + + + + Hamming + Хамминга + + + + Hanning + Ханнинга + + + + Blackman + БлÑкмена + + + + Gaussian + ГауÑÑова + + + + Parzen + Парзена + + + + Nuttall + Ðутталла + + + + Blackman-Harris + БлÑкмена-ХарриÑа + + + + Preferences + Параметры + + + + Playback resampler type + Тип реÑÑÐ¼Ð¿Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸ воÑпроизведении + + + + Fastest + Самый быÑтрый + + + + Standard + Обычный + + + + Highest quality + ÐаивыÑшего качеÑтва + + + + Spectrogram y-axis smoothing: + Сглаживание Ñпектрограммы по оÑи Y: + + + + None - blocky but accurate + Ðикакого — Ñтупеньками, но аккуратно + + + + Interpolate - fast but fuzzy + Интерполировать — быÑтро, но грÑзно + + + + Zero pad FFT - slow but clear + Zero pad FFT — медленно, но чиÑто + + + + PreferencesDialog + + + Application Preferences + ÐаÑтройки программы + + + + Sonic Visualiser Application Preferences + ÐаÑтройки программы Sonic Visualiser + + + + Apply + Применить + + + + %1: + %1: + + + + OK + ОК + + + + Cancel + Отменить + + + + PropertyBox + + + Show + Показать + + + + Play + ВоÑпроизвеÑти + + + + Playback Pan / Balance + Панорама/Ð±Ð°Ð»Ð°Ð½Ñ Ð¿Ñ€Ð¸ воÑпроизведении + + + + Playback Gain + УÑиление воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + dB + Дб + + + + (current value: %1%2) + (текущее значение: %1%2) + + + + (current value: %1) + (текущее значение: %1) + + + + Toggle Visibility of %1 + Переключить видимоÑть %1 + + + + Toggle Playback of %1 + Переключить воÑпроизведение %1 + + + + Toggle %1 property of %2 + Переключить %1 ÑвойÑтво %2 + + + + Adjust %1 property of %2%3 + Скорректировать %1 ÑвойÑтво %2%3 + + + + PropertyContainer + + + yes + да + + + + on + вкл + + + + true + + + + + no + нет + + + + off + выкл + + + + false + + + + + PropertyContainer::SetPropertyCommand + + + Set %1 Property + УÑтановить ÑвойÑтво %1 + + + + PropertyStack + + + Click to change the current active layer + Щёлкните Ð´Ð»Ñ Ñмены активного ÑÐ»Ð¾Ñ + + + + QApplication + + + FFT cache resize failed + Ðе удалоÑÑŒ изменить размер кÑша FFT + + + + Failed to create or resize an FFT model slice. +There may be insufficient memory or disc space to continue. + + + + + +Sonic Visualiser is a program for viewing and exploring audio data +for semantic music analysis and annotation. + +Usage: + + %1 [--no-audio] [--no-osc] [<file> ...] + + --no-audio: Do not attempt to open an audio output device + --no-osc: Do not provide an Open Sound Control port for remote control + <file>: One or more Sonic Visualiser (.sv) and audio files may be provided. + + + + + + QFile + + + File "%1" does not exist + Файл "%1" не ÑущеÑтвует + + + + Failed to open file "%1" + Ðе удалоÑÑŒ открыть файл "%1" + + + + QFileDialog + + + Locate file "%1" + Ðайти файл "%1" + + + + Audio files (%1) +All files (*.*) + Звуковые файлы (%1) +Ð’Ñе файлы (*.*) + + + + QMessageBox + + + Failed to open file + Ðе удалоÑÑŒ открыть файл + + + + Audio file "%1" could not be opened. +Locate it? + Ðе удалоÑÑŒ открыть звуковой файл "%1". +Ðайти его? + + + + File "%1" could not be opened + Ðе удалоÑÑŒ открыть файл "%1" + + + + QObject + + + Stop + ОÑтановить + + + + Decoding %1... + ДекодируетÑÑ %1... + + + + RangeInputDialog + + + to + + + + + OK + ОК + + + + Cancel + Отменить + + + + RemoteFile + + + Downloading %1... + + + + + Cancel + Отменить + + + + Failed to connect to FTP server + + + + + Login failed + + + + + Failed to change to correct directory + + + + + FTP download aborted + + + + + Download cancelled + + + + + Failed to create local file %1 + + + + + File contains no data! + + + + + RemoveLayerCommand + + + Delete %1 Layer + Удалить %1 Ñлой + + + + SliceLayer + + + %1 - %2 + %1 - %2 + + + + Time:%1 - %2 +Range:%3 samples +Bin:%4 +%5 value:%6 + ВремÑ:%1 - %2 +Диапазон:%3 ÑÑмплов +Bin:%4 +%5 значение:%6 + + + + First + + + + + Mean + + + + + Peak + Пик + + + + Time:%1 - %2 +Range:%3 samples + ВремÑ:%1 - %2 +Диапазон:%3 ÑÑмпл(-ов) + + + + 0dB + 0 Дб + + + + -Inf + -Inf + + + + x10 + x10 + + + + Colour + Цвет + + + + Plot Type + Тип графика + + + + Scale + МаÑштаб + + + + Normalize + Ðормализовать + + + + Gain + УÑиление + + + + Sampling Mode + + + + + Plot X Scale + + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Linear + Линейный + + + + Meter + + + + + dB + Дб + + + + Any + + + + + Lines + Линии + + + + Steps + + + + + Blocks + + + + + Colours + + + + + Linear Bins + + + + + Log Bins + + + + + Rev Log Bins + + + + + <unknown> + <неизвеÑтно> + + + + SparseModel + + + Add Point + Добавить точку + + + + Delete Point + Удалить точку + + + + Re-Label Point + + + + + SpectrogramLayer + + + Colour + Цвет + + + + Colour Scale + + + + + Window Size + Размер окна + + + + Normalize Columns + Ðормализовать Ñтолбцы + + + + Bin Display + + + + + Threshold + Порог + + + + Gain + УÑиление + + + + Colour Rotation + Вращение цвета + + + + Min Frequency + Мин. чаÑтота + + + + Max Frequency + МакÑ. чаÑтота + + + + Frequency Scale + + + + + Window + Окно + + + + Scale + МаÑштаб + + + + Default + По умолчанию + + + + White on Black + Белое на чёрном + + + + Black on White + Чёрное на белом + + + + Red on Blue + КраÑное на Ñинем + + + + Yellow on Black + Жёлтое на чёрном + + + + Blue on Black + Синее на чёрном + + + + Fruit Salad + Фруктовый Ñалат + + + + Linear + Линейный + + + + Meter + + + + + dB + Дб + + + + Phase + Фаза + + + + No min + + + + + 10 Hz + 10 Гц + + + + 20 Hz + 20 Гц + + + + 40 Hz + 40 Гц + + + + 100 Hz + 100 Гц + + + + 250 Hz + 250 Гц + + + + 500 Hz + 500 Гц + + + + 1 KHz + 1 КГц + + + + 4 KHz + 4 КГц + + + + 10 KHz + 10 КГц + + + + 1.5 KHz + 1,5 КГц + + + + 2 KHz + 2 КГц + + + + 6 KHz + 6 КГц + + + + 8 KHz + 8 КГц + + + + 12 KHz + 12 КГц + + + + 16 KHz + 16 КГц + + + + No max + + + + + Log + Логарифм. + + + + All Bins + + + + + Peak Bins + + + + + Frequencies + ЧаÑтоты + + + + <unknown> + <неизвеÑтно> + + + + Peak Frequency:%1 - %2 Hz + + ÐŸÐ¸ÐºÐ¾Ð²Ð°Ñ Ñ‡Ð°Ñтота:%1 - %2 Гц + + + + + Peak Frequency:%1 Hz + + ÐŸÐ¸ÐºÐ¾Ð²Ð°Ñ Ñ‡Ð°Ñтота:%1 Гц + + + + + Peak Pitch:%3 - %4 + + + + + + Peak Pitch:%2 + + + + + + Time:%1 - %2 + + ВремÑ:%1 - %2 + + + + + Time:%1 + + ВремÑ:%1 + + + + + %1Bin Frequency:%2 - %3 Hz +%4Bin Pitch:%5 - %6 + + + + + + %1Bin Frequency:%2 Hz +%3Bin Pitch:%4 + + + + + + -Inf + -Inf + + + + dB:%1 - %2 + Дб:%1 - %2 + + + + dB:%1 + Дб:%1 + + + + +Phase:%1 - %2 + +Фаза:%1 - %2 + + + + +Phase:%1 + +Фаза:%1 + + + + Window Overlap + Перекрытие окон + + + + Smoothing + + + + + None + Ðичего + + + + 25 % + 25 % + + + + 50 % + 50 % + + + + 75 % + 75 % + + + + 87.5 % + 87,5 % + + + + 93.75 % + 93,75 % + + + + Normalize Visible Area + Ðормализовать видимую облаÑть + + + + Bins + + + + + dB^2 + Дб^2 + + + + FFT cache failed + Ðе удалоÑÑŒ кÑшировать FFT + + + + Failed to create the FFT model for this spectrogram. +There may be insufficient memory or disc space to continue. + + + + + dBV^2 + + + + + dBV + + + + + SpectrumLayer + + + Colour + Цвет + + + + Scale + МаÑштаб + + + + Channels + Каналы + + + + Window Size + Размер окна + + + + Window Overlap + Перекрытие окон + + + + Normalize + Ðормализовать + + + + Gain + УÑиление + + + + Window + Окно + + + + Energy Scale + МаÑштаб Ñнергии + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Linear + Линейный + + + + dB + Дб + + + + Mean + + + + + None + Ðичего + + + + 25 % + 25 % + + + + 50 % + 50 % + + + + 75 % + 75 % + + + + 87.5 % + 87,5 % + + + + 93.75 % + 93,75 % + + + + <unknown> + <неизвеÑтно> + + + + %1 - %2 + %1 - %2 + + + + %1 - %2 Hz + %1 - %2 Гц + + + + %1 Hz + %1 Гц + + + + -Inf + -Inf + + + + %1 + %1 + + + + %1 +Bin:%2 (%3) +%4 value:%5 +dB:%6 + + + + + First + + + + + Peak + Пик + + + + %1 +Bin:%2 (%3) +Value:%4 +dB:%5 + + + + + SubdividingMenu + + + %1 - %2 + %1 - %2 + + + + TextLayer + + + Empty Label + ОчиÑтить метку + + + + Colour + Цвет + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + <unknown> + <неизвеÑтно> + + + + <no text> + <без текÑта> + + + + In progress + Ð’ процеÑÑе + + + + Time:%1 +Height:%2 +Label:%3 + ВремÑ:%1 +Ð’Ñ‹Ñота:%2 +Метка:%3 + + + + Enter label + Введите метку + + + + Please enter a new label: + Введите текÑÑ‚ новой метки: + + + + Drag Label + Перетащите метку + + + + Move Label + ПеремеÑтите метку + + + + Move Label Horizontally + ПеремеÑтите по горизонтали + + + + Move Label Vertically + ПеремеÑтите по вертикали + + + + Drag Selection + Перетащите выделение + + + + Resize Selection + Смените размер Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + + + + Delete Selection + Удалите выделение + + + + Paste + Ð’Ñтавить + + + + New Point + ÐÐ¾Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° + + + + Thumbwheel + + + %1: %2%3 + %1: %2%3 + + + + %2%3 + %2%3 + + + + New value for %1, from %2 to %3 %4: + Ðовое значение Ð´Ð»Ñ %1, от %2 до %3 %4: + + + + New value for %1, from %2 to %3: + Ðовое значение Ð´Ð»Ñ %1, от %2 до %3: + + + + Enter a new value from %1 to %2 %3: + Введите новое значение от %1 до %2 %3: + + + + Enter a new value from %1 to %2: + Введите новое значение от %1 до %2: + + + + Enter new value + Введите новое значение + + + + TimeInstantLayer + + + New Point + ÐÐ¾Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° + + + + Colour + Цвет + + + + Plot Type + Тип графика + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Instants + + + + + Segmentation + Ð¡ÐµÐ³Ð¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + + + + <unknown> + <неизвеÑтно> + + + + In progress + Ð’ процеÑÑе + + + + No local points + Ðет локальных точек + + + + Time:%1 +No label + ВремÑ:%1 +Без метки + + + + Time:%1 +Label:%2 + ВремÑ:%1 +Метка:%2 + + + + Draw Point + ÐариÑовать точку + + + + Add Point at %1 s + Добавить точку в %1 Ñ + + + + Drag Point + Перетащить точку + + + + Move Point to %1 s + Перетащить точку к %1 Ñ + + + + Edit Point + Изменить точку + + + + Drag Selection + Перетащить выделение + + + + Resize Selection + + + + + Delete Selection + Удалить выделение + + + + Paste + Ð’Ñтавить + + + + TimeRulerLayer + + + Colour + Цвет + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + <unknown> + <неизвеÑтно> + + + + TimeValueLayer + + + New Point + ÐÐ¾Ð²Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° + + + + Colour + Цвет + + + + Plot Type + Тип графика + + + + Vertical Scale + Верт. маÑштаб + + + + Scale Units + Единицы шкалы + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Points + Точки + + + + Stems + + + + + Connected Points + Соединённые точки + + + + Lines + Линии + + + + Curve + ÐšÑ€Ð¸Ð²Ð°Ñ + + + + Segmentation + Ð¡ÐµÐ³Ð¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + + + + Auto-Align + Ðвтовыравнивание + + + + Linear Scale + Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ ÑˆÐºÐ°Ð»Ð° + + + + Log Scale + Логарифм. шкала + + + + <unknown> + <неизвеÑтно> + + + + In progress + Ð’ процеÑÑе + + + + No local points + Ðет локальных точек + + + + Time:%1 +Value:%2%3 +No label + ВремÑ:%1 +Значение:%2%3 +Без метки + + + + Time:%1 +Value:%2%3 +Label:%4 + ВремÑ:%1 +Значение:%2%3 +Метка:%4 + + + + Draw Point + ÐариÑовать точку + + + + Drag Point + Перетащить точку + + + + Edit Point + Изменить точку + + + + Relocate Point + ПеремеÑтить точку + + + + Change Point Value + Изменить значение точки + + + + Drag Selection + Перетащить выделение + + + + Resize Selection + + + + + Delete Selected Points + Удалить выбранные точки + + + + Paste + Ð’Ñтавить + + + + The items you are pasting do not have values. +What values do you want to use for these items? + + + + + Some of the items you are pasting do not have values. +What values do you want to use for these items? + + + + + Zero for all items + + + + + Whole numbers counting from 1 + + + + + Item's audio sample frame number + + + + + Item's time in seconds + + + + + Duration from the item to the following item + + + + + Tempo in bpm derived from the duration + + + + + Value of the nearest existing item + + + + + Value extracted from the item's label (where possible) + + + + + Choose value calculation + Выбрать вычиÑление Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ + + + + Scale + МаÑштаб + + + + Linear + Линейный + + + + Log + Логарифм. + + + + +/-1 + +/-1 + + + + TipDialog + + + Tip of the Day + + + + + Show tip on startup + + + + + << Previous + + + + + Next >> + + + + + Close + + + + + TransformFactory + + + %1: %2 + %1: %2 + + + + %1: Output %2 + %1: Выход %2 + + + + Analysis + Ðнализ + + + + Effects Data + Данные Ñффектов + + + + Effects + Эффекты + + + + Generators + Генераторы + + + + %1 <%2> + %1 <%2> + + + + [\(<].*$ + [\(<].*$ + + + + <unknown maker> + + + + + Extract features using "%1" plugin (from %2) + + + + + Extract features using "%1" output of "%2" plugin (from %3) + + + + + %1 using "%2" plugin (from %3) + + + + + %1 using "%2" output of "%3" plugin (from %4) + + + + + Extract "%1" data output from "%2" effect plugin (from %3) + + + + + Extract data output %1 from "%2" effect plugin (from %3) + + + + + Transform audio signal with "%1" effect plugin (from %2) + + + + + Generate audio signal using "%1" plugin (from %2) + + + + + View + + + Global Scroll + Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ° + + + + Global Zoom + Глобальный маÑштаб + + + + Follow Playback + Следовать за воÑпроизведением + + + + Scroll + Прокручивать + + + + Page + ПоÑтранично + + + + Off + Выкл + + + + <unknown> + <неизвеÑтно> + + + + Waiting for layers to be ready... + Ожидание готовноÑти Ñлоёв... + + + + Cancel + Отменить + + + + Rendering image... + ОтриÑовываетÑÑ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ðµ... + + + + ViewManager::SetSelectionCommand + + + Clear Selection + СнÑть выделение + + + + Select + Выделить + + + + WaveformLayer + + + Colour + Цвет + + + + Scale + МаÑштаб + + + + Gain + УÑиление + + + + Normalize Visible Area + Ðормализовать видимую облаÑть + + + + Channels + Каналы + + + + Black + Чёрный + + + + Red + КраÑный + + + + Blue + Синий + + + + Green + Зелёный + + + + Purple + Пурпурный + + + + Orange + Оранжевый + + + + Linear + Линейный + + + + Meter + + + + + dB + Дб + + + + Separate + + + + + Mean + + + + + Butterfly + + + + + <unknown> + <неизвеÑтно> + + + + Time:%1 - %2 + ВремÑ:%1 - %2 + + + + Time:%1 + ВремÑ:%1 + + + + Level: + Уровень: + + + + Left: + Левый: + + + + Right: + Правый: + + + + Channel %1 + Канал %1 + + + + +%1%2 - %3 (%4 dB peak) + +%1%2 - %3 (пик в %4 Дб) + + + + +%1%2 (%3 dB peak) + +%1%2 (пик в %3 Дб) + + + + 0dB + 0 Дб + + + + -Inf + -Inf + + + + WindowShapePreview + + + V / time + + + + + dB / freq + Дб / чаÑтота + + + diff -r 000000000000 -r fc9323a41f5a sv/i18n/tips_en.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/i18n/tips_en.xml Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,73 @@ + + + + + + +

Welcome to Sonic Visualiser!

Sonic Visualiser is a +complex application, but it's easy enough to get started with.

+

Try importing an audio file and using the Pane menu to add some +different views of it.

Check out the links on the Help menu for +tutorials and other documentation!

+ +
+ + +

Sonic Visualiser's window is organised into "panes" and +"layers". To begin with, you have one pane. Import an audio +file to see it displayed in a waveform layer on that pane.

+ +
+ + +

Each pane can contain any number of layers, which appear stacked +from "front" to "back" on the same timeline. A layer can contain a +view of audio, or of points (time instants) or data plots (time-values).

+ +
+ + +

There's a "layer property box" to the right of each pane, with one +tab for each of the layers on that pane. Click on a layer's tab to +bring that layer to the front. You can then adjust its colour and +other display properties in the property box. + +The first tab is always for the pane itself, which has a few +adjustable properties of its own.

+ +
+ + +

If you have more than one pane, only one of them will be "current", +marked with a black bar at the left side. Just click on another pane +to make it current. Most editing operations affect the layer that's +at the front of the current pane.

+ +
+ + +

You can use different zoom levels for different panes by un-checking +the Global Zoom control for one of them. By default, all panes will +zoom and scroll together.

+ +
+ + +

You can speed up and slow down playback using the Playback Speedup +control in the bottom-right of the window.

+ +
+ + +

The Transforms menu lists things you can do to extract features from +or process your audio. The available outputs of any Vamp +feature-extraction plugins or LADSPA audio effects plugins you have +will show up here.

+ +
+ +
+ + diff -r 000000000000 -r fc9323a41f5a sv/icons/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/icons/README Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,3 @@ +Icons are from various GPL'd sources, including the GIMP image editor, +KDE crystalsvg icon set, Rosegarden audio and MIDI sequencer, and Hydrogen +drum machine. Some modifications and new drawings by Chris Cannam. diff -r 000000000000 -r fc9323a41f5a sv/icons/annotation.png Binary file sv/icons/annotation.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/colour3d.png Binary file sv/icons/colour3d.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/draw-curve.png Binary file sv/icons/draw-curve.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/draw.png Binary file sv/icons/draw.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/editcopy.png Binary file sv/icons/editcopy.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/editcut.png Binary file sv/icons/editcut.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/editdelete.png Binary file sv/icons/editdelete.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/editpaste.png Binary file sv/icons/editpaste.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/exit.png Binary file sv/icons/exit.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fader_background.png Binary file sv/icons/fader_background.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fader_knob.png Binary file sv/icons/fader_knob.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fader_knob_red.png Binary file sv/icons/fader_knob_red.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fader_leds.png Binary file sv/icons/fader_leds.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/faders.png Binary file sv/icons/faders.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/ffwd-end.png Binary file sv/icons/ffwd-end.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/ffwd.png Binary file sv/icons/ffwd.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fileclose.png Binary file sv/icons/fileclose.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filenew-22.png Binary file sv/icons/filenew-22.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filenew.png Binary file sv/icons/filenew.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fileopen-22.png Binary file sv/icons/fileopen-22.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fileopen.png Binary file sv/icons/fileopen.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fileopenaudio.png Binary file sv/icons/fileopenaudio.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/fileopensession.png Binary file sv/icons/fileopensession.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filesave-22.png Binary file sv/icons/filesave-22.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filesave.png Binary file sv/icons/filesave.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filesaveas-22.png Binary file sv/icons/filesaveas-22.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/filesaveas.png Binary file sv/icons/filesaveas.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/help.png Binary file sv/icons/help.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/hh.gif Binary file sv/icons/hh.gif has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/hh.png Binary file sv/icons/hh.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/info.png Binary file sv/icons/info.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/instants.png Binary file sv/icons/instants.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/mono.png Binary file sv/icons/mono.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/move.png Binary file sv/icons/move.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/navigate.png Binary file sv/icons/navigate.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/new.png Binary file sv/icons/new.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/notes.png Binary file sv/icons/notes.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/pane.png Binary file sv/icons/pane.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/playloop.png Binary file sv/icons/playloop.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/playpause-orig.png Binary file sv/icons/playpause-orig.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/playpause.png Binary file sv/icons/playpause.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/playselection.png Binary file sv/icons/playselection.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/playselectionloop.png Binary file sv/icons/playselectionloop.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/redo.png Binary file sv/icons/redo.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/rewind-start.png Binary file sv/icons/rewind-start.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/rewind.png Binary file sv/icons/rewind.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/select.png Binary file sv/icons/select.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/sharpen.png Binary file sv/icons/sharpen.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/speaker.png Binary file sv/icons/speaker.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/spectrogram-22x22.png Binary file sv/icons/spectrogram-22x22.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/spectrogram.png Binary file sv/icons/spectrogram.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/spectrum.png Binary file sv/icons/spectrum.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/stereo.png Binary file sv/icons/stereo.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/svicon16.png Binary file sv/icons/svicon16.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/svicon32.png Binary file sv/icons/svicon32.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/text.png Binary file sv/icons/text.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/timeruler.png Binary file sv/icons/timeruler.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/undo.png Binary file sv/icons/undo.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/values.png Binary file sv/icons/values.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/waveform.png Binary file sv/icons/waveform.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/zoom-fit.png Binary file sv/icons/zoom-fit.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/zoom-in.png Binary file sv/icons/zoom-in.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/zoom-out.png Binary file sv/icons/zoom-out.png has changed diff -r 000000000000 -r fc9323a41f5a sv/icons/zoom.png Binary file sv/icons/zoom.png has changed diff -r 000000000000 -r fc9323a41f5a sv/main/MainWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/main/MainWindow.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,4774 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "version.h" + +#include "MainWindow.h" +#include "document/Document.h" +#include "PreferencesDialog.h" + +#include "view/Pane.h" +#include "view/PaneStack.h" +#include "data/model/WaveFileModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "view/ViewManager.h" +#include "base/Preferences.h" +#include "layer/WaveformLayer.h" +#include "layer/TimeRulerLayer.h" +#include "layer/TimeInstantLayer.h" +#include "layer/TimeValueLayer.h" +#include "layer/Colour3DPlotLayer.h" +#include "layer/SliceLayer.h" +#include "layer/SliceableLayer.h" +#include "widgets/Fader.h" +#include "view/Overview.h" +#include "widgets/PropertyBox.h" +#include "widgets/PropertyStack.h" +#include "widgets/AudioDial.h" +#include "widgets/LayerTree.h" +#include "widgets/ListInputDialog.h" +#include "widgets/SubdividingMenu.h" +#include "widgets/NotifyingPushButton.h" +#include "audioio/AudioCallbackPlaySource.h" +#include "audioio/AudioCallbackPlayTarget.h" +#include "audioio/AudioTargetFactory.h" +#include "audioio/PlaySpeedRangeMapper.h" +#include "data/fileio/AudioFileReaderFactory.h" +#include "data/fileio/DataFileReaderFactory.h" +#include "data/fileio/WavFileWriter.h" +#include "data/fileio/CSVFileWriter.h" +#include "data/fileio/BZipFileDevice.h" +#include "data/fileio/RemoteFile.h" +#include "data/fft/FFTDataServer.h" +#include "base/RecentFiles.h" +#include "transform/TransformFactory.h" +#include "base/PlayParameterRepository.h" +#include "base/XmlExportable.h" +#include "base/CommandHistory.h" +#include "base/Profiler.h" +#include "base/Clipboard.h" +#include "osc/OSCQueue.h" + +// For version information +#include "vamp/vamp.h" +#include "vamp-sdk/PluginBase.h" +#include "plugin/api/ladspa.h" +#include "plugin/api/dssi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using std::cerr; +using std::endl; + +using std::vector; +using std::map; +using std::set; + + +MainWindow::MainWindow(bool withAudioOutput, bool withOSCSupport) : + m_document(0), + m_paneStack(0), + m_viewManager(0), + m_overview(0), + m_timeRulerLayer(0), + m_audioOutput(withAudioOutput), + m_playSource(0), + m_playTarget(0), + m_oscQueue(withOSCSupport ? new OSCQueue() : 0), + m_recentFiles("RecentFiles", 20), + m_recentTransforms("RecentTransforms", 20), + m_mainMenusCreated(false), + m_paneMenu(0), + m_layerMenu(0), + m_transformsMenu(0), + m_existingLayersMenu(0), + m_sliceMenu(0), + m_recentFilesMenu(0), + m_recentTransformsMenu(0), + m_rightButtonMenu(0), + m_rightButtonLayerMenu(0), + m_rightButtonTransformsMenu(0), + m_documentModified(false), + m_openingAudioFile(false), + m_abandoning(false), + m_preferencesDialog(0) +{ + setWindowTitle(tr("Sonic Visualiser")); + + UnitDatabase::getInstance()->registerUnit("Hz"); + UnitDatabase::getInstance()->registerUnit("dB"); + + connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), + this, SLOT(documentModified())); + connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), + this, SLOT(documentRestored())); + + QFrame *frame = new QFrame; + setCentralWidget(frame); + + QGridLayout *layout = new QGridLayout; + + m_viewManager = new ViewManager(); + connect(m_viewManager, SIGNAL(selectionChanged()), + this, SLOT(updateMenuStates())); + connect(m_viewManager, SIGNAL(inProgressSelectionChanged()), + this, SLOT(inProgressSelectionChanged())); + + m_descriptionLabel = new QLabel; + + m_paneStack = new PaneStack(frame, m_viewManager); + connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), + this, SLOT(currentPaneChanged(Pane *))); + connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), + this, SLOT(currentLayerChanged(Pane *, Layer *))); + connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), + this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); + connect(m_paneStack, SIGNAL(propertyStacksResized()), + this, SLOT(propertyStacksResized())); + connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + + m_overview = new Overview(frame); + m_overview->setViewManager(m_viewManager); + m_overview->setFixedHeight(40); + m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + + m_panLayer = new WaveformLayer; + m_panLayer->setChannelMode(WaveformLayer::MergeChannels); +// m_panLayer->setScale(WaveformLayer::MeterScale); +// m_panLayer->setAutoNormalize(true); + m_panLayer->setBaseColour(Qt::darkGreen); + m_panLayer->setAggressiveCacheing(true); + m_overview->addLayer(m_panLayer); + + m_playSource = new AudioCallbackPlaySource(m_viewManager); + + connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), + this, SLOT(sampleRateMismatch(size_t, size_t, bool))); + connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), + this, SLOT(audioOverloadPluginDisabled())); + + m_fader = new Fader(frame, false); + connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_playSpeed = new AudioDial(frame); + m_playSpeed->setMinimum(0); + m_playSpeed->setMaximum(200); + m_playSpeed->setValue(100); + m_playSpeed->setFixedWidth(24); + m_playSpeed->setFixedHeight(24); + m_playSpeed->setNotchesVisible(true); + m_playSpeed->setPageStep(10); + m_playSpeed->setObjectName(tr("Playback Speedup")); + m_playSpeed->setDefaultValue(100); + m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper(0, 200)); + m_playSpeed->setShowToolTip(true); + connect(m_playSpeed, SIGNAL(valueChanged(int)), + this, SLOT(playSpeedChanged(int))); + connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_playSharpen = new NotifyingPushButton(frame); + m_playSharpen->setToolTip(tr("Sharpen percussive transients")); + m_playSharpen->setFixedSize(20, 20); +// m_playSharpen->setFlat(true); + m_playSharpen->setEnabled(false); + m_playSharpen->setCheckable(true); + m_playSharpen->setChecked(false); + m_playSharpen->setIcon(QIcon(":icons/sharpen.png")); + connect(m_playSharpen, SIGNAL(clicked()), this, SLOT(playSharpenToggled())); + connect(m_playSharpen, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_playSharpen, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_playMono = new NotifyingPushButton(frame); + m_playMono->setToolTip(tr("Run time stretcher in mono only")); + m_playMono->setFixedSize(20, 20); +// m_playMono->setFlat(true); + m_playMono->setEnabled(false); + m_playMono->setCheckable(true); + m_playMono->setChecked(false); + m_playMono->setIcon(QIcon(":icons/mono.png")); + connect(m_playMono, SIGNAL(clicked()), this, SLOT(playMonoToggled())); + connect(m_playMono, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_playMono, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + QSettings settings; + settings.beginGroup("MainWindow"); + m_playSharpen->setChecked(settings.value("playsharpen", true).toBool()); + m_playMono->setChecked(settings.value("playmono", false).toBool()); + settings.endGroup(); + + layout->setSpacing(4); + layout->addWidget(m_paneStack, 0, 0, 1, 5); + layout->addWidget(m_overview, 1, 0); + layout->addWidget(m_fader, 1, 1); + layout->addWidget(m_playSpeed, 1, 2); + layout->addWidget(m_playSharpen, 1, 3); + layout->addWidget(m_playMono, 1, 4); + + m_paneStack->setPropertyStackMinWidth + (m_fader->width() + m_playSpeed->width() + m_playSharpen->width() + + m_playMono->width() + layout->spacing() * 4); + + layout->setColumnStretch(0, 10); + + frame->setLayout(layout); + + connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), + this, SLOT(outputLevelsChanged(float, float))); + + connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)), + this, SLOT(playbackFrameChanged(unsigned long))); + + connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)), + this, SLOT(globalCentreFrameChanged(unsigned long))); + + connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)), + this, SLOT(viewCentreFrameChanged(View *, unsigned long))); + + connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)), + this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); + + connect(Preferences::getInstance(), + SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, + SLOT(preferenceChanged(PropertyContainer::PropertyName))); + +// preferenceChanged("Property Box Layout"); + + if (m_oscQueue && m_oscQueue->isOK()) { + connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC())); + QTimer *oscTimer = new QTimer(this); + connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC())); + oscTimer->start(1000); + } + + setupMenus(); + setupToolbars(); + + statusBar(); + + newSession(); +} + +MainWindow::~MainWindow() +{ +// std::cerr << "MainWindow::~MainWindow()" << std::endl; + + if (!m_abandoning) { + closeSession(); + } + delete m_playTarget; + delete m_playSource; + delete m_viewManager; + delete m_oscQueue; + Profiles::getInstance()->dump(); +} + +QString +MainWindow::getOpenFileName(FileFinder::FileType type) +{ + FileFinder *ff = FileFinder::getInstance(); + switch (type) { + case FileFinder::SessionFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::AudioFile: + return ff->getOpenFileName(type, m_audioFile); + case FileFinder::LayerFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::SessionOrAudioFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::ImageFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::AnyFile: + if (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0) { // can import a layer + return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile); + } else { + return ff->getOpenFileName(FileFinder::SessionOrAudioFile, + m_sessionFile); + } + } + return ""; +} + +QString +MainWindow::getSaveFileName(FileFinder::FileType type) +{ + FileFinder *ff = FileFinder::getInstance(); + switch (type) { + case FileFinder::SessionFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::AudioFile: + return ff->getSaveFileName(type, m_audioFile); + case FileFinder::LayerFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::SessionOrAudioFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::ImageFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::AnyFile: + return ff->getSaveFileName(type, m_sessionFile); + } + return ""; +} + +void +MainWindow::registerLastOpenedFilePath(FileFinder::FileType type, QString path) +{ + FileFinder *ff = FileFinder::getInstance(); + ff->registerLastOpenedFilePath(type, path); +} + +void +MainWindow::setupMenus() +{ + if (!m_mainMenusCreated) { + m_rightButtonMenu = new QMenu(); + + // No -- we don't want tear-off enabled on the right-button + // menu. If it is enabled, then simply right-clicking and + // releasing will pop up the menu, activate the tear-off, and + // leave the torn-off menu window in front of the main window. + // That isn't desirable. I'm not sure it ever would be, in a + // context menu -- perhaps technically a Qt bug? +// m_rightButtonMenu->setTearOffEnabled(true); + } + + if (m_rightButtonLayerMenu) { + m_rightButtonLayerMenu->clear(); + } else { + m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); + m_rightButtonLayerMenu->setTearOffEnabled(true); + m_rightButtonMenu->addSeparator(); + } + + if (m_rightButtonTransformsMenu) { + m_rightButtonTransformsMenu->clear(); + } else { + m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform")); + m_rightButtonTransformsMenu->setTearOffEnabled(true); + m_rightButtonMenu->addSeparator(); + } + + if (!m_mainMenusCreated) { + CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); + m_rightButtonMenu->addSeparator(); + } + + setupFileMenu(); + setupEditMenu(); + setupViewMenu(); + setupPaneAndLayerMenus(); + setupTransformsMenu(); + setupHelpMenu(); + + m_mainMenusCreated = true; +} + +void +MainWindow::setupFileMenu() +{ + if (m_mainMenusCreated) return; + + QMenu *menu = menuBar()->addMenu(tr("&File")); + menu->setTearOffEnabled(true); + QToolBar *toolbar = addToolBar(tr("File Toolbar")); + + QIcon icon(":icons/filenew.png"); + icon.addFile(":icons/filenew-22.png"); + QAction *action = new QAction(icon, tr("&New Session"), this); + action->setShortcut(tr("Ctrl+N")); + action->setStatusTip(tr("Abandon the current Sonic Visualiser session and start a new one")); + connect(action, SIGNAL(triggered()), this, SLOT(newSession())); + menu->addAction(action); + toolbar->addAction(action); + + icon = QIcon(":icons/fileopensession.png"); + action = new QAction(icon, tr("&Open Session..."), this); + action->setShortcut(tr("Ctrl+O")); + action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(openSession())); + menu->addAction(action); + + icon = QIcon(":icons/fileopen.png"); + icon.addFile(":icons/fileopen-22.png"); + + action = new QAction(icon, tr("&Open..."), this); + action->setStatusTip(tr("Open a session file, audio file, or layer")); + connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); + toolbar->addAction(action); + + icon = QIcon(":icons/filesave.png"); + icon.addFile(":icons/filesave-22.png"); + action = new QAction(icon, tr("&Save Session"), this); + action->setShortcut(tr("Ctrl+S")); + action->setStatusTip(tr("Save the current session into a Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); + connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + toolbar->addAction(action); + + icon = QIcon(":icons/filesaveas.png"); + icon.addFile(":icons/filesaveas-22.png"); + action = new QAction(icon, tr("Save Session &As..."), this); + action->setStatusTip(tr("Save the current session into a new Sonic Visualiser session file")); + connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); + menu->addAction(action); + toolbar->addAction(action); + + menu->addSeparator(); + + icon = QIcon(":icons/fileopenaudio.png"); + action = new QAction(icon, tr("&Import Audio File..."), this); + action->setShortcut(tr("Ctrl+I")); + action->setStatusTip(tr("Import an existing audio file")); + connect(action, SIGNAL(triggered()), this, SLOT(importAudio())); + menu->addAction(action); + + action = new QAction(tr("Import Secondary Audio File..."), this); + action->setShortcut(tr("Ctrl+Shift+I")); + action->setStatusTip(tr("Import an extra audio file as a separate layer")); + connect(action, SIGNAL(triggered()), this, SLOT(importMoreAudio())); + connect(this, SIGNAL(canImportMoreAudio(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("&Export Audio File..."), this); + action->setStatusTip(tr("Export selection as an audio file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); + connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Import Annotation &Layer..."), this); + action->setShortcut(tr("Ctrl+L")); + action->setStatusTip(tr("Import layer data from an existing file")); + connect(action, SIGNAL(triggered()), this, SLOT(importLayer())); + connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Export Annotation Layer..."), this); + action->setStatusTip(tr("Export layer data to a file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportLayer())); + connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Export Image File..."), this); + action->setStatusTip(tr("Export a single pane to an image file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportImage())); + connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Open Lo&cation..."), this); + action->setShortcut(tr("Ctrl+Shift+O")); + action->setStatusTip(tr("Open or import a file from a remote URL")); + connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); + menu->addAction(action); + + menu->addSeparator(); + + m_recentFilesMenu = menu->addMenu(tr("&Recent Files")); + m_recentFilesMenu->setTearOffEnabled(true); + setupRecentFilesMenu(); + connect(&m_recentFiles, SIGNAL(recentChanged()), + this, SLOT(setupRecentFilesMenu())); + + menu->addSeparator(); + action = new QAction(tr("&Preferences..."), this); + action->setStatusTip(tr("Adjust the application preferences")); + connect(action, SIGNAL(triggered()), this, SLOT(preferences())); + menu->addAction(action); + + /*!!! + menu->addSeparator(); + + action = new QAction(tr("Play / Pause"), this); + action->setShortcut(tr("Space")); + action->setStatusTip(tr("Start or stop playback from the current position")); + connect(action, SIGNAL(triggered()), this, SLOT(play())); + menu->addAction(action); + */ + + menu->addSeparator(); + action = new QAction(QIcon(":/icons/exit.png"), + tr("&Quit"), this); + action->setShortcut(tr("Ctrl+Q")); + action->setStatusTip(tr("Exit Sonic Visualiser")); + connect(action, SIGNAL(triggered()), this, SLOT(close())); + menu->addAction(action); +} + +void +MainWindow::setupEditMenu() +{ + if (m_mainMenusCreated) return; + + QMenu *menu = menuBar()->addMenu(tr("&Edit")); + menu->setTearOffEnabled(true); + CommandHistory::getInstance()->registerMenu(menu); + + menu->addSeparator(); + + QAction *action = new QAction(QIcon(":/icons/editcut.png"), + tr("Cu&t"), this); + action->setShortcut(tr("Ctrl+X")); + action->setStatusTip(tr("Cut the selection from the current layer to the clipboard")); + connect(action, SIGNAL(triggered()), this, SLOT(cut())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editcopy.png"), + tr("&Copy"), this); + action->setShortcut(tr("Ctrl+C")); + action->setStatusTip(tr("Copy the selection from the current layer to the clipboard")); + connect(action, SIGNAL(triggered()), this, SLOT(copy())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editpaste.png"), + tr("&Paste"), this); + action->setShortcut(tr("Ctrl+V")); + action->setStatusTip(tr("Paste from the clipboard to the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(paste())); + connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("&Delete Selected Items"), this); + action->setShortcut(tr("Del")); + action->setStatusTip(tr("Delete the selection from the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteSelected())); + connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + m_rightButtonMenu->addSeparator(); + + action = new QAction(tr("Select &All"), this); + action->setShortcut(tr("Ctrl+A")); + action->setStatusTip(tr("Select the whole duration of the current session")); + connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Select &Visible Range"), this); + action->setShortcut(tr("Ctrl+Shift+A")); + action->setStatusTip(tr("Select the time range corresponding to the current window width")); + connect(action, SIGNAL(triggered()), this, SLOT(selectVisible())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Select to &Start"), this); + action->setShortcut(tr("Shift+Left")); + action->setStatusTip(tr("Select from the start of the session to the current playback position")); + connect(action, SIGNAL(triggered()), this, SLOT(selectToStart())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Select to &End"), this); + action->setShortcut(tr("Shift+Right")); + action->setStatusTip(tr("Select from the current playback position to the end of the session")); + connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("C&lear Selection"), this); + action->setShortcut(tr("Esc")); + action->setStatusTip(tr("Clear the selection")); + connect(action, SIGNAL(triggered()), this, SLOT(clearSelection())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("&Insert Instant at Playback Position"), this); + action->setShortcut(tr("Enter")); + action->setStatusTip(tr("Insert a new time instant at the current playback position, in a new layer if necessary")); + connect(action, SIGNAL(triggered()), this, SLOT(insertInstant())); + connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Insert Instants at Selection &Boundaries"), this); + action->setShortcut(tr("Shift+Enter")); + action->setStatusTip(tr("Insert new time instants at the start and end of the current selection, in a new layer if necessary")); + connect(action, SIGNAL(triggered()), this, SLOT(insertInstantsAtBoundaries())); + connect(this, SIGNAL(canInsertInstantsAtBoundaries(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + // Laptop shortcut (no keypad Enter key) + connect(new QShortcut(tr(";"), this), SIGNAL(activated()), + this, SLOT(insertInstant())); +} + +void +MainWindow::setupViewMenu() +{ + if (m_mainMenusCreated) return; + + QAction *action = 0; + + QMenu *menu = menuBar()->addMenu(tr("&View")); + menu->setTearOffEnabled(true); + action = new QAction(tr("Scroll &Left"), this); + action->setShortcut(tr("Left")); + action->setStatusTip(tr("Scroll the current pane to the left")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Scroll &Right"), this); + action->setShortcut(tr("Right")); + action->setStatusTip(tr("Scroll the current pane to the right")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("&Jump Left"), this); + action->setShortcut(tr("Ctrl+Left")); + action->setStatusTip(tr("Scroll the current pane a big step to the left")); + connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("J&ump Right"), this); + action->setShortcut(tr("Ctrl+Right")); + action->setStatusTip(tr("Scroll the current pane a big step to the right")); + connect(action, SIGNAL(triggered()), this, SLOT(jumpRight())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(QIcon(":/icons/zoom-in.png"), + tr("Zoom &In"), this); + action->setShortcut(tr("Up")); + action->setStatusTip(tr("Increase the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(QIcon(":/icons/zoom-out.png"), + tr("Zoom &Out"), this); + action->setShortcut(tr("Down")); + action->setStatusTip(tr("Decrease the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("Restore &Default Zoom"), this); + action->setStatusTip(tr("Restore the zoom level to the default")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(QIcon(":/icons/zoom-fit.png"), + tr("Zoom to &Fit"), this); + action->setStatusTip(tr("Zoom to show the whole file")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + QActionGroup *overlayGroup = new QActionGroup(this); + + action = new QAction(tr("Show &No Overlays"), this); + action->setShortcut(tr("0")); + action->setStatusTip(tr("Hide centre indicator, frame times, layer names and scale")); + connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays())); + action->setCheckable(true); + action->setChecked(false); + overlayGroup->addAction(action); + menu->addAction(action); + + action = new QAction(tr("Show &Minimal Overlays"), this); + action->setShortcut(tr("9")); + action->setStatusTip(tr("Show centre indicator only")); + connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays())); + action->setCheckable(true); + action->setChecked(false); + overlayGroup->addAction(action); + menu->addAction(action); + + action = new QAction(tr("Show &Standard Overlays"), this); + action->setShortcut(tr("8")); + action->setStatusTip(tr("Show centre indicator, frame times and scale")); + connect(action, SIGNAL(triggered()), this, SLOT(showStandardOverlays())); + action->setCheckable(true); + action->setChecked(true); + overlayGroup->addAction(action); + menu->addAction(action); + + action = new QAction(tr("Show &All Overlays"), this); + action->setShortcut(tr("7")); + action->setStatusTip(tr("Show all texts and scale")); + connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays())); + action->setCheckable(true); + action->setChecked(false); + overlayGroup->addAction(action); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Show &Zoom Wheels"), this); + action->setShortcut(tr("Z")); + action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically")); + connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels())); + action->setCheckable(true); + action->setChecked(m_viewManager->getZoomWheelsEnabled()); + menu->addAction(action); + + action = new QAction(tr("Show Property Bo&xes"), this); + action->setShortcut(tr("X")); + action->setStatusTip(tr("Show the layer property boxes at the side of the main window")); + connect(action, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes())); + action->setCheckable(true); + action->setChecked(true); + menu->addAction(action); + + action = new QAction(tr("Show Status &Bar"), this); + action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window")); + connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar())); + action->setCheckable(true); + action->setChecked(true); + menu->addAction(action); + + QSettings settings; + settings.beginGroup("MainWindow"); + bool sb = settings.value("showstatusbar", true).toBool(); + if (!sb) { + action->setChecked(false); + statusBar()->hide(); + } + settings.endGroup(); + +/*!!! This one doesn't work properly yet + + menu->addSeparator(); + + action = new QAction(tr("Show La&yer Hierarchy"), this); + action->setShortcut(tr("Alt+L")); + action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session")); + connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree())); + menu->addAction(action); + */ +} + +void +MainWindow::setupPaneAndLayerMenus() +{ + if (m_paneMenu) { + m_paneActions.clear(); + m_paneMenu->clear(); + } else { + m_paneMenu = menuBar()->addMenu(tr("&Pane")); + m_paneMenu->setTearOffEnabled(true); + } + + if (m_layerMenu) { + m_layerActions.clear(); + m_layerMenu->clear(); + } else { + m_layerMenu = menuBar()->addMenu(tr("&Layer")); + m_layerMenu->setTearOffEnabled(true); + } + + QMenu *menu = m_paneMenu; + + QAction *action = new QAction(QIcon(":/icons/pane.png"), tr("Add &New Pane"), this); + action->setShortcut(tr("Alt+N")); + action->setStatusTip(tr("Add a new pane containing only a time ruler")); + connect(action, SIGNAL(triggered()), this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); + m_paneActions[action] = PaneConfiguration(LayerFactory::TimeRuler); + menu->addAction(action); + + menu->addSeparator(); + + menu = m_layerMenu; + +// menu->addSeparator(); + + LayerFactory::LayerTypeSet emptyLayerTypes = + LayerFactory::getInstance()->getValidEmptyLayerTypes(); + + for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin(); + i != emptyLayerTypes.end(); ++i) { + + QIcon icon; + QString mainText, tipText, channelText; + LayerFactory::LayerType type = *i; + QString name = LayerFactory::getInstance()->getLayerPresentationName(type); + + icon = QIcon(QString(":/icons/%1.png") + .arg(LayerFactory::getInstance()->getLayerIconName(type))); + + mainText = tr("Add New %1 Layer").arg(name); + tipText = tr("Add a new empty layer of type %1").arg(name); + + action = new QAction(icon, mainText, this); + action->setStatusTip(tipText); + + if (type == LayerFactory::Text) { + action->setShortcut(tr("Alt+T")); + } + + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); + } + + m_rightButtonLayerMenu->addSeparator(); + menu->addSeparator(); + + LayerFactory::LayerType backgroundTypes[] = { + LayerFactory::Waveform, + LayerFactory::Spectrogram, + LayerFactory::MelodicRangeSpectrogram, + LayerFactory::PeakFrequencySpectrogram, + LayerFactory::Spectrum + }; + + std::vector models; + if (m_document) models = m_document->getTransformInputModels(); //!!! not well named for this! + bool plural = (models.size() > 1); + if (models.empty()) { + models.push_back(getMainModel()); // probably 0 + } + + for (unsigned int i = 0; + i < sizeof(backgroundTypes)/sizeof(backgroundTypes[0]); ++i) { + + for (int menuType = 0; menuType <= 1; ++menuType) { // pane, layer + + if (menuType == 0) menu = m_paneMenu; + else menu = m_layerMenu; + + QMenu *submenu = 0; + + QIcon icon; + QString mainText, shortcutText, tipText, channelText; + LayerFactory::LayerType type = backgroundTypes[i]; + bool mono = true; + + switch (type) { + + case LayerFactory::Waveform: + icon = QIcon(":/icons/waveform.png"); + mainText = tr("Add &Waveform"); + if (menuType == 0) { + shortcutText = tr("Alt+W"); + tipText = tr("Add a new pane showing a waveform view"); + } else { + tipText = tr("Add a new layer showing a waveform view"); + } + mono = false; + break; + + case LayerFactory::Spectrogram: + icon = QIcon(":/icons/spectrogram.png"); + mainText = tr("Add &Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+S"); + tipText = tr("Add a new pane showing a spectrogram"); + } else { + tipText = tr("Add a new layer showing a spectrogram"); + } + break; + + case LayerFactory::MelodicRangeSpectrogram: + icon = QIcon(":/icons/spectrogram.png"); + mainText = tr("Add &Melodic Range Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+M"); + tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches"); + } else { + tipText = tr("Add a new layer showing a spectrogram set up for an overview of note pitches"); + } + break; + + case LayerFactory::PeakFrequencySpectrogram: + icon = QIcon(":/icons/spectrogram.png"); + mainText = tr("Add &Peak Frequency Spectrogram"); + if (menuType == 0) { + shortcutText = tr("Alt+P"); + tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies"); + } else { + tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies"); + } + break; + + case LayerFactory::Spectrum: + icon = QIcon(":/icons/spectrum.png"); + mainText = tr("Add Spectr&um"); + if (menuType == 0) { + shortcutText = tr("Alt+U"); + tipText = tr("Add a new pane showing a frequency spectrum"); + } else { + tipText = tr("Add a new layer showing a frequency spectrum"); + } + break; + + default: break; + } + + std::vector candidateModels; + if (menuType == 0) { + candidateModels = models; + } else { + candidateModels.push_back(0); + } + + for (std::vector::iterator mi = + candidateModels.begin(); + mi != candidateModels.end(); ++mi) { + + Model *model = *mi; + + int channels = 0; + if (model) { + DenseTimeValueModel *dtvm = + dynamic_cast(model); + if (dtvm) channels = dtvm->getChannelCount(); + } + if (channels < 1 && getMainModel()) { + channels = getMainModel()->getChannelCount(); + } + if (channels < 1) channels = 1; + + for (int c = 0; c <= channels; ++c) { + + if (c == 1 && channels == 1) continue; + bool isDefault = (c == 0); + bool isOnly = (isDefault && (channels == 1)); + + if (menuType == 1) { + if (isDefault) isOnly = true; + else continue; + } + + if (isOnly && (!plural || menuType == 1)) { + + if (menuType == 1 && type != LayerFactory::Waveform) { + action = new QAction(mainText, this); + } else { + action = new QAction(icon, mainText, this); + } + + action->setShortcut(shortcutText); + action->setStatusTip(tipText); + if (menuType == 0) { + connect(action, SIGNAL(triggered()), + this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), + action, SLOT(setEnabled(bool))); + m_paneActions[action] = PaneConfiguration(type); + } else { + connect(action, SIGNAL(triggered()), + this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), + action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + } + menu->addAction(action); + + } else { + + if (!submenu) { + submenu = menu->addMenu(mainText); + submenu->setTearOffEnabled(true); + } else if (isDefault) { + submenu->addSeparator(); + } + + QString actionText; + if (c == 0) { + if (mono) { + actionText = tr("&All Channels Mixed"); + } else { + actionText = tr("&All Channels"); + } + } else { + actionText = tr("Channel &%1").arg(c); + } + + if (model) { + actionText = tr("%1: %2") + .arg(model->objectName()) + .arg(actionText); + } + + if (isDefault) { + action = new QAction(icon, actionText, this); + if (!model || model == getMainModel()) { + action->setShortcut(shortcutText); + } + } else { + action = new QAction(actionText, this); + } + + action->setStatusTip(tipText); + + if (menuType == 0) { + connect(action, SIGNAL(triggered()), + this, SLOT(addPane())); + connect(this, SIGNAL(canAddPane(bool)), + action, SLOT(setEnabled(bool))); + m_paneActions[action] = + PaneConfiguration(type, model, c - 1); + } else { + connect(action, SIGNAL(triggered()), + this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), + action, SLOT(setEnabled(bool))); + m_layerActions[action] = type; + } + + submenu->addAction(action); + } + } + } + } + } + + menu = m_paneMenu; + + menu->addSeparator(); + + action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Pane"), this); + action->setShortcut(tr("Alt+D")); + action->setStatusTip(tr("Delete the currently active pane")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane())); + connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu = m_layerMenu; + + action = new QAction(QIcon(":/icons/timeruler.png"), tr("Add &Time Ruler"), this); + action->setStatusTip(tr("Add a new layer showing a time ruler")); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_layerActions[action] = LayerFactory::TimeRuler; + menu->addAction(action); + + menu->addSeparator(); + + m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer")); + m_existingLayersMenu->setTearOffEnabled(true); + m_rightButtonLayerMenu->addMenu(m_existingLayersMenu); + + m_sliceMenu = menu->addMenu(tr("Add S&lice of Layer")); + m_sliceMenu->setTearOffEnabled(true); + m_rightButtonLayerMenu->addMenu(m_sliceMenu); + + setupExistingLayersMenus(); + + m_rightButtonLayerMenu->addSeparator(); + menu->addSeparator(); + + action = new QAction(tr("&Rename Layer..."), this); + action->setShortcut(tr("Alt+R")); + action->setStatusTip(tr("Rename the currently active layer")); + connect(action, SIGNAL(triggered()), this, SLOT(renameCurrentLayer())); + connect(this, SIGNAL(canRenameLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); + + action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Layer"), this); + action->setShortcut(tr("Alt+Shift+D")); + action->setStatusTip(tr("Delete the currently active layer")); + connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer())); + connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonLayerMenu->addAction(action); +} + +void +MainWindow::setupTransformsMenu() +{ + if (m_transformsMenu) { + m_transformActions.clear(); + m_transformActionsReverse.clear(); + m_transformsMenu->clear(); + } else { + m_transformsMenu = menuBar()->addMenu(tr("&Transform")); + m_transformsMenu->setTearOffEnabled(true); + } + + TransformFactory::TransformList transforms = + TransformFactory::getInstance()->getAllTransforms(); + + vector types = + TransformFactory::getInstance()->getAllTransformTypes(); + + map > categoryMenus; + map > makerMenus; + + map byPluginNameMenus; + map > pluginNameMenus; + + set pendingMenus; + + m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms")); + m_recentTransformsMenu->setTearOffEnabled(true); + m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu); + connect(&m_recentTransforms, SIGNAL(recentChanged()), + this, SLOT(setupRecentTransformsMenu())); + + m_transformsMenu->addSeparator(); + m_rightButtonTransformsMenu->addSeparator(); + + for (vector::iterator i = types.begin(); i != types.end(); ++i) { + + if (i != types.begin()) { + m_transformsMenu->addSeparator(); + m_rightButtonTransformsMenu->addSeparator(); + } + + QString byCategoryLabel = tr("%1 by Category").arg(*i); + SubdividingMenu *byCategoryMenu = new SubdividingMenu(byCategoryLabel, + 20, 40); + byCategoryMenu->setTearOffEnabled(true); + m_transformsMenu->addMenu(byCategoryMenu); + m_rightButtonTransformsMenu->addMenu(byCategoryMenu); + pendingMenus.insert(byCategoryMenu); + + vector categories = + TransformFactory::getInstance()->getTransformCategories(*i); + + for (vector::iterator j = categories.begin(); + j != categories.end(); ++j) { + + QString category = *j; + if (category == "") category = tr("Unclassified"); + + if (categories.size() < 2) { + categoryMenus[*i][category] = byCategoryMenu; + continue; + } + + QStringList components = category.split(" > "); + QString key; + + for (QStringList::iterator k = components.begin(); + k != components.end(); ++k) { + + QString parentKey = key; + if (key != "") key += " > "; + key += *k; + + if (categoryMenus[*i].find(key) == categoryMenus[*i].end()) { + SubdividingMenu *m = new SubdividingMenu(*k, 20, 40); + m->setTearOffEnabled(true); + pendingMenus.insert(m); + categoryMenus[*i][key] = m; + if (parentKey == "") { + byCategoryMenu->addMenu(m); + } else { + categoryMenus[*i][parentKey]->addMenu(m); + } + } + } + } + + QString byPluginNameLabel = tr("%1 by Plugin Name").arg(*i); + byPluginNameMenus[*i] = new SubdividingMenu(byPluginNameLabel); + byPluginNameMenus[*i]->setTearOffEnabled(true); + m_transformsMenu->addMenu(byPluginNameMenus[*i]); + m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]); + pendingMenus.insert(byPluginNameMenus[*i]); + + QString byMakerLabel = tr("%1 by Maker").arg(*i); + SubdividingMenu *byMakerMenu = new SubdividingMenu(byMakerLabel, 20, 40); + byMakerMenu->setTearOffEnabled(true); + m_transformsMenu->addMenu(byMakerMenu); + m_rightButtonTransformsMenu->addMenu(byMakerMenu); + pendingMenus.insert(byMakerMenu); + + vector makers = + TransformFactory::getInstance()->getTransformMakers(*i); + + for (vector::iterator j = makers.begin(); + j != makers.end(); ++j) { + + QString maker = *j; + if (maker == "") maker = tr("Unknown"); + maker.replace(QRegExp(tr(" [\\(<].*$")), ""); + + makerMenus[*i][maker] = new SubdividingMenu(maker, 30, 40); + makerMenus[*i][maker]->setTearOffEnabled(true); + byMakerMenu->addMenu(makerMenus[*i][maker]); + pendingMenus.insert(makerMenus[*i][maker]); + } + } + + for (unsigned int i = 0; i < transforms.size(); ++i) { + + QString name = transforms[i].name; + if (name == "") name = transforms[i].identifier; + +// std::cerr << "Plugin Name: " << name.toStdString() << std::endl; + + QString type = transforms[i].type; + + QString category = transforms[i].category; + if (category == "") category = tr("Unclassified"); + + QString maker = transforms[i].maker; + if (maker == "") maker = tr("Unknown"); + maker.replace(QRegExp(tr(" [\\(<].*$")), ""); + + QString pluginName = name.section(": ", 0, 0); + QString output = name.section(": ", 1); + + QAction *action = new QAction(tr("%1...").arg(name), this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + m_transformActions[action] = transforms[i].identifier; + m_transformActionsReverse[transforms[i].identifier] = action; + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + + action->setStatusTip(transforms[i].description); + + if (categoryMenus[type].find(category) == categoryMenus[type].end()) { + std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " + << "No category menu for transform \"" + << name.toStdString() << "\" (category = \"" + << category.toStdString() << "\")" << std::endl; + } else { + categoryMenus[type][category]->addAction(action); + } + + if (makerMenus[type].find(maker) == makerMenus[type].end()) { + std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " + << "No maker menu for transform \"" + << name.toStdString() << "\" (maker = \"" + << maker.toStdString() << "\")" << std::endl; + } else { + makerMenus[type][maker]->addAction(action); + } + + action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + m_transformActions[action] = transforms[i].identifier; + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + action->setStatusTip(transforms[i].description); + +// cerr << "Transform: \"" << name.toStdString() << "\": plugin name \"" << pluginName.toStdString() << "\"" << endl; + + if (pluginNameMenus[type].find(pluginName) == + pluginNameMenus[type].end()) { + + SubdividingMenu *parentMenu = byPluginNameMenus[type]; + parentMenu->setTearOffEnabled(true); + + if (output == "") { + parentMenu->addAction(pluginName, action); + } else { + pluginNameMenus[type][pluginName] = + parentMenu->addMenu(pluginName); + connect(this, SIGNAL(canAddLayer(bool)), + pluginNameMenus[type][pluginName], + SLOT(setEnabled(bool))); + } + } + + if (pluginNameMenus[type].find(pluginName) != + pluginNameMenus[type].end()) { + pluginNameMenus[type][pluginName]->addAction(action); + } + } + + for (set::iterator i = pendingMenus.begin(); + i != pendingMenus.end(); ++i) { + (*i)->entriesAdded(); + } + + setupRecentTransformsMenu(); +} + +void +MainWindow::setupHelpMenu() +{ + if (m_mainMenusCreated) return; + + QMenu *menu = menuBar()->addMenu(tr("&Help")); + menu->setTearOffEnabled(true); + + QAction *action = new QAction(QIcon(":icons/help.png"), + tr("&Help Reference"), this); + action->setStatusTip(tr("Open the Sonic Visualiser reference manual")); + connect(action, SIGNAL(triggered()), this, SLOT(help())); + menu->addAction(action); + + action = new QAction(tr("Sonic Visualiser on the &Web"), this); + action->setStatusTip(tr("Open the Sonic Visualiser website")); + connect(action, SIGNAL(triggered()), this, SLOT(website())); + menu->addAction(action); + + action = new QAction(tr("&About Sonic Visualiser"), this); + action->setStatusTip(tr("Show information about Sonic Visualiser")); + connect(action, SIGNAL(triggered()), this, SLOT(about())); + menu->addAction(action); +} + +void +MainWindow::setupRecentFilesMenu() +{ + m_recentFilesMenu->clear(); + vector files = m_recentFiles.getRecent(); + for (size_t i = 0; i < files.size(); ++i) { + QAction *action = new QAction(files[i], this); + connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); + m_recentFilesMenu->addAction(action); + } +} + +void +MainWindow::setupRecentTransformsMenu() +{ + m_recentTransformsMenu->clear(); + vector transforms = m_recentTransforms.getRecent(); + for (size_t i = 0; i < transforms.size(); ++i) { + TransformActionReverseMap::iterator ti = + m_transformActionsReverse.find(transforms[i]); + if (ti == m_transformActionsReverse.end()) { + std::cerr << "WARNING: MainWindow::setupRecentTransformsMenu: " + << "Unknown transform \"" << transforms[i].toStdString() + << "\" in recent transforms list" << std::endl; + continue; + } + m_recentTransformsMenu->addAction(ti->second); + } +} + +void +MainWindow::setupExistingLayersMenus() +{ + if (!m_existingLayersMenu) return; // should have been created by setupMenus + +// std::cerr << "MainWindow::setupExistingLayersMenu" << std::endl; + + m_existingLayersMenu->clear(); + m_existingLayerActions.clear(); + + m_sliceMenu->clear(); + m_sliceActions.clear(); + + vector orderedLayers; + set observedLayers; + set sliceableLayers; + + LayerFactory *factory = LayerFactory::getInstance(); + + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + + Pane *pane = m_paneStack->getPane(i); + if (!pane) continue; + + for (int j = 0; j < pane->getLayerCount(); ++j) { + + Layer *layer = pane->getLayer(j); + if (!layer) continue; + if (observedLayers.find(layer) != observedLayers.end()) { +// std::cerr << "found duplicate layer " << layer << std::endl; + continue; + } + +// std::cerr << "found new layer " << layer << " (name = " +// << layer->getLayerPresentationName().toStdString() << ")" << std::endl; + + orderedLayers.push_back(layer); + observedLayers.insert(layer); + + if (factory->isLayerSliceable(layer)) { + sliceableLayers.insert(layer); + } + } + } + + map observedNames; + + for (size_t i = 0; i < orderedLayers.size(); ++i) { + + Layer *layer = orderedLayers[i]; + + QString name = layer->getLayerPresentationName(); + int n = ++observedNames[name]; + if (n > 1) name = QString("%1 <%2>").arg(name).arg(n); + + QIcon icon = QIcon(QString(":/icons/%1.png") + .arg(factory->getLayerIconName + (factory->getLayerType(layer)))); + + QAction *action = new QAction(icon, name, this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_existingLayerActions[action] = layer; + + m_existingLayersMenu->addAction(action); + + if (sliceableLayers.find(layer) != sliceableLayers.end()) { + action = new QAction(icon, name, this); + connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); + connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); + m_sliceActions[action] = layer; + m_sliceMenu->addAction(action); + } + } + + m_sliceMenu->setEnabled(!m_sliceActions.empty()); +} + +void +MainWindow::setupToolbars() +{ + QToolBar *toolbar = addToolBar(tr("Transport Toolbar")); + + QAction *action = toolbar->addAction(QIcon(":/icons/rewind-start.png"), + tr("Rewind to Start")); + action->setShortcut(tr("Home")); + action->setStatusTip(tr("Rewind to the start")); + connect(action, SIGNAL(triggered()), this, SLOT(rewindStart())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/rewind.png"), + tr("Rewind")); + action->setShortcut(tr("PageUp")); + action->setStatusTip(tr("Rewind to the previous time instant in the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(rewind())); + connect(this, SIGNAL(canRewind(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/playpause.png"), + tr("Play / Pause")); + action->setCheckable(true); + action->setShortcut(tr("Space")); + action->setStatusTip(tr("Start or stop playback from the current position")); + connect(action, SIGNAL(triggered()), this, SLOT(play())); + connect(m_playSource, SIGNAL(playStatusChanged(bool)), + action, SLOT(setChecked(bool))); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/ffwd.png"), + tr("Fast Forward")); + action->setShortcut(tr("PageDown")); + action->setStatusTip(tr("Fast forward to the next time instant in the current layer")); + connect(action, SIGNAL(triggered()), this, SLOT(ffwd())); + connect(this, SIGNAL(canFfwd(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/ffwd-end.png"), + tr("Fast Forward to End")); + action->setShortcut(tr("End")); + action->setStatusTip(tr("Fast-forward to the end")); + connect(action, SIGNAL(triggered()), this, SLOT(ffwdEnd())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + toolbar = addToolBar(tr("Play Mode Toolbar")); + + action = toolbar->addAction(QIcon(":/icons/playselection.png"), + tr("Constrain Playback to Selection")); + action->setCheckable(true); + action->setChecked(m_viewManager->getPlaySelectionMode()); + action->setShortcut(tr("s")); + action->setStatusTip(tr("Constrain playback to the selected area")); + connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), + action, SLOT(setChecked(bool))); + connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); + connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool))); + + action = toolbar->addAction(QIcon(":/icons/playloop.png"), + tr("Loop Playback")); + action->setCheckable(true); + action->setChecked(m_viewManager->getPlayLoopMode()); + action->setShortcut(tr("l")); + action->setStatusTip(tr("Loop playback")); + connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), + action, SLOT(setChecked(bool))); + connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled())); + connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); + + toolbar = addToolBar(tr("Edit Toolbar")); + CommandHistory::getInstance()->registerToolbar(toolbar); + + toolbar = addToolBar(tr("Tools Toolbar")); + QActionGroup *group = new QActionGroup(this); + + action = toolbar->addAction(QIcon(":/icons/navigate.png"), + tr("Navigate")); + action->setCheckable(true); + action->setChecked(true); + action->setShortcut(tr("1")); + action->setStatusTip(tr("Navigate")); + connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); + group->addAction(action); + m_toolActions[ViewManager::NavigateMode] = action; + + action = toolbar->addAction(QIcon(":/icons/select.png"), + tr("Select")); + action->setCheckable(true); + action->setShortcut(tr("2")); + action->setStatusTip(tr("Select ranges")); + connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected())); + group->addAction(action); + m_toolActions[ViewManager::SelectMode] = action; + + action = toolbar->addAction(QIcon(":/icons/move.png"), + tr("Edit")); + action->setCheckable(true); + action->setShortcut(tr("3")); + action->setStatusTip(tr("Edit items in layer")); + connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); + connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); + group->addAction(action); + m_toolActions[ViewManager::EditMode] = action; + + action = toolbar->addAction(QIcon(":/icons/draw.png"), + tr("Draw")); + action->setCheckable(true); + action->setShortcut(tr("4")); + action->setStatusTip(tr("Draw new items in layer")); + connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected())); + connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); + group->addAction(action); + m_toolActions[ViewManager::DrawMode] = action; + +// action = toolbar->addAction(QIcon(":/icons/text.png"), +// tr("Text")); +// action->setCheckable(true); +// action->setShortcut(tr("5")); +// connect(action, SIGNAL(triggered()), this, SLOT(toolTextSelected())); +// group->addAction(action); +// m_toolActions[ViewManager::TextMode] = action; + + toolNavigateSelected(); +} + +void +MainWindow::updateMenuStates() +{ + Pane *currentPane = 0; + Layer *currentLayer = 0; + + if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentLayer = currentPane->getSelectedLayer(); + + bool haveCurrentPane = + (currentPane != 0); + bool haveCurrentLayer = + (haveCurrentPane && + (currentLayer != 0)); + bool haveMainModel = + (getMainModel() != 0); + bool havePlayTarget = + (m_playTarget != 0); + bool haveSelection = + (m_viewManager && + !m_viewManager->getSelections().empty()); + bool haveCurrentEditableLayer = + (haveCurrentLayer && + currentLayer->isLayerEditable()); + bool haveCurrentTimeInstantsLayer = + (haveCurrentLayer && + dynamic_cast(currentLayer)); + bool haveCurrentTimeValueLayer = + (haveCurrentLayer && + dynamic_cast(currentLayer)); + bool haveCurrentColour3DPlot = + (haveCurrentLayer && + dynamic_cast(currentLayer)); + bool haveClipboardContents = + (m_viewManager && + !m_viewManager->getClipboard().empty()); + + emit canAddPane(haveMainModel); + emit canDeleteCurrentPane(haveCurrentPane); + emit canZoom(haveMainModel && haveCurrentPane); + emit canScroll(haveMainModel && haveCurrentPane); + emit canAddLayer(haveMainModel && haveCurrentPane); + emit canImportMoreAudio(haveMainModel); + emit canImportLayer(haveMainModel && haveCurrentPane); + emit canExportAudio(haveMainModel); + emit canExportLayer(haveMainModel && + (haveCurrentEditableLayer || haveCurrentColour3DPlot)); + emit canExportImage(haveMainModel && haveCurrentPane); + emit canDeleteCurrentLayer(haveCurrentLayer); + emit canRenameLayer(haveCurrentLayer); + emit canEditLayer(haveCurrentEditableLayer); + emit canSelect(haveMainModel && haveCurrentPane); + emit canPlay(/*!!! haveMainModel && */ havePlayTarget); + emit canFfwd(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); + emit canRewind(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); + emit canPaste(haveCurrentEditableLayer && haveClipboardContents); + emit canInsertInstant(haveCurrentPane); + emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection); + emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); + emit canClearSelection(haveSelection); + emit canEditSelection(haveSelection && haveCurrentEditableLayer); + emit canSave(m_sessionFile != "" && m_documentModified); +} + +void +MainWindow::updateDescriptionLabel() +{ + if (!getMainModel()) { + m_descriptionLabel->setText(tr("No audio file loaded.")); + return; + } + + QString description; + + size_t ssr = getMainModel()->getSampleRate(); + size_t tsr = ssr; + if (m_playSource) tsr = m_playSource->getTargetSampleRate(); + + if (ssr != tsr) { + description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr); + } else { + description = QString("%1Hz").arg(ssr); + } + + description = QString("%1 - %2") + .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr) + .toText(false).c_str()) + .arg(description); + + m_descriptionLabel->setText(description); +} + +void +MainWindow::documentModified() +{ +// std::cerr << "MainWindow::documentModified" << std::endl; + + if (!m_documentModified) { + setWindowTitle(tr("%1 (modified)").arg(windowTitle())); + } + + m_documentModified = true; + updateMenuStates(); +} + +void +MainWindow::documentRestored() +{ +// std::cerr << "MainWindow::documentRestored" << std::endl; + + if (m_documentModified) { + QString wt(windowTitle()); + wt.replace(tr(" (modified)"), ""); + setWindowTitle(wt); + } + + m_documentModified = false; + updateMenuStates(); +} + +void +MainWindow::playLoopToggled() +{ + QAction *action = dynamic_cast(sender()); + + if (action) { + m_viewManager->setPlayLoopMode(action->isChecked()); + } else { + m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); + } +} + +void +MainWindow::playSelectionToggled() +{ + QAction *action = dynamic_cast(sender()); + + if (action) { + m_viewManager->setPlaySelectionMode(action->isChecked()); + } else { + m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); + } +} + +void +MainWindow::currentPaneChanged(Pane *p) +{ + updateMenuStates(); + updateVisibleRangeDisplay(p); +} + +void +MainWindow::currentLayerChanged(Pane *p, Layer *) +{ + updateMenuStates(); + updateVisibleRangeDisplay(p); +} + +void +MainWindow::toolNavigateSelected() +{ + m_viewManager->setToolMode(ViewManager::NavigateMode); +} + +void +MainWindow::toolSelectSelected() +{ + m_viewManager->setToolMode(ViewManager::SelectMode); +} + +void +MainWindow::toolEditSelected() +{ + m_viewManager->setToolMode(ViewManager::EditMode); +} + +void +MainWindow::toolDrawSelected() +{ + m_viewManager->setToolMode(ViewManager::DrawMode); +} + +//void +//MainWindow::toolTextSelected() +//{ +// m_viewManager->setToolMode(ViewManager::TextMode); +//} + +void +MainWindow::selectAll() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindow::selectToStart() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + m_viewManager->getGlobalCentreFrame())); +} + +void +MainWindow::selectToEnd() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindow::selectVisible() +{ + Model *model = getMainModel(); + if (!model) return; + + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + size_t startFrame, endFrame; + + if (currentPane->getStartFrame() < 0) startFrame = 0; + else startFrame = currentPane->getStartFrame(); + + if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame(); + else endFrame = currentPane->getEndFrame(); + + m_viewManager->setSelection(Selection(startFrame, endFrame)); +} + +void +MainWindow::clearSelection() +{ + m_viewManager->clearSelections(); +} + +void +MainWindow::cut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + layer->deleteSelection(*i); + } + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindow::copy() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + } +} + +void +MainWindow::paste() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + //!!! if we have no current layer, we should create one of the most + // appropriate type + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + Clipboard::PointList contents = clipboard.getPoints(); +/* + long minFrame = 0; + bool have = false; + for (int i = 0; i < contents.size(); ++i) { + if (!contents[i].haveFrame()) continue; + if (!have || contents[i].getFrame() < minFrame) { + minFrame = contents[i].getFrame(); + have = true; + } + } + + long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame; + + layer->paste(clipboard, frameOffset); +*/ + layer->paste(clipboard, 0, true); +} + +void +MainWindow::deleteSelected() +{ + if (m_paneStack->getCurrentPane() && + m_paneStack->getCurrentPane()->getSelectedLayer()) { + + MultiSelection::SelectionList selections = + m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + + m_paneStack->getCurrentPane()->getSelectedLayer()->deleteSelection(*i); + } + } +} + +void +MainWindow::insertInstant() +{ + int frame = m_viewManager->getPlaybackFrame(); + insertInstantAt(frame); +} + +void +MainWindow::insertInstantsAtBoundaries() +{ + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + size_t start = i->getStartFrame(); + size_t end = i->getEndFrame(); + if (start != end) { + insertInstantAt(i->getStartFrame()); + insertInstantAt(i->getEndFrame()); + } + } +} + +void +MainWindow::insertInstantAt(size_t frame) +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) { + return; + } + + Layer *layer = dynamic_cast + (pane->getSelectedLayer()); + + if (!layer) { + for (int i = pane->getLayerCount(); i > 0; --i) { + layer = dynamic_cast(pane->getLayer(i - 1)); + if (layer) break; + } + + if (!layer) { + CommandHistory::getInstance()->startCompoundOperation + (tr("Add Point"), true); + layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); + if (layer) { + m_document->addLayerToView(pane, layer); + m_paneStack->setCurrentLayer(pane, layer); + } + CommandHistory::getInstance()->endCompoundOperation(); + } + } + + if (layer) { + + Model *model = layer->getModel(); + SparseOneDimensionalModel *sodm = dynamic_cast + (model); + + if (sodm) { + SparseOneDimensionalModel::Point point + (frame, QString("%1").arg(sodm->getPointCount() + 1)); + CommandHistory::getInstance()->addCommand + (new SparseOneDimensionalModel::AddPointCommand(sodm, point, + tr("Add Points")), + true, true); // bundled + } + } +} + +void +MainWindow::importAudio() +{ + QString path = getOpenFileName(FileFinder::AudioFile); + + if (path != "") { + if (openAudioFile(path, ReplaceMainModel) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio file \"%1\" could not be opened").arg(path)); + } + } +} + +void +MainWindow::importMoreAudio() +{ + QString path = getOpenFileName(FileFinder::AudioFile); + + if (path != "") { + if (openAudioFile(path, CreateAdditionalModel) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio file \"%1\" could not be opened").arg(path)); + } + } +} + +void +MainWindow::exportAudio() +{ + if (!getMainModel()) return; + + QString path = getSaveFileName(FileFinder::AudioFile); + + if (path == "") return; + + bool ok = false; + QString error; + + MultiSelection ms = m_viewManager->getSelection(); + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + bool multiple = false; + + MultiSelection *selectionToWrite = 0; + + if (selections.size() == 1) { + + QStringList items; + items << tr("Export the selected region only") + << tr("Export the whole audio file"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select region to export"), + tr("Which region from the original audio file do you want to export?"), + items, 0, &ok); + + if (!ok || item.isEmpty()) return; + + if (item == items[0]) selectionToWrite = &ms; + + } else if (selections.size() > 1) { + + QStringList items; + items << tr("Export the selected regions into a single audio file") + << tr("Export the selected regions into separate files") + << tr("Export the whole audio file"); + + QString item = ListInputDialog::getItem + (this, tr("Select region to export"), + tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"), + items, 0, &ok); + + if (!ok || item.isEmpty()) return; + + if (item == items[0]) { + + selectionToWrite = &ms; + + } else if (item == items[1]) { + + multiple = true; + + int n = 1; + QString base = path; + base.replace(".wav", ""); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + + MultiSelection subms; + subms.setSelection(*i); + + QString subpath = QString("%1.%2.wav").arg(base).arg(n); + ++n; + + if (QFileInfo(subpath).exists()) { + error = tr("Fragment file %1 already exists, aborting").arg(subpath); + break; + } + + WavFileWriter subwriter(subpath, + getMainModel()->getSampleRate(), + getMainModel()->getChannelCount()); + subwriter.writeModel(getMainModel(), &subms); + ok = subwriter.isOK(); + + if (!ok) { + error = subwriter.getError(); + break; + } + } + } + } + + if (!multiple) { + WavFileWriter writer(path, + getMainModel()->getSampleRate(), + getMainModel()->getChannelCount()); + writer.writeModel(getMainModel(), selectionToWrite); + ok = writer.isOK(); + error = writer.getError(); + } + + if (ok) { + if (!multiple) { + m_recentFiles.addFile(path); + } + } else { + QMessageBox::critical(this, tr("Failed to write file"), error); + } +} + +void +MainWindow::importLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::importLayer: no current pane" << std::endl; + return; + } + + if (!getMainModel()) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl; + return; + } + + QString path = getOpenFileName(FileFinder::LayerFile); + + if (path != "") { + + if (openLayerFile(path) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("File %1 could not be opened.").arg(path)); + return; + } + } +} + +MainWindow::FileOpenStatus +MainWindow::openLayerFile(QString path) +{ + return openLayerFile(path, path); +} + +MainWindow::FileOpenStatus +MainWindow::openLayerFile(QString path, QString location) +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::openLayerFile: no current pane" << std::endl; + return FileOpenFailed; + } + + if (!getMainModel()) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindow::openLayerFile: No main model -- hence no default sample rate available" << std::endl; + return FileOpenFailed; + } + + bool realFile = (location == path); + + if (path.endsWith(".svl") || path.endsWith(".xml")) { + + PaneCallback callback(this); + QFile file(path); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + std::cerr << "ERROR: MainWindow::openLayerFile(" + << location.toStdString() + << "): Failed to open file for reading" << std::endl; + return FileOpenFailed; + } + + SVFileReader reader(m_document, callback, location); + reader.setCurrentPane(pane); + + QXmlInputSource inputSource(&file); + reader.parse(inputSource); + + if (!reader.isOK()) { + std::cerr << "ERROR: MainWindow::openLayerFile(" + << location.toStdString() + << "): Failed to read XML file: " + << reader.getErrorString().toStdString() << std::endl; + return FileOpenFailed; + } + + m_recentFiles.addFile(location); + + if (realFile) { + registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog + } + + return FileOpenSucceeded; + + } else { + + Model *model = DataFileReaderFactory::load(path, getMainModel()->getSampleRate()); + + if (model) { + + Layer *newLayer = m_document->createImportedLayer(model); + + if (newLayer) { + + m_document->addLayerToView(pane, newLayer); + m_recentFiles.addFile(location); + + if (realFile) { + registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog + } + + return FileOpenSucceeded; + } + } + } + + return FileOpenFailed; +} + +void +MainWindow::exportLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + if (!layer) return; + + Model *model = layer->getModel(); + if (!model) return; + + QString path = getSaveFileName(FileFinder::LayerFile); + + if (path == "") return; + + if (QFileInfo(path).suffix() == "") path += ".svl"; + + QString error; + + if (path.endsWith(".xml") || path.endsWith(".svl")) { + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + error = tr("Failed to open file %1 for writing").arg(path); + } else { + QTextStream out(&file); + out << "\n" + << "\n" + << "\n" + << " \n"; + + model->toXml(out, " "); + + out << " \n" + << " \n"; + + layer->toXml(out, " "); + + out << " \n" + << "\n"; + } + + } else { + + CSVFileWriter writer(path, model, + (path.endsWith(".csv") ? "," : "\t")); + writer.write(); + + if (!writer.isOK()) { + error = writer.getError(); + } + } + + if (error != "") { + QMessageBox::critical(this, tr("Failed to write file"), error); + } else { + m_recentFiles.addFile(path); + } +} + +void +MainWindow::exportImage() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + QString path = getSaveFileName(FileFinder::ImageFile); + + if (path == "") return; + + if (QFileInfo(path).suffix() == "") path += ".png"; + + bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty(); + + QSize total, visible, selected; + total = pane->getImageSize(); + visible = pane->getImageSize(pane->getFirstVisibleFrame(), + pane->getLastVisibleFrame()); + + size_t sf0 = 0, sf1 = 0; + + if (haveSelection) { + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + sf0 = selections.begin()->getStartFrame(); + MultiSelection::SelectionList::iterator e = selections.end(); + --e; + sf1 = e->getEndFrame(); + selected = pane->getImageSize(sf0, sf1); + } + + QStringList items; + items << tr("Export the whole pane (%1x%2 pixels)") + .arg(total.width()).arg(total.height()); + items << tr("Export the visible area only (%1x%2 pixels)") + .arg(visible.width()).arg(visible.height()); + if (haveSelection) { + items << tr("Export the selection extent (%1x%2 pixels)") + .arg(selected.width()).arg(selected.height()); + } else { + items << tr("Export the selection extent"); + } + + QSettings settings; + settings.beginGroup("MainWindow"); + int deflt = settings.value("lastimageexportregion", 0).toInt(); + if (deflt == 2 && !haveSelection) deflt = 1; + if (deflt == 0 && total.width() > 32767) deflt = 1; + + ListInputDialog *lid = new ListInputDialog + (this, tr("Select region to export"), + tr("Which region of the current pane do you want to export as an image?"), + items, deflt); + + if (!haveSelection) { + lid->setItemAvailability(2, false); + } + if (total.width() > 32767) { // appears to be the limit of a QImage + lid->setItemAvailability(0, false); + lid->setFootnote(tr("Note: the whole pane is too wide to be exported as a single image.")); + } + + bool ok = lid->exec(); + QString item = lid->getCurrentString(); + delete lid; + + if (!ok || item.isEmpty()) return; + + settings.setValue("lastimageexportregion", deflt); + + QImage *image = 0; + + if (item == items[0]) { + image = pane->toNewImage(); + } else if (item == items[1]) { + image = pane->toNewImage(pane->getFirstVisibleFrame(), + pane->getLastVisibleFrame()); + } else if (haveSelection) { + image = pane->toNewImage(sf0, sf1); + } + + if (!image) return; + + if (!image->save(path, "PNG")) { + QMessageBox::critical(this, tr("Failed to save image file"), + tr("Failed to save image file %1").arg(path)); + } + + delete image; +} + +MainWindow::FileOpenStatus +MainWindow::openAudioFile(QString path, AudioFileOpenMode mode) +{ + return openAudioFile(path, path, mode); +} + +MainWindow::FileOpenStatus +MainWindow::openAudioFile(QString path, QString location, AudioFileOpenMode mode) +{ + if (!(QFileInfo(path).exists() && + QFileInfo(path).isFile() && + QFileInfo(path).isReadable())) { + return FileOpenFailed; + } + + m_openingAudioFile = true; + + WaveFileModel *newModel = new WaveFileModel(path, location); + + if (!newModel->isOK()) { + delete newModel; + m_openingAudioFile = false; + return FileOpenFailed; + } + + bool setAsMain = true; + static bool prevSetAsMain = true; + + bool realFile = (location == path); + + if (mode == CreateAdditionalModel) setAsMain = false; + else if (mode == AskUser) { + if (m_document->getMainModel()) { + + QStringList items; + items << tr("Replace the existing main waveform") + << tr("Load this file into a new waveform pane"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select target for import"), + tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"), + items, prevSetAsMain ? 0 : 1, &ok); + + if (!ok || item.isEmpty()) { + delete newModel; + m_openingAudioFile = false; + return FileOpenCancelled; + } + + setAsMain = (item == items[0]); + prevSetAsMain = setAsMain; + } + } + + if (setAsMain) { + + Model *prevMain = getMainModel(); + if (prevMain) { + m_playSource->removeModel(prevMain); + PlayParameterRepository::getInstance()->removeModel(prevMain); + } + + PlayParameterRepository::getInstance()->addModel(newModel); + + m_document->setMainModel(newModel); + setupMenus(); + + if (m_sessionFile == "") { + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(location).fileName())); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + } else { + setWindowTitle(tr("Sonic Visualiser: %1 [%2]") + .arg(QFileInfo(m_sessionFile).fileName()) + .arg(QFileInfo(location).fileName())); + if (m_documentModified) { + m_documentModified = false; + documentModified(); // so as to restore "(modified)" window title + } + } + + if (realFile) m_audioFile = path; + + } else { // !setAsMain + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import \"%1\"").arg(QFileInfo(location).fileName()), true); + + m_document->addImportedModel(newModel); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (!m_timeRulerLayer) { + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + + m_document->addLayerToView(pane, m_timeRulerLayer); + + Layer *newLayer = m_document->createImportedLayer(newModel); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + + updateMenuStates(); + m_recentFiles.addFile(location); + if (realFile) { + registerLastOpenedFilePath(FileFinder::AudioFile, path); // for file dialog + } + m_openingAudioFile = false; + + return FileOpenSucceeded; +} + +void +MainWindow::createPlayTarget() +{ + if (m_playTarget) return; + + m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource); + if (!m_playTarget) { + QMessageBox::warning + (this, tr("Couldn't open audio device"), + tr("Could not open an audio device for playback.\nAudio playback will not be available during this session.\n"), + QMessageBox::Ok, 0); + } + connect(m_fader, SIGNAL(valueChanged(float)), + m_playTarget, SLOT(setOutputGain(float))); +} + +WaveFileModel * +MainWindow::getMainModel() +{ + if (!m_document) return 0; + return m_document->getMainModel(); +} + +const WaveFileModel * +MainWindow::getMainModel() const +{ + if (!m_document) return 0; + return m_document->getMainModel(); +} + +void +MainWindow::newSession() +{ + if (!checkSaveModified()) return; + + closeSession(); + createDocument(); + + Pane *pane = m_paneStack->addPane(); + + connect(pane, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + + if (!m_timeRulerLayer) { + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + + m_document->addLayerToView(pane, m_timeRulerLayer); + + Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); + m_document->addLayerToView(pane, waveform); + + m_overview->registerView(pane); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + updateMenuStates(); +} + +void +MainWindow::createDocument() +{ + m_document = new Document; + + connect(m_document, SIGNAL(layerAdded(Layer *)), + this, SLOT(layerAdded(Layer *))); + connect(m_document, SIGNAL(layerRemoved(Layer *)), + this, SLOT(layerRemoved(Layer *))); + connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), + this, SLOT(layerAboutToBeDeleted(Layer *))); + connect(m_document, SIGNAL(layerInAView(Layer *, bool)), + this, SLOT(layerInAView(Layer *, bool))); + + connect(m_document, SIGNAL(modelAdded(Model *)), + this, SLOT(modelAdded(Model *))); + connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)), + this, SLOT(mainModelChanged(WaveFileModel *))); + connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), + this, SLOT(modelAboutToBeDeleted(Model *))); + + connect(m_document, SIGNAL(modelGenerationFailed(QString)), + this, SLOT(modelGenerationFailed(QString))); + connect(m_document, SIGNAL(modelRegenerationFailed(QString, QString)), + this, SLOT(modelRegenerationFailed(QString, QString))); +} + +void +MainWindow::closeSession() +{ + if (!checkSaveModified()) return; + + while (m_paneStack->getPaneCount() > 0) { + + Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_overview->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + while (m_paneStack->getHiddenPaneCount() > 0) { + + Pane *pane = m_paneStack->getHiddenPane + (m_paneStack->getHiddenPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_overview->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + delete m_document; + m_document = 0; + m_viewManager->clearSelections(); + m_timeRulerLayer = 0; // document owned this + + m_sessionFile = ""; + setWindowTitle(tr("Sonic Visualiser")); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); +} + +void +MainWindow::openSession() +{ + if (!checkSaveModified()) return; + + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + QString path = getOpenFileName(FileFinder::SessionFile); + + if (path.isEmpty()) return; + + if (openSessionFile(path) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } +} + +void +MainWindow::openSomething() +{ + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + QString path = getOpenFileName(FileFinder::AnyFile); + + if (path.isEmpty()) return; + + if (path.endsWith(".sv")) { + + if (!checkSaveModified()) return; + + if (openSessionFile(path) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } + + } else { + + if (openAudioFile(path, AskUser) == FileOpenFailed) { + + if (!canImportLayer || (openLayerFile(path) == FileOpenFailed)) { + + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" could not be opened").arg(path)); + } + } + } +} + +void +MainWindow::openLocation() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + QString lastLocation = settings.value("lastremote", "").toString(); + + bool ok = false; + QString text = QInputDialog::getText + (this, tr("Open Location"), + tr("Please enter the URL of the location to open:"), + QLineEdit::Normal, lastLocation, &ok); + + if (!ok) return; + + settings.setValue("lastremote", text); + + if (text.isEmpty()) return; + + if (openURL(QUrl(text)) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open location"), + tr("URL \"%1\" could not be opened").arg(text)); + } +} + +void +MainWindow::openRecentFile() +{ + QObject *obj = sender(); + QAction *action = dynamic_cast(obj); + + if (!action) { + std::cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" + << std::endl; + return; + } + + QString path = action->text(); + if (path == "") return; + + QUrl url(path); + if (RemoteFile::canHandleScheme(url)) { + openURL(url); + return; + } + + if (path.endsWith("sv")) { + + if (!checkSaveModified()) return; + + if (openSessionFile(path) == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Session file \"%1\" could not be opened").arg(path)); + } + + } else { + + if (openAudioFile(path, AskUser) == FileOpenFailed) { + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + if (!canImportLayer || (openLayerFile(path) == FileOpenFailed)) { + + QMessageBox::critical(this, tr("Failed to open file"), + tr("File \"%1\" could not be opened").arg(path)); + } + } + } +} + +MainWindow::FileOpenStatus +MainWindow::openURL(QUrl url) +{ + if (url.scheme().toLower() == "file") { + return openSomeFile(url.toLocalFile()); + } else if (!RemoteFile::canHandleScheme(url)) { + QMessageBox::critical(this, tr("Unsupported scheme in URL"), + tr("The URL scheme \"%1\" is not supported") + .arg(url.scheme())); + return FileOpenFailed; + } else { + RemoteFile rf(url); + rf.wait(); + if (!rf.isOK()) { + QMessageBox::critical(this, tr("File download failed"), + tr("Failed to download URL \"%1\": %2") + .arg(url.toString()).arg(rf.getErrorString())); + return FileOpenFailed; + } + FileOpenStatus status; + if ((status = openSomeFile(rf.getLocalFilename(), url.toString())) != + FileOpenSucceeded) { + rf.deleteLocalFile(); + } + return status; + } +} + +MainWindow::FileOpenStatus +MainWindow::openSomeFile(QString path, AudioFileOpenMode mode) +{ + return openSomeFile(path, path, mode); +} + +MainWindow::FileOpenStatus +MainWindow::openSomeFile(QString path, QString location, + AudioFileOpenMode mode) +{ + FileOpenStatus status; + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + if ((status = openAudioFile(path, location, mode)) != FileOpenFailed) { + return status; + } else if ((status = openSessionFile(path, location)) != FileOpenFailed) { + return status; + } else if (!canImportLayer) { + return FileOpenFailed; + } else if ((status = openLayerFile(path, location)) != FileOpenFailed) { + return status; + } else { + return FileOpenFailed; + } +} + +MainWindow::FileOpenStatus +MainWindow::openSessionFile(QString path) +{ + return openSessionFile(path, path); +} + +MainWindow::FileOpenStatus +MainWindow::openSessionFile(QString path, QString location) +{ + BZipFileDevice bzFile(path); + if (!bzFile.open(QIODevice::ReadOnly)) { + std::cerr << "Failed to open session file \"" << location.toStdString() + << "\": " << bzFile.errorString().toStdString() << std::endl; + return FileOpenFailed; + } + + if (!checkSaveModified()) return FileOpenCancelled; + + QString error; + closeSession(); + createDocument(); + + PaneCallback callback(this); + m_viewManager->clearSelections(); + + SVFileReader reader(m_document, callback, location); + QXmlInputSource inputSource(&bzFile); + reader.parse(inputSource); + + if (!reader.isOK()) { + error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); + } + + bzFile.close(); + + bool ok = (error == ""); + + bool realFile = (location == path); + + if (ok) { + + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(location).fileName())); + + if (realFile) m_sessionFile = path; + + setupMenus(); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + updateMenuStates(); + + m_recentFiles.addFile(location); + + if (realFile) { + registerLastOpenedFilePath(FileFinder::SessionFile, path); // for file dialog + } + + } else { + setWindowTitle(tr("Sonic Visualiser")); + } + + return ok ? FileOpenSucceeded : FileOpenFailed; +} + +void +MainWindow::closeEvent(QCloseEvent *e) +{ +// std::cerr << "MainWindow::closeEvent" << std::endl; + + if (m_openingAudioFile) { +// std::cerr << "Busy - ignoring close event" << std::endl; + e->ignore(); + return; + } + + if (!m_abandoning && !checkSaveModified()) { +// std::cerr << "Ignoring close event" << std::endl; + e->ignore(); + return; + } + + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("size", size()); + settings.setValue("position", pos()); + settings.endGroup(); + + e->accept(); + return; +} + +bool +MainWindow::commitData(bool mayAskUser) +{ + if (mayAskUser) { + return checkSaveModified(); + } else { + if (!m_documentModified) return true; + + // If we can't check with the user first, then we can't save + // to the original session file (even if we have it) -- have + // to use a temporary file + + QString svDirBase = ".sv1"; + QString svDir = QDir::home().filePath(svDirBase); + + if (!QFileInfo(svDir).exists()) { + if (!QDir::home().mkdir(svDirBase)) return false; + } else { + if (!QFileInfo(svDir).isDir()) return false; + } + + // This name doesn't have to be unguessable +#ifndef _WIN32 + QString fname = QString("tmp-%1-%2.sv") + .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) + .arg(QProcess().pid()); +#else + QString fname = QString("tmp-%1.sv") + .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); +#endif + QString fpath = QDir(svDir).filePath(fname); + if (saveSessionFile(fpath)) { + m_recentFiles.addFile(fpath); + return true; + } else { + return false; + } + } +} + +bool +MainWindow::checkSaveModified() +{ + // Called before some destructive operation (e.g. new session, + // exit program). Return true if we can safely proceed, false to + // cancel. + + if (!m_documentModified) return true; + + int button = + QMessageBox::warning(this, + tr("Session modified"), + tr("The current session has been modified.\nDo you want to save it?"), + QMessageBox::Yes, + QMessageBox::No, + QMessageBox::Cancel); + + if (button == QMessageBox::Yes) { + saveSession(); + if (m_documentModified) { // save failed -- don't proceed! + return false; + } else { + return true; // saved, so it's safe to continue now + } + } else if (button == QMessageBox::No) { + m_documentModified = false; // so we know to abandon it + return true; + } + + // else cancel + return false; +} + +void +MainWindow::saveSession() +{ + if (m_sessionFile != "") { + if (!saveSessionFile(m_sessionFile)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); + } else { + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + } + } else { + saveSessionAs(); + } +} + +void +MainWindow::saveSessionAs() +{ + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + QString path = getSaveFileName(FileFinder::SessionFile); + + if (path == "") return; + + if (!saveSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(path)); + } else { + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(QFileInfo(path).fileName())); + m_sessionFile = path; + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + m_recentFiles.addFile(path); + } +} + +bool +MainWindow::saveSessionFile(QString path) +{ + BZipFileDevice bzFile(path); + if (!bzFile.open(QIODevice::WriteOnly)) { + std::cerr << "Failed to open session file \"" << path.toStdString() + << "\" for writing: " + << bzFile.errorString().toStdString() << std::endl; + return false; + } + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + QTextStream out(&bzFile); + toXml(out); + out.flush(); + + QApplication::restoreOverrideCursor(); + + if (!bzFile.isOK()) { + QMessageBox::critical(this, tr("Failed to write file"), + tr("Failed to write to file \"%1\": %2") + .arg(path).arg(bzFile.errorString())); + bzFile.close(); + return false; + } + + bzFile.close(); + return true; +} + +void +MainWindow::toXml(QTextStream &out) +{ + QString indent(" "); + + out << "\n"; + out << "\n"; + out << "\n"; + + m_document->toXml(out, "", ""); + + out << "\n"; + + out << QString(" \n") + .arg(width()).arg(height()); + + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + + Pane *pane = m_paneStack->getPane(i); + + if (pane) { + pane->toXml(out, indent); + } + } + + out << "\n"; + + m_viewManager->getSelection().toXml(out); + + out << "\n"; +} + +Pane * +MainWindow::addPaneToStack() +{ + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + return command->getPane(); +} + +void +MainWindow::zoomIn() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(true); +} + +void +MainWindow::zoomOut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(false); +} + +void +MainWindow::zoomToFit() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Model *model = getMainModel(); + if (!model) return; + + size_t start = model->getStartFrame(); + size_t end = model->getEndFrame(); + size_t pixels = currentPane->width(); + size_t zoomLevel = (end - start) / pixels; + + currentPane->setZoomLevel(zoomLevel); + currentPane->setStartFrame(start); +} + +void +MainWindow::zoomDefault() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->setZoomLevel(1024); +} + +void +MainWindow::scrollLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, false); +} + +void +MainWindow::jumpLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, true); +} + +void +MainWindow::scrollRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, false); +} + +void +MainWindow::jumpRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, true); +} + +void +MainWindow::showNoOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::NoOverlays); +} + +void +MainWindow::showMinimalOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); +} + +void +MainWindow::showStandardOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::StandardOverlays); +} + +void +MainWindow::showAllOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::AllOverlays); +} + +void +MainWindow::toggleZoomWheels() +{ + if (m_viewManager->getZoomWheelsEnabled()) { + m_viewManager->setZoomWheelsEnabled(false); + } else { + m_viewManager->setZoomWheelsEnabled(true); + } +} + +void +MainWindow::togglePropertyBoxes() +{ + if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) { + if (Preferences::getInstance()->getPropertyBoxLayout() == + Preferences::VerticallyStacked) { + m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); + } else { + m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); + } + } else { + m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); + } +} + +void +MainWindow::toggleStatusBar() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + bool sb = settings.value("showstatusbar", true).toBool(); + + if (sb) { + statusBar()->hide(); + } else { + statusBar()->show(); + } + + settings.setValue("showstatusbar", !sb); + + settings.endGroup(); +} + +void +MainWindow::preferenceChanged(PropertyContainer::PropertyName name) +{ + if (name == "Property Box Layout") { + if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) { + if (Preferences::getInstance()->getPropertyBoxLayout() == + Preferences::VerticallyStacked) { + m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); + } else { + m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); + } + } + } +} + +void +MainWindow::play() +{ + if (m_playSource->isPlaying()) { + stop(); + } else { + playbackFrameChanged(m_viewManager->getPlaybackFrame()); + m_playSource->play(m_viewManager->getPlaybackFrame()); + } +} + +void +MainWindow::ffwd() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + ++frame; + + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + + if (!dynamic_cast(layer) && + !dynamic_cast(layer)) return; + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapRight)) { + frame = getMainModel()->getEndFrame(); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindow::ffwdEnd() +{ + if (!getMainModel()) return; + m_viewManager->setPlaybackFrame(getMainModel()->getEndFrame()); +} + +void +MainWindow::rewind() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + if (frame > 0) --frame; + + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = pane->getSelectedLayer(); + + if (!dynamic_cast(layer) && + !dynamic_cast(layer)) return; + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapLeft)) { + frame = getMainModel()->getEndFrame(); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindow::rewindStart() +{ + if (!getMainModel()) return; + m_viewManager->setPlaybackFrame(getMainModel()->getStartFrame()); +} + +void +MainWindow::stop() +{ + m_playSource->stop(); + + if (m_paneStack && m_paneStack->getCurrentPane()) { + updateVisibleRangeDisplay(m_paneStack->getCurrentPane()); + } else { + m_myStatusMessage = ""; + statusBar()->showMessage(""); + } +} + +void +MainWindow::addPane() +{ + QObject *s = sender(); + QAction *action = dynamic_cast(s); + + if (!action) { + std::cerr << "WARNING: MainWindow::addPane: sender is not an action" + << std::endl; + return; + } + + PaneActionMap::iterator i = m_paneActions.find(action); + + if (i == m_paneActions.end()) { + std::cerr << "WARNING: MainWindow::addPane: unknown action " + << action->objectName().toStdString() << std::endl; + return; + } + + addPane(i->second, action->text()); +} + +void +MainWindow::addPane(const PaneConfiguration &configuration, QString text) +{ + CommandHistory::getInstance()->startCompoundOperation(text, true); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (configuration.layer == LayerFactory::Spectrum) { + pane->setPlaybackFollow(PlaybackScrollContinuous); + pane->setFollowGlobalZoom(false); + pane->setZoomLevel(512); + } + + if (configuration.layer != LayerFactory::TimeRuler && + configuration.layer != LayerFactory::Spectrum) { + + if (!m_timeRulerLayer) { +// std::cerr << "no time ruler layer, creating one" << std::endl; + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + +// std::cerr << "adding time ruler layer " << m_timeRulerLayer << std::endl; + + m_document->addLayerToView(pane, m_timeRulerLayer); + } + + Layer *newLayer = m_document->createLayer(configuration.layer); + + Model *suggestedModel = configuration.sourceModel; + Model *model = 0; + + if (suggestedModel) { + + // check its validity + std::vector inputModels = m_document->getTransformInputModels(); + for (size_t j = 0; j < inputModels.size(); ++j) { + if (inputModels[j] == suggestedModel) { + model = suggestedModel; + break; + } + } + + if (!model) { + std::cerr << "WARNING: Model " << (void *)suggestedModel + << " appears in pane action map, but is not reported " + << "by document as a valid transform source" << std::endl; + } + } + + if (!model) model = m_document->getMainModel(); + + m_document->setModel(newLayer, model); + + m_document->setChannel(newLayer, configuration.channel); + m_document->addLayerToView(pane, newLayer); + + m_paneStack->setCurrentPane(pane); + m_paneStack->setCurrentLayer(pane, newLayer); + +// std::cerr << "MainWindow::addPane: global centre frame is " +// << m_viewManager->getGlobalCentreFrame() << std::endl; +// pane->setCentreFrame(m_viewManager->getGlobalCentreFrame()); + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +MainWindow::AddPaneCommand::AddPaneCommand(MainWindow *mw) : + m_mw(mw), + m_pane(0), + m_prevCurrentPane(0), + m_added(false) +{ +} + +MainWindow::AddPaneCommand::~AddPaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindow::AddPaneCommand::getName() const +{ + return tr("Add Pane"); +} + +void +MainWindow::AddPaneCommand::execute() +{ + if (!m_pane) { + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_pane = m_mw->m_paneStack->addPane(); + + connect(m_pane, SIGNAL(contextHelpChanged(const QString &)), + m_mw, SLOT(contextHelpChanged(const QString &))); + } else { + m_mw->m_paneStack->showPane(m_pane); + } + + m_mw->m_paneStack->setCurrentPane(m_pane); + m_mw->m_overview->registerView(m_pane); + m_added = true; +} + +void +MainWindow::AddPaneCommand::unexecute() +{ + m_mw->m_paneStack->hidePane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_mw->m_overview->unregisterView(m_pane); + m_added = false; +} + +MainWindow::RemovePaneCommand::RemovePaneCommand(MainWindow *mw, Pane *pane) : + m_mw(mw), + m_pane(pane), + m_added(true) +{ +} + +MainWindow::RemovePaneCommand::~RemovePaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindow::RemovePaneCommand::getName() const +{ + return tr("Remove Pane"); +} + +void +MainWindow::RemovePaneCommand::execute() +{ + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_mw->m_paneStack->hidePane(m_pane); + m_mw->m_overview->unregisterView(m_pane); + m_added = false; +} + +void +MainWindow::RemovePaneCommand::unexecute() +{ + m_mw->m_paneStack->showPane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_mw->m_overview->registerView(m_pane); + m_added = true; +} + +void +MainWindow::addLayer() +{ + QObject *s = sender(); + QAction *action = dynamic_cast(s); + + if (!action) { + std::cerr << "WARNING: MainWindow::addLayer: sender is not an action" + << std::endl; + return; + } + + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + std::cerr << "WARNING: MainWindow::addLayer: no current pane" << std::endl; + return; + } + + ExistingLayerActionMap::iterator ei = m_existingLayerActions.find(action); + + if (ei != m_existingLayerActions.end()) { + Layer *newLayer = ei->second; + m_document->addLayerToView(pane, newLayer); + m_paneStack->setCurrentLayer(pane, newLayer); + return; + } + + ei = m_sliceActions.find(action); + + if (ei != m_sliceActions.end()) { + Layer *newLayer = m_document->createLayer(LayerFactory::Slice); +// document->setModel(newLayer, ei->second->getModel()); + SliceableLayer *source = dynamic_cast(ei->second); + SliceLayer *dest = dynamic_cast(newLayer); + if (source && dest) { + dest->setSliceableModel(source->getSliceableModel()); + connect(source, SIGNAL(sliceableModelReplaced(const Model *, const Model *)), + dest, SLOT(sliceableModelReplaced(const Model *, const Model *))); + connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), + dest, SLOT(modelAboutToBeDeleted(Model *))); + } + m_document->addLayerToView(pane, newLayer); + m_paneStack->setCurrentLayer(pane, newLayer); + return; + } + + TransformActionMap::iterator i = m_transformActions.find(action); + + if (i == m_transformActions.end()) { + + LayerActionMap::iterator i = m_layerActions.find(action); + + if (i == m_layerActions.end()) { + std::cerr << "WARNING: MainWindow::addLayer: unknown action " + << action->objectName().toStdString() << std::endl; + return; + } + + LayerFactory::LayerType type = i->second; + + LayerFactory::LayerTypeSet emptyTypes = + LayerFactory::getInstance()->getValidEmptyLayerTypes(); + + Layer *newLayer; + + if (emptyTypes.find(type) != emptyTypes.end()) { + + newLayer = m_document->createEmptyLayer(type); + m_toolActions[ViewManager::DrawMode]->trigger(); + + } else { + + newLayer = m_document->createMainModelLayer(type); + } + + m_document->addLayerToView(pane, newLayer); + m_paneStack->setCurrentLayer(pane, newLayer); + + return; + } + + TransformId transform = i->second; + TransformFactory *factory = TransformFactory::getInstance(); + + QString configurationXml; + + int channel = -1; + // pick up the default channel from any existing layers on the same pane + for (int j = 0; j < pane->getLayerCount(); ++j) { + int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j)); + if (c != -1) { + channel = c; + break; + } + } + + // We always ask for configuration, even if the plugin isn't + // supposed to be configurable, because we need to let the user + // change the execution context (block size etc). + + PluginTransform::ExecutionContext context(channel); + + std::vector candidateInputModels = + m_document->getTransformInputModels(); + + Model *inputModel = factory->getConfigurationForTransform(transform, + candidateInputModels, + context, + configurationXml, + m_playSource); + if (!inputModel) return; + +// std::cerr << "MainWindow::addLayer: Input model is " << inputModel << " \"" << inputModel->objectName().toStdString() << "\"" << std::endl; + + Layer *newLayer = m_document->createDerivedLayer(transform, + inputModel, + context, + configurationXml); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + m_document->setChannel(newLayer, context.channel); + m_recentTransforms.add(transform); + m_paneStack->setCurrentLayer(pane, newLayer); + } + + updateMenuStates(); +} + +void +MainWindow::deleteCurrentPane() +{ + CommandHistory::getInstance()->startCompoundOperation + (tr("Delete Pane"), true); + + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + while (pane->getLayerCount() > 0) { + Layer *layer = pane->getLayer(0); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } else { + break; + } + } + + RemovePaneCommand *command = new RemovePaneCommand(this, pane); + CommandHistory::getInstance()->addCommand(command); + } + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +void +MainWindow::deleteCurrentLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + Layer *layer = pane->getSelectedLayer(); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } + } + updateMenuStates(); +} + +void +MainWindow::renameCurrentLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + Layer *layer = pane->getSelectedLayer(); + if (layer) { + bool ok = false; + QString newName = QInputDialog::getText + (this, tr("Rename Layer"), + tr("New name for this layer:"), + QLineEdit::Normal, layer->objectName(), &ok); + if (ok) { + layer->setObjectName(newName); + setupExistingLayersMenus(); + } + } + } +} + +void +MainWindow::playSpeedChanged(int position) +{ + PlaySpeedRangeMapper mapper(0, 200); + + float percent = m_playSpeed->mappedValue(); + + float factor = mapper.getFactorForValue(percent); + +// float factor = mapper.getFactorForPosition(position); +// float percent = mapper.getValueForPosition(position); + + std::cerr << "speed = " << position << " percent = " << percent << " factor = " << factor << std::endl; + +//!!! bool slow = (position < 100); + bool something = (position != 100); +/*!!! + int pc = lrintf(percent); + + m_playSpeed->setToolTip(tr("Playback speed: %1%2%") + .arg(!slow ? "+" : "") + .arg(pc)); +*/ + m_playSharpen->setEnabled(something); + m_playMono->setEnabled(something); + bool sharpen = (something && m_playSharpen->isChecked()); + bool mono = (something && m_playMono->isChecked()); + m_playSource->setTimeStretch(factor, sharpen, mono); +} + +void +MainWindow::playSharpenToggled() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("playsharpen", m_playSharpen->isChecked()); + settings.endGroup(); + + playSpeedChanged(m_playSpeed->value()); +} + +void +MainWindow::playMonoToggled() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("playmono", m_playMono->isChecked()); + settings.endGroup(); + + playSpeedChanged(m_playSpeed->value()); +} + +void +MainWindow::playbackFrameChanged(unsigned long frame) +{ + if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + + RealTime now = RealTime::frame2RealTime + (frame, getMainModel()->getSampleRate()); + + if (now.sec == m_lastPlayStatusSec) return; + + RealTime then = RealTime::frame2RealTime + (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate()); + + QString nowStr; + QString thenStr; + QString remainingStr; + + if (then.sec > 10) { + nowStr = now.toSecText().c_str(); + thenStr = then.toSecText().c_str(); + remainingStr = (then - now).toSecText().c_str(); + m_lastPlayStatusSec = now.sec; + } else { + nowStr = now.toText(true).c_str(); + thenStr = then.toText(true).c_str(); + remainingStr = (then - now).toText(true).c_str(); + } + + m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)") + .arg(nowStr).arg(thenStr).arg(remainingStr); + + statusBar()->showMessage(m_myStatusMessage); +} + +void +MainWindow::globalCentreFrameChanged(unsigned long ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (!p->getFollowGlobalPan()) return; + updateVisibleRangeDisplay(p); +} + +void +MainWindow::viewCentreFrameChanged(View *v, unsigned long ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (v == p) updateVisibleRangeDisplay(p); +} + +void +MainWindow::viewZoomLevelChanged(View *v, unsigned long , bool ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (v == p) updateVisibleRangeDisplay(p); +} + +void +MainWindow::updateVisibleRangeDisplay(Pane *p) const +{ + if (!getMainModel() || !p) { + return; + } + + bool haveSelection = false; + size_t startFrame = 0, endFrame = 0; + + if (m_viewManager && m_viewManager->haveInProgressSelection()) { + + bool exclusive = false; + Selection s = m_viewManager->getInProgressSelection(exclusive); + + if (!s.isEmpty()) { + haveSelection = true; + startFrame = s.getStartFrame(); + endFrame = s.getEndFrame(); + } + } + + if (!haveSelection) { + startFrame = p->getFirstVisibleFrame(); + endFrame = p->getLastVisibleFrame(); + } + + RealTime start = RealTime::frame2RealTime + (startFrame, getMainModel()->getSampleRate()); + + RealTime end = RealTime::frame2RealTime + (endFrame, getMainModel()->getSampleRate()); + + RealTime duration = end - start; + + QString startStr, endStr, durationStr; + startStr = start.toText(true).c_str(); + endStr = end.toText(true).c_str(); + durationStr = duration.toText(true).c_str(); + + if (haveSelection) { + m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") + .arg(startStr).arg(endStr).arg(durationStr); + } else { + m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") + .arg(startStr).arg(endStr).arg(durationStr); + } + + statusBar()->showMessage(m_myStatusMessage); +} + +void +MainWindow::outputLevelsChanged(float left, float right) +{ + m_fader->setPeakLeft(left); + m_fader->setPeakRight(right); +} + +void +MainWindow::sampleRateMismatch(size_t requested, size_t actual, + bool willResample) +{ + if (!willResample) { + //!!! more helpful message needed + QMessageBox::information + (this, tr("Sample rate mismatch"), + tr("The sample rate of this audio file (%1 Hz) does not match\nthe current playback rate (%2 Hz).\n\nThe file will play at the wrong speed and pitch.") + .arg(requested).arg(actual)); + } + + updateDescriptionLabel(); +} + +void +MainWindow::audioOverloadPluginDisabled() +{ + QMessageBox::information + (this, tr("Audio processing overload"), + tr("Audio effects plugin auditioning has been disabled\ndue to a processing overload.")); +} + +void +MainWindow::layerAdded(Layer *) +{ +// std::cerr << "MainWindow::layerAdded(" << layer << ")" << std::endl; +// setupExistingLayersMenu(); + updateMenuStates(); +} + +void +MainWindow::layerRemoved(Layer *) +{ +// std::cerr << "MainWindow::layerRemoved(" << layer << ")" << std::endl; + setupExistingLayersMenus(); + updateMenuStates(); +} + +void +MainWindow::layerAboutToBeDeleted(Layer *layer) +{ +// std::cerr << "MainWindow::layerAboutToBeDeleted(" << layer << ")" << std::endl; + if (layer == m_timeRulerLayer) { +// std::cerr << "(this is the time ruler layer)" << std::endl; + m_timeRulerLayer = 0; + } +} + +void +MainWindow::layerInAView(Layer *layer, bool inAView) +{ +// std::cerr << "MainWindow::layerInAView(" << layer << "," << inAView << ")" << std::endl; + + // Check whether we need to add or remove model from play source + Model *model = layer->getModel(); + if (model) { + if (inAView) { + m_playSource->addModel(model); + } else { + bool found = false; + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + Pane *pane = m_paneStack->getPane(i); + if (!pane) continue; + for (int j = 0; j < pane->getLayerCount(); ++j) { + Layer *pl = pane->getLayer(j); + if (pl && pl->getModel() == model) { + found = true; + break; + } + } + if (found) break; + } + if (!found) m_playSource->removeModel(model); + } + } + + setupExistingLayersMenus(); + updateMenuStates(); +} + +void +MainWindow::modelAdded(Model *model) +{ +// std::cerr << "MainWindow::modelAdded(" << model << ")" << std::endl; + m_playSource->addModel(model); + if (dynamic_cast(model)) { + setupPaneAndLayerMenus(); + } +} + +void +MainWindow::mainModelChanged(WaveFileModel *model) +{ +// std::cerr << "MainWindow::mainModelChanged(" << model << ")" << std::endl; + updateDescriptionLabel(); + m_panLayer->setModel(model); + if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); + if (model && !m_playTarget && m_audioOutput) createPlayTarget(); +} + +void +MainWindow::modelAboutToBeDeleted(Model *model) +{ +// std::cerr << "MainWindow::modelAboutToBeDeleted(" << model << ")" << std::endl; + m_playSource->removeModel(model); + FFTDataServer::modelAboutToBeDeleted(model); +} + +void +MainWindow::modelGenerationFailed(QString transformName) +{ + QMessageBox::warning + (this, + tr("Failed to generate layer"), + tr("Failed to generate a derived layer.\n\nThe layer transform \"%1\" failed.\n\nThis probably means that a plugin failed to initialise, perhaps because it\nrejected the processing block size that was requested.") + .arg(transformName), + QMessageBox::Ok, 0); +} + +void +MainWindow::modelRegenerationFailed(QString layerName, QString transformName) +{ + QMessageBox::warning + (this, + tr("Failed to regenerate layer"), + tr("Failed to regenerate derived layer \"%1\".\n\nThe layer transform \"%2\" failed to run.\n\nThis probably means the layer used a plugin that is not currently available.") + .arg(layerName).arg(transformName), + QMessageBox::Ok, 0); +} + +void +MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) +{ +// std::cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << std::endl; + m_paneStack->setCurrentPane(pane); + m_rightButtonMenu->popup(position); +} + +void +MainWindow::propertyStacksResized() +{ +/* + std::cerr << "MainWindow::propertyStacksResized" << std::endl; + Pane *pane = m_paneStack->getCurrentPane(); + if (pane && m_overview) { + std::cerr << "Fixed width: " << pane->width() << std::endl; + m_overview->setFixedWidth(pane->width()); + } +*/ +} + +void +MainWindow::showLayerTree() +{ + QTreeView *view = new QTreeView(); + LayerTreeModel *tree = new LayerTreeModel(m_paneStack); + view->expand(tree->index(0, 0, QModelIndex())); + view->setModel(tree); + view->show(); +} + +void +MainWindow::pollOSC() +{ + if (!m_oscQueue || m_oscQueue->isEmpty()) return; + std::cerr << "MainWindow::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl; + + if (m_openingAudioFile) return; + + OSCMessage message = m_oscQueue->readMessage(); + + if (message.getTarget() != 0) { + return; //!!! for now -- this class is target 0, others not handled yet + } + + handleOSCMessage(message); +} + +void +MainWindow::handleOSCMessage(const OSCMessage &message) +{ + std::cerr << "MainWindow::handleOSCMessage: thread id = " + << QThread::currentThreadId() << std::endl; + + // This large function should really be abstracted out. + + if (message.getMethod() == "open") { + + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + QString path = message.getArg(0).toString(); + if (openSomeFile(path, ReplaceMainModel) != FileOpenSucceeded) { + std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" + << path.toStdString() << "\"" << std::endl; + } + //!!! we really need to spin here and not return until the + // file has been completely decoded... + } + + } else if (message.getMethod() == "openadditional") { + + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + QString path = message.getArg(0).toString(); + if (openSomeFile(path, CreateAdditionalModel) != FileOpenSucceeded) { + std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" + << path.toStdString() << "\"" << std::endl; + } + } + + } else if (message.getMethod() == "recent" || + message.getMethod() == "last") { + + int n = 0; + if (message.getMethod() == "recent" && + message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::Int)) { + n = message.getArg(0).toInt() - 1; + } + std::vector recent = m_recentFiles.getRecent(); + if (n >= 0 && n < int(recent.size())) { + if (openSomeFile(recent[n], ReplaceMainModel) != FileOpenSucceeded) { + std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" + << recent[n].toStdString() << "\"" << std::endl; + } + } + + } else if (message.getMethod() == "save") { + + QString path; + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + path = message.getArg(0).toString(); + if (QFileInfo(path).exists()) { + std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in save" << std::endl; + } else { + saveSessionFile(path); + } + } + + } else if (message.getMethod() == "export") { + + QString path; + if (getMainModel()) { + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + path = message.getArg(0).toString(); + if (QFileInfo(path).exists()) { + std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in export" << std::endl; + } else { + WavFileWriter writer(path, + getMainModel()->getSampleRate(), + getMainModel()->getChannelCount()); + MultiSelection ms = m_viewManager->getSelection(); + if (!ms.getSelections().empty()) { + writer.writeModel(getMainModel(), &ms); + } else { + writer.writeModel(getMainModel()); + } + } + } + } + + } else if (message.getMethod() == "jump" || + message.getMethod() == "play") { + + if (getMainModel()) { + + unsigned long frame = m_viewManager->getPlaybackFrame(); + bool selection = false; + bool play = (message.getMethod() == "play"); + + if (message.getArgCount() == 1) { + + if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "selection") { + + selection = true; + + } else if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "end") { + + frame = getMainModel()->getEndFrame(); + + } else if (message.getArg(0).canConvert(QVariant::Double)) { + + double time = message.getArg(0).toDouble(); + if (time < 0.0) time = 0.0; + + frame = lrint(time * getMainModel()->getSampleRate()); + } + } + + if (frame > getMainModel()->getEndFrame()) { + frame = getMainModel()->getEndFrame(); + } + + if (play) { + m_viewManager->setPlaySelectionMode(selection); + } + + if (selection) { + MultiSelection::SelectionList sl = m_viewManager->getSelections(); + if (!sl.empty()) { + frame = sl.begin()->getStartFrame(); + } + } + + m_viewManager->setPlaybackFrame(frame); + + if (play && !m_playSource->isPlaying()) { + m_playSource->play(frame); + } + } + + } else if (message.getMethod() == "stop") { + + if (m_playSource->isPlaying()) m_playSource->stop(); + + } else if (message.getMethod() == "loop") { + + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + + QString str = message.getArg(0).toString(); + if (str == "on") { + m_viewManager->setPlayLoopMode(true); + } else if (str == "off") { + m_viewManager->setPlayLoopMode(false); + } + } + + } else if (message.getMethod() == "select" || + message.getMethod() == "addselect") { + + if (getMainModel()) { + + int f0 = getMainModel()->getStartFrame(); + int f1 = getMainModel()->getEndFrame(); + + bool done = false; + + if (message.getArgCount() == 2 && + message.getArg(0).canConvert(QVariant::Double) && + message.getArg(1).canConvert(QVariant::Double)) { + + double t0 = message.getArg(0).toDouble(); + double t1 = message.getArg(1).toDouble(); + if (t1 < t0) { double temp = t0; t0 = t1; t1 = temp; } + if (t0 < 0.0) t0 = 0.0; + if (t1 < 0.0) t1 = 0.0; + + f0 = lrint(t0 * getMainModel()->getSampleRate()); + f1 = lrint(t1 * getMainModel()->getSampleRate()); + + Pane *pane = m_paneStack->getCurrentPane(); + Layer *layer = 0; + if (pane) layer = pane->getSelectedLayer(); + if (layer) { + size_t resolution; + layer->snapToFeatureFrame(pane, f0, resolution, + Layer::SnapLeft); + layer->snapToFeatureFrame(pane, f1, resolution, + Layer::SnapRight); + } + + } else if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + + QString str = message.getArg(0).toString(); + if (str == "none") { + m_viewManager->clearSelections(); + done = true; + } + } + + if (!done) { + if (message.getMethod() == "select") { + m_viewManager->setSelection(Selection(f0, f1)); + } else { + m_viewManager->addSelection(Selection(f0, f1)); + } + } + } + + } else if (message.getMethod() == "add") { + + if (getMainModel()) { + + if (message.getArgCount() >= 1 && + message.getArg(0).canConvert(QVariant::String)) { + + int channel = -1; + if (message.getArgCount() == 2 && + message.getArg(0).canConvert(QVariant::Int)) { + channel = message.getArg(0).toInt(); + if (channel < -1 || + channel > int(getMainModel()->getChannelCount())) { + std::cerr << "WARNING: MainWindow::handleOSCMessage: channel " + << channel << " out of range" << std::endl; + channel = -1; + } + } + + QString str = message.getArg(0).toString(); + + LayerFactory::LayerType type = + LayerFactory::getInstance()->getLayerTypeForName(str); + + if (type == LayerFactory::UnknownLayer) { + std::cerr << "WARNING: MainWindow::handleOSCMessage: unknown layer " + << "type " << str.toStdString() << std::endl; + } else { + + PaneConfiguration configuration(type, + getMainModel(), + channel); + + addPane(configuration, + tr("Add %1 Pane") + .arg(LayerFactory::getInstance()-> + getLayerPresentationName(type))); + } + } + } + + } else if (message.getMethod() == "undo") { + + CommandHistory::getInstance()->undo(); + + } else if (message.getMethod() == "redo") { + + CommandHistory::getInstance()->redo(); + + } else if (message.getMethod() == "set") { + + if (message.getArgCount() == 2 && + message.getArg(0).canConvert(QVariant::String) && + message.getArg(1).canConvert(QVariant::Double)) { + + QString property = message.getArg(0).toString(); + float value = (float)message.getArg(1).toDouble(); + + if (property == "gain") { + if (value < 0.0) value = 0.0; + m_fader->setValue(value); + if (m_playTarget) m_playTarget->setOutputGain(value); + } else if (property == "speedup") { + m_playSpeed->setMappedValue(value); + } else if (property == "overlays") { + if (value < 0.5) { + m_viewManager->setOverlayMode(ViewManager::NoOverlays); + } else if (value < 1.5) { + m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); + } else if (value < 2.5) { + m_viewManager->setOverlayMode(ViewManager::StandardOverlays); + } else { + m_viewManager->setOverlayMode(ViewManager::AllOverlays); + } + } else if (property == "zoomwheels") { + m_viewManager->setZoomWheelsEnabled(value > 0.5); + } else if (property == "propertyboxes") { + bool toggle = ((value < 0.5) != + (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks)); + if (toggle) togglePropertyBoxes(); + } + + } else { + PropertyContainer *container = 0; + Pane *pane = m_paneStack->getCurrentPane(); + if (pane && + message.getArgCount() == 3 && + message.getArg(0).canConvert(QVariant::String) && + message.getArg(1).canConvert(QVariant::String) && + message.getArg(2).canConvert(QVariant::String)) { + if (message.getArg(0).toString() == "pane") { + container = pane->getPropertyContainer(0); + } else if (message.getArg(0).toString() == "layer") { + container = pane->getSelectedLayer(); + } + } + if (container) { + QString nameString = message.getArg(1).toString(); + QString valueString = message.getArg(2).toString(); + container->setPropertyWithCommand(nameString, valueString); + } + } + + } else if (message.getMethod() == "setcurrent") { + + int paneIndex = -1, layerIndex = -1; + bool wantLayer = false; + + if (message.getArgCount() >= 1 && + message.getArg(0).canConvert(QVariant::Int)) { + + paneIndex = message.getArg(0).toInt() - 1; + + if (message.getArgCount() >= 2 && + message.getArg(1).canConvert(QVariant::Int)) { + wantLayer = true; + layerIndex = message.getArg(1).toInt() - 1; + } + } + + if (paneIndex >= 0 && paneIndex < m_paneStack->getPaneCount()) { + Pane *pane = m_paneStack->getPane(paneIndex); + m_paneStack->setCurrentPane(pane); + if (layerIndex >= 0 && layerIndex < pane->getLayerCount()) { + Layer *layer = pane->getLayer(layerIndex); + m_paneStack->setCurrentLayer(pane, layer); + } else if (wantLayer && layerIndex == -1) { + m_paneStack->setCurrentLayer(pane, 0); + } + } + + } else if (message.getMethod() == "delete") { + + if (message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + + QString target = message.getArg(0).toString(); + + if (target == "pane") { + + deleteCurrentPane(); + + } else if (target == "layer") { + + deleteCurrentLayer(); + + } else { + + std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown delete target " << target.toStdString() << std::endl; + } + } + + } else if (message.getMethod() == "zoom") { + + if (message.getArgCount() == 1) { + if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "in") { + zoomIn(); + } else if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "out") { + zoomOut(); + } else if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "default") { + zoomDefault(); + } else if (message.getArg(0).canConvert(QVariant::Double)) { + double level = message.getArg(0).toDouble(); + Pane *currentPane = m_paneStack->getCurrentPane(); + if (level < 1.0) level = 1.0; + if (currentPane) currentPane->setZoomLevel(lrint(level)); + } + } + + } else if (message.getMethod() == "zoomvertical") { + + Pane *pane = m_paneStack->getCurrentPane(); + Layer *layer = 0; + if (pane && pane->getLayerCount() > 0) { + layer = pane->getLayer(pane->getLayerCount() - 1); + } + int defaultStep = 0; + int steps = 0; + if (layer) { + steps = layer->getVerticalZoomSteps(defaultStep); + if (message.getArgCount() == 1 && steps > 0) { + if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "in") { + int step = layer->getCurrentVerticalZoomStep() + 1; + if (step < steps) layer->setVerticalZoomStep(step); + } else if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "out") { + int step = layer->getCurrentVerticalZoomStep() - 1; + if (step >= 0) layer->setVerticalZoomStep(step); + } else if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "default") { + layer->setVerticalZoomStep(defaultStep); + } + } else if (message.getArgCount() == 2) { + if (message.getArg(0).canConvert(QVariant::Double) && + message.getArg(1).canConvert(QVariant::Double)) { + double min = message.getArg(0).toDouble(); + double max = message.getArg(1).toDouble(); + layer->setDisplayExtents(min, max); + } + } + } + + } else if (message.getMethod() == "quit") { + + m_abandoning = true; + close(); + + } else if (message.getMethod() == "resize") { + + if (message.getArgCount() == 2) { + + int width = 0, height = 0; + + if (message.getArg(1).canConvert(QVariant::Int)) { + + height = message.getArg(1).toInt(); + + if (message.getArg(0).canConvert(QVariant::String) && + message.getArg(0).toString() == "pane") { + + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) pane->resize(pane->width(), height); + + } else if (message.getArg(0).canConvert(QVariant::Int)) { + + width = message.getArg(0).toInt(); + resize(width, height); + } + } + } + + } else if (message.getMethod() == "transform") { + + Pane *pane = m_paneStack->getCurrentPane(); + + if (getMainModel() && + pane && + message.getArgCount() == 1 && + message.getArg(0).canConvert(QVariant::String)) { + + TransformId transform = message.getArg(0).toString(); + + Layer *newLayer = m_document->createDerivedLayer + (transform, + getMainModel(), + TransformFactory::getInstance()->getDefaultContextForTransform + (transform, getMainModel()), + ""); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + m_recentTransforms.add(transform); + m_paneStack->setCurrentLayer(pane, newLayer); + } + } + + } else { + std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown or unsupported " + << "method \"" << message.getMethod().toStdString() + << "\"" << std::endl; + } + +} + +void +MainWindow::preferences() +{ + if (!m_preferencesDialog.isNull()) { + m_preferencesDialog->show(); + m_preferencesDialog->raise(); + return; + } + + m_preferencesDialog = new PreferencesDialog(this); + + // DeleteOnClose is safe here, because m_preferencesDialog is a + // QPointer that will be zeroed when the dialog is deleted. We + // use it in preference to leaving the dialog lying around because + // if you Cancel the dialog, it resets the preferences state + // without resetting its own widgets, so its state will be + // incorrect when next shown unless we construct it afresh + m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); + + m_preferencesDialog->show(); +} + +void +MainWindow::mouseEnteredWidget() +{ + QWidget *w = dynamic_cast(sender()); + if (!w) return; + + if (w == m_fader) { + contextHelpChanged(tr("Adjust the master playback level")); + } else if (w == m_playSpeed) { + contextHelpChanged(tr("Adjust the master playback speed")); + } else if (w == m_playSharpen && w->isEnabled()) { + contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); + } else if (w == m_playMono && w->isEnabled()) { + contextHelpChanged(tr("Toggle mono mode for playback time scaling")); + } +} + +void +MainWindow::mouseLeftWidget() +{ + contextHelpChanged(""); +} + +void +MainWindow::inProgressSelectionChanged() +{ + Pane *currentPane = 0; + if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); + if (currentPane) updateVisibleRangeDisplay(currentPane); +} + +void +MainWindow::contextHelpChanged(const QString &s) +{ + if (s == "" && m_myStatusMessage != "") { + statusBar()->showMessage(m_myStatusMessage); + return; + } + statusBar()->showMessage(s); +} + +void +MainWindow::website() +{ + openHelpUrl(tr("http://www.sonicvisualiser.org/")); +} + +void +MainWindow::help() +{ + openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/1.0/en/")); +} + +void +MainWindow::openHelpUrl(QString url) +{ + // This method mostly lifted from Qt Assistant source code + + QProcess *process = new QProcess(this); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); + + QStringList args; + +#ifdef Q_OS_MAC + args.append(url); + process->start("open", args); +#else +#ifdef Q_OS_WIN32 + + QString pf(getenv("ProgramFiles")); + QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); + + args.append(url); + process->start(command, args); + +#else +#ifdef Q_WS_X11 + if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { + args.append("exec"); + args.append(url); + process->start("kfmclient", args); + } else if (!qgetenv("BROWSER").isEmpty()) { + args.append(url); + process->start(qgetenv("BROWSER"), args); + } else { + args.append(url); + process->start("firefox", args); + } +#endif +#endif +#endif +} + +void +MainWindow::about() +{ + bool debug = false; + QString version = "(unknown version)"; + +#ifdef BUILD_DEBUG + debug = true; +#endif +#ifdef SV_VERSION +#ifdef SVNREV + version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV); +#else + version = tr("Release %1").arg(SV_VERSION); +#endif +#else +#ifdef SVNREV + version = tr("Unreleased : Revision %1").arg(SVNREV); +#endif +#endif + + QString aboutText; + + aboutText += tr("

About Sonic Visualiser

"); + aboutText += tr("

Sonic Visualiser is a program for viewing and exploring audio data for
semantic music analysis and annotation.

"); + aboutText += tr("

%1 : %2 configuration

") + .arg(version) + .arg(debug ? tr("Debug") : tr("Release")); + +#ifndef BUILD_STATIC + aboutText += tr("
Using Qt v%1 © Trolltech AS").arg(QT_VERSION_STR); +#else +#ifdef QT_SHARED + aboutText += tr("
Using Qt v%1 © Trolltech AS").arg(QT_VERSION_STR); +#endif +#endif + +#ifdef BUILD_STATIC + aboutText += tr("

Statically linked"); +#ifndef QT_SHARED + aboutText += tr("
With Qt (v%1) © Trolltech AS").arg(QT_VERSION_STR); +#endif +#ifdef HAVE_JACK +#ifdef JACK_VERSION + aboutText += tr("
With JACK audio output (v%1) © Paul Davis and Jack O'Quin").arg(JACK_VERSION); +#else + aboutText += tr("
With JACK audio output © Paul Davis and Jack O'Quin"); +#endif +#endif +#ifdef HAVE_PORTAUDIO + aboutText += tr("
With PortAudio audio output © Ross Bencina and Phil Burk"); +#endif +#ifdef HAVE_OGGZ +#ifdef OGGZ_VERSION + aboutText += tr("
With Ogg file decoder (oggz v%1, fishsound v%2) © CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION); +#else + aboutText += tr("
With Ogg file decoder © CSIRO Australia"); +#endif +#endif +#ifdef HAVE_MAD +#ifdef MAD_VERSION + aboutText += tr("
With MAD mp3 decoder (v%1) © Underbit Technologies Inc").arg(MAD_VERSION); +#else + aboutText += tr("
With MAD mp3 decoder © Underbit Technologies Inc"); +#endif +#endif +#ifdef HAVE_SAMPLERATE +#ifdef SAMPLERATE_VERSION + aboutText += tr("
With libsamplerate (v%1) © Erik de Castro Lopo").arg(SAMPLERATE_VERSION); +#else + aboutText += tr("
With libsamplerate © Erik de Castro Lopo"); +#endif +#endif +#ifdef HAVE_SNDFILE +#ifdef SNDFILE_VERSION + aboutText += tr("
With libsndfile (v%1) © Erik de Castro Lopo").arg(SNDFILE_VERSION); +#else + aboutText += tr("
With libsndfile © Erik de Castro Lopo"); +#endif +#endif +#ifdef HAVE_FFTW3F +#ifdef FFTW3_VERSION + aboutText += tr("
With FFTW3 (v%1) © Matteo Frigo and MIT").arg(FFTW3_VERSION); +#else + aboutText += tr("
With FFTW3 © Matteo Frigo and MIT"); +#endif +#endif +#ifdef HAVE_VAMP + aboutText += tr("
With Vamp plugin support (API v%1, host SDK v%2) © Chris Cannam").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION); +#endif + aboutText += tr("
With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); + aboutText += tr("
With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); +#ifdef HAVE_LIBLO +#ifdef LIBLO_VERSION + aboutText += tr("
With liblo Lite OSC library (v%1) © Steve Harris").arg(LIBLO_VERSION); +#else + aboutText += tr("
With liblo Lite OSC library © Steve Harris").arg(LIBLO_VERSION); +#endif + if (m_oscQueue && m_oscQueue->isOK()) { + aboutText += tr("

The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL()); + } +#endif + aboutText += "

"; +#endif + + aboutText += + "

Sonic Visualiser Copyright © 2005 - 2007 Chris Cannam and
" + "Queen Mary, University of London.

" + "

This program is free software; you can redistribute it and/or
" + "modify it under the terms of the GNU General Public License as
" + "published by the Free Software Foundation; either version 2 of the
" + "License, or (at your option) any later version.
See the file " + "COPYING included with this distribution for more information.

"; + + QMessageBox::about(this, tr("About Sonic Visualiser"), aboutText); +} + diff -r 000000000000 -r fc9323a41f5a sv/main/MainWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/main/MainWindow.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,408 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _MAIN_WINDOW_H_ +#define _MAIN_WINDOW_H_ + +#include +#include +#include +#include +#include + +#include "base/Command.h" +#include "view/ViewManager.h" +#include "base/PropertyContainer.h" +#include "base/RecentFiles.h" +#include "layer/LayerFactory.h" +#include "transform/Transform.h" +#include "document/SVFileReader.h" +#include "data/fileio/FileFinder.h" +#include + +class Document; +class PaneStack; +class Pane; +class View; +class Fader; +class Overview; +class Layer; +class WaveformLayer; +class WaveFileModel; +class AudioCallbackPlaySource; +class AudioCallbackPlayTarget; +class CommandHistory; +class QMenu; +class AudioDial; +class QLabel; +class QCheckBox; +class PreferencesDialog; +class QPushButton; +class OSCQueue; +class OSCMessage; + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(bool withAudioOutput = true, + bool withOSCSupport = true); + virtual ~MainWindow(); + + enum AudioFileOpenMode { + ReplaceMainModel, + CreateAdditionalModel, + AskUser + }; + + enum FileOpenStatus { + FileOpenSucceeded, + FileOpenFailed, + FileOpenCancelled + }; + + FileOpenStatus openSomeFile(QString path, AudioFileOpenMode = AskUser); + FileOpenStatus openAudioFile(QString path, AudioFileOpenMode = AskUser); + FileOpenStatus openLayerFile(QString path); + FileOpenStatus openSessionFile(QString path); + FileOpenStatus openURL(QUrl url); + + bool saveSessionFile(QString path); + bool commitData(bool mayAskUser); // on session shutdown + +signals: + // Used to toggle the availability of menu actions + void canAddPane(bool); + void canDeleteCurrentPane(bool); + void canAddLayer(bool); + void canImportMoreAudio(bool); + void canImportLayer(bool); + void canExportAudio(bool); + void canExportLayer(bool); + void canExportImage(bool); + void canRenameLayer(bool); + void canEditLayer(bool); + void canSelect(bool); + void canClearSelection(bool); + void canEditSelection(bool); + void canPaste(bool); + void canInsertInstant(bool); + void canInsertInstantsAtBoundaries(bool); + void canDeleteCurrentLayer(bool); + void canZoom(bool); + void canScroll(bool); + void canPlay(bool); + void canFfwd(bool); + void canRewind(bool); + void canPlaySelection(bool); + void canSave(bool); + +public slots: + void preferenceChanged(PropertyContainer::PropertyName); + +protected slots: + void openSession(); + void importAudio(); + void importMoreAudio(); + void openSomething(); + void openLocation(); + void openRecentFile(); + void exportAudio(); + void importLayer(); + void exportLayer(); + void exportImage(); + void saveSession(); + void saveSessionAs(); + void newSession(); + void closeSession(); + void preferences(); + + void zoomIn(); + void zoomOut(); + void zoomToFit(); + void zoomDefault(); + void scrollLeft(); + void scrollRight(); + void jumpLeft(); + void jumpRight(); + + void showNoOverlays(); + void showMinimalOverlays(); + void showStandardOverlays(); + void showAllOverlays(); + + void toggleZoomWheels(); + void togglePropertyBoxes(); + void toggleStatusBar(); + + void play(); + void ffwd(); + void ffwdEnd(); + void rewind(); + void rewindStart(); + void stop(); + + void addPane(); + void addLayer(); + void deleteCurrentPane(); + void renameCurrentLayer(); + void deleteCurrentLayer(); + + void playLoopToggled(); + void playSelectionToggled(); + void playSpeedChanged(int); + void playSharpenToggled(); + void playMonoToggled(); + void sampleRateMismatch(size_t, size_t, bool); + void audioOverloadPluginDisabled(); + + void playbackFrameChanged(unsigned long); + void globalCentreFrameChanged(unsigned long); + void viewCentreFrameChanged(View *, unsigned long); + void viewZoomLevelChanged(View *, unsigned long, bool); + void outputLevelsChanged(float, float); + + void currentPaneChanged(Pane *); + void currentLayerChanged(Pane *, Layer *); + + void toolNavigateSelected(); + void toolSelectSelected(); + void toolEditSelected(); + void toolDrawSelected(); + + void selectAll(); + void selectToStart(); + void selectToEnd(); + void selectVisible(); + void clearSelection(); + void cut(); + void copy(); + void paste(); + void deleteSelected(); + void insertInstant(); + void insertInstantAt(size_t); + void insertInstantsAtBoundaries(); + + void documentModified(); + void documentRestored(); + + void updateMenuStates(); + void updateDescriptionLabel(); + + void layerAdded(Layer *); + void layerRemoved(Layer *); + void layerAboutToBeDeleted(Layer *); + void layerInAView(Layer *, bool); + + void mainModelChanged(WaveFileModel *); + void modelAdded(Model *); + void modelAboutToBeDeleted(Model *); + + void modelGenerationFailed(QString); + void modelRegenerationFailed(QString, QString); + + void rightButtonMenuRequested(Pane *, QPoint point); + + void propertyStacksResized(); + + void setupRecentFilesMenu(); + void setupRecentTransformsMenu(); + + void showLayerTree(); + + void pollOSC(); + void handleOSCMessage(const OSCMessage &); + + void mouseEnteredWidget(); + void mouseLeftWidget(); + void contextHelpChanged(const QString &); + void inProgressSelectionChanged(); + + void website(); + void help(); + void about(); + +protected: + QString m_sessionFile; + QString m_audioFile; + Document *m_document; + + QLabel *m_descriptionLabel; + PaneStack *m_paneStack; + ViewManager *m_viewManager; + Overview *m_overview; + Fader *m_fader; + AudioDial *m_playSpeed; + QPushButton *m_playSharpen; + QPushButton *m_playMono; + WaveformLayer *m_panLayer; + Layer *m_timeRulerLayer; + + bool m_audioOutput; + AudioCallbackPlaySource *m_playSource; + AudioCallbackPlayTarget *m_playTarget; + + OSCQueue *m_oscQueue; + + RecentFiles m_recentFiles; + RecentFiles m_recentTransforms; + + bool m_mainMenusCreated; + QMenu *m_paneMenu; + QMenu *m_layerMenu; + QMenu *m_transformsMenu; + QMenu *m_existingLayersMenu; + QMenu *m_sliceMenu; + QMenu *m_recentFilesMenu; + QMenu *m_recentTransformsMenu; + QMenu *m_rightButtonMenu; + QMenu *m_rightButtonLayerMenu; + QMenu *m_rightButtonTransformsMenu; + + bool m_documentModified; + bool m_openingAudioFile; + bool m_abandoning; + + int m_lastPlayStatusSec; + mutable QString m_myStatusMessage; + + QPointer m_preferencesDialog; + + WaveFileModel *getMainModel(); + const WaveFileModel *getMainModel() const; + void createDocument(); + + struct PaneConfiguration { + PaneConfiguration(LayerFactory::LayerType _layer + = LayerFactory::TimeRuler, + Model *_source = 0, + int _channel = -1) : + layer(_layer), sourceModel(_source), channel(_channel) { } + LayerFactory::LayerType layer; + Model *sourceModel; + int channel; + }; + + typedef std::map PaneActionMap; + PaneActionMap m_paneActions; + + typedef std::map TransformActionMap; + TransformActionMap m_transformActions; + + typedef std::map TransformActionReverseMap; + TransformActionReverseMap m_transformActionsReverse; + + typedef std::map LayerActionMap; + LayerActionMap m_layerActions; + + typedef std::map ExistingLayerActionMap; + ExistingLayerActionMap m_existingLayerActions; + ExistingLayerActionMap m_sliceActions; + + typedef std::map ToolActionMap; + ToolActionMap m_toolActions; + + void setupMenus(); + void setupFileMenu(); + void setupEditMenu(); + void setupViewMenu(); + void setupPaneAndLayerMenus(); + void setupTransformsMenu(); + void setupHelpMenu(); + void setupExistingLayersMenus(); + void setupToolbars(); + + Pane *addPaneToStack(); + + void addPane(const PaneConfiguration &configuration, QString text); + + class PaneCallback : public SVFileReaderPaneCallback + { + public: + PaneCallback(MainWindow *mw) : m_mw(mw) { } + virtual Pane *addPane() { return m_mw->addPaneToStack(); } + virtual void setWindowSize(int width, int height) { + m_mw->resize(width, height); + } + virtual void addSelection(int start, int end) { + m_mw->m_viewManager->addSelection(Selection(start, end)); + } + protected: + MainWindow *m_mw; + }; + + class AddPaneCommand : public Command + { + public: + AddPaneCommand(MainWindow *mw); + virtual ~AddPaneCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + Pane *getPane() { return m_pane; } + + protected: + MainWindow *m_mw; + Pane *m_pane; // Main window owns this, but I determine its lifespan + Pane *m_prevCurrentPane; // I don't own this + bool m_added; + }; + + class RemovePaneCommand : public Command + { + public: + RemovePaneCommand(MainWindow *mw, Pane *pane); + virtual ~RemovePaneCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + protected: + MainWindow *m_mw; + Pane *m_pane; // Main window owns this, but I determine its lifespan + Pane *m_prevCurrentPane; // I don't own this + bool m_added; + }; + + virtual void closeEvent(QCloseEvent *e); + bool checkSaveModified(); + + FileOpenStatus openSomeFile(QString path, QString location, + AudioFileOpenMode = AskUser); + FileOpenStatus openAudioFile(QString path, QString location, + AudioFileOpenMode = AskUser); + FileOpenStatus openLayerFile(QString path, QString location); + FileOpenStatus openSessionFile(QString path, QString location); + + QString getOpenFileName(FileFinder::FileType type); + QString getSaveFileName(FileFinder::FileType type); + void registerLastOpenedFilePath(FileFinder::FileType type, QString path); + + void createPlayTarget(); + + void openHelpUrl(QString url); + + void updateVisibleRangeDisplay(Pane *p) const; + + void toXml(QTextStream &stream); +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a sv/main/PreferencesDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/main/PreferencesDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,229 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PreferencesDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "widgets/WindowTypeSelector.h" +#include "base/Preferences.h" + +PreferencesDialog::PreferencesDialog(QWidget *parent, Qt::WFlags flags) : + QDialog(parent, flags) +{ + setWindowTitle(tr("Application Preferences")); + + Preferences *prefs = Preferences::getInstance(); + + QGridLayout *grid = new QGridLayout; + setLayout(grid); + + QGroupBox *groupBox = new QGroupBox; + groupBox->setTitle(tr("Sonic Visualiser Application Preferences")); + grid->addWidget(groupBox, 0, 0); + + QGridLayout *subgrid = new QGridLayout; + groupBox->setLayout(subgrid); + + // Create this first, as slots that get called from the ctor will + // refer to it + m_applyButton = new QPushButton(tr("Apply")); + + int min, max, deflt, i; + + m_windowType = WindowType(prefs->getPropertyRangeAndValue + ("Window Type", &min, &max, &deflt)); + m_windowTypeSelector = new WindowTypeSelector(m_windowType); + + connect(m_windowTypeSelector, SIGNAL(windowTypeChanged(WindowType)), + this, SLOT(windowTypeChanged(WindowType))); + + QComboBox *smoothing = new QComboBox; + + int sm = prefs->getPropertyRangeAndValue("Spectrogram Smoothing", &min, &max, + &deflt); + m_spectrogramSmoothing = sm; + + for (i = min; i <= max; ++i) { + smoothing->addItem(prefs->getPropertyValueLabel("Spectrogram Smoothing", i)); + } + + smoothing->setCurrentIndex(sm); + + connect(smoothing, SIGNAL(currentIndexChanged(int)), + this, SLOT(spectrogramSmoothingChanged(int))); + + QComboBox *propertyLayout = new QComboBox; + int pl = prefs->getPropertyRangeAndValue("Property Box Layout", &min, &max, + &deflt); + m_propertyLayout = pl; + + for (i = min; i <= max; ++i) { + propertyLayout->addItem(prefs->getPropertyValueLabel("Property Box Layout", i)); + } + + propertyLayout->setCurrentIndex(pl); + + connect(propertyLayout, SIGNAL(currentIndexChanged(int)), + this, SLOT(propertyLayoutChanged(int))); + + m_tuningFrequency = prefs->getTuningFrequency(); + + QDoubleSpinBox *frequency = new QDoubleSpinBox; + frequency->setMinimum(100.0); + frequency->setMaximum(5000.0); + frequency->setSuffix(" Hz"); + frequency->setSingleStep(1); + frequency->setValue(m_tuningFrequency); + frequency->setDecimals(2); + + connect(frequency, SIGNAL(valueChanged(double)), + this, SLOT(tuningFrequencyChanged(double))); + + QComboBox *resampleQuality = new QComboBox; + + int rsq = prefs->getPropertyRangeAndValue("Resample Quality", &min, &max, + &deflt); + m_resampleQuality = rsq; + + for (i = min; i <= max; ++i) { + resampleQuality->addItem(prefs->getPropertyValueLabel("Resample Quality", i)); + } + + resampleQuality->setCurrentIndex(rsq); + + connect(resampleQuality, SIGNAL(currentIndexChanged(int)), + this, SLOT(resampleQualityChanged(int))); + + int row = 0; + + subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel + ("Property Box Layout"))), + row, 0); + subgrid->addWidget(propertyLayout, row++, 1, 1, 2); + + subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel + ("Tuning Frequency"))), + row, 0); + subgrid->addWidget(frequency, row++, 1, 1, 2); + + subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel + ("Resample Quality"))), + row, 0); + subgrid->addWidget(resampleQuality, row++, 1, 1, 2); + + subgrid->addWidget(new QLabel(prefs->getPropertyLabel + ("Spectrogram Smoothing")), + row, 0); + subgrid->addWidget(smoothing, row++, 1, 1, 2); + + subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel + ("Window Type"))), + row, 0); + subgrid->addWidget(m_windowTypeSelector, row++, 1, 2, 2); + subgrid->setRowStretch(row, 10); + row++; + + QHBoxLayout *hbox = new QHBoxLayout; + grid->addLayout(hbox, 1, 0); + + QPushButton *ok = new QPushButton(tr("OK")); + QPushButton *cancel = new QPushButton(tr("Cancel")); + hbox->addStretch(10); + hbox->addWidget(ok); + hbox->addWidget(m_applyButton); + hbox->addWidget(cancel); + connect(ok, SIGNAL(clicked()), this, SLOT(okClicked())); + connect(m_applyButton, SIGNAL(clicked()), this, SLOT(applyClicked())); + connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked())); + + m_applyButton->setEnabled(false); +} + +PreferencesDialog::~PreferencesDialog() +{ + std::cerr << "PreferencesDialog::~PreferencesDialog()" << std::endl; +} + +void +PreferencesDialog::windowTypeChanged(WindowType type) +{ + m_windowType = type; + m_applyButton->setEnabled(true); +} + +void +PreferencesDialog::spectrogramSmoothingChanged(int smoothing) +{ + m_spectrogramSmoothing = smoothing; + m_applyButton->setEnabled(true); +} + +void +PreferencesDialog::propertyLayoutChanged(int layout) +{ + m_propertyLayout = layout; + m_applyButton->setEnabled(true); +} + +void +PreferencesDialog::tuningFrequencyChanged(double freq) +{ + m_tuningFrequency = freq; + m_applyButton->setEnabled(true); +} + +void +PreferencesDialog::resampleQualityChanged(int q) +{ + m_resampleQuality = q; + m_applyButton->setEnabled(true); +} + +void +PreferencesDialog::okClicked() +{ + applyClicked(); + accept(); +} + +void +PreferencesDialog::applyClicked() +{ + Preferences *prefs = Preferences::getInstance(); + prefs->setWindowType(WindowType(m_windowType)); + prefs->setSpectrogramSmoothing(Preferences::SpectrogramSmoothing + (m_spectrogramSmoothing)); + prefs->setPropertyBoxLayout(Preferences::PropertyBoxLayout + (m_propertyLayout)); + prefs->setTuningFrequency(m_tuningFrequency); + prefs->setResampleQuality(m_resampleQuality); + m_applyButton->setEnabled(false); +} + +void +PreferencesDialog::cancelClicked() +{ + reject(); +} + diff -r 000000000000 -r fc9323a41f5a sv/main/PreferencesDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/main/PreferencesDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PREFERENCES_DIALOG_H_ +#define _PREFERENCES_DIALOG_H_ + +#include + +#include "base/Window.h" + +class WindowTypeSelector; +class QPushButton; + +class PreferencesDialog : public QDialog +{ + Q_OBJECT + +public: + PreferencesDialog(QWidget *parent = 0, Qt::WFlags flags = 0); + virtual ~PreferencesDialog(); + +protected slots: + void windowTypeChanged(WindowType type); + void spectrogramSmoothingChanged(int state); + void propertyLayoutChanged(int layout); + void tuningFrequencyChanged(double freq); + void resampleQualityChanged(int quality); + + void okClicked(); + void applyClicked(); + void cancelClicked(); + +protected: + WindowTypeSelector *m_windowTypeSelector; + QPushButton *m_applyButton; + + WindowType m_windowType; + int m_spectrogramSmoothing; + int m_propertyLayout; + float m_tuningFrequency; + int m_resampleQuality; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/main/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/main/main.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,330 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "MainWindow.h" + +#include "system/System.h" +#include "system/Init.h" +#include "base/TempDirectory.h" +#include "base/PropertyContainer.h" +#include "base/Preferences.h" +#include "widgets/TipDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/*! \mainpage Sonic Visualiser + +\section interesting Summary of interesting classes + + - Data models: Model and subclasses, e.g. WaveFileModel + + - Graphical layers: Layer and subclasses, displayed on View and its + subclass widgets. + + - Main window class, document class, and file parser: MainWindow, + Document, SVFileReader + + - Turning one model (e.g. audio) into another (e.g. more audio, or a + curve extracted from it): Transform and subclasses + + - Creating the plugins used by transforms: RealTimePluginFactory, + FeatureExtractionPluginFactory. See also the API documentation for + Vamp feature extraction plugins at + http://www.vamp-plugins.org/code-doc/. + + - File reading and writing code: AudioFileReader and subclasses, + WavFileWriter, DataFileReader, SVFileReader + + - FFT calculation and cacheing: FFTModel, FFTDataServer + + - Widgets that show groups of editable properties: PropertyBox for + layer properties (contained in a PropertyStack), PluginParameterBox + for plugins (contained in a PluginParameterDialog) + + - Audio playback: AudioCallbackPlaySource and subclasses, + AudioCallbackPlayTarget and subclasses, AudioGenerator + +\section model Data sources: the Model hierarchy + + A Model is something containing, or knowing how to obtain, data. + + For example, WaveFileModel is a model that knows how to get data + from an audio file; SparseTimeValueModel is a model containing + editable "curve" data. + + Models typically subclass one of a number of abstract subclasses of + Model. For example, WaveFileModel subclasses DenseTimeValueModel, + which describes an interface for models that have a value at each + time point for a given sampling resolution. (Note that + WaveFileModel does not actually read the files itself: it uses + AudioFileReader classes for that. It just makes data from the + files available in a Model.) SparseTimeValueModel uses the + SparseModel template class, which provides most of the + implementation for models that contain a series of points of some + sort -- also used by NoteModel, TextModel, and + SparseOneDimensionalModel. + + Everything that goes on the screen originates from a model, via a + layer (see below). The models are contained in a Document object. + There is no containment hierarchy or ordering of models in the + document. One model is the main model, which defines the sample + rate for playback. + + A model may also be marked as a "derived" model, which means it was + generated from another model using some transform (feature + extraction or effect plugin, etc) -- the idea being that they can + be re-generated using the same transform if a new source model is + loaded. + +\section layer Things that can display data: the Layer hierarchy + + A Layer is something that knows how to draw parts of a model onto a + timeline. + + For example, WaveformLayer is a layer which draws waveforms, based + on WaveFileModel; TimeValueLayer draws curves, based on + SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on + WaveFileModel (via FFTModel). + + The most basic functions of a layer are: to draw itself onto a + Pane, against a timeline on the x axis; and to permit user + interaction. If you were thinking of adding the capability to + display a new sort of something, then you would want to add a new + layer type. (You may also need a new model type, depending on + whether any existing model can capture the data you need.) + Depending on the sort of data in question, there are various + existing layers that might be appropriate to start from -- for + example, a layer that displays images that the user has imported + and associated with particular times might have something in common + with the existing TextLayer which displays pieces of text that are + associated with particular times. + + Although layers are visual objects, they are contained in the + Document in Sonic Visualiser rather than being managed together + with display widgets. The Sonic Visualiser file format has + separate data and layout sections, and the layers are defined in + the data section and then referred to in the layout section which + determines which layers may go on which panes (see Pane below). + + Once a layer class is defined, some basic data about it needs to be + set up in the LayerFactory class, and then it will appear in the + menus and so on on the main window. + +\section view Widgets that are used to show layers: The View hierarchy + + A View is a widget that displays a stack of layers. The most + important subclass is Pane, the widget that is used to show most of + the data in the main window of Sonic Visualiser. + + All a pane really does is contain a set of layers and get them to + render themselves (one on top of the other, with the topmost layer + being the one that is currently interacted with), cache the + results, negotiate user interaction with them, and so on. This is + generally fiddly, if not especially interesting. Panes are + strictly layout objects and are not stored in the Document class; + instead the MainWindow contains a PaneStack widget (the widget that + takes up most of Sonic Visualiser's main window) which contains a + set of panes stacked vertically. + + Another View subclass is Overview, which is the widget that + contains that green waveform showing the entire file at the bottom + of the window. + +*/ + +static QMutex cleanupMutex; + +static void +signalHandler(int /* signal */) +{ + // Avoid this happening more than once across threads + + cleanupMutex.lock(); + std::cerr << "signalHandler: cleaning up and exiting" << std::endl; + TempDirectory::getInstance()->cleanup(); + exit(0); // without releasing mutex +} + +class SVApplication : public QApplication +{ +public: + SVApplication(int argc, char **argv) : + QApplication(argc, argv), + m_mainWindow(0) { } + virtual ~SVApplication() { } + + void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } + void releaseMainWindow() { m_mainWindow = 0; } + + virtual void commitData(QSessionManager &manager) { + if (!m_mainWindow) return; + bool mayAskUser = manager.allowsInteraction(); + bool success = m_mainWindow->commitData(mayAskUser); + manager.release(); + if (!success) manager.cancel(); + } + +protected: + MainWindow *m_mainWindow; +}; + +int +main(int argc, char **argv) +{ + SVApplication application(argc, argv); + + QStringList args = application.arguments(); + + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + +#ifndef Q_WS_WIN32 + signal(SIGHUP, signalHandler); + signal(SIGQUIT, signalHandler); +#endif + + svSystemSpecificInitialisation(); + + bool audioOutput = true; + bool oscSupport = true; + + if (args.contains("--help") || args.contains("-h") || args.contains("-?")) { + std::cerr << QApplication::tr( + "\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation.\n\nUsage:\n\n %1 [--no-audio] [--no-osc] [ ...]\n\n --no-audio: Do not attempt to open an audio output device\n --no-osc: Do not provide an Open Sound Control port for remote control\n : One or more Sonic Visualiser (.sv) and audio files may be provided.\n").arg(argv[0]).toStdString() << std::endl; + exit(2); + } + + if (args.contains("--no-audio")) audioOutput = false; + if (args.contains("--no-osc")) oscSupport = false; + + QApplication::setOrganizationName("sonic-visualiser"); + QApplication::setOrganizationDomain("sonicvisualiser.org"); + QApplication::setApplicationName("sonic-visualiser"); + QApplication::setWindowIcon(QIcon(":icons/svicon16.png")); + + QString language = QLocale::system().name(); + + QTranslator qtTranslator; + QString qtTrName = QString("qt_%1").arg(language); + std::cerr << "Loading " << qtTrName.toStdString() << "..." << std::endl; + qtTranslator.load(qtTrName); + application.installTranslator(&qtTranslator); + + QTranslator svTranslator; + QString svTrName = QString("sonic-visualiser_%1").arg(language); + std::cerr << "Loading " << svTrName.toStdString() << "..." << std::endl; + svTranslator.load(svTrName, ":i18n"); + application.installTranslator(&svTranslator); + + // Permit size_t and PropertyName to be used as args in queued signal calls + qRegisterMetaType("size_t"); + qRegisterMetaType("PropertyContainer::PropertyName"); + + MainWindow gui(audioOutput, oscSupport); + application.setMainWindow(&gui); + + QDesktopWidget *desktop = QApplication::desktop(); + QRect available = desktop->availableGeometry(); + + int width = available.width() * 2 / 3; + int height = available.height() / 2; + if (height < 450) height = available.height() * 2 / 3; + if (width > height * 2) width = height * 2; + + QSettings settings; + settings.beginGroup("MainWindow"); + QSize size = settings.value("size", QSize(width, height)).toSize(); + gui.resize(size); + if (settings.contains("position")) { + gui.move(settings.value("position").toPoint()); + } + settings.endGroup(); + + gui.show(); + + // The MainWindow class seems to have trouble dealing with this if + // it tries to adapt to this preference before the constructor is + // complete. As a lazy hack, apply it explicitly from here + gui.preferenceChanged("Property Box Layout"); + + bool haveSession = false; + bool haveMainModel = false; + + for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { + + MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; + + if (i == args.begin()) continue; + if (i->startsWith('-')) continue; + + if (i->startsWith("http:") || i->startsWith("ftp:")) { + status = gui.openURL(QUrl(*i)); + continue; + } + + QString path = *i; + + if (path.endsWith("sv")) { + if (!haveSession) { + status = gui.openSessionFile(path); + if (status == MainWindow::FileOpenSucceeded) { + haveSession = true; + haveMainModel = true; + } + } else { + std::cerr << "WARNING: Ignoring additional session file argument \"" << path.toStdString() << "\"" << std::endl; + status = MainWindow::FileOpenSucceeded; + } + } + if (status != MainWindow::FileOpenSucceeded) { + if (!haveMainModel) { + status = gui.openSomeFile(path, MainWindow::ReplaceMainModel); + if (status == MainWindow::FileOpenSucceeded) haveMainModel = true; + } else { + status = gui.openSomeFile(path, MainWindow::CreateAdditionalModel); + } + } + if (status == MainWindow::FileOpenFailed) { + QMessageBox::critical + (&gui, QMessageBox::tr("Failed to open file"), + QMessageBox::tr("File \"%1\" could not be opened").arg(path)); + } + } +/* + TipDialog tipDialog; + if (tipDialog.isOK()) { + tipDialog.exec(); + } +*/ + int rv = application.exec(); +// std::cerr << "application.exec() returned " << rv << std::endl; + + cleanupMutex.lock(); + TempDirectory::getInstance()->cleanup(); + application.releaseMainWindow(); + + return rv; +} diff -r 000000000000 -r fc9323a41f5a sv/osc/OSCMessage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/OSCMessage.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,52 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include "OSCMessage.h" + + +OSCMessage::~OSCMessage() +{ + clearArgs(); +} + +void +OSCMessage::clearArgs() +{ + m_args.clear(); +} + +void +OSCMessage::addArg(QVariant arg) +{ + m_args.push_back(arg); +} + +size_t +OSCMessage::getArgCount() const +{ + return m_args.size(); +} + +const QVariant & +OSCMessage::getArg(size_t i) const +{ + return m_args[i]; +} + diff -r 000000000000 -r fc9323a41f5a sv/osc/OSCMessage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/OSCMessage.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#ifndef _OSC_MESSAGE_H_ +#define _OSC_MESSAGE_H_ + +#include +#include + +#include +#include + +class OSCMessage +{ +public: + OSCMessage() { } + ~OSCMessage(); + + void setTarget(const int &target) { m_target = target; } + int getTarget() const { return m_target; } + + void setTargetData(const int &targetData) { m_targetData = targetData; } + int getTargetData() const { return m_targetData; } + + void setMethod(QString method) { m_method = method; } + QString getMethod() const { return m_method; } + + void clearArgs(); + void addArg(QVariant arg); + + size_t getArgCount() const; + const QVariant &getArg(size_t i) const; + +private: + int m_target; + int m_targetData; + QString m_method; + std::vector m_args; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/osc/OSCQueue.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/OSCQueue.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,219 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and QMUL. +*/ + +#include "OSCQueue.h" + +#include + +#define OSC_MESSAGE_QUEUE_SIZE 1023 + +#ifdef HAVE_LIBLO + +void +OSCQueue::oscError(int num, const char *msg, const char *path) +{ + std::cerr << "ERROR: OSCQueue::oscError: liblo server error " << num + << " in path " << path << ": " << msg << std::endl; +} + +int +OSCQueue::oscMessageHandler(const char *path, const char *types, lo_arg **argv, + int argc, lo_message, void *user_data) +{ + OSCQueue *queue = static_cast(user_data); + + int target; + int targetData; + QString method; + + if (!queue->parseOSCPath(path, target, targetData, method)) { + return 1; + } + + OSCMessage message; + message.setTarget(target); + message.setTargetData(targetData); + message.setMethod(method); + + int i = 0; + + while (types && i < argc && types[i]) { + + char type = types[i]; + lo_arg *arg = argv[i]; + + switch (type) { + case 'i': message.addArg(arg->i); break; + case 'h': message.addArg(arg->h); break; + case 'f': message.addArg(arg->f); break; + case 'd': message.addArg(arg->d); break; + case 'c': message.addArg(arg->c); break; + case 't': message.addArg(arg->i); break; + case 's': message.addArg(&arg->s); break; + default: std::cerr << "WARNING: OSCQueue::oscMessageHandler: " + << "Unsupported OSC type '" << type << "'" + << std::endl; + break; + } + + ++i; + } + + queue->postMessage(message); + return 0; +} + +#endif + +OSCQueue::OSCQueue() : +#ifdef HAVE_LIBLO + m_thread(0), +#endif + m_buffer(OSC_MESSAGE_QUEUE_SIZE) +{ +#ifdef HAVE_LIBLO + m_thread = lo_server_thread_new(NULL, oscError); + + lo_server_thread_add_method(m_thread, NULL, NULL, + oscMessageHandler, this); + + lo_server_thread_start(m_thread); + + std::cout << "OSCQueue::OSCQueue: Base OSC URL is " + << lo_server_thread_get_url(m_thread) << std::endl; +#endif +} + +OSCQueue::~OSCQueue() +{ +#ifdef HAVE_LIBLO + if (m_thread) { + lo_server_thread_stop(m_thread); + } +#endif + + while (m_buffer.getReadSpace() > 0) { + delete m_buffer.readOne(); + } +} + +bool +OSCQueue::isOK() const +{ +#ifdef HAVE_LIBLO + return (m_thread != 0); +#else + return false; +#endif +} + +QString +OSCQueue::getOSCURL() const +{ + QString url = ""; +#ifdef HAVE_LIBLO + url = lo_server_thread_get_url(m_thread); +#endif + return url; +} + +size_t +OSCQueue::getMessagesAvailable() const +{ + return m_buffer.getReadSpace(); +} + +OSCMessage +OSCQueue::readMessage() +{ + OSCMessage *message = m_buffer.readOne(); + OSCMessage rmessage = *message; + delete message; + return rmessage; +} + +void +OSCQueue::postMessage(OSCMessage message) +{ + int count = 0, max = 5; + while (m_buffer.getWriteSpace() == 0) { + if (count == max) { + std::cerr << "ERROR: OSCQueue::postMessage: OSC message queue is full and not clearing -- abandoning incoming message" << std::endl; + return; + } + std::cerr << "WARNING: OSCQueue::postMessage: OSC message queue (capacity " << m_buffer.getSize() << " is full!" << std::endl; + std::cerr << "Waiting for something to be processed" << std::endl; +#ifdef _WIN32 + Sleep(1); +#else + sleep(1); +#endif + count++; + } + + OSCMessage *mp = new OSCMessage(message); + m_buffer.write(&mp, 1); + std::cerr << "OSCQueue::postMessage: Posted OSC message: target " + << message.getTarget() << ", target data " << message.getTargetData() + << ", method " << message.getMethod().toStdString() << std::endl; + emit messagesAvailable(); +} + +bool +OSCQueue::parseOSCPath(QString path, int &target, int &targetData, + QString &method) +{ + while (path.startsWith("/")) { + path = path.right(path.length()-1); + } + + int i = 0; + + bool ok = false; + target = path.section('/', i, i).toInt(&ok); + + if (!ok) { + target = 0; + } else { + ++i; + targetData = path.section('/', i, i).toInt(&ok); + if (!ok) { + targetData = 0; + } else { + ++i; + } + } + + method = path.section('/', i, -1); + + if (method.contains('/')) { + std::cerr << "ERROR: OSCQueue::parseOSCPath: malformed path \"" + << path.toStdString() << "\" (should be target/data/method or " + << "target/method or method, where target and data " + << "are numeric)" << std::endl; + return false; + } + + std::cerr << "OSCQueue::parseOSCPath: good path \"" << path.toStdString() + << "\"" << std::endl; + + return true; +} + diff -r 000000000000 -r fc9323a41f5a sv/osc/OSCQueue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/OSCQueue.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam and QMUL. +*/ + +#ifndef _OSC_QUEUE_H_ +#define _OSC_QUEUE_H_ + +#include "OSCMessage.h" + +#include "base/RingBuffer.h" + +#include + +#ifdef HAVE_LIBLO +#include +#endif + +class OSCQueue : public QObject +{ + Q_OBJECT + +public: + OSCQueue(); + virtual ~OSCQueue(); + + bool isOK() const; + + bool isEmpty() const { return getMessagesAvailable() == 0; } + size_t getMessagesAvailable() const; + OSCMessage readMessage(); + + QString getOSCURL() const; + +signals: + void messagesAvailable(); + +protected: +#ifdef HAVE_LIBLO + lo_server_thread m_thread; + + static void oscError(int, const char *, const char *); + static int oscMessageHandler(const char *, const char *, lo_arg **, + int, lo_message, void *); +#endif + + void postMessage(OSCMessage); + bool parseOSCPath(QString path, int &target, int &targetData, QString &method); + + RingBuffer m_buffer; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/osc/demoscript.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/demoscript.sh Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,541 @@ +#!/bin/bash + +audio=/data/music +preferred=$audio/free +list=audiofiles.txt +used=audiofiles-used.txt + +df=vamp:vamp-aubio:aubioonset:detectionfunction +#df=vamp:qm-vamp-plugins:qm-tempotracker:detection_fn +onsets=vamp:vamp-aubio:aubioonset:onsets +#onsets=vamp:qm-vamp-plugins:qm-tempotracker:beats +beats=vamp:vamp-aubio:aubiotempo:beats +#beats=$onsets +#onsets=$beats +chromagram=vamp:qm-vamp-plugins:qm-chromagram:chromagram +notes=vamp:vamp-aubio:aubionotes:notes + +pid=`cat /tmp/demoscript.pid 2>/dev/null` +if [ -n "$pid" ]; then + kill "$pid" +fi +echo $$ > /tmp/demoscript.pid +trap "rm /tmp/demoscript.pid" 0 + +sv-command quit +sleep 1 +killall -9 sonic-visualiser +sleep 1 + +pick_file() +{ + file="" + count=`wc -l "$list" 2>/dev/null | awk '{ print $1 }'` + if [ ! -f "$list" ] || [ "$count" -eq "0" ] ; then + find "$audio" -name \*.ogg -print >> "$list" + find "$audio" -name \*.mp3 -print >> "$list" + find "$audio" -name \*.wav -print >> "$list" + find "$preferred" -name \*.ogg -print >> "$list" + find "$preferred" -name \*.mp3 -print >> "$list" + find "$preferred" -name \*.wav -print >> "$list" + count=`wc -l "$list" 2>/dev/null | awk '{ print $1 }'` + fi + while [ -z "$file" ]; do + index=$((RANDOM % $count)) + file=`tail +"$index" "$list" | head -1` + [ -f "$file" ] || continue + done + fgrep -v "$file" "$list" > "$list"_ && mv "$list"_ "$list" + echo "$file" +} + +load_a_file() +{ + file=`pick_file` + if ! sv-command open "$file"; then + pid="`pidof sonic-visualiser`" + if [ -z "$pid" ]; then + ( setsid sonic-visualiser -geometry 1000x500+10+100 & ) + sleep 2 + sudo renice +19 `pidof sonic-visualiser` + sudo renice +18 `pidof Xorg` + sv-command resize 1000 500 + load_a_file + else + echo "ERROR: Unable to contact sonic-visualiser pid $pid" 1>&2 + exit 1 + fi + fi +} + +show_stuff() +{ + sv-command set overlays 2 +# sv-command set zoomwheels 1 + sv-command set propertyboxes 1 +} + +hide_stuff() +{ + sv-command set overlays 0 +# sv-command set zoomwheels 0 + sv-command set propertyboxes 0 +} + +reset() +{ + for pane in 1 2 3 4 5; do + for layer in 1 2 3 4 5 6 7 8 9 10; do + sv-command delete layer + done + sv-command delete pane + done + sv-command zoom default + sv-command add waveform + show_stuff +} + +scroll_and_zoom() +{ + sv-command set overlays 0 + sv-command set zoomwheels 0 + sv-command set propertyboxes 0 +# sv-command setcurrent 1 1 +# sv-command delete layer +# sv-command setcurrent 1 1 + sv-command set layer Colour Red + sleep 1 + sv-command set pane Global-Zoom off + sv-command set pane Global-Scroll off + sv-command set pane Follow-Playback Scroll + for zoom in 950 900 850 800 750 700 650 600 550 512 450 400 350 300 256 192 160 128 96 64 48 32 24 16; do + sv-command zoom $zoom + sleep 0.1 + done +} + +play() +{ + sv-command play "$@" +} + +fade_in() +{ + sv-command set gain 0 + sleep 0.5 + play "$@" + for gain in 0.001 0.01 0.05 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1; do + sv-command set gain $gain + sleep 0.1 + done +} + +fade_out() +{ + for gain in 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0.05 0.01 0.001; do + sv-command set gain $gain + sleep 0.1 + done + stop + sv-command set gain 1 +} + +slow() +{ +# for speed in -1 -10 -20 -30 -40 -50 -60 -70 -80 -100 -140 -200 -250 -300 -400 -500 -700 -800 -900 -1000; do +# sv-command set speedup "$speed" +# sleep 1 +# done + for speed in -20 -100 -1000; do + sv-command set speedup "$speed" + sleep 10 + done +} + +stop() +{ + sv-command stop "$@" + sv-command set speedup 0 +} + +quit() +{ + sv-command quit +} + +add_melodic_range_spectrogram() +{ + sv-command set propertyboxes 1 + sv-command add spectrogram + sv-command set layer Window-Size 8192 +# sv-command set layer Window-Size 4096 + sv-command set layer Window-Overlap 4 +# sv-command set layer Window-Overlap 3 + sv-command set layer Frequency-Scale Log + sv-command set layer Colour-Scale Meter +} + +zoom_in_spectrogram() +{ + sv-command zoomvertical 43 8000 + for x in 1 2 3 4 5 6; do + max=$((8000 - 1000*$x)) + sv-command zoomvertical 43 "$max" + sleep 0.5 + done + for x in 1 2 3 4 5; do + max=$((2000 - 100 * $x)) + sv-command zoomvertical 43 "$max" + sleep 0.5 + done +} + +zoom_in_spectrogram_further() +{ + for x in 1 2 3 4 5; do + sv-command zoomvertical in + done +} + +playback_bits() +{ + sv-command setcurrent 1 + sv-command set pane Global-Zoom off + sv-command set pane Global-Scroll off + sv-command set pane Follow-Playback Scroll + sv-command jump 10 + sv-command setcurrent 1 1 + sv-command delete layer + sv-command setcurrent 1 1 +# sv-command setcurrent 1 2 + sv-command set layer Colour Blue + sleep 5 + hide_stuff + sv-command set overlays 0 + sv-command set zoomwheels 0 + sv-command set propertyboxes 0 + fade_in + sleep 10 +# sv-command set layer Colour Blue +# sleep 1 +# sv-command set layer Colour Orange +# sleep 1 +# sv-command set layer Colour Red +# sleep 1 +# sv-command set layer Colour Green +# sleep 1 +# sleep 1 + + +# scroll_and_zoom + +# sv-command set overlays 0 +# sv-command set zoomwheels 0 +# sv-command set propertyboxes 0 +# sv-command setcurrent 1 1 +# sv-command delete layer +# sv-command setcurrent 1 1 +# sv-command set layer Colour Red +# sleep 1 +# sv-command set pane Global-Zoom off +# sv-command set pane Global-Scroll off +# sv-command set pane Follow-Playback Scroll + sv-command set zoomwheels 1 + sleep 1 + for zoom in 950 900 850 800 750 700 650 600 550 512 450 400 350 300 256 192 160 128 96 64 48 32 24 16; do + sv-command zoom $zoom + sleep 0.1 + done + + sleep 1 + sv-command set zoomwheels 0 + sv-command zoom 16 + + sleep 10 + #slow + #sv-command set layer Normalize-Visible-Area on +# for zoom in 15 14 13 12 11 10 9 8 7 6 5 4 ; do +# sv-command zoom $zoom +# sleep 0.1 + # done + sleep 1 + sv-command set zoomwheels 0 + slow + sleep 7 + fade_out + sv-command setcurrent 1 + sv-command set pane Follow-Playback Page + sv-command set pane Global-Zoom on + sv-command set pane Global-Scroll on + done_playback_bits=1 +} + +spectrogram_bits() +{ + sv-command set pane Global-Zoom on + sv-command zoom 1024 + add_melodic_range_spectrogram + sv-command zoom 1024 + sleep 5 + sv-command jump 10 + sleep 20 + zoom_in_spectrogram + sleep 20 + + sv-command select 7.5 11 + fade_in selection + sleep 10 + sv-command set speedup -200 + sleep 10 + sv-command setcurrent 1 + sv-command delete pane + sv-command zoom in + sv-command setcurrent 1 2 + sv-command set layer Normalize-Columns off + sv-command set layer Normalize-Visible-Area on + sleep 20 + sv-command set speedup 0 + sleep 10 + sv-command select none +# fade_out + +# if [ -n "$done_playback_bits" ]; then +# sv-command setcurrent 1 +# sv-command zoom out +# sv-command zoom outvamp:qm-vamp-plugins:qm-chromagram:chromagram +# sv-command zoom out +# sv-command zoom out +# sv-command zoom out +# sv-command setcurrent 2 +# fi + +# hide_stuff +# fade_in + sleep 10 +# sv-command set layer Bin-Display Frequencies +# sv-command set layer Normalize-Columns on +# sleep 20 + sv-command set layer Bin-Display "All Bins" + sv-command set layer Normalize-Columns on + sv-command set layer Normalize-Visible-Area off + sv-command set layer Colour-Scale 0 + sv-command set layer Colour "Red on Blue" + sv-command zoomvertical 23 800 + sleep 20 + sv-command transform $onsets + sv-command set layer Colour Orange + sleep 20 + fade_out + sleep 1 +# sv-command jump 10 +# sv-command setcurrent 1 2 +# sv-command set layer Colour "Black on White" +# sv-command transform $notes +# sv-command set layer Colour Orange + sleep 10 +# sv-command setcurrent 1 3 +# sv-command delete layer + sv-command setcurrent 1 3 + sv-command delete layer + sv-command setcurrent 1 2 + sv-command set layer Colour Default + done_spectrogram_bits=1 + +# zoom_in_spectrogram_further +} + +onset_bits() +{ + show_stuff + sv-command set zoomwheels 0 + sv-command setcurrent 1 + sv-command set pane Global-Zoom on + sv-command set pane Global-Scroll on + sleep 0.5 + sv-command set layer Colour Blue + sleep 0.5 + sv-command set layer Colour Orange + sleep 0.5 + sv-command set layer Colour Red + sleep 0.5 + sv-command set layer Colour Green + sleep 1 +# sleep 1 +# if [ -n "$done_spectrogram_bits" ]; then +# sv-command setcurrent 2 +# sv-command delete pane +# fi +# sv-command zoom default +# sv-command zoom in +# sv-command zoom in +# sv-command zoom in + sv-command zoom 192 + sv-command zoom in + sv-command add timeruler + sv-command jump 0 + sv-command transform $df + sv-command set layer Colour Black + sleep 5 + sv-command set layer Plot-Type Curve + sleep 5 + sv-command jump 30 + sv-command setcurrent 1 + sv-command set pane Follow-Playback Page + sv-command transform $df + sv-command set layer Colour Red + sleep 5 + sv-command jump 30 + sleep 5 + if [ "$RANDOM" -lt 16384 ]; then + sv-command set layer Vertical-Scale "Log Scale" + fi + sv-command set layer Plot-Type Segmentation + sleep 5 +# hide_stuff + sleep 10 + sv-command set overlays 0 + sv-command set propertyboxes 0 +# sv-command setcurrent 1 1 +# sv-command set layer Colour Black +# sv-command setcurrent 1 2 + sleep 2 + fade_in + sleep 2 + sv-command transform $onsets + sv-command set layer Colour Black + sv-command setcurrent 2 + sv-command transform $onsets + sv-command set layer Colour Blue + sleep 20 +# sv-command setcurrent 2 +# sv-command transform vamp:qm-vamp-plugins:qm-tempotracker:beats +# sv-command transform $beats + sleep 20 +# fade_out +# show_stuff +} + +selection_bits() +{ +# reset + sv-command set overlays 1 + sv-command set zoomwheels 0 + sv-command resize 1000 500 + sv-command zoom default + sv-command setcurrent 2 + sv-command delete pane +# if [ -n "$done_playback_bits" ]; then + sv-command setcurrent 1 2 +# else +# sv-command setcurrent 1 3 +# fi + sv-command delete layer +# if [ -n "$done_playback_bits" ]; then + sv-command setcurrent 1 2 +# else +# sv-command setcurrent 1 3 +# fi + sv-command delete layer + sv-command setcurrent 1 2 + sv-command set layer Colour Orange +# sv-command transform vamp:qm-vamp-plugins:qm-tempotracker:beats + sv-command transform $beats +# sv-command setcurrent 1 2 + sv-command set layer Colour Black + sleep 20 + sv-command loop on + base=$((RANDOM % 100)) + sv-command select $base $base.3 +# fade_in selection + play selection + sleep 8 + base=$((base + 4)) + sv-command addselect $base $base.1 + #sleep 12 + base=$((base + 2)) + sv-command addselect $base $base.1 + #sleep 6 + base=$((base + 2)) + sv-command addselect $base $base.3 + #sleep 6 + base=$((base + 3)) + sv-command addselect $base $base.3 + #sleep 6 + base=$((base + 2)) + sv-command addselect $base $base.3 + sleep 4 + sv-command delete layer + sleep 16 + sv-command set speedup -50 + sleep 14 + sv-command set speedup 50 + sleep 8 + sv-command set speedup 100 + sleep 5 + sv-command set speedup 200 + fade_out +# sleep 10 + sv-command select none + sv-command set overlays 2 + sv-command set propertyboxes 1 +# sv-command setcurrent 1 3 +# sv-command delete layer + sv-command setcurrent 1 2 + sv-command set layer Colour Black +} + +chromagram_bits() +{ +# add_melodic_range_spectrogram +# sleep 10 + sv-command add timeruler + sleep 5 + sv-command jump 10 + sv-command zoom out + sleep 5 + sv-command transform $chromagram + sleep 40 + sv-command zoom out + fade_in + sleep 20 + fade_out +} + +while /bin/true; do + +sleep 2 +load_a_file +sv-command loop on + +sv-command resize 1000 500 +show_stuff +sleep 5 +sleep 20 +playback_bits + +#sleep 10 +sv-command resize 1000 700 +sv-command zoom default +show_stuff +onset_bits + +selection_bits + +#sv-command resize 1000 700 + +#sleep 10 +sv-command resize 1000 700 +#show_stuff +spectrogram_bits + +#sleep 10 +#sv-command jump 0 +#show_stuff +#chromagram_bits + +sleep 20 + +#reset +killall -9 sonic-visualiser + +done diff -r 000000000000 -r fc9323a41f5a sv/osc/sv-command --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/sv-command Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,55 @@ +#!/bin/sh +# +# A very simple command shell for Sonic Visualiser. +# +# This provides a wrapper for the sv-osc-send program, which is a +# generic OSC sending program (not specific to SV, despite its name). +# This script attempts to guess the OSC port number for an SV +# process running on the local host, and then composes a method name +# and arguments into a complete OSC call. +# +# You can either run this with the method and its arguments on the +# command line, e.g. "sv-command set layer Frequency-Scale Log", or +# you can provide a series of method + argument commands on stdin. +# +# Unless you use the -q option, this script will echo the OSC URL +# and arguments that it is sending for each command. +# +# Note that the method and arguments may not contain spaces. +# +# Chris Cannam, Nov 2006 + +quiet= +if [ "$1" = "-q" ]; then + quiet=true; shift; +fi + +# The yucky bit + +port=`lsof -c sonic- | \ + grep UDP | \ + sed -e 's/^.*[^0-9]\([0-9][0-9]*\) *$/\1/' | \ + grep -v ' ' | \ + head -1 ` + +host=127.0.0.1 +scheme=osc.udp + +if [ -z "$port" ]; then + echo "Sonic Visualiser OSC port not found" + exit 1 +fi + +if [ -n "$1" ]; then + command=$1; shift + [ -z "$quiet" ] && echo "$scheme://$host:$port/$command" "$@" + sv-osc-send "$scheme://$host:$port/$command" "$@" +else + while read command a1 a2 a3 a4 a5; do + [ -z "$command" ] && continue + [ -z "$quiet" ] && echo "$scheme://$host:$port/$command" $a1 $a2 $a3 $a4 $a5 + sv-osc-send "$scheme://$host:$port/$command" $a1 $a2 $a3 $a4 $a5 + done +fi + +exit 0 diff -r 000000000000 -r fc9323a41f5a sv/osc/sv-osc-send.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/osc/sv-osc-send.c Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include +#include +#include +#include +#include + +void +usage(char *program_name) +{ + char *base_name = strrchr(program_name, '/'); + + if (base_name && *(base_name + 1) != 0) { + base_name += 1; + } else { + base_name = program_name; + } + + fprintf(stderr, "\nusage: %s []\n\n", program_name); + fprintf(stderr, "example OSC URLs:\n\n" + " osc.udp://localhost:19383/path/test 1.0 4.2\n" + " osc.udp://my.host.org:10886/3/13/load file\n\n"); + fprintf(stderr, "numeric arguments will be treated as OSC 'f' floating point types.\n\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + lo_address a; + char *url, *host, *port, *path; + lo_message message; + unsigned int i; + + if (argc < 2) { + usage(argv[0]); + /* does not return */ + } + url = argv[1]; + + host = lo_url_get_hostname(url); + port = lo_url_get_port(url); + path = lo_url_get_path(url); + a = lo_address_new(host, port); + + message = lo_message_new(); + + for (i = 0; i + 2 < argc; ++i) { + + int index = i + 2; + char *param; + + param = argv[index]; + if (!isdigit(param[0])) { + lo_message_add_string(message, argv[index]); + } else { + lo_message_add_float(message, atof(argv[index])); + } + } + + lo_send_message(a, path, message); + + if (lo_address_errno(a)) { + printf("liblo error: %s\n", lo_address_errstr(a)); + } + + free(host); + free(port); + free(path); + + return 0; +} + diff -r 000000000000 -r fc9323a41f5a sv/samples/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/samples/README Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,2 @@ +Samples from standard Hydrogen drum machine kits, except for piano.wav +(ancestry unknown) and click.wav (random noise). diff -r 000000000000 -r fc9323a41f5a sv/samples/bass.wav Binary file sv/samples/bass.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/beep.wav Binary file sv/samples/beep.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/bounce.wav Binary file sv/samples/bounce.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/clap.wav Binary file sv/samples/clap.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/click.wav Binary file sv/samples/click.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/cowbell.wav Binary file sv/samples/cowbell.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/hihat.wav Binary file sv/samples/hihat.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/kick.wav Binary file sv/samples/kick.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/organ.wav Binary file sv/samples/organ.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/piano.wav Binary file sv/samples/piano.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/snare.wav Binary file sv/samples/snare.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/stick.wav Binary file sv/samples/stick.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/strike.wav Binary file sv/samples/strike.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/samples/tap.wav Binary file sv/samples/tap.wav has changed diff -r 000000000000 -r fc9323a41f5a sv/sound-access.qrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/sound-access.qrc Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,78 @@ + + + icons/waveform.png + icons/spectrum.png + icons/spectrogram.png + icons/timeruler.png + icons/pane.png + icons/instants.png + icons/notes.png + icons/values.png + icons/colour3d.png + icons/playpause.png + icons/ffwd.png + icons/ffwd-end.png + icons/rewind.png + icons/rewind-start.png + icons/playselection.png + icons/playloop.png + icons/fader_background.png + icons/fader_knob.png + icons/fader_knob_red.png + icons/fader_leds.png + icons/faders.png + icons/select.png + icons/text.png + icons/draw.png + icons/draw-curve.png + icons/move.png + icons/navigate.png + icons/zoom.png + icons/zoom-in.png + icons/zoom-out.png + icons/zoom-fit.png + icons/undo.png + icons/redo.png + icons/new.png + icons/exit.png + icons/speaker.png + icons/annotation.png + icons/fileopen.png + icons/fileopensession.png + icons/fileopenaudio.png + icons/fileopen-22.png + icons/fileclose.png + icons/filenew.png + icons/filenew-22.png + icons/filesave.png + icons/filesave-22.png + icons/filesaveas.png + icons/filesaveas-22.png + icons/editdelete.png + icons/editcut.png + icons/editcopy.png + icons/editpaste.png + icons/mono.png + icons/stereo.png + icons/sharpen.png + icons/help.png + icons/svicon16.png + icons/svicon32.png + samples/bass.wav + samples/beep.wav + samples/bounce.wav + samples/clap.wav + samples/click.wav + samples/cowbell.wav + samples/hihat.wav + samples/kick.wav + samples/organ.wav + samples/piano.wav + samples/snare.wav + samples/stick.wav + samples/strike.wav + samples/tap.wav + i18n/sound-access_ru.qm + i18n/tips_en.xml + + diff -r 000000000000 -r fc9323a41f5a sv/sound_access.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/sound_access.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1031 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a sv/sv.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/sv.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,75 @@ + +TEMPLATE = app + +SV_UNIT_PACKAGES = vamp vamp-hostsdk fftw3f samplerate jack portaudio mad oggz fishsound lrdf raptor sndfile liblo +load(../sv.prf) + +CONFIG += sv qt thread warn_on stl rtti exceptions +QT += xml network + +TARGET = sound-access + +DEPENDPATH += . .. audioio document i18n main osc transform +INCLUDEPATH += . .. audioio document transform osc main +LIBPATH = ../view ../layer ../data ../widgets ../plugin ../base ../system $$LIBPATH + +contains(DEFINES, BUILD_STATIC):LIBS -= -ljack + +LIBS = -lsvview -lsvlayer -lsvdata -lsvwidgets -lsvplugin -lsvbase -lsvsystem $$LIBS + +PRE_TARGETDEPS += ../view/libsvview.a \ + ../layer/libsvlayer.a \ + ../data/libsvdata.a \ + ../widgets/libsvwidgets.a \ + ../plugin/libsvplugin.a \ + ../base/libsvbase.a \ + ../system/libsvsystem.a + +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += audioio/AudioCallbackPlaySource.h \ + audioio/AudioCallbackPlayTarget.h \ + audioio/AudioCoreAudioTarget.h \ + audioio/AudioGenerator.h \ + audioio/AudioJACKTarget.h \ + audioio/AudioPortAudioTarget.h \ + audioio/AudioTargetFactory.h \ + audioio/PhaseVocoderTimeStretcher.h \ + audioio/PlaySpeedRangeMapper.h \ + document/Document.h \ + document/SVFileReader.h \ + main/MainWindow.h \ + main/PreferencesDialog.h \ + osc/OSCMessage.h \ + osc/OSCQueue.h \ + transform/FeatureExtractionPluginTransform.h \ + transform/PluginTransform.h \ + transform/RealTimePluginTransform.h \ + transform/Transform.h \ + transform/TransformFactory.h +SOURCES += audioio/AudioCallbackPlaySource.cpp \ + audioio/AudioCallbackPlayTarget.cpp \ + audioio/AudioCoreAudioTarget.cpp \ + audioio/AudioGenerator.cpp \ + audioio/AudioJACKTarget.cpp \ + audioio/AudioPortAudioTarget.cpp \ + audioio/AudioTargetFactory.cpp \ + audioio/PhaseVocoderTimeStretcher.cpp \ + audioio/PlaySpeedRangeMapper.cpp \ + document/Document.cpp \ + document/SVFileReader.cpp \ + main/main.cpp \ + main/MainWindow.cpp \ + main/PreferencesDialog.cpp \ + osc/OSCMessage.cpp \ + osc/OSCQueue.cpp \ + transform/FeatureExtractionPluginTransform.cpp \ + transform/PluginTransform.cpp \ + transform/RealTimePluginTransform.cpp \ + transform/Transform.cpp \ + transform/TransformFactory.cpp +RESOURCES += sound-access.qrc + + diff -r 000000000000 -r fc9323a41f5a sv/transform/FeatureExtractionPluginTransform.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/FeatureExtractionPluginTransform.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,525 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FeatureExtractionPluginTransform.h" + +#include "plugin/FeatureExtractionPluginFactory.h" +#include "plugin/PluginXml.h" +#include "vamp-sdk/Plugin.h" + +#include "data/model/Model.h" +#include "base/Window.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/EditableDenseThreeDimensionalModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/NoteModel.h" +#include "data/model/FFTModel.h" +#include "data/model/WaveFileModel.h" + +#include + +#include + +FeatureExtractionPluginTransform::FeatureExtractionPluginTransform(Model *inputModel, + QString pluginId, + const ExecutionContext &context, + QString configurationXml, + QString outputName) : + PluginTransform(inputModel, context), + m_plugin(0), + m_descriptor(0), + m_outputFeatureNo(0) +{ +// std::cerr << "FeatureExtractionPluginTransform::FeatureExtractionPluginTransform: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl; + + FeatureExtractionPluginFactory *factory = + FeatureExtractionPluginFactory::instanceFor(pluginId); + + if (!factory) { + std::cerr << "FeatureExtractionPluginTransform: No factory available for plugin id \"" + << pluginId.toStdString() << "\"" << std::endl; + return; + } + + m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate()); + + if (!m_plugin) { + std::cerr << "FeatureExtractionPluginTransform: Failed to instantiate plugin \"" + << pluginId.toStdString() << "\"" << std::endl; + return; + } + + if (configurationXml != "") { + PluginXml(m_plugin).setParametersFromXml(configurationXml); + } + + DenseTimeValueModel *input = getInput(); + if (!input) return; + + size_t channelCount = input->getChannelCount(); + if (m_plugin->getMaxChannelCount() < channelCount) { + channelCount = 1; + } + if (m_plugin->getMinChannelCount() > channelCount) { + std::cerr << "FeatureExtractionPluginTransform:: " + << "Can't provide enough channels to plugin (plugin min " + << m_plugin->getMinChannelCount() << ", max " + << m_plugin->getMaxChannelCount() << ", input model has " + << input->getChannelCount() << ")" << std::endl; + return; + } + + std::cerr << "Initialising feature extraction plugin with channels = " + << channelCount << ", step = " << m_context.stepSize + << ", block = " << m_context.blockSize << std::endl; + + if (!m_plugin->initialise(channelCount, + m_context.stepSize, + m_context.blockSize)) { + std::cerr << "FeatureExtractionPluginTransform: Plugin " + << m_plugin->getIdentifier() << " failed to initialise!" << std::endl; + return; + } + + Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors(); + + if (outputs.empty()) { + std::cerr << "FeatureExtractionPluginTransform: Plugin \"" + << pluginId.toStdString() << "\" has no outputs" << std::endl; + return; + } + + for (size_t i = 0; i < outputs.size(); ++i) { + if (outputName == "" || outputs[i].identifier == outputName.toStdString()) { + m_outputFeatureNo = i; + m_descriptor = new Vamp::Plugin::OutputDescriptor + (outputs[i]); + break; + } + } + + if (!m_descriptor) { + std::cerr << "FeatureExtractionPluginTransform: Plugin \"" + << pluginId.toStdString() << "\" has no output named \"" + << outputName.toStdString() << "\"" << std::endl; + return; + } + +// std::cerr << "FeatureExtractionPluginTransform: output sample type " +// << m_descriptor->sampleType << std::endl; + + int binCount = 1; + float minValue = 0.0, maxValue = 0.0; + bool haveExtents = false; + + if (m_descriptor->hasFixedBinCount) { + binCount = m_descriptor->binCount; + } + +// std::cerr << "FeatureExtractionPluginTransform: output bin count " +// << binCount << std::endl; + + if (binCount > 0 && m_descriptor->hasKnownExtents) { + minValue = m_descriptor->minValue; + maxValue = m_descriptor->maxValue; + haveExtents = true; + } + + size_t modelRate = m_input->getSampleRate(); + size_t modelResolution = 1; + + switch (m_descriptor->sampleType) { + + case Vamp::Plugin::OutputDescriptor::VariableSampleRate: + if (m_descriptor->sampleRate != 0.0) { + modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001); + } + break; + + case Vamp::Plugin::OutputDescriptor::OneSamplePerStep: + modelResolution = m_context.stepSize; + break; + + case Vamp::Plugin::OutputDescriptor::FixedSampleRate: + modelRate = size_t(m_descriptor->sampleRate + 0.001); + break; + } + + if (binCount == 0) { + + m_output = new SparseOneDimensionalModel(modelRate, modelResolution, + false); + + } else if (binCount == 1) { + + SparseTimeValueModel *model; + if (haveExtents) { + model = new SparseTimeValueModel + (modelRate, modelResolution, minValue, maxValue, false); + } else { + model = new SparseTimeValueModel + (modelRate, modelResolution, false); + } + model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str()); + + m_output = model; + + } else if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::VariableSampleRate) { + + // We don't have a sparse 3D model, so interpret this as a + // note model. There's nothing to define which values to use + // as which parameters of the note -- for the moment let's + // treat the first as pitch, second as duration in frames, + // third (if present) as velocity. (Our note model doesn't + // yet store velocity.) + //!!! todo: ask the user! + + NoteModel *model; + if (haveExtents) { + model = new NoteModel + (modelRate, modelResolution, minValue, maxValue, false); + } else { + model = new NoteModel + (modelRate, modelResolution, false); + } + model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str()); + + m_output = model; + + } else { + + EditableDenseThreeDimensionalModel *model = + new EditableDenseThreeDimensionalModel + (modelRate, modelResolution, binCount, false); + + if (!m_descriptor->binNames.empty()) { + std::vector names; + for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) { + names.push_back(m_descriptor->binNames[i].c_str()); + } + model->setBinNames(names); + } + + m_output = model; + } +} + +FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform() +{ + delete m_plugin; + delete m_descriptor; +} + +DenseTimeValueModel * +FeatureExtractionPluginTransform::getInput() +{ + DenseTimeValueModel *dtvm = + dynamic_cast(getInputModel()); + if (!dtvm) { + std::cerr << "FeatureExtractionPluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl; + } + return dtvm; +} + +void +FeatureExtractionPluginTransform::run() +{ + DenseTimeValueModel *input = getInput(); + if (!input) return; + + while (!input->isReady()) { + if (dynamic_cast(input)) break; // no need to wait + std::cerr << "FeatureExtractionPluginTransform::run: Waiting for input model to be ready..." << std::endl; + sleep(1); + } + + if (!m_output) return; + + size_t sampleRate = m_input->getSampleRate(); + + size_t channelCount = input->getChannelCount(); + if (m_plugin->getMaxChannelCount() < channelCount) { + channelCount = 1; + } + + float **buffers = new float*[channelCount]; + for (size_t ch = 0; ch < channelCount; ++ch) { + buffers[ch] = new float[m_context.blockSize + 2]; + } + + bool frequencyDomain = (m_plugin->getInputDomain() == + Vamp::Plugin::FrequencyDomain); + std::vector fftModels; + + if (frequencyDomain) { + for (size_t ch = 0; ch < channelCount; ++ch) { + FFTModel *model = new FFTModel + (getInput(), + channelCount == 1 ? m_context.channel : ch, + m_context.windowType, + m_context.blockSize, + m_context.stepSize, + m_context.blockSize, + false); + if (!model->isOK()) { + QMessageBox::critical + (0, tr("FFT cache failed"), + tr("Failed to create the FFT model for this transform.\n" + "There may be insufficient memory or disc space to continue.")); + delete model; + setCompletion(100); + return; + } + model->resume(); + fftModels.push_back(model); + } + } + + long startFrame = m_input->getStartFrame(); + long endFrame = m_input->getEndFrame(); + long blockFrame = startFrame; + + long prevCompletion = 0; + + while (!m_abandoned) { + + if (frequencyDomain) { + if (blockFrame - int(m_context.blockSize)/2 > endFrame) break; + } else { + if (blockFrame >= endFrame) break; + } + +// std::cerr << "FeatureExtractionPluginTransform::run: blockFrame " +// << blockFrame << std::endl; + + long completion = + (((blockFrame - startFrame) / m_context.stepSize) * 99) / + ( (endFrame - startFrame) / m_context.stepSize); + + // channelCount is either m_input->channelCount or 1 + + for (size_t ch = 0; ch < channelCount; ++ch) { + if (frequencyDomain) { + int column = (blockFrame - startFrame) / m_context.stepSize; + for (size_t i = 0; i <= m_context.blockSize/2; ++i) { + fftModels[ch]->getValuesAt + (column, i, buffers[ch][i*2], buffers[ch][i*2+1]); + } + } else { + getFrames(ch, channelCount, + blockFrame, m_context.blockSize, buffers[ch]); + } + } + + Vamp::Plugin::FeatureSet features = m_plugin->process + (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate)); + + for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) { + Vamp::Plugin::Feature feature = + features[m_outputFeatureNo][fi]; + addFeature(blockFrame, feature); + } + + if (blockFrame == startFrame || completion > prevCompletion) { + setCompletion(completion); + prevCompletion = completion; + } + + blockFrame += m_context.stepSize; + } + + if (m_abandoned) return; + + Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures(); + + for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) { + Vamp::Plugin::Feature feature = + features[m_outputFeatureNo][fi]; + addFeature(blockFrame, feature); + } + + if (frequencyDomain) { + for (size_t ch = 0; ch < channelCount; ++ch) { + delete fftModels[ch]; + } + } + + setCompletion(100); +} + +void +FeatureExtractionPluginTransform::getFrames(int channel, int channelCount, + long startFrame, long size, + float *buffer) +{ + long offset = 0; + + if (startFrame < 0) { + for (int i = 0; i < size && startFrame + i < 0; ++i) { + buffer[i] = 0.0f; + } + offset = -startFrame; + size -= offset; + if (size <= 0) return; + startFrame = 0; + } + + long got = getInput()->getValues + ((channelCount == 1 ? m_context.channel : channel), + startFrame, startFrame + size, buffer + offset); + + while (got < size) { + buffer[offset + got] = 0.0; + ++got; + } + + if (m_context.channel == -1 && channelCount == 1 && + getInput()->getChannelCount() > 1) { + // use mean instead of sum, as plugin input + int cc = getInput()->getChannelCount(); + for (long i = 0; i < size; ++i) { + buffer[i] /= cc; + } + } +} + +void +FeatureExtractionPluginTransform::addFeature(size_t blockFrame, + const Vamp::Plugin::Feature &feature) +{ + size_t inputRate = m_input->getSampleRate(); + +// std::cerr << "FeatureExtractionPluginTransform::addFeature(" +// << blockFrame << ")" << std::endl; + + int binCount = 1; + if (m_descriptor->hasFixedBinCount) { + binCount = m_descriptor->binCount; + } + + size_t frame = blockFrame; + + if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::VariableSampleRate) { + + if (!feature.hasTimestamp) { + std::cerr + << "WARNING: FeatureExtractionPluginTransform::addFeature: " + << "Feature has variable sample rate but no timestamp!" + << std::endl; + return; + } else { + frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate); + } + + } else if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::FixedSampleRate) { + + if (feature.hasTimestamp) { + //!!! warning: sampleRate may be non-integral + frame = Vamp::RealTime::realTime2Frame(feature.timestamp, + lrintf(m_descriptor->sampleRate)); + } else { + frame = m_output->getEndFrame(); + } + } + + if (binCount == 0) { + + SparseOneDimensionalModel *model = getOutput(); + if (!model) return; + model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str())); + + } else if (binCount == 1) { + + float value = 0.0; + if (feature.values.size() > 0) value = feature.values[0]; + + SparseTimeValueModel *model = getOutput(); + if (!model) return; + model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str())); + + } else if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::VariableSampleRate) { + + float pitch = 0.0; + if (feature.values.size() > 0) pitch = feature.values[0]; + + float duration = 1; + if (feature.values.size() > 1) duration = feature.values[1]; + + float velocity = 100; + if (feature.values.size() > 2) velocity = feature.values[2]; + + NoteModel *model = getOutput(); + if (!model) return; + + model->addPoint(NoteModel::Point(frame, pitch, + lrintf(duration), + feature.label.c_str())); + + } else { + + DenseThreeDimensionalModel::Column values = feature.values; + + EditableDenseThreeDimensionalModel *model = + getOutput(); + if (!model) return; + + model->setColumn(frame / model->getResolution(), values); + } +} + +void +FeatureExtractionPluginTransform::setCompletion(int completion) +{ + int binCount = 1; + if (m_descriptor->hasFixedBinCount) { + binCount = m_descriptor->binCount; + } + + std::cerr << "FeatureExtractionPluginTransform::setCompletion(" + << completion << ")" << std::endl; + + if (binCount == 0) { + + SparseOneDimensionalModel *model = getOutput(); + if (!model) return; + model->setCompletion(completion); + + } else if (binCount == 1) { + + SparseTimeValueModel *model = getOutput(); + if (!model) return; + model->setCompletion(completion); + + } else if (m_descriptor->sampleType == + Vamp::Plugin::OutputDescriptor::VariableSampleRate) { + + NoteModel *model = getOutput(); + if (!model) return; + model->setCompletion(completion); + + } else { + + EditableDenseThreeDimensionalModel *model = + getOutput(); + if (!model) return; + model->setCompletion(completion); + } +} + diff -r 000000000000 -r fc9323a41f5a sv/transform/FeatureExtractionPluginTransform.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/FeatureExtractionPluginTransform.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FEATURE_EXTRACTION_PLUGIN_TRANSFORM_H_ +#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORM_H_ + +#include "PluginTransform.h" + +class DenseTimeValueModel; + +class FeatureExtractionPluginTransform : public PluginTransform +{ + Q_OBJECT + +public: + FeatureExtractionPluginTransform(Model *inputModel, + QString plugin, + const ExecutionContext &context, + QString configurationXml = "", + QString outputName = ""); + virtual ~FeatureExtractionPluginTransform(); + +protected: + virtual void run(); + + Vamp::Plugin *m_plugin; + Vamp::Plugin::OutputDescriptor *m_descriptor; + int m_outputFeatureNo; + + void addFeature(size_t blockFrame, + const Vamp::Plugin::Feature &feature); + + void setCompletion(int); + + void getFrames(int channel, int channelCount, + long startFrame, long size, float *buffer); + + // just casts + DenseTimeValueModel *getInput(); + template ModelClass *getOutput() { + ModelClass *mc = dynamic_cast(m_output); + if (!mc) { + std::cerr << "FeatureExtractionPluginTransform::getOutput: Output model not conformable" << std::endl; + } + return mc; + } +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/transform/PluginTransform.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/PluginTransform.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PluginTransform.h" + +#include "vamp-sdk/PluginHostAdapter.h" + +PluginTransform::PluginTransform(Model *inputModel, + const ExecutionContext &context) : + Transform(inputModel), + m_context(context) +{ +} + +PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _bs) : + channel(_c), + domain(Vamp::Plugin::TimeDomain), + stepSize(_bs ? _bs : 1024), + blockSize(_bs ? _bs : 1024), + windowType(HanningWindow) +{ +} + +PluginTransform::ExecutionContext::ExecutionContext(int _c, size_t _ss, + size_t _bs, WindowType _wt) : + channel(_c), + domain(Vamp::Plugin::FrequencyDomain), + stepSize(_ss ? _ss : (_bs ? _bs / 2 : 512)), + blockSize(_bs ? _bs : 1024), + windowType(_wt) +{ +} + +PluginTransform::ExecutionContext::ExecutionContext(int _c, + const Vamp::PluginBase *_plugin) : + channel(_c), + domain(Vamp::Plugin::TimeDomain), + stepSize(0), + blockSize(0), + windowType(HanningWindow) +{ + makeConsistentWithPlugin(_plugin); +} + +bool +PluginTransform::ExecutionContext::operator==(const ExecutionContext &c) +{ + return (c.channel == channel && + c.domain == domain && + c.stepSize == stepSize && + c.blockSize == blockSize && + c.windowType == windowType); +} + +void +PluginTransform::ExecutionContext::makeConsistentWithPlugin(const Vamp::PluginBase *_plugin) +{ + const Vamp::Plugin *vp = dynamic_cast(_plugin); + if (!vp) { + vp = dynamic_cast(_plugin); //!!! why? + } + + if (!vp) { + domain = Vamp::Plugin::TimeDomain; + if (!stepSize) { + if (!blockSize) blockSize = 1024; + stepSize = blockSize; + } else { + if (!blockSize) blockSize = stepSize; + } + } else { + domain = vp->getInputDomain(); + if (!stepSize) stepSize = vp->getPreferredStepSize(); + if (!blockSize) blockSize = vp->getPreferredBlockSize(); + if (!blockSize) blockSize = 1024; + if (!stepSize) { + if (domain == Vamp::Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } + } +} + + diff -r 000000000000 -r fc9323a41f5a sv/transform/PluginTransform.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/PluginTransform.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,60 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLUGIN_TRANSFORM_H_ +#define _PLUGIN_TRANSFORM_H_ + +#include "Transform.h" + +#include "base/Window.h" + +#include "vamp-sdk/Plugin.h" + +//!!! should this just move back up to Transform? It is after all used +//directly in all sorts of generic places, like Document + +class PluginTransform : public Transform +{ +public: + class ExecutionContext { + public: + // Time domain: + ExecutionContext(int _c = -1, size_t _bs = 0); + + // Frequency domain: + ExecutionContext(int _c, size_t _ss, size_t _bs, WindowType _wt); + + // From plugin defaults: + ExecutionContext(int _c, const Vamp::PluginBase *_plugin); + + bool operator==(const ExecutionContext &); + + void makeConsistentWithPlugin(const Vamp::PluginBase *_plugin); + + int channel; + Vamp::Plugin::InputDomain domain; + size_t stepSize; + size_t blockSize; + WindowType windowType; + }; + +protected: + PluginTransform(Model *inputModel, + const ExecutionContext &context); + + ExecutionContext m_context; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/transform/RealTimePluginTransform.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/RealTimePluginTransform.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,253 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RealTimePluginTransform.h" + +#include "plugin/RealTimePluginFactory.h" +#include "plugin/RealTimePluginInstance.h" +#include "plugin/PluginXml.h" + +#include "data/model/Model.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "data/model/WritableWaveFileModel.h" +#include "data/model/WaveFileModel.h" + +#include + +RealTimePluginTransform::RealTimePluginTransform(Model *inputModel, + QString pluginId, + const ExecutionContext &context, + QString configurationXml, + QString units, + int output) : + PluginTransform(inputModel, context), + m_pluginId(pluginId), + m_configurationXml(configurationXml), + m_units(units), + m_plugin(0), + m_outputNo(output) +{ + if (!m_context.blockSize) m_context.blockSize = 1024; + +// std::cerr << "RealTimePluginTransform::RealTimePluginTransform: plugin " << pluginId.toStdString() << ", output " << output << std::endl; + + RealTimePluginFactory *factory = + RealTimePluginFactory::instanceFor(pluginId); + + if (!factory) { + std::cerr << "RealTimePluginTransform: No factory available for plugin id \"" + << pluginId.toStdString() << "\"" << std::endl; + return; + } + + DenseTimeValueModel *input = getInput(); + if (!input) return; + + m_plugin = factory->instantiatePlugin(pluginId, 0, 0, + m_input->getSampleRate(), + m_context.blockSize, + input->getChannelCount()); + + if (!m_plugin) { + std::cerr << "RealTimePluginTransform: Failed to instantiate plugin \"" + << pluginId.toStdString() << "\"" << std::endl; + return; + } + + if (configurationXml != "") { + PluginXml(m_plugin).setParametersFromXml(configurationXml); + } + + if (m_outputNo >= 0 && + m_outputNo >= int(m_plugin->getControlOutputCount())) { + std::cerr << "RealTimePluginTransform: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl; + return; + } + + if (m_outputNo == -1) { + + size_t outputChannels = m_plugin->getAudioOutputCount(); + if (outputChannels > input->getChannelCount()) { + outputChannels = input->getChannelCount(); + } + + WritableWaveFileModel *model = new WritableWaveFileModel + (input->getSampleRate(), outputChannels); + + m_output = model; + + } else { + + SparseTimeValueModel *model = new SparseTimeValueModel + (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false); + + if (units != "") model->setScaleUnits(units); + + m_output = model; + } +} + +RealTimePluginTransform::~RealTimePluginTransform() +{ + delete m_plugin; +} + +DenseTimeValueModel * +RealTimePluginTransform::getInput() +{ + DenseTimeValueModel *dtvm = + dynamic_cast(getInputModel()); + if (!dtvm) { + std::cerr << "RealTimePluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl; + } + return dtvm; +} + +void +RealTimePluginTransform::run() +{ + DenseTimeValueModel *input = getInput(); + if (!input) return; + + while (!input->isReady()) { + if (dynamic_cast(input)) break; // no need to wait + std::cerr << "RealTimePluginTransform::run: Waiting for input model to be ready..." << std::endl; + sleep(1); + } + + SparseTimeValueModel *stvm = dynamic_cast(m_output); + WritableWaveFileModel *wwfm = dynamic_cast(m_output); + if (!stvm && !wwfm) return; + + if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return; + + size_t sampleRate = input->getSampleRate(); + size_t channelCount = input->getChannelCount(); + if (!wwfm && m_context.channel != -1) channelCount = 1; + + size_t blockSize = m_plugin->getBufferSize(); + + float **inbufs = m_plugin->getAudioInputBuffers(); + + size_t startFrame = m_input->getStartFrame(); + size_t endFrame = m_input->getEndFrame(); + size_t blockFrame = startFrame; + + size_t prevCompletion = 0; + + size_t latency = m_plugin->getLatency(); + + while (blockFrame < endFrame && !m_abandoned) { + + size_t completion = + (((blockFrame - startFrame) / blockSize) * 99) / + ( (endFrame - startFrame) / blockSize); + + size_t got = 0; + + if (channelCount == 1) { + if (inbufs && inbufs[0]) { + got = input->getValues + (m_context.channel, blockFrame, blockFrame + blockSize, inbufs[0]); + while (got < blockSize) { + inbufs[0][got++] = 0.0; + } + } + for (size_t ch = 1; ch < m_plugin->getAudioInputCount(); ++ch) { + for (size_t i = 0; i < blockSize; ++i) { + inbufs[ch][i] = inbufs[0][i]; + } + } + } else { + for (size_t ch = 0; ch < channelCount; ++ch) { + if (inbufs && inbufs[ch]) { + got = input->getValues + (ch, blockFrame, blockFrame + blockSize, inbufs[ch]); + while (got < blockSize) { + inbufs[ch][got++] = 0.0; + } + } + } + for (size_t ch = channelCount; ch < m_plugin->getAudioInputCount(); ++ch) { + for (size_t i = 0; i < blockSize; ++i) { + inbufs[ch][i] = inbufs[ch % channelCount][i]; + } + } + } + +/* + std::cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< std::endl; + + for (size_t ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) { + std::cerr << "Input channel " << ch << std::endl; + for (size_t i = 0; i < 100; ++i) { + std::cerr << inbufs[ch][i] << " "; + if (isnan(inbufs[ch][i])) { + std::cerr << "\n\nWARNING: NaN in audio input" << std::endl; + } + } + } +*/ + + m_plugin->run(Vamp::RealTime::frame2RealTime(blockFrame, sampleRate)); + + if (stvm) { + + float value = m_plugin->getControlOutputValue(m_outputNo); + + size_t pointFrame = blockFrame; + if (pointFrame > latency) pointFrame -= latency; + else pointFrame = 0; + + stvm->addPoint(SparseTimeValueModel::Point + (pointFrame, value, "")); + + } else if (wwfm) { + + float **outbufs = m_plugin->getAudioOutputBuffers(); + + if (outbufs) { + + if (blockFrame >= latency) { + wwfm->addSamples(outbufs, blockSize); + } else if (blockFrame + blockSize >= latency) { + size_t offset = latency - blockFrame; + size_t count = blockSize - offset; + float **tmp = new float *[channelCount]; + for (size_t c = 0; c < channelCount; ++c) { + tmp[c] = outbufs[c] + offset; + } + wwfm->addSamples(tmp, count); + delete[] tmp; + } + } + } + + if (blockFrame == startFrame || completion > prevCompletion) { + if (stvm) stvm->setCompletion(completion); + if (wwfm) wwfm->setCompletion(completion); + prevCompletion = completion; + } + + blockFrame += blockSize; + } + + if (m_abandoned) return; + + if (stvm) stvm->setCompletion(100); + if (wwfm) wwfm->setCompletion(100); +} + diff -r 000000000000 -r fc9323a41f5a sv/transform/RealTimePluginTransform.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/RealTimePluginTransform.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _REAL_TIME_PLUGIN_TRANSFORM_H_ +#define _REAL_TIME_PLUGIN_TRANSFORM_H_ + +#include "PluginTransform.h" +#include "plugin/RealTimePluginInstance.h" + +class DenseTimeValueModel; + +class RealTimePluginTransform : public PluginTransform +{ +public: + RealTimePluginTransform(Model *inputModel, + QString plugin, + const ExecutionContext &context, + QString configurationXml = "", + QString units = "", + int output = -1); // -1 -> audio, 0+ -> data + virtual ~RealTimePluginTransform(); + +protected: + virtual void run(); + + QString m_pluginId; + QString m_configurationXml; + QString m_units; + + RealTimePluginInstance *m_plugin; + int m_outputNo; + + // just casts + DenseTimeValueModel *getInput(); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a sv/transform/Transform.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/Transform.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,32 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Transform.h" + +Transform::Transform(Model *m) : + m_input(m), + m_output(0), + m_detached(false), + m_abandoned(false) +{ +} + +Transform::~Transform() +{ + m_abandoned = true; + wait(); + if (!m_detached) delete m_output; +} + diff -r 000000000000 -r fc9323a41f5a sv/transform/Transform.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/Transform.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,61 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TRANSFORM_H_ +#define _TRANSFORM_H_ + +#include "base/Thread.h" + +#include "data/model/Model.h" + +typedef QString TransformId; + +/** + * A Transform turns one data model into another. + * + * Typically in this application, a Transform might have a + * DenseTimeValueModel as its input (e.g. an audio waveform) and a + * SparseOneDimensionalModel (e.g. detected beats) as its output. + * + * The Transform typically runs in the background, as a separate + * thread populating the output model. The model is available to the + * user of the Transform immediately, but may be initially empty until + * the background thread has populated it. + */ + +class Transform : public Thread +{ +public: + virtual ~Transform(); + + // Just a hint to the processing thread that it should give up. + // Caller should still wait() and/or delete the transform before + // assuming its input and output models are no longer required. + void abandon() { m_abandoned = true; } + + Model *getInputModel() { return m_input; } + Model *getOutputModel() { return m_output; } + Model *detachOutputModel() { m_detached = true; return m_output; } + +protected: + Transform(Model *m); + + Model *m_input; // I don't own this + Model *m_output; // I own this, unless... + bool m_detached; // ... this is true. + bool m_abandoned; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a sv/transform/TransformFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/TransformFactory.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,838 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TransformFactory.h" + +#include "FeatureExtractionPluginTransform.h" +#include "RealTimePluginTransform.h" + +#include "plugin/FeatureExtractionPluginFactory.h" +#include "plugin/RealTimePluginFactory.h" +#include "plugin/PluginXml.h" + +#include "widgets/PluginParameterDialog.h" + +#include "data/model/DenseTimeValueModel.h" + +#include "vamp-sdk/PluginHostAdapter.h" + +#include "sv/audioio/AudioCallbackPlaySource.h" //!!! shouldn't include here + +#include +#include + +#include + +TransformFactory * +TransformFactory::m_instance = new TransformFactory; + +TransformFactory * +TransformFactory::getInstance() +{ + return m_instance; +} + +TransformFactory::~TransformFactory() +{ +} + +TransformFactory::TransformList +TransformFactory::getAllTransforms() +{ + if (m_transforms.empty()) populateTransforms(); + + std::set dset; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + dset.insert(i->second); + } + + TransformList list; + for (std::set::const_iterator i = dset.begin(); + i != dset.end(); ++i) { + list.push_back(*i); + } + + return list; +} + +std::vector +TransformFactory::getAllTransformTypes() +{ + if (m_transforms.empty()) populateTransforms(); + + std::set types; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + types.insert(i->second.type); + } + + std::vector rv; + for (std::set::iterator i = types.begin(); i != types.end(); ++i) { + rv.push_back(*i); + } + + return rv; +} + +std::vector +TransformFactory::getTransformCategories(QString transformType) +{ + if (m_transforms.empty()) populateTransforms(); + + std::set categories; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + if (i->second.type == transformType) { + categories.insert(i->second.category); + } + } + + bool haveEmpty = false; + + std::vector rv; + for (std::set::iterator i = categories.begin(); + i != categories.end(); ++i) { + if (*i != "") rv.push_back(*i); + else haveEmpty = true; + } + + if (haveEmpty) rv.push_back(""); // make sure empty category sorts last + + return rv; +} + +std::vector +TransformFactory::getTransformMakers(QString transformType) +{ + if (m_transforms.empty()) populateTransforms(); + + std::set makers; + for (TransformDescriptionMap::const_iterator i = m_transforms.begin(); + i != m_transforms.end(); ++i) { + if (i->second.type == transformType) { + makers.insert(i->second.maker); + } + } + + bool haveEmpty = false; + + std::vector rv; + for (std::set::iterator i = makers.begin(); + i != makers.end(); ++i) { + if (*i != "") rv.push_back(*i); + else haveEmpty = true; + } + + if (haveEmpty) rv.push_back(""); // make sure empty category sorts last + + return rv; +} + +void +TransformFactory::populateTransforms() +{ + TransformDescriptionMap transforms; + + populateFeatureExtractionPlugins(transforms); + populateRealTimePlugins(transforms); + + // disambiguate plugins with similar names + + std::map names; + std::map pluginSources; + std::map pluginMakers; + + for (TransformDescriptionMap::iterator i = transforms.begin(); + i != transforms.end(); ++i) { + + TransformDesc desc = i->second; + + QString td = desc.name; + QString tn = td.section(": ", 0, 0); + QString pn = desc.identifier.section(":", 1, 1); + + if (pluginSources.find(tn) != pluginSources.end()) { + if (pluginSources[tn] != pn && pluginMakers[tn] != desc.maker) { + ++names[tn]; + } + } else { + ++names[tn]; + pluginSources[tn] = pn; + pluginMakers[tn] = desc.maker; + } + } + + std::map counts; + m_transforms.clear(); + + for (TransformDescriptionMap::iterator i = transforms.begin(); + i != transforms.end(); ++i) { + + TransformDesc desc = i->second; + QString identifier = desc.identifier; + QString maker = desc.maker; + + QString td = desc.name; + QString tn = td.section(": ", 0, 0); + QString to = td.section(": ", 1); + + if (names[tn] > 1) { + maker.replace(QRegExp(tr(" [\\(<].*$")), ""); + tn = QString("%1 [%2]").arg(tn).arg(maker); + } + + if (to != "") { + desc.name = QString("%1: %2").arg(tn).arg(to); + } else { + desc.name = tn; + } + + m_transforms[identifier] = desc; + } +} + +void +TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms) +{ + std::vector plugs = + FeatureExtractionPluginFactory::getAllPluginIdentifiers(); + + for (size_t i = 0; i < plugs.size(); ++i) { + + QString pluginId = plugs[i]; + + FeatureExtractionPluginFactory *factory = + FeatureExtractionPluginFactory::instanceFor(pluginId); + + if (!factory) { + std::cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl; + continue; + } + + Vamp::Plugin *plugin = + factory->instantiatePlugin(pluginId, 48000); + + if (!plugin) { + std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl; + continue; + } + + QString pluginName = plugin->getName().c_str(); + QString category = factory->getPluginCategory(pluginId); + + Vamp::Plugin::OutputList outputs = + plugin->getOutputDescriptors(); + + for (size_t j = 0; j < outputs.size(); ++j) { + + QString transformId = QString("%1:%2") + .arg(pluginId).arg(outputs[j].identifier.c_str()); + + QString userName; + QString friendlyName; + QString units = outputs[j].unit.c_str(); + QString description = plugin->getDescription().c_str(); + QString maker = plugin->getMaker().c_str(); + if (maker == "") maker = tr(""); + + if (description == "") { + if (outputs.size() == 1) { + description = tr("Extract features using \"%1\" plugin (from %2)") + .arg(pluginName).arg(maker); + } else { + description = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)") + .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker); + } + } else { + if (outputs.size() == 1) { + description = tr("%1 using \"%2\" plugin (from %3)") + .arg(description).arg(pluginName).arg(maker); + } else { + description = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)") + .arg(description).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker); + } + } + + if (outputs.size() == 1) { + userName = pluginName; + friendlyName = pluginName; + } else { + userName = QString("%1: %2") + .arg(pluginName) + .arg(outputs[j].name.c_str()); + friendlyName = outputs[j].name.c_str(); + } + + bool configurable = (!plugin->getPrograms().empty() || + !plugin->getParameterDescriptors().empty()); + +// std::cerr << "Feature extraction plugin transform: " << transformId.toStdString() << std::endl; + + transforms[transformId] = + TransformDesc(tr("Analysis"), + category, + transformId, + userName, + friendlyName, + description, + maker, + units, + configurable); + } + } +} + +void +TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms) +{ + std::vector plugs = + RealTimePluginFactory::getAllPluginIdentifiers(); + + static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$"); + + for (size_t i = 0; i < plugs.size(); ++i) { + + QString pluginId = plugs[i]; + + RealTimePluginFactory *factory = + RealTimePluginFactory::instanceFor(pluginId); + + if (!factory) { + std::cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl; + continue; + } + + const RealTimePluginDescriptor *descriptor = + factory->getPluginDescriptor(pluginId); + + if (!descriptor) { + std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl; + continue; + } + +//!!! if (descriptor->controlOutputPortCount == 0 || +// descriptor->audioInputPortCount == 0) continue; + +// std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl; + + QString pluginName = descriptor->name.c_str(); + QString category = factory->getPluginCategory(pluginId); + bool configurable = (descriptor->parameterCount > 0); + QString maker = descriptor->maker.c_str(); + if (maker == "") maker = tr(""); + + if (descriptor->audioInputPortCount > 0) { + + for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) { + + QString transformId = QString("%1:%2").arg(pluginId).arg(j); + QString userName; + QString units; + QString portName; + + if (j < descriptor->controlOutputPortNames.size() && + descriptor->controlOutputPortNames[j] != "") { + + portName = descriptor->controlOutputPortNames[j].c_str(); + + userName = tr("%1: %2") + .arg(pluginName) + .arg(portName); + + if (unitRE.indexIn(portName) >= 0) { + units = unitRE.cap(1); + } + + } else if (descriptor->controlOutputPortCount > 1) { + + userName = tr("%1: Output %2") + .arg(pluginName) + .arg(j + 1); + + } else { + + userName = pluginName; + } + + QString description; + + if (portName != "") { + description = tr("Extract \"%1\" data output from \"%2\" effect plugin (from %3)") + .arg(portName) + .arg(pluginName) + .arg(maker); + } else { + description = tr("Extract data output %1 from \"%2\" effect plugin (from %3)") + .arg(j + 1) + .arg(pluginName) + .arg(maker); + } + + transforms[transformId] = + TransformDesc(tr("Effects Data"), + category, + transformId, + userName, + userName, + description, + maker, + units, + configurable); + } + } + + if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) { + + if (descriptor->audioOutputPortCount > 0) { + + QString transformId = QString("%1:A").arg(pluginId); + QString type = tr("Effects"); + + QString description = tr("Transform audio signal with \"%1\" effect plugin (from %2)") + .arg(pluginName) + .arg(maker); + + if (descriptor->audioInputPortCount == 0) { + type = tr("Generators"); + QString description = tr("Generate audio signal using \"%1\" plugin (from %2)") + .arg(pluginName) + .arg(maker); + } + + transforms[transformId] = + TransformDesc(type, + category, + transformId, + pluginName, + pluginName, + description, + maker, + "", + configurable); + } + } + } +} + +QString +TransformFactory::getTransformName(TransformId identifier) +{ + if (m_transforms.find(identifier) != m_transforms.end()) { + return m_transforms[identifier].name; + } else return ""; +} + +QString +TransformFactory::getTransformFriendlyName(TransformId identifier) +{ + if (m_transforms.find(identifier) != m_transforms.end()) { + return m_transforms[identifier].friendlyName; + } else return ""; +} + +QString +TransformFactory::getTransformUnits(TransformId identifier) +{ + if (m_transforms.find(identifier) != m_transforms.end()) { + return m_transforms[identifier].units; + } else return ""; +} + +bool +TransformFactory::isTransformConfigurable(TransformId identifier) +{ + if (m_transforms.find(identifier) != m_transforms.end()) { + return m_transforms[identifier].configurable; + } else return false; +} + +bool +TransformFactory::getTransformChannelRange(TransformId identifier, + int &min, int &max) +{ + QString id = identifier.section(':', 0, 2); + + if (FeatureExtractionPluginFactory::instanceFor(id)) { + + Vamp::Plugin *plugin = + FeatureExtractionPluginFactory::instanceFor(id)-> + instantiatePlugin(id, 48000); + if (!plugin) return false; + + min = plugin->getMinChannelCount(); + max = plugin->getMaxChannelCount(); + delete plugin; + + return true; + + } else if (RealTimePluginFactory::instanceFor(id)) { + + const RealTimePluginDescriptor *descriptor = + RealTimePluginFactory::instanceFor(id)-> + getPluginDescriptor(id); + if (!descriptor) return false; + + min = descriptor->audioInputPortCount; + max = descriptor->audioInputPortCount; + + return true; + } + + return false; +} + +bool +TransformFactory::getChannelRange(TransformId identifier, Vamp::PluginBase *plugin, + int &minChannels, int &maxChannels) +{ + Vamp::Plugin *vp = 0; + if ((vp = dynamic_cast(plugin)) || + (vp = dynamic_cast(plugin))) { + minChannels = vp->getMinChannelCount(); + maxChannels = vp->getMaxChannelCount(); + return true; + } else { + return getTransformChannelRange(identifier, minChannels, maxChannels); + } +} + +Model * +TransformFactory::getConfigurationForTransform(TransformId identifier, + const std::vector &candidateInputModels, + PluginTransform::ExecutionContext &context, + QString &configurationXml, + AudioCallbackPlaySource *source) +{ + if (candidateInputModels.empty()) return 0; + + //!!! This will need revision -- we'll have to have a callback + //from the dialog for when the candidate input model is changed, + //as we'll need to reinitialise the channel settings in the dialog + Model *inputModel = candidateInputModels[0]; //!!! for now + QStringList candidateModelNames; + std::map modelMap; + for (size_t i = 0; i < candidateInputModels.size(); ++i) { + QString modelName = candidateInputModels[i]->objectName(); + QString origModelName = modelName; + int dupcount = 1; + while (modelMap.find(modelName) != modelMap.end()) { + modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount); + } + modelMap[modelName] = candidateInputModels[i]; + candidateModelNames.push_back(modelName); + } + + QString id = identifier.section(':', 0, 2); + QString output = identifier.section(':', 3); + QString outputLabel = ""; + QString outputDescription = ""; + + bool ok = false; + configurationXml = m_lastConfigurations[identifier]; + +// std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl; + + Vamp::PluginBase *plugin = 0; + + bool frequency = false; + bool effect = false; + bool generator = false; + + if (FeatureExtractionPluginFactory::instanceFor(id)) { + + Vamp::Plugin *vp = + FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin + (id, inputModel->getSampleRate()); + + if (vp) { + + plugin = vp; + frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain); + + std::vector od = + vp->getOutputDescriptors(); + if (od.size() > 1) { + for (size_t i = 0; i < od.size(); ++i) { + if (od[i].identifier == output.toStdString()) { + outputLabel = od[i].name.c_str(); + outputDescription = od[i].description.c_str(); + break; + } + } + } + } + + } else if (RealTimePluginFactory::instanceFor(id)) { + + RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id); + const RealTimePluginDescriptor *desc = factory->getPluginDescriptor(id); + + if (desc->audioInputPortCount > 0 && + desc->audioOutputPortCount > 0 && + !desc->isSynth) { + effect = true; + } + + if (desc->audioInputPortCount == 0) { + generator = true; + } + + if (output != "A") { + int outputNo = output.toInt(); + if (outputNo >= 0 && outputNo < int(desc->controlOutputPortCount)) { + outputLabel = desc->controlOutputPortNames[outputNo].c_str(); + } + } + + size_t sampleRate = inputModel->getSampleRate(); + size_t blockSize = 1024; + size_t channels = 1; + if (effect && source) { + sampleRate = source->getTargetSampleRate(); + blockSize = source->getTargetBlockSize(); + channels = source->getTargetChannelCount(); + } + + RealTimePluginInstance *rtp = factory->instantiatePlugin + (id, 0, 0, sampleRate, blockSize, channels); + + plugin = rtp; + + if (effect && source && rtp) { + source->setAuditioningPlugin(rtp); + } + } + + if (plugin) { + + context = PluginTransform::ExecutionContext(context.channel, plugin); + + if (configurationXml != "") { + PluginXml(plugin).setParametersFromXml(configurationXml); + } + + int sourceChannels = 1; + if (dynamic_cast(inputModel)) { + sourceChannels = dynamic_cast(inputModel) + ->getChannelCount(); + } + + int minChannels = 1, maxChannels = sourceChannels; + getChannelRange(identifier, plugin, minChannels, maxChannels); + + int targetChannels = sourceChannels; + if (!effect) { + if (sourceChannels < minChannels) targetChannels = minChannels; + if (sourceChannels > maxChannels) targetChannels = maxChannels; + } + + int defaultChannel = context.channel; + + PluginParameterDialog *dialog = new PluginParameterDialog(plugin); + + if (candidateModelNames.size() > 1 && !generator) { + dialog->setCandidateInputModels(candidateModelNames); + } + + if (targetChannels > 0) { + dialog->setChannelArrangement(sourceChannels, targetChannels, + defaultChannel); + } + + dialog->setOutputLabel(outputLabel, outputDescription); + + dialog->setShowProcessingOptions(true, frequency); + + if (dialog->exec() == QDialog::Accepted) { + ok = true; + } + + QString selectedInput = dialog->getInputModel(); + if (selectedInput != "") { + if (modelMap.find(selectedInput) != modelMap.end()) { + inputModel = modelMap[selectedInput]; + std::cerr << "Found selected input \"" << selectedInput.toStdString() << "\" in model map, result is " << inputModel << std::endl; + } else { + std::cerr << "Failed to find selected input \"" << selectedInput.toStdString() << "\" in model map" << std::endl; + } + } + + configurationXml = PluginXml(plugin).toXmlString(); + context.channel = dialog->getChannel(); + + dialog->getProcessingParameters(context.stepSize, + context.blockSize, + context.windowType); + + context.makeConsistentWithPlugin(plugin); + + delete dialog; + + if (effect && source) { + source->setAuditioningPlugin(0); // will delete our plugin + } else { + delete plugin; + } + } + + if (ok) m_lastConfigurations[identifier] = configurationXml; + + return ok ? inputModel : 0; +} + +PluginTransform::ExecutionContext +TransformFactory::getDefaultContextForTransform(TransformId identifier, + Model *inputModel) +{ + PluginTransform::ExecutionContext context(-1); + + QString id = identifier.section(':', 0, 2); + + if (FeatureExtractionPluginFactory::instanceFor(id)) { + + Vamp::Plugin *vp = + FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin + (id, inputModel ? inputModel->getSampleRate() : 48000); + + if (vp) context = PluginTransform::ExecutionContext(-1, vp); + + } + + return context; +} + +Transform * +TransformFactory::createTransform(TransformId identifier, Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Transform *transform = 0; + + QString id = identifier.section(':', 0, 2); + QString output = identifier.section(':', 3); + + if (FeatureExtractionPluginFactory::instanceFor(id)) { + transform = new FeatureExtractionPluginTransform(inputModel, + id, + context, + configurationXml, + output); + } else if (RealTimePluginFactory::instanceFor(id)) { + transform = new RealTimePluginTransform(inputModel, + id, + context, + configurationXml, + getTransformUnits(identifier), + output == "A" ? -1 : + output.toInt()); + } else { + std::cerr << "TransformFactory::createTransform: Unknown transform \"" + << identifier.toStdString() << "\"" << std::endl; + return transform; + } + + if (transform) transform->setObjectName(identifier); + return transform; +} + +Model * +TransformFactory::transform(TransformId identifier, Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Transform *t = createTransform(identifier, inputModel, context, + configurationXml); + + if (!t) return 0; + + connect(t, SIGNAL(finished()), this, SLOT(transformFinished())); + + m_runningTransforms.insert(t); + + t->start(); + Model *model = t->detachOutputModel(); + + if (model) { + QString imn = inputModel->objectName(); + QString trn = getTransformFriendlyName(identifier); + if (imn != "") { + if (trn != "") { + model->setObjectName(tr("%1: %2").arg(imn).arg(trn)); + } else { + model->setObjectName(imn); + } + } else if (trn != "") { + model->setObjectName(trn); + } + } + + return model; +} + +void +TransformFactory::transformFinished() +{ + QObject *s = sender(); + Transform *transform = dynamic_cast(s); + + std::cerr << "TransformFactory::transformFinished(" << transform << ")" << std::endl; + + if (!transform) { + std::cerr << "WARNING: TransformFactory::transformFinished: sender is not a transform" << std::endl; + return; + } + + if (m_runningTransforms.find(transform) == m_runningTransforms.end()) { + std::cerr << "WARNING: TransformFactory::transformFinished(" + << transform + << "): I have no record of this transform running!" + << std::endl; + } + + m_runningTransforms.erase(transform); + + transform->wait(); // unnecessary but reassuring + delete transform; +} + +void +TransformFactory::modelAboutToBeDeleted(Model *m) +{ + TransformSet affected; + + for (TransformSet::iterator i = m_runningTransforms.begin(); + i != m_runningTransforms.end(); ++i) { + + Transform *t = *i; + + if (t->getInputModel() == m || t->getOutputModel() == m) { + affected.insert(t); + } + } + + for (TransformSet::iterator i = affected.begin(); + i != affected.end(); ++i) { + + Transform *t = *i; + + t->abandon(); + + t->wait(); // this should eventually call back on + // transformFinished, which will remove from + // m_runningTransforms and delete. + } +} + diff -r 000000000000 -r fc9323a41f5a sv/transform/TransformFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv/transform/TransformFactory.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,183 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TRANSFORM_FACTORY_H_ +#define _TRANSFORM_FACTORY_H_ + +#include "Transform.h" +#include "PluginTransform.h" + +#include +#include + +namespace Vamp { class PluginBase; } + +class AudioCallbackPlaySource; + +class TransformFactory : public QObject +{ + Q_OBJECT + +public: + virtual ~TransformFactory(); + + static TransformFactory *getInstance(); + + // The identifier is intended to be computer-referenceable, and + // unique within the application. The name is intended to be + // human readable. In principle it doesn't have to be unique, but + // the factory will add suffixes to ensure that it is, all the + // same (just to avoid user confusion). The friendly name is a + // shorter version of the name. The type is also intended to be + // user-readable, for use in menus. + + struct TransformDesc { + + TransformDesc() { } + TransformDesc(QString _type, QString _category, + TransformId _identifier, QString _name, + QString _friendlyName, QString _description, + QString _maker, QString _units, bool _configurable) : + type(_type), category(_category), + identifier(_identifier), name(_name), + friendlyName(_friendlyName), description(_description), + maker(_maker), units(_units), configurable(_configurable) { } + + QString type; // e.g. feature extraction plugin + QString category; // e.g. time > onsets + TransformId identifier; // e.g. vamp:vamp-aubio:aubioonset + QString name; // plugin's name if 1 output, else "name: output" + QString friendlyName; // short text for layer name + QString description; // sentence describing transform + QString maker; + QString units; + bool configurable; + + bool operator<(const TransformDesc &od) const { + return (name < od.name); + }; + }; + typedef std::vector TransformList; + + TransformList getAllTransforms(); + + std::vector getAllTransformTypes(); + + std::vector getTransformCategories(QString transformType); + std::vector getTransformMakers(QString transformType); + + /** + * Get a configuration XML string for the given transform (by + * asking the user, most likely). Returns the selected input + * model if the transform is acceptable, 0 if the operation should + * be cancelled. Audio callback play source may be used to + * audition effects plugins, if provided. + */ + Model *getConfigurationForTransform(TransformId identifier, + const std::vector &candidateInputModels, + PluginTransform::ExecutionContext &context, + QString &configurationXml, + AudioCallbackPlaySource *source = 0); + + /** + * Get the default execution context for the given transform + * and input model (if known). + */ + PluginTransform::ExecutionContext getDefaultContextForTransform(TransformId identifier, + Model *inputModel = 0); + + /** + * Return the output model resulting from applying the named + * transform to the given input model. The transform may still be + * working in the background when the model is returned; check the + * output model's isReady completion status for more details. + * + * If the transform is unknown or the input model is not an + * appropriate type for the given transform, or if some other + * problem occurs, return 0. + * + * The returned model is owned by the caller and must be deleted + * when no longer needed. + */ + Model *transform(TransformId identifier, Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml = ""); + + /** + * Full name of a transform, suitable for putting on a menu. + */ + QString getTransformName(TransformId identifier); + + /** + * Brief but friendly name of a transform, suitable for use + * as the name of the output layer. + */ + QString getTransformFriendlyName(TransformId identifier); + + QString getTransformUnits(TransformId identifier); + + /** + * Return true if the transform has any configurable parameters, + * i.e. if getConfigurationForTransform can ever return a non-trivial + * (not equivalent to empty) configuration string. + */ + bool isTransformConfigurable(TransformId identifier); + + /** + * If the transform has a prescribed number or range of channel + * inputs, return true and set minChannels and maxChannels to the + * minimum and maximum number of channel inputs the transform can + * accept. Return false if it doesn't care. + */ + bool getTransformChannelRange(TransformId identifier, + int &minChannels, int &maxChannels); + +protected slots: + void transformFinished(); + + void modelAboutToBeDeleted(Model *); + +protected: + Transform *createTransform(TransformId identifier, Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml); + + struct TransformIdent + { + TransformId identifier; + QString configurationXml; + }; + + typedef std::map TransformConfigurationMap; + TransformConfigurationMap m_lastConfigurations; + + typedef std::map TransformDescriptionMap; + TransformDescriptionMap m_transforms; + + typedef std::set TransformSet; + TransformSet m_runningTransforms; + + void populateTransforms(); + void populateFeatureExtractionPlugins(TransformDescriptionMap &); + void populateRealTimePlugins(TransformDescriptionMap &); + + bool getChannelRange(TransformId identifier, + Vamp::PluginBase *plugin, int &min, int &max); + + static TransformFactory *m_instance; +}; + + +#endif diff -r 000000000000 -r fc9323a41f5a sv_mingw.prf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sv_mingw.prf Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,174 @@ + +### +### BEGIN CONFIGURABLE STUFF +### + +CONFIG += release +# precompile_header + +# Whizzy optimization flags here +# +#linux-g++:QMAKE_CXXFLAGS_RELEASE += -DNDEBUG -DNO_TIMING -O2 -march=pentium3 -mfpmath=sse -ffast-math +QMAKE_CXXFLAGS_RELEASE += -DNDEBUG -DNO_TIMING -O2 +# QMAKE_CXXFLAGS_RELEASE += -O3 -march=pentium4 -mfpmath=sse -msse -msse2 -ffast-math -fomit-frame-pointer +# QMAKE_CXXFLAGS_RELEASE += -O3 -march=athlon-mp -mfpmath=sse -fomit-frame-pointer + +# To do a static build with gcc on Linux +# +LIBS += -Wl,-Bstatic +DEFINES += BUILD_STATIC + +#PRECOMPILED_HEADER = /work/sonic-visualiser/pch.h + + +# These are testable on platforms with pkg-config. If you don't have +# pkg-config, edit the "else" block below (see comments). +# +PKGCONFIG_PACKAGES = vamp vamp-sdk oggz fishsound mad fftw3f sndfile samplerate lrdf raptor jack liblo + +# No pkg-config test for the bzip2 library. This library is required. +# If you don't have it, install it. +# +DEFINES += HAVE_BZ2 +INCLUDEPATH += ../../packages/bzip2-1.0.4 +LIBS += -L../../packages/bzip2-1.0.4 -lbz2 + +# No pkg-config test for PortAudio. If you don't have it, comment these out. +# We support PortAudio v18 and v19; the default is v19. If you want to use +# v18, see below. +# +DEFINES += HAVE_PORTAUDIO +INCLUDEPATH += ../../packages/portaudio/include +LIBS += -L../../packages/portaudio -lportaudio +# +# If you want to use PortAudio v18, uncomment this line (as well as +# HAVE_PORTAUDIO above): +# +#DEFINES += HAVE_PORTAUDIO_v18 + + +#!system(pkg-config --atleast-pkgconfig-version=0) { + + # If you don't have pkg-config, comment out (or install) any of the + # following that you lack. If you have pkg-config, you should be + # able to ignore all this provided the right symbols are defined + # in PKGCONFIG_PACKAGES above. + # +# DEFINES += HAVE_JACK # Optional -- an audio playback option +# DEFINES += HAVE_OGGZ # Optional -- to import .ogg files +# DEFINES += HAVE_FISHSOUND # Optional -- to import .ogg files + DEFINES += HAVE_MAD # Optional -- to import .mp3 files + # +# LIBS += -ljack +# LIBS += -loggz -lfishsound + INCLUDEPATH += ../../packages/libmad-0.15.1b + LIBS += -L../../packages/libmad-0.15.1b -lmad + + # These ones are mandatory. + # If you don't have them, you'll have to find them. + # + DEFINES += HAVE_VAMP HAVE_VAMP_SDK # Required -- for analysis plugins + DEFINES += HAVE_FFTW3F # Required -- for all sorts of things + DEFINES += HAVE_SNDFILE # Required -- to import and export .wav files + DEFINES += HAVE_SAMPLERATE # Required -- for resampling + # +# INCLUDEPATH += ../../vamp-plugin-sdk ../../packages/fftw-3.1.2-dll ../../packages/libsndfile-1_0_17 ../../packages/libsamplerate-0.1.2/src +# LIBS += -L../../vamp-plugin-sdk/vamp-sdk -L../../packages/fftw-3.1.2-dll -L../../packages/libsndfile-1.0.17 -L../../packages/libsamplerate-0.1.2 + INCLUDEPATH += ../../vamp-plugin-sdk ../../packages/fftw-3.1.2-dll ../../packages/libsndfile-1.0.17/src ../../packages/libsamplerate-0.1.2/src + LIBS += -L../../vamp-plugin-sdk/vamp-sdk -L../../packages/fftw-3.1.2-dll -L../../packages/libsndfile-1.0.17 -L../../packages/libsamplerate-0.1.2 + # + LIBS += -lvamp-sdk -Wl,-Bdynamic -lfftw3f-3 -Wl,-Bstatic -lsndfile -lsamplerate +# LIBS += -lvamp-sdk -Wl,-Bdynamic -Wl,-Bstatic -lsndfile -lsamplerate +# LIBS += -lvamp-sdk -lsndfile -lsamplerate +#} + +### +### END CONFIGURABLE STUFF +### + + +#system(pkg-config --atleast-pkgconfig-version=0) { +linux-g++ { + + # If you have pkg-config, this block should locate all packages + # for you provided they have .pc files and are listed in + # PKGCONFIG_PACKAGES. + # + for(PKG, PKGCONFIG_PACKAGES) { + contains(SV_UNIT_PACKAGES, $$PKG) { + system(pkg-config --exists $$PKG) { + VERSION = $$system(pkg-config --modversion $$PKG) + PACKAGE_SYMBOL = $$system(echo $$PKG | tr '[a-z-]' '[A-Z_]') + VERSION_SYMBOL = $$PACKAGE_SYMBOL'_VERSION' + DEFINES += HAVE_$$PACKAGE_SYMBOL + QMAKE_CXXFLAGS += -D"'"$$VERSION_SYMBOL='"'$$VERSION'"'"'" + QMAKE_CXXFLAGS += $$system(pkg-config --cflags $$PKG) + LIBS += $$system(pkg-config --libs $$PKG) + message("Using pkg-config package $$PKG with version $$VERSION") + } else { + message("WARNING: Failed to find pkg-config package $$PKG") + } + } + } +} + +contains(SV_UNIT_PACKAGES, portaudio) { + contains(DEFINES, HAVE_PORTAUDIO) { + message("Including PortAudio support for audio playback") + } else { + message("WARNING: PortAudio audio playback support will not be included") + } +} + +contains(SV_UNIT_PACKAGES, jack) { + contains(DEFINES, HAVE_JACK) { + message("Including JACK support for audio playback") + } else { + !win32:message("WARNING: JACK audio playback support will not be included") + !contains(DEFINES, HAVE_PORTAUDIO) { + message("WARNING: No audio playback support is configured!") + } + } +} + +contains(SV_UNIT_PACKAGES, oggz) { + contains(DEFINES, HAVE_OGGZ) { + contains(DEFINES, HAVE_FISHSOUND) { + message("Including .ogg file import") + } else { + message("WARNING: .ogg file import will not be included") + } + } else { + message("WARNING: .ogg file import will not be included") + } +} + +contains(SV_UNIT_PACKAGES, mad) { + contains(DEFINES, HAVE_MAD) { + message("Including .mp3 file import") + } else { + message("WARNING: .mp3 file import will not be included") + } +} + +contains(SV_UNIT_PACKAGES, vamp):!contains(DEFINES, HAVE_VAMP):error("Vamp plugin API required") +contains(SV_UNIT_PACKAGES, vamp-sdk):!contains(DEFINES, HAVE_VAMP_SDK):error("Vamp plugin SDK required") +contains(SV_UNIT_PACKAGES, bz2):!contains(DEFINES, HAVE_BZ2):error("bzip2 library required") +#contains(SV_UNIT_PACKAGES, fftw3f):!contains(DEFINES, HAVE_FFTW3F):error("FFTW3f library required") +contains(SV_UNIT_PACKAGES, sndfile):!contains(DEFINES, HAVE_SNDFILE):error("sndfile library required") +contains(SV_UNIT_PACKAGES, samplerate):!contains(DEFINES, HAVE_SAMPLERATE):error("libsamplerate required") + +#VERSION_CFLAGS += -D"'"SVNREV='"'$$system(svnversion -n .)'"'"'" + +QMAKE_CXXFLAGS_DEBUG += -DBUILD_DEBUG $$VERSION_CFLAGS +QMAKE_CXXFLAGS_RELEASE += -DBUILD_RELEASE $$VERSION_CFLAGS + +linux-g++ { + contains(DEFINES, BUILD_STATIC) { + LIBS += -lFLAC -ldl + } +} + +# Restore dynamic linkage, in case we went static earlier +linux-g++:LIBS += -Wl,-Bdynamic + diff -r 000000000000 -r fc9323a41f5a system/Init.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/Init.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include +#include + +#include + +#include "System.h" + +#ifdef Q_WS_X11 +#include +#include +#include +#include + +static int handle_x11_error(Display *dpy, XErrorEvent *err) +{ + char errstr[256]; + XGetErrorText(dpy, err->error_code, errstr, 256); + if (err->error_code != BadWindow) { + std::cerr << "Sonic Visualiser: X Error: " + << errstr << " " << int(err->error_code) + << "\nin major opcode: " + << int(err->request_code) << std::endl; + } + return 0; +} +#endif + +#ifdef Q_WS_WIN32 + +#include +#include + +// Set default file open mode to binary +//#undef _fmode +//int _fmode = _O_BINARY; + +void redirectStderr() +{ + HANDLE stderrHandle = GetStdHandle(STD_ERROR_HANDLE); + if (!stderrHandle) return; + + AllocConsole(); + + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(stderrHandle, &info); + info.dwSize.Y = 1000; + SetConsoleScreenBufferSize(stderrHandle, info.dwSize); + + int h = _open_osfhandle((long)stderrHandle, _O_TEXT); + if (h) { + FILE *fd = _fdopen(h, "w"); + if (fd) { + *stderr = *fd; + setvbuf(stderr, NULL, _IONBF, 0); + } + } +} + +#endif + +extern void svSystemSpecificInitialisation() +{ +#ifdef Q_WS_X11 + XSetErrorHandler(handle_x11_error); +#endif + +#ifdef Q_WS_WIN32 + redirectStderr(); + QFont fn = qApp->font(); + fn.setFamily("Tahoma"); + qApp->setFont(fn); +#else +#ifdef Q_WS_X11 + QFont fn = qApp->font(); + fn.setPointSize(fn.pointSize() + 2); + qApp->setFont(fn); +#endif +#endif +} + + + diff -r 000000000000 -r fc9323a41f5a system/Init.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/Init.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,21 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SV_SYSTEM_INIT_H_ +#define _SV_SYSTEM_INIT_H_ + +extern void svSystemSpecificInitialisation(); + +#endif diff -r 000000000000 -r fc9323a41f5a system/System.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/System.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,275 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "System.h" + +#include +#include + +//#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#include + +#ifdef __APPLE__ +extern "C" { +void * +rpl_realloc (void *p, size_t n) +{ + p = realloc(p, n); + if (p == 0 && n == 0) + { + p = malloc(0); + } + return p; +} +} +#endif + +#ifdef _WIN32 + +extern "C" { + +void usleep(unsigned long usec) +{ + ::Sleep(usec / 1000); +} + +void gettimeofday(struct timeval *tv, void *tz) +{ + union { + long long ns100; + FILETIME ft; + } now; + + ::GetSystemTimeAsFileTime(&now.ft); + tv->tv_usec = (long)((now.ns100 / 10LL) % 1000000LL); + tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); +} + +} + +#endif + +ProcessStatus +GetProcessStatus(int pid) +{ +#ifdef _WIN32 + HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!handle) { + return ProcessNotRunning; + } else { + CloseHandle(handle); + return ProcessRunning; + } +#else + if (kill(getpid(), 0) == 0) { + if (kill(pid, 0) == 0) { + return ProcessRunning; + } else { + return ProcessNotRunning; + } + } else { + return UnknownProcessStatus; + } +#endif +} + +#ifdef _WIN32 +/* MEMORYSTATUSEX is missing from older Windows headers, so define a + local replacement. This trick from MinGW source code. Ugh */ +typedef struct +{ + DWORD dwLength; + DWORD dwMemoryLoad; + DWORDLONG ullTotalPhys; + DWORDLONG ullAvailPhys; + DWORDLONG ullTotalPageFile; + DWORDLONG ullAvailPageFile; + DWORDLONG ullTotalVirtual; + DWORDLONG ullAvailVirtual; + DWORDLONG ullAvailExtendedVirtual; +} lMEMORYSTATUSEX; +typedef WINBOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*); +#endif + +void +GetRealMemoryMBAvailable(int &available, int &total) +{ + available = -1; + total = -1; + +#ifdef _WIN32 + + static bool checked = false; + static bool exFound = false; + static PFN_MS_EX ex; + + if (!checked) { + + HMODULE h = GetModuleHandleA("kernel32.dll"); + + if (h) { + if ((ex = (PFN_MS_EX)GetProcAddress(h, "GlobalMemoryStatusEx"))) { + exFound = true; + } + } + + checked = true; + } + + DWORDLONG wavail = 0; + DWORDLONG wtotal = 0; + + if (exFound) { + + lMEMORYSTATUSEX lms; + lms.dwLength = sizeof(lms); + if (!ex(&lms)) { + std::cerr << "WARNING: GlobalMemoryStatusEx failed: error code " + << GetLastError() << std::endl; + return; + } + wavail = lms.ullAvailPhys; + wtotal = lms.ullTotalPhys; + + } else { + + /* Fall back to GlobalMemoryStatus which is always available. + but returns wrong results for physical memory > 4GB */ + + MEMORYSTATUS ms; + GlobalMemoryStatus(&ms); + wavail = ms.dwAvailPhys; + wtotal = ms.dwTotalPhys; + } + + DWORDLONG size = wavail / 1048576; + if (size > INT_MAX) size = INT_MAX; + available = int(size); + + size = wtotal / 1048576; + if (size > INT_MAX) size = INT_MAX; + total = int(size); + + return; + +#else +#ifdef __APPLE__ + + unsigned int val; + int mib[2]; + size_t size_sys; + + mib[0] = CTL_HW; + + mib[1] = HW_PHYSMEM; + size_sys = sizeof(val); + sysctl(mib, 2, &val, &size_sys, NULL, 0); + if (val) total = val / 1048576; + + mib[1] = HW_USERMEM; + size_sys = sizeof(val); + sysctl(mib, 2, &val, &size_sys, NULL, 0); + if (val) available = val / 1048576; + + return; + +#else + + FILE *meminfo = fopen("/proc/meminfo", "r"); + if (!meminfo) return; + + char buf[256]; + while (!feof(meminfo)) { + fgets(buf, 256, meminfo); + bool isMemFree = (strncmp(buf, "MemFree:", 8) == 0); + bool isMemTotal = (!isMemFree && (strncmp(buf, "MemTotal:", 9) == 0)); + if (isMemFree || isMemTotal) { + QString line = QString(buf).trimmed(); + QStringList elements = line.split(' ', QString::SkipEmptyParts); + QString unit = "kB"; + if (elements.size() > 2) unit = elements[2]; + int size = elements[1].toInt(); +// std::cerr << "have size \"" << size << "\", unit \"" +// << unit.toStdString() << "\"" << std::endl; + if (unit.toLower() == "gb") size = size * 1024; + else if (unit.toLower() == "mb") size = size; + else if (unit.toLower() == "kb") size = size / 1024; + else size = size / 1048576; + + if (isMemFree) available = size; + else total = size; + } + if (available != -1 && total != -1) { + fclose(meminfo); + return; + } + } + fclose(meminfo); + + return; + +#endif +#endif +} + +int +GetDiscSpaceMBAvailable(const char *path) +{ +#ifdef _WIN32 + ULARGE_INTEGER available, total, totalFree; + if (GetDiskFreeSpaceExA(path, &available, &total, &totalFree)) { + __int64 a = available.QuadPart; + a /= 1048576; + if (a > INT_MAX) a = INT_MAX; + return int(a); + } else { + std::cerr << "WARNING: GetDiskFreeSpaceEx failed: error code " + << GetLastError() << std::endl; + return -1; + } +#else + struct statvfs buf; + if (!statvfs(path, &buf)) { + // do the multiplies and divides in this order to reduce the + // likelihood of arithmetic overflow + std::cerr << "statvfs(" << path << ") says available: " << buf.f_bavail << ", block size: " << buf.f_bsize << std::endl; + uint64_t available = ((buf.f_bavail / 1024) * buf.f_bsize) / 1024; + if (available > INT_MAX) available = INT_MAX; + return int(available); + } else { + perror("statvfs failed"); + return -1; + } +#endif +} + + +double mod(double x, double y) { return x - (y * floor(x / y)); } +float modf(float x, float y) { return x - (y * floorf(x / y)); } + +double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; } +float princargf(float a) { return modf(a + M_PI, -2 * M_PI) + M_PI; } + diff -r 000000000000 -r fc9323a41f5a system/System.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/System.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,226 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SYSTEM_H_ +#define _SYSTEM_H_ + +#ifdef _WIN32 + +#include +#include +#include + +#ifdef USE_VC +#include +#include +#include +#include + +typedef int WINBOOL; + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long ssize_t; +typedef unsigned short mode_t; + +#define _S_IWUSR _S_IWRITE +#define _S_IRUSR _S_IREAD +#define S_IRUSR _S_IRUSR +#define S_IWUSR _S_IWUSR + +/* If we're not using GNU C, elide __attribute__ */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +/* Win32 doesn't seem to have these functions. +** Therefore implement inline versions of these functions here. +*/ + +__inline long int lrint (double flt) +{ int intgr; + _asm + { fld flt + fistp intgr + } ; + + return intgr ; +} + +__inline long int lrintf (float flt) +{ int intgr; + _asm + { fld flt + fistp intgr + } ; + + return intgr ; +} + +__inline double nearbyint(double x) +{ + unsigned int tmpMSW1; + unsigned int tmpMSW2; + __asm { + // get current state + fnstcw tmpMSW1 + } + // set bit 5 + tmpMSW2 = tmpMSW1 | 0x00000020; + __asm { + // and load + fldcw tmpMSW2 + // do the job + fld x + frndint + // clear exception + fclex + // restore old state + fldcw tmpMSW1 + } +} + +#ifndef isinf +#define isinf(d) ((_fpclass(d) == _FPCLASS_PINF) ? 1 \ + : ((_fpclass(d) == _FPCLASS_NINF) ? -1 : 0)) +#endif +/* _isnan(x) returns nonzero if (x == NaN) and zero otherwise. */ +#ifndef isnan +#define isnan(d) (_isnan(d)) +#endif + +#define ftruncate _chsize + +#else /*USE_VC*/ +#include +#include +#endif /*USE_VC*/ + +#define MLOCK(a,b) 1 +#define MUNLOCK(a,b) 1 +#define MUNLOCK_SAMPLEBLOCK(a) 1 + +#define DLOPEN(a,b) LoadLibrary((a).toStdWString().c_str()) +#define DLSYM(a,b) GetProcAddress((HINSTANCE)(a),(b)) +#define DLCLOSE(a) (!FreeLibrary((HINSTANCE)(a))) +#define DLERROR() "" + +#define PLUGIN_GLOB "*.dll" +#define PATH_SEPARATOR ';' + +// The default Vamp plugin path is obtained from a function in the +// Vamp SDK (Vamp::PluginHostAdapter::getPluginPath). + +// At the time of writing, at least, the vast majority of LADSPA +// plugins on Windows hosts will have been put there for use in +// Audacity. It's a bit of a shame that Audacity uses its own Program +// Files directory for plugins that any host may want to use... maybe +// they were just following the example of VSTs, which are usually +// found in Steinberg's Program Files directory. Anyway, we can +// greatly increase our chances of picking up some LADSPA plugins by +// default if we include the Audacity plugin location as well as an +// (imho) more sensible place. + +#define DEFAULT_LADSPA_PATH "%ProgramFiles%\\LADSPA Plugins;%ProgramFiles%\\Audacity\\Plug-Ins" +#define DEFAULT_DSSI_PATH "%ProgramFiles%\\DSSI Plugins" + +#define getpid _getpid + +extern "C" { +void usleep(unsigned long usec); +void gettimeofday(struct timeval *p, void *tz); +} + +#else /* ! _WIN32 */ + +#include +#include + +#define MLOCK(a,b) ::mlock((a),(b)) +#define MUNLOCK(a,b) (::munlock((a),(b)) ? (::perror("munlock failed"), 0) : 0) +#define MUNLOCK_SAMPLEBLOCK(a) do { if (!(a).empty()) { const float &b = *(a).begin(); MUNLOCK(&b, (a).capacity() * sizeof(float)); } } while(0); +//#define MLOCK(a,b) 1 +//#define MUNLOCK(a,b) 1 +//#define MUNLOCK_SAMPLEBLOCK(a) 1 + +#define DLOPEN(a,b) dlopen((a).toStdString().c_str(),(b)) +#define DLSYM(a,b) dlsym((a),(b)) +#define DLCLOSE(a) dlclose((a)) +#define DLERROR() dlerror() + +#ifdef __APPLE__ + +#define PLUGIN_GLOB "*.dylib *.so" +#define PATH_SEPARATOR ':' + +#define DEFAULT_LADSPA_PATH "$HOME/Library/Audio/Plug-Ins/LADSPA:/Library/Audio/Plug-Ins/LADSPA" +#define DEFAULT_DSSI_PATH "$HOME/Library/Audio/Plug-Ins/DSSI:/Library/Audio/Plug-Ins/DSSI" + +#else + +#define PLUGIN_GLOB "*.so" +#define PATH_SEPARATOR ':' + +#define DEFAULT_LADSPA_PATH "$HOME/ladspa:$HOME/.ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa" +#define DEFAULT_DSSI_PATH "$HOME/dssi:$HOME/.dssi:/usr/local/lib/dssi:/usr/lib/dssi" + +#endif /* __APPLE__ */ + +#endif /* ! _WIN32 */ + +#ifdef __GNUC__ + +#ifndef min +#define min(x) std::min((x)) +#endif + +#ifndef max +#define max(x) std::max((x)) +#endif + +#ifndef isnan +#define isnan(x) std::isnan((x)) +#endif + +#ifndef isinf +#define isinf(x) std::isinf((x)) +#endif + +#endif /* __GNUC__ */ + +enum ProcessStatus { ProcessRunning, ProcessNotRunning, UnknownProcessStatus }; +extern ProcessStatus GetProcessStatus(int pid); + +// Return a vague approximation to the number of free megabytes of real memory. +// Return -1 if unknown. +extern void GetRealMemoryMBAvailable(int &available, int &total); + +// Return a vague approximation to the number of free megabytes of disc space +// on the partition containing the given path. Return -1 if unknown. +extern int GetDiscSpaceMBAvailable(const char *path); + +#include + +extern double mod(double x, double y); +extern float modf(float x, float y); + +extern double princarg(double a); +extern float princargf(float a); + +#endif /* ! _SYSTEM_H_ */ + + diff -r 000000000000 -r fc9323a41f5a system/svsystem.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/svsystem.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a system/system.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/system/system.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,15 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions + +TARGET = svsystem + +DEPENDPATH += . +INCLUDEPATH += . + +# Input +HEADERS += Init.h System.h +SOURCES += Init.cpp System.cpp diff -r 000000000000 -r fc9323a41f5a update-i18n.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/update-i18n.sh Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,16 @@ +#!/bin/sh + +LUPDATE="lupdate" +LRELEASE="lrelease" + +LANGUAGES="ru" + +for LANG in $LANGUAGES; do + $LUPDATE */*.h */*/*.h */*.cpp */*/*.cpp \ + -ts sv/i18n/sonic-visualiser_$LANG.ts +done + +for LANG in $LANGUAGES; do + $LRELEASE sv/i18n/sonic-visualiser_$LANG.ts +done + diff -r 000000000000 -r fc9323a41f5a version.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/version.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1 @@ +#define SV_VERSION "1.0" diff -r 000000000000 -r fc9323a41f5a view/Overview.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/Overview.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,241 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Overview.h" +#include "layer/Layer.h" +#include "data/model/Model.h" +#include "base/ZoomConstraint.h" +#include "system/System.h" + +#include +#include +#include + +using std::cerr; +using std::endl; + +Overview::Overview(QWidget *w) : + View(w, false), + m_clickedInRange(false) +{ + setObjectName(tr("Overview")); + m_followPan = false; + m_followZoom = false; + setPlaybackFollow(PlaybackIgnore); +} + +void +Overview::modelChanged(size_t startFrame, size_t endFrame) +{ + View::modelChanged(startFrame, endFrame); +} + +void +Overview::modelReplaced() +{ + View::modelReplaced(); +} + +void +Overview::registerView(View *view) +{ + m_views.insert(view); + update(); +} + +void +Overview::unregisterView(View *view) +{ + m_views.erase(view); + update(); +} + +void +Overview::globalCentreFrameChanged(unsigned long) +{ + update(); +} + +void +Overview::viewCentreFrameChanged(View *v, unsigned long) +{ + if (m_views.find(v) != m_views.end()) { + update(); + } +} + +void +Overview::viewZoomLevelChanged(View *v, unsigned long, bool) +{ + if (v == this) return; + if (m_views.find(v) != m_views.end()) { + update(); + } +} + +void +Overview::viewManagerPlaybackFrameChanged(unsigned long f) +{ + bool changed = false; + + if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true; + m_playPointerFrame = f; + + if (changed) update(); +} + +void +Overview::paintEvent(QPaintEvent *e) +{ + // Recalculate zoom in case the size of the widget has changed. + +// std::cerr << "Overview::paintEvent: width is " << width() << ", centre frame " << m_centreFrame << std::endl; + + size_t startFrame = getModelsStartFrame(); + size_t frameCount = getModelsEndFrame() - getModelsStartFrame(); + int zoomLevel = frameCount / width(); + if (zoomLevel < 1) zoomLevel = 1; + zoomLevel = getZoomConstraintBlockSize(zoomLevel, + ZoomConstraint::RoundUp); + if (zoomLevel != m_zoomLevel) { + m_zoomLevel = zoomLevel; + emit zoomLevelChanged(m_zoomLevel, m_followZoom); + } + size_t centreFrame = startFrame + m_zoomLevel * (width() / 2); + if (centreFrame > (startFrame + getModelsEndFrame())/2) { + centreFrame = (startFrame + getModelsEndFrame())/2; + } + if (centreFrame != m_centreFrame) { +// std::cerr << "Overview::paintEvent: Centre frame changed from " +// << m_centreFrame << " to " << centreFrame << " and thus start frame from " << getStartFrame(); + m_centreFrame = centreFrame; +// std::cerr << " to " << getStartFrame() << std::endl; + emit centreFrameChanged(m_centreFrame, false, PlaybackIgnore); + } + + View::paintEvent(e); + + QPainter paint; + paint.begin(this); + + QRect r(rect()); + + if (e) { + r = e->rect(); + paint.setClipRect(r); + } + + paint.setPen(Qt::black); + + int y = 0; + + int prevx0 = -10; + int prevx1 = -10; + + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + if (!*i) continue; + + View *w = (View *)*i; + + long f0 = w->getFrameForX(0); + long f1 = w->getFrameForX(w->width()); + + int x0 = getXForFrame(f0); + int x1 = getXForFrame(f1); + + if (x0 != prevx0 || x1 != prevx1) { + y += height() / 10 + 1; + prevx0 = x0; + prevx1 = x1; + } + + if (x1 <= x0) x1 = x0 + 1; + + paint.drawRect(x0, y, x1 - x0, height() - 2 * y); + } + + paint.end(); +} + +void +Overview::mousePressEvent(QMouseEvent *e) +{ + m_clickPos = e->pos(); + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + if (*i) { + m_clickedInRange = true; + m_dragCentreFrame = ((View *)*i)->getCentreFrame(); + break; + } + } +} + +void +Overview::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_clickedInRange) { + mouseMoveEvent(e); + } + m_clickedInRange = false; +} + +void +Overview::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clickedInRange) return; + + long xoff = int(e->x()) - int(m_clickPos.x()); + long frameOff = xoff * m_zoomLevel; + + size_t newCentreFrame = m_dragCentreFrame; + if (frameOff > 0) { + newCentreFrame += frameOff; + } else if (newCentreFrame >= size_t(-frameOff)) { + newCentreFrame += frameOff; + } else { + newCentreFrame = 0; + } + + if (newCentreFrame >= getModelsEndFrame()) { + newCentreFrame = getModelsEndFrame(); + if (newCentreFrame > 0) --newCentreFrame; + } + + if (max(m_centreFrame, newCentreFrame) - + min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) { + emit centreFrameChanged(newCentreFrame, true, PlaybackScrollContinuous); + } +} + +void +Overview::mouseDoubleClickEvent(QMouseEvent *e) +{ + long frame = getFrameForX(e->x()); + emit centreFrameChanged(frame, true, PlaybackScrollContinuous); +} + +void +Overview::enterEvent(QEvent *) +{ + emit contextHelpChanged(tr("Click and drag to navigate; double-click to jump")); +} + +void +Overview::leaveEvent(QEvent *) +{ + emit contextHelpChanged(""); +} + + diff -r 000000000000 -r fc9323a41f5a view/Overview.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/Overview.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,71 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _OVERVIEW_H_ +#define _OVERVIEW_H_ + +#include "View.h" + +#include + +class QWidget; +class QPaintEvent; +class Layer; +class View; + +#include + +class Overview : public View +{ + Q_OBJECT + +public: + Overview(QWidget *parent = 0); + + void registerView(View *view); + void unregisterView(View *view); + + virtual QString getPropertyContainerIconName() const { return "panner"; } + +public slots: + virtual void modelChanged(size_t startFrame, size_t endFrame); + virtual void modelReplaced(); + + virtual void globalCentreFrameChanged(unsigned long); + virtual void viewCentreFrameChanged(View *, unsigned long); + virtual void viewZoomLevelChanged(View *, unsigned long, bool); + virtual void viewManagerPlaybackFrameChanged(unsigned long); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + virtual bool shouldLabelSelections() const { return false; } + + QPoint m_clickPos; + QPoint m_mousePos; + bool m_clickedInRange; + size_t m_dragCentreFrame; + + typedef std::set ViewSet; + ViewSet m_views; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a view/Pane.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/Pane.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1890 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Pane.h" +#include "layer/Layer.h" +#include "data/model/Model.h" +#include "base/ZoomConstraint.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "ViewManager.h" +#include "base/CommandHistory.h" +#include "layer/WaveformLayer.h" + +#include +#include +#include +#include + +//!!! for HUD -- pull out into a separate class +#include +#include +#include +#include "widgets/Thumbwheel.h" +#include "widgets/Panner.h" +#include "widgets/RangeInputDialog.h" +#include "widgets/NotifyingPushButton.h" + +using std::cerr; +using std::endl; + +Pane::Pane(QWidget *w) : + View(w, true), + m_identifyFeatures(false), + m_clickedInRange(false), + m_shiftPressed(false), + m_ctrlPressed(false), + m_navigating(false), + m_resizing(false), + m_centreLineVisible(true), + m_scaleWidth(0), + m_headsUpDisplay(0), + m_vpan(0), + m_hthumb(0), + m_vthumb(0), + m_reset(0) +{ + setObjectName("Pane"); + setMouseTracking(true); + + updateHeadsUpDisplay(); +} + +void +Pane::updateHeadsUpDisplay() +{ + Profiler profiler("Pane::updateHeadsUpDisplay", true); + + if (!isVisible()) return; + +/* + int count = 0; + int currentLevel = 1; + int level = 1; + while (true) { + if (getZoomLevel() == level) currentLevel = count; + int newLevel = getZoomConstraintBlockSize(level + 1, + ZoomConstraint::RoundUp); + if (newLevel == level) break; + if (newLevel == 131072) break; //!!! just because + level = newLevel; + ++count; + } + + std::cerr << "Have " << count+1 << " zoom levels" << std::endl; +*/ + + Layer *layer = 0; + if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); + + if (!m_headsUpDisplay) { + + m_headsUpDisplay = new QFrame(this); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + layout->setSpacing(0); + m_headsUpDisplay->setLayout(layout); + + m_hthumb = new Thumbwheel(Qt::Horizontal); + m_hthumb->setObjectName(tr("Horizontal Zoom")); + layout->addWidget(m_hthumb, 1, 0, 1, 2); + m_hthumb->setFixedWidth(70); + m_hthumb->setFixedHeight(16); + m_hthumb->setDefaultValue(0); + m_hthumb->setSpeed(0.6); + connect(m_hthumb, SIGNAL(valueChanged(int)), this, + SLOT(horizontalThumbwheelMoved(int))); + connect(m_hthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_hthumb, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_vpan = new Panner; + layout->addWidget(m_vpan, 0, 1); + m_vpan->setFixedWidth(12); + m_vpan->setFixedHeight(70); + m_vpan->setAlpha(80, 130); + connect(m_vpan, SIGNAL(rectExtentsChanged(float, float, float, float)), + this, SLOT(verticalPannerMoved(float, float, float, float))); + connect(m_vpan, SIGNAL(doubleClicked()), + this, SLOT(editVerticalPannerExtents())); + connect(m_vpan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_vpan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_vthumb = new Thumbwheel(Qt::Vertical); + m_vthumb->setObjectName(tr("Vertical Zoom")); + layout->addWidget(m_vthumb, 0, 2); + m_vthumb->setFixedWidth(16); + m_vthumb->setFixedHeight(70); + connect(m_vthumb, SIGNAL(valueChanged(int)), this, + SLOT(verticalThumbwheelMoved(int))); + connect(m_vthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_vthumb, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + if (layer) { + RangeMapper *rm = layer->getNewVerticalZoomRangeMapper(); + if (rm) m_vthumb->setRangeMapper(rm); + } + + m_reset = new NotifyingPushButton; + m_reset->setFixedHeight(16); + m_reset->setFixedWidth(16); + layout->addWidget(m_reset, 1, 2); + connect(m_reset, SIGNAL(clicked()), m_hthumb, SLOT(resetToDefault())); + connect(m_reset, SIGNAL(clicked()), m_vthumb, SLOT(resetToDefault())); + connect(m_reset, SIGNAL(clicked()), m_vpan, SLOT(resetToDefault())); + connect(m_reset, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_reset, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + } + + int count = 0; + int current = 0; + int level = 1; + + //!!! pull out into function (presumably in View) + bool haveConstraint = false; + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); + ++i) { + if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) { + haveConstraint = true; + break; + } + } + + if (haveConstraint) { + while (true) { + if (getZoomLevel() == level) current = count; + int newLevel = getZoomConstraintBlockSize(level + 1, + ZoomConstraint::RoundUp); + if (newLevel == level) break; + level = newLevel; + if (++count == 50) break; + } + } else { + // if we have no particular constraints, we can really spread out + while (true) { + if (getZoomLevel() >= level) current = count; + int step = level / 10; + int pwr = 0; + while (step > 0) { + ++pwr; + step /= 2; + } + step = 1; + while (pwr > 0) { + step *= 2; + --pwr; + } +// std::cerr << level << std::endl; + level += step; + if (++count == 100 || level > 262144) break; + } + } + +// std::cerr << "Have " << count << " zoom levels" << std::endl; + + m_hthumb->setMinimumValue(0); + m_hthumb->setMaximumValue(count); + m_hthumb->setValue(count - current); + +// std::cerr << "set value to " << count-current << std::endl; + +// std::cerr << "default value is " << m_hthumb->getDefaultValue() << std::endl; + + if (count != 50 && m_hthumb->getDefaultValue() == 0) { + m_hthumb->setDefaultValue(count - current); +// std::cerr << "set default value to " << m_hthumb->getDefaultValue() << std::endl; + } + + bool haveVThumb = false; + + if (layer) { + int defaultStep = 0; + int max = layer->getVerticalZoomSteps(defaultStep); + if (max == 0) { + m_vthumb->hide(); + } else { + haveVThumb = true; + m_vthumb->show(); + m_vthumb->blockSignals(true); + m_vthumb->setMinimumValue(0); + m_vthumb->setMaximumValue(max); + m_vthumb->setDefaultValue(defaultStep); + m_vthumb->setValue(layer->getCurrentVerticalZoomStep()); + m_vthumb->blockSignals(false); + +// std::cerr << "Vertical thumbwheel: min 0, max " << max +// << ", default " << defaultStep << ", value " +// << m_vthumb->getValue() << std::endl; + + } + } + + updateVerticalPanner(); + + if (m_manager && m_manager->getZoomWheelsEnabled() && + width() > 120 && height() > 100) { + if (!m_headsUpDisplay->isVisible()) { + m_headsUpDisplay->show(); + } + if (haveVThumb) { + m_headsUpDisplay->setFixedHeight(m_vthumb->height() + m_hthumb->height()); + m_headsUpDisplay->move(width() - 86, height() - 86); + } else { + m_headsUpDisplay->setFixedHeight(m_hthumb->height()); + m_headsUpDisplay->move(width() - 86, height() - 16); + } + } else { + m_headsUpDisplay->hide(); + } +} + +void +Pane::updateVerticalPanner() +{ + if (!m_vpan || !m_manager || !m_manager->getZoomWheelsEnabled()) return; + + // In principle we should show or hide the panner on the basis of + // whether the top layer has adjustable display extents, and we do + // that below. However, we have no basis for layout of the panner + // if the vertical scroll wheel is not also present. So if we + // have no vertical scroll wheel, we should remove the panner as + // well. Ideally any layer that implements display extents should + // implement vertical zoom steps as well, but they don't all at + // the moment. + + Layer *layer = 0; + if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); + int discard; + if (layer && layer->getVerticalZoomSteps(discard) == 0) { + m_vpan->hide(); + return; + } + + float vmin, vmax, dmin, dmax; + if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax) && vmax != vmin) { + float y0 = (dmin - vmin) / (vmax - vmin); + float y1 = (dmax - vmin) / (vmax - vmin); + m_vpan->blockSignals(true); + m_vpan->setRectExtents(0, 1.0 - y1, 1, y1 - y0); + m_vpan->blockSignals(false); + m_vpan->show(); + } else { + m_vpan->hide(); + } +} + +bool +Pane::shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos) const +{ + QPoint discard; + bool b0, b1; + + if (layer == getSelectedLayer() && + !shouldIlluminateLocalSelection(discard, b0, b1)) { + + pos = m_identifyPoint; + return m_identifyFeatures; + } + + return false; +} + +bool +Pane::shouldIlluminateLocalSelection(QPoint &pos, + bool &closeToLeft, + bool &closeToRight) const +{ + if (m_identifyFeatures && + m_manager && + m_manager->getToolMode() == ViewManager::EditMode && + !m_manager->getSelections().empty() && + !selectionIsBeingEdited()) { + + Selection s(getSelectionAt(m_identifyPoint.x(), + closeToLeft, closeToRight)); + + if (!s.isEmpty()) { + if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) { + + pos = m_identifyPoint; + return true; + } + } + } + + return false; +} + +bool +Pane::selectionIsBeingEdited() const +{ + if (!m_editingSelection.isEmpty()) { + if (m_mousePos != m_clickPos && + getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) { + return true; + } + } + return false; +} + +void +Pane::setCentreLineVisible(bool visible) +{ + m_centreLineVisible = visible; + update(); +} + +void +Pane::paintEvent(QPaintEvent *e) +{ +// Profiler profiler("Pane::paintEvent", true); + + QPainter paint; + + QRect r(rect()); + + if (e) { + r = e->rect(); + } +/* + paint.begin(this); + paint.setClipRect(r); + + if (hasLightBackground()) { + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + } else { + paint.setPen(Qt::black); + paint.setBrush(Qt::black); + } + paint.drawRect(r); + + paint.end(); +*/ + View::paintEvent(e); + + paint.begin(this); + + if (e) { + paint.setClipRect(r); + } + + const Model *waveformModel = 0; // just for reporting purposes + + int fontHeight = paint.fontMetrics().height(); + int fontAscent = paint.fontMetrics().ascent(); + + if (m_manager && + !m_manager->isPlaying() && + m_manager->getToolMode() == ViewManager::SelectMode) { + + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + + std::vector crosshairExtents; + + if ((*vi)->getCrosshairExtents(this, paint, m_identifyPoint, + crosshairExtents)) { + (*vi)->paintCrosshairs(this, paint, m_identifyPoint); + break; + } else if ((*vi)->isLayerOpaque()) { + break; + } + } + } + + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + + if (dynamic_cast(*vi)) { + waveformModel = (*vi)->getModel(); + } + + if (!m_manager || !m_manager->shouldShowVerticalScale()) { + m_scaleWidth = 0; + } else { + m_scaleWidth = (*vi)->getVerticalScaleWidth(this, paint); + } + + if (m_scaleWidth > 0 && r.left() < m_scaleWidth) { + +// Profiler profiler("Pane::paintEvent - painting vertical scale", true); + +// std::cerr << "Pane::paintEvent: calling paint.save() in vertical scale block" << std::endl; + paint.save(); + + paint.setPen(Qt::black); + paint.setBrush(Qt::white); + paint.drawRect(0, -1, m_scaleWidth, height()+1); + + paint.setBrush(Qt::NoBrush); + (*vi)->paintVerticalScale + (this, paint, QRect(0, 0, m_scaleWidth, height())); + + paint.restore(); + } + + if (m_identifyFeatures) { + + QPoint pos = m_identifyPoint; + QString desc = (*vi)->getFeatureDescription(this, pos); + + if (desc != "") { + + paint.save(); + + int tabStop = + paint.fontMetrics().width(tr("Some lengthy prefix:")); + + QRect boundingRect = + paint.fontMetrics().boundingRect + (rect(), + Qt::AlignRight | Qt::AlignTop | Qt::TextExpandTabs, + desc, tabStop); + + if (hasLightBackground()) { + paint.setPen(Qt::NoPen); + paint.setBrush(QColor(250, 250, 250, 200)); + } else { + paint.setPen(Qt::NoPen); + paint.setBrush(QColor(50, 50, 50, 200)); + } + + int extra = paint.fontMetrics().descent(); + paint.drawRect(width() - boundingRect.width() - 10 - extra, + 10 - extra, + boundingRect.width() + 2 * extra, + boundingRect.height() + extra); + + if (hasLightBackground()) { + paint.setPen(QColor(150, 20, 0)); + } else { + paint.setPen(QColor(255, 150, 100)); + } + + QTextOption option; + option.setWrapMode(QTextOption::NoWrap); + option.setAlignment(Qt::AlignRight | Qt::AlignTop); + option.setTabStop(tabStop); + paint.drawText(QRectF(width() - boundingRect.width() - 10, 10, + boundingRect.width(), + boundingRect.height()), + desc, + option); + + paint.restore(); + } + } + + break; + } + + int sampleRate = getModelsSampleRate(); + paint.setBrush(Qt::NoBrush); + + if (m_centreLineVisible && + m_manager && + m_manager->shouldShowCentreLine()) { + + QColor c = QColor(0, 0, 0); + if (!hasLightBackground()) { + c = QColor(240, 240, 240); + } + paint.setPen(c); + int x = width() / 2 + 1; + paint.drawLine(x, 0, x, height() - 1); + paint.drawLine(x-1, 1, x+1, 1); + paint.drawLine(x-2, 0, x+2, 0); + paint.drawLine(x-1, height() - 2, x+1, height() - 2); + paint.drawLine(x-2, height() - 1, x+2, height() - 1); + + paint.setPen(QColor(50, 50, 50)); + + int y = height() - fontHeight + + fontAscent - 6; + + LayerList::iterator vi = m_layers.end(); + + if (vi != m_layers.begin()) { + + switch ((*--vi)->getPreferredFrameCountPosition()) { + + case Layer::PositionTop: + y = fontAscent + 6; + break; + + case Layer::PositionMiddle: + y = (height() - fontHeight) / 2 + + fontAscent; + break; + + case Layer::PositionBottom: + // y already set correctly + break; + } + } + + if (m_manager && m_manager->shouldShowFrameCount()) { + + if (sampleRate) { + + QString text(QString::fromStdString + (RealTime::frame2RealTime + (m_centreFrame, sampleRate).toText(true))); + + int tw = paint.fontMetrics().width(text); + int x = width()/2 - 4 - tw; + + drawVisibleText(paint, x, y, text, OutlinedText); + } + + QString text = QString("%1").arg(m_centreFrame); + + int x = width()/2 + 4; + + drawVisibleText(paint, x, y, text, OutlinedText); + } + + } else { + + paint.setPen(QColor(50, 50, 50)); + } + + if (waveformModel && + m_manager && + m_manager->shouldShowDuration() && + r.y() + r.height() >= height() - fontHeight - 6) { + + size_t modelRate = waveformModel->getSampleRate(); + size_t playbackRate = m_manager->getPlaybackSampleRate(); + size_t outputRate = m_manager->getOutputSampleRate(); + + QString srNote = ""; + + // Show (R) for waveform models that will be resampled on + // playback, and (X) for waveform models that will be played + // at the wrong rate because their rate differs from the + // current playback rate (which is not necessarily that of the + // main model). + + if (playbackRate != 0) { + if (modelRate == playbackRate) { + if (modelRate != outputRate) srNote = " " + tr("(R)"); + } else { + srNote = " " + tr("(X)"); + } + } + + QString desc = tr("%1 / %2Hz%3") + .arg(RealTime::frame2RealTime(waveformModel->getEndFrame(), + sampleRate) + .toText(false).c_str()) + .arg(modelRate) + .arg(srNote); + + if (r.x() < m_scaleWidth + 5 + paint.fontMetrics().width(desc)) { + drawVisibleText(paint, m_scaleWidth + 5, + height() - fontHeight + fontAscent - 6, + desc, OutlinedText); + } + } + + if (m_manager && + m_manager->shouldShowLayerNames() && + r.y() + r.height() >= height() - int(m_layers.size()) * fontHeight - 6) { + + std::vector texts; + int maxTextWidth = 0; + + for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + QString text = (*i)->getLayerPresentationName(); + int tw = paint.fontMetrics().width(text); + bool reduced = false; + while (tw > width() / 3 && text.length() > 4) { + if (!reduced && text.length() > 8) { + text = text.left(text.length() - 4); + } else { + text = text.left(text.length() - 2); + } + reduced = true; + tw = paint.fontMetrics().width(text + "..."); + } + if (reduced) { + texts.push_back(text + "..."); + } else { + texts.push_back(text); + } + if (tw > maxTextWidth) maxTextWidth = tw; + } + + int lly = height() - 6; + int llx = width() - maxTextWidth - 5; + + if (m_manager->getZoomWheelsEnabled()) { + lly -= 20; + llx -= 36; + } + + if (r.x() + r.width() >= llx) { + + for (size_t i = 0; i < texts.size(); ++i) { + + if (i + 1 == texts.size()) { + paint.setPen(Qt::black); + } + + drawVisibleText(paint, llx, + lly - fontHeight + fontAscent, + texts[i], OutlinedText); + + lly -= fontHeight; + } + } + } + + if (m_clickedInRange && m_shiftPressed) { + if (m_manager && (m_manager->getToolMode() == ViewManager::NavigateMode)) { + //!!! be nice if this looked a bit more in keeping with the + //selection block + paint.setPen(Qt::blue); + paint.drawRect(m_clickPos.x(), m_clickPos.y(), + m_mousePos.x() - m_clickPos.x(), + m_mousePos.y() - m_clickPos.y()); + } + } + + if (selectionIsBeingEdited()) { + + int offset = m_mousePos.x() - m_clickPos.x(); + int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset; + int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset; + + if (m_editingSelectionEdge < 0) { + p1 = getXForFrame(m_editingSelection.getEndFrame()); + } else if (m_editingSelectionEdge > 0) { + p0 = getXForFrame(m_editingSelection.getStartFrame()); + } + + paint.save(); + if (hasLightBackground()) { + paint.setPen(QPen(Qt::black, 2)); + } else { + paint.setPen(QPen(Qt::white, 2)); + } + + //!!! duplicating display policy with View::drawSelections + + if (m_editingSelectionEdge < 0) { + paint.drawLine(p0, 1, p1, 1); + paint.drawLine(p0, 0, p0, height()); + paint.drawLine(p0, height() - 1, p1, height() - 1); + } else if (m_editingSelectionEdge > 0) { + paint.drawLine(p0, 1, p1, 1); + paint.drawLine(p1, 0, p1, height()); + paint.drawLine(p0, height() - 1, p1, height() - 1); + } else { + paint.setBrush(Qt::NoBrush); + paint.drawRect(p0, 1, p1 - p0, height() - 2); + } + paint.restore(); + } + + paint.end(); +} + +bool +Pane::render(QPainter &paint, int xorigin, size_t f0, size_t f1) +{ + if (!View::render(paint, xorigin + m_scaleWidth, f0, f1)) { + return false; + } + + if (m_scaleWidth > 0) { + + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + + paint.save(); + + paint.setPen(Qt::black); + paint.setBrush(Qt::white); + paint.drawRect(xorigin, -1, m_scaleWidth, height()+1); + + paint.setBrush(Qt::NoBrush); + (*vi)->paintVerticalScale + (this, paint, QRect(xorigin, 0, m_scaleWidth, height())); + + paint.restore(); + break; + } + } + + return true; +} + +QImage * +Pane::toNewImage(size_t f0, size_t f1) +{ + size_t x0 = f0 / getZoomLevel(); + size_t x1 = f1 / getZoomLevel(); + + QImage *image = new QImage(x1 - x0 + m_scaleWidth, + height(), QImage::Format_RGB32); + + int formerScaleWidth = m_scaleWidth; + + if (m_manager && m_manager->shouldShowVerticalScale()) { + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + QPainter paint(image); + m_scaleWidth = (*vi)->getVerticalScaleWidth(this, paint); + break; + } + } else { + m_scaleWidth = 0; + } + + if (m_scaleWidth != formerScaleWidth) { + delete image; + image = new QImage(x1 - x0 + m_scaleWidth, + height(), QImage::Format_RGB32); + } + + QPainter *paint = new QPainter(image); + if (!render(*paint, 0, f0, f1)) { + delete paint; + delete image; + return 0; + } else { + delete paint; + return image; + } +} + +QSize +Pane::getImageSize(size_t f0, size_t f1) +{ + QSize s = View::getImageSize(f0, f1); + QImage *image = new QImage(100, 100, QImage::Format_RGB32); + QPainter paint(image); + + int sw = 0; + if (m_manager && m_manager->shouldShowVerticalScale()) { + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + QPainter paint(image); + sw = (*vi)->getVerticalScaleWidth(this, paint); + break; + } + } + + return QSize(sw + s.width(), s.height()); +} + +size_t +Pane::getFirstVisibleFrame() const +{ + long f0 = getFrameForX(m_scaleWidth); + size_t f = View::getFirstVisibleFrame(); + if (f0 < 0 || f0 < long(f)) return f; + return f0; +} + +Selection +Pane::getSelectionAt(int x, bool &closeToLeftEdge, bool &closeToRightEdge) const +{ + closeToLeftEdge = closeToRightEdge = false; + + if (!m_manager) return Selection(); + + long testFrame = getFrameForX(x - 5); + if (testFrame < 0) { + testFrame = getFrameForX(x); + if (testFrame < 0) return Selection(); + } + + Selection selection = m_manager->getContainingSelection(testFrame, true); + if (selection.isEmpty()) return selection; + + int lx = getXForFrame(selection.getStartFrame()); + int rx = getXForFrame(selection.getEndFrame()); + + int fuzz = 2; + if (x < lx - fuzz || x > rx + fuzz) return Selection(); + + int width = rx - lx; + fuzz = 3; + if (width < 12) fuzz = width / 4; + if (fuzz < 1) fuzz = 1; + + if (x < lx + fuzz) closeToLeftEdge = true; + if (x > rx - fuzz) closeToRightEdge = true; + + return selection; +} + +bool +Pane::canTopLayerMoveVertical() +{ + float vmin, vmax, dmin, dmax; + if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return false; + if (dmin <= vmin && dmax >= vmax) return false; + return true; +} + +bool +Pane::getTopLayerDisplayExtents(float &vmin, float &vmax, + float &dmin, float &dmax, + QString *unit) +{ + Layer *layer = 0; + if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); + if (!layer) return false; + bool vlog; + QString vunit; + bool rv = (layer->getValueExtents(vmin, vmax, vlog, vunit) && + layer->getDisplayExtents(dmin, dmax)); + if (unit) *unit = vunit; + return rv; +} + +bool +Pane::setTopLayerDisplayExtents(float dmin, float dmax) +{ + Layer *layer = 0; + if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); + if (!layer) return false; + return layer->setDisplayExtents(dmin, dmax); +} + +void +Pane::mousePressEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::RightButton) { + emit contextHelpChanged(""); + emit rightButtonMenuRequested(mapToGlobal(e->pos())); + return; + } + + m_clickPos = e->pos(); + m_clickedInRange = true; + m_editingSelection = Selection(); + m_editingSelectionEdge = 0; + m_shiftPressed = (e->modifiers() & Qt::ShiftModifier); + m_ctrlPressed = (e->modifiers() & Qt::ControlModifier); + m_dragMode = UnresolvedDrag; + + ViewManager::ToolMode mode = ViewManager::NavigateMode; + if (m_manager) mode = m_manager->getToolMode(); + + m_navigating = false; + + if (mode == ViewManager::NavigateMode || (e->buttons() & Qt::MidButton)) { + + if (mode != ViewManager::NavigateMode) { + setCursor(Qt::PointingHandCursor); + } + + m_navigating = true; + m_dragCentreFrame = m_centreFrame; + m_dragStartMinValue = 0; + + float vmin, vmax, dmin, dmax; + if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) { + m_dragStartMinValue = dmin; + } + + } else if (mode == ViewManager::SelectMode) { + + if (!hasTopLayerTimeXAxis()) return; + + bool closeToLeft = false, closeToRight = false; + Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight); + + if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { + + m_manager->removeSelection(selection); + + if (closeToLeft) { + m_selectionStartFrame = selection.getEndFrame(); + } else { + m_selectionStartFrame = selection.getStartFrame(); + } + + m_manager->setInProgressSelection(selection, false); + m_resizing = true; + + } else { + + int mouseFrame = getFrameForX(e->x()); + size_t resolution = 1; + int snapFrame = mouseFrame; + + Layer *layer = getSelectedLayer(); + if (layer && !m_shiftPressed) { + layer->snapToFeatureFrame(this, snapFrame, + resolution, Layer::SnapLeft); + } + + if (snapFrame < 0) snapFrame = 0; + m_selectionStartFrame = snapFrame; + if (m_manager) { + m_manager->setInProgressSelection(Selection(snapFrame, + snapFrame + resolution), + !m_ctrlPressed); + } + + m_resizing = false; + } + + update(); + + } else if (mode == ViewManager::DrawMode) { + + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawStart(this, e); + } + + } else if (mode == ViewManager::EditMode) { + + if (!editSelectionStart(e)) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->editStart(this, e); + } + } + } + + emit paneInteractedWith(); +} + +void +Pane::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::RightButton) { + return; + } + + ViewManager::ToolMode mode = ViewManager::NavigateMode; + if (m_manager) mode = m_manager->getToolMode(); + + if (m_clickedInRange) { + mouseMoveEvent(e); + } + + if (m_navigating || mode == ViewManager::NavigateMode) { + + m_navigating = false; + + if (mode != ViewManager::NavigateMode) { + // restore cursor + toolModeChanged(); + } + + if (m_shiftPressed) { + + int x0 = min(m_clickPos.x(), m_mousePos.x()); + int x1 = max(m_clickPos.x(), m_mousePos.x()); + + int y0 = min(m_clickPos.y(), m_mousePos.y()); + int y1 = max(m_clickPos.y(), m_mousePos.y()); + + zoomToRegion(x0, y0, x1, y1); + } + + } else if (mode == ViewManager::SelectMode) { + + if (!hasTopLayerTimeXAxis()) return; + + if (m_manager && m_manager->haveInProgressSelection()) { + + bool exclusive; + Selection selection = m_manager->getInProgressSelection(exclusive); + + if (selection.getEndFrame() < selection.getStartFrame() + 2) { + selection = Selection(); + } + + m_manager->clearInProgressSelection(); + + if (exclusive) { + m_manager->setSelection(selection); + } else { + m_manager->addSelection(selection); + } + } + + update(); + + } else if (mode == ViewManager::DrawMode) { + + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawEnd(this, e); + update(); + } + + } else if (mode == ViewManager::EditMode) { + + if (!editSelectionEnd(e)) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->editEnd(this, e); + update(); + } + } + } + + m_clickedInRange = false; + + emit paneInteractedWith(); +} + +void +Pane::mouseMoveEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::RightButton) { + return; + } + + updateContextHelp(&e->pos()); + + ViewManager::ToolMode mode = ViewManager::NavigateMode; + if (m_manager) mode = m_manager->getToolMode(); + + QPoint prevPoint = m_identifyPoint; + m_identifyPoint = e->pos(); + + if (!m_clickedInRange) { + + if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) { + bool closeToLeft = false, closeToRight = false; + getSelectionAt(e->x(), closeToLeft, closeToRight); + if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { + setCursor(Qt::SizeHorCursor); + } else { + setCursor(Qt::ArrowCursor); + } + } + + if (!m_manager->isPlaying()) { + + if (getSelectedLayer()) { + + bool previouslyIdentifying = m_identifyFeatures; + m_identifyFeatures = true; + + if (m_identifyFeatures != previouslyIdentifying || + m_identifyPoint != prevPoint) { + update(); + } + } + } + + return; + } + + if (m_navigating || mode == ViewManager::NavigateMode) { + + if (m_shiftPressed) { + + m_mousePos = e->pos(); + update(); + + } else { + + dragTopLayer(e); + } + + } else if (mode == ViewManager::SelectMode) { + + if (!hasTopLayerTimeXAxis()) return; + + dragExtendSelection(e); + + } else if (mode == ViewManager::DrawMode) { + + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->drawDrag(this, e); + } + + } else if (mode == ViewManager::EditMode) { + + if (!editSelectionDrag(e)) { + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->editDrag(this, e); + } + } + } +} + +void +Pane::zoomToRegion(int x0, int y0, int x1, int y1) +{ + int w = x1 - x0; + + long newStartFrame = getFrameForX(x0); + + long visibleFrames = getEndFrame() - getStartFrame(); + if (newStartFrame <= -visibleFrames) { + newStartFrame = -visibleFrames + 1; + } + + if (newStartFrame >= long(getModelsEndFrame())) { + newStartFrame = getModelsEndFrame() - 1; + } + + float ratio = float(w) / float(width()); +// std::cerr << "ratio: " << ratio << std::endl; + size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio); + if (newZoomLevel < 1) newZoomLevel = 1; + +// std::cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << std::endl; + setZoomLevel(getZoomConstraintBlockSize(newZoomLevel)); + setStartFrame(newStartFrame); + + QString unit; + float min, max; + bool log; + Layer *layer = 0; + for (LayerList::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + if ((*i)->getValueExtents(min, max, log, unit) && + (*i)->getDisplayExtents(min, max)) { + layer = *i; + break; + } + } + + if (layer) { + if (log) { + min = (min < 0.0) ? -log10f(-min) : (min == 0.0) ? 0.0 : log10f(min); + max = (max < 0.0) ? -log10f(-max) : (max == 0.0) ? 0.0 : log10f(max); + } + float rmin = min + ((max - min) * (height() - y1)) / height(); + float rmax = min + ((max - min) * (height() - y0)) / height(); + std::cerr << "min: " << min << ", max: " << max << ", y0: " << y0 << ", y1: " << y1 << ", h: " << height() << ", rmin: " << rmin << ", rmax: " << rmax << std::endl; + if (log) { + rmin = powf(10, rmin); + rmax = powf(10, rmax); + } + std::cerr << "finally: rmin: " << rmin << ", rmax: " << rmax << " " << unit.toStdString() << std::endl; + + layer->setDisplayExtents(rmin, rmax); + updateVerticalPanner(); + } +} + +void +Pane::dragTopLayer(QMouseEvent *e) +{ + // We need to avoid making it too easy to drag both + // horizontally and vertically, in the case where the + // mouse is moved "mostly" in horizontal or vertical axis + // with only a small variation in the other axis. This is + // particularly important during playback (when we want to + // avoid small horizontal motions) or in slow refresh + // layers like spectrogram (when we want to avoid small + // vertical motions). + // + // To this end we have horizontal and vertical thresholds + // and a series of states: unresolved, horizontally or + // vertically constrained, free. + // + // When the mouse first moves, we're unresolved: we + // restrict ourselves to whichever direction seems safest, + // until the mouse has passed a small threshold distance + // from the click point. Then we lock in to one of the + // constrained modes, based on which axis that distance + // was measured in first. Finally, if it turns out we've + // also moved more than a certain larger distance in the + // other direction as well, we may switch into free mode. + // + // If the top layer is incapable of being dragged + // vertically, the logic is short circuited. + + int xdiff = e->x() - m_clickPos.x(); + int ydiff = e->y() - m_clickPos.y(); + int smallThreshold = 10, bigThreshold = 50; + + bool canMoveVertical = canTopLayerMoveVertical(); + bool canMoveHorizontal = true; + + if (!canMoveHorizontal) { + m_dragMode = HorizontalDrag; + } + + if (m_dragMode == UnresolvedDrag) { + + if (abs(ydiff) > smallThreshold && + abs(ydiff) > abs(xdiff) * 2) { + m_dragMode = VerticalDrag; + } else if (abs(xdiff) > smallThreshold && + abs(xdiff) > abs(ydiff) * 2) { + m_dragMode = HorizontalDrag; + } else if (abs(xdiff) > smallThreshold && + abs(ydiff) > smallThreshold) { + m_dragMode = FreeDrag; + } else { + // When playing, we don't want to disturb the play + // position too easily; when not playing, we don't + // want to move up/down too easily + if (m_manager && m_manager->isPlaying()) { + canMoveHorizontal = false; + } else { + canMoveVertical = false; + } + } + } + + if (m_dragMode == VerticalDrag) { + if (abs(xdiff) > bigThreshold) m_dragMode = FreeDrag; + else canMoveHorizontal = false; + } + + if (m_dragMode == HorizontalDrag && canMoveVertical) { + if (abs(ydiff) > bigThreshold) m_dragMode = FreeDrag; + else canMoveVertical = false; + } + + if (canMoveHorizontal) { + + long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x()); + + size_t newCentreFrame = m_dragCentreFrame; + + if (frameOff < 0) { + newCentreFrame -= frameOff; + } else if (newCentreFrame >= size_t(frameOff)) { + newCentreFrame -= frameOff; + } else { + newCentreFrame = 0; + } + + if (newCentreFrame >= getModelsEndFrame()) { + newCentreFrame = getModelsEndFrame(); + if (newCentreFrame > 0) --newCentreFrame; + } + + if (getXForFrame(m_centreFrame) != getXForFrame(newCentreFrame)) { + setCentreFrame(newCentreFrame); + } + } + + if (canMoveVertical) { + + float vmin = 0.f, vmax = 0.f; + float dmin = 0.f, dmax = 0.f; + + if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) { + +// std::cerr << "ydiff = " << ydiff << std::endl; + + float perpix = (dmax - dmin) / height(); + float valdiff = ydiff * perpix; +// std::cerr << "valdiff = " << valdiff << std::endl; + + float newmin = m_dragStartMinValue + valdiff; + float newmax = m_dragStartMinValue + (dmax - dmin) + valdiff; + if (newmin < vmin) { + newmax += vmin - newmin; + newmin += vmin - newmin; + } + if (newmax > vmax) { + newmin -= newmax - vmax; + newmax -= newmax - vmax; + } +// std::cerr << "(" << dmin << ", " << dmax << ") -> (" +// << newmin << ", " << newmax << ") (drag start " << m_dragStartMinValue << ")" << std::endl; + + setTopLayerDisplayExtents(newmin, newmax); + updateVerticalPanner(); + } + } +} + +void +Pane::dragExtendSelection(QMouseEvent *e) +{ + int mouseFrame = getFrameForX(e->x()); + size_t resolution = 1; + int snapFrameLeft = mouseFrame; + int snapFrameRight = mouseFrame; + + Layer *layer = getSelectedLayer(); + if (layer && !m_shiftPressed) { + layer->snapToFeatureFrame(this, snapFrameLeft, + resolution, Layer::SnapLeft); + layer->snapToFeatureFrame(this, snapFrameRight, + resolution, Layer::SnapRight); + } + +// std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl; + + if (snapFrameLeft < 0) snapFrameLeft = 0; + if (snapFrameRight < 0) snapFrameRight = 0; + + size_t min, max; + + if (m_selectionStartFrame > size_t(snapFrameLeft)) { + min = snapFrameLeft; + max = m_selectionStartFrame; + } else if (size_t(snapFrameRight) > m_selectionStartFrame) { + min = m_selectionStartFrame; + max = snapFrameRight; + } else { + min = snapFrameLeft; + max = snapFrameRight; + } + + if (m_manager) { + m_manager->setInProgressSelection(Selection(min, max), + !m_resizing && !m_ctrlPressed); + } + + bool doScroll = false; + if (!m_manager) doScroll = true; + if (!m_manager->isPlaying()) doScroll = true; + if (m_followPlay != PlaybackScrollContinuous) doScroll = true; + + if (doScroll) { + int offset = mouseFrame - getStartFrame(); + int available = getEndFrame() - getStartFrame(); + if (offset >= available * 0.95) { + int move = int(offset - available * 0.95) + 1; + setCentreFrame(m_centreFrame + move); + } else if (offset <= available * 0.10) { + int move = int(available * 0.10 - offset) + 1; + if (move < 0) { + setCentreFrame(m_centreFrame + (-move)); + } else if (m_centreFrame > move) { + setCentreFrame(m_centreFrame - move); + } else { + setCentreFrame(0); + } + } + } + + update(); +} + +void +Pane::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::RightButton) { + return; + } + +// std::cerr << "mouseDoubleClickEvent" << std::endl; + + m_clickPos = e->pos(); + m_clickedInRange = true; + m_shiftPressed = (e->modifiers() & Qt::ShiftModifier); + m_ctrlPressed = (e->modifiers() & Qt::ControlModifier); + + ViewManager::ToolMode mode = ViewManager::NavigateMode; + if (m_manager) mode = m_manager->getToolMode(); + + if (mode == ViewManager::NavigateMode || + mode == ViewManager::EditMode) { + + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + layer->editOpen(this, e); + } + } +} + +void +Pane::leaveEvent(QEvent *) +{ + bool previouslyIdentifying = m_identifyFeatures; + m_identifyFeatures = false; + if (previouslyIdentifying) update(); + emit contextHelpChanged(""); +} + +void +Pane::resizeEvent(QResizeEvent *) +{ + updateHeadsUpDisplay(); +} + +void +Pane::wheelEvent(QWheelEvent *e) +{ + //std::cerr << "wheelEvent, delta " << e->delta() << std::endl; + + int count = e->delta(); + + if (count > 0) { + if (count >= 120) count /= 120; + else count = 1; + } + + if (count < 0) { + if (count <= -120) count /= 120; + else count = -1; + } + + if (e->modifiers() & Qt::ControlModifier) { + + // Scroll left or right, rapidly + + if (getStartFrame() < 0 && + getEndFrame() >= getModelsEndFrame()) return; + + long delta = ((width() / 2) * count * m_zoomLevel); + + if (int(m_centreFrame) < delta) { + setCentreFrame(0); + } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) { + setCentreFrame(getModelsEndFrame()); + } else { + setCentreFrame(m_centreFrame - delta); + } + + } else { + + // Zoom in or out + + int newZoomLevel = m_zoomLevel; + + while (count > 0) { + if (newZoomLevel <= 2) { + newZoomLevel = 1; + break; + } + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, + ZoomConstraint::RoundDown); + --count; + } + + while (count < 0) { + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, + ZoomConstraint::RoundUp); + ++count; + } + + if (newZoomLevel != m_zoomLevel) { + setZoomLevel(newZoomLevel); + } + } + + emit paneInteractedWith(); +} + +void +Pane::horizontalThumbwheelMoved(int value) +{ + //!!! dupe with updateHeadsUpDisplay + + int count = 0; + int level = 1; + + + //!!! pull out into function (presumably in View) + bool haveConstraint = false; + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); + ++i) { + if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) { + haveConstraint = true; + break; + } + } + + if (haveConstraint) { + while (true) { + if (m_hthumb->getMaximumValue() - value == count) break; + int newLevel = getZoomConstraintBlockSize(level + 1, + ZoomConstraint::RoundUp); + if (newLevel == level) break; + level = newLevel; + if (++count == 50) break; + } + } else { + while (true) { + if (m_hthumb->getMaximumValue() - value == count) break; + int step = level / 10; + int pwr = 0; + while (step > 0) { + ++pwr; + step /= 2; + } + step = 1; + while (pwr > 0) { + step *= 2; + --pwr; + } +// std::cerr << level << std::endl; + level += step; + if (++count == 100 || level > 262144) break; + } + } + +// std::cerr << "new level is " << level << std::endl; + setZoomLevel(level); +} + +void +Pane::verticalThumbwheelMoved(int value) +{ + Layer *layer = 0; + if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); + if (layer) { + int defaultStep = 0; + int max = layer->getVerticalZoomSteps(defaultStep); + if (max == 0) { + updateHeadsUpDisplay(); + return; + } + if (value > max) { + value = max; + } + layer->setVerticalZoomStep(value); + updateVerticalPanner(); + } +} + +void +Pane::verticalPannerMoved(float x0, float y0, float w, float h) +{ + float vmin, vmax, dmin, dmax; + if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return; + float y1 = y0 + h; + float newmax = vmin + ((1.0 - y0) * (vmax - vmin)); + float newmin = vmin + ((1.0 - y1) * (vmax - vmin)); + std::cerr << "verticalPannerMoved: (" << x0 << "," << y0 << "," << w + << "," << h << ") -> (" << newmin << "," << newmax << ")" << std::endl; + setTopLayerDisplayExtents(newmin, newmax); +} + +void +Pane::editVerticalPannerExtents() +{ + if (!m_vpan || !m_manager || !m_manager->getZoomWheelsEnabled()) return; + + float vmin, vmax, dmin, dmax; + QString unit; + if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax, &unit) + || vmax == vmin) { + return; + } + + RangeInputDialog dialog(tr("Enter new range"), + tr("New vertical display range, from %1 to %2 %4:") + .arg(vmin).arg(vmax).arg(unit), + unit, vmin, vmax, this); + dialog.setRange(dmin, dmax); + + if (dialog.exec() == QDialog::Accepted) { + dialog.getRange(dmin, dmax); + setTopLayerDisplayExtents(dmin, dmax); + updateVerticalPanner(); + } +} + +bool +Pane::editSelectionStart(QMouseEvent *e) +{ + if (!m_identifyFeatures || + !m_manager || + m_manager->getToolMode() != ViewManager::EditMode) { + return false; + } + + bool closeToLeft, closeToRight; + Selection s(getSelectionAt(e->x(), closeToLeft, closeToRight)); + if (s.isEmpty()) return false; + m_editingSelection = s; + m_editingSelectionEdge = (closeToLeft ? -1 : closeToRight ? 1 : 0); + m_mousePos = e->pos(); + return true; +} + +bool +Pane::editSelectionDrag(QMouseEvent *e) +{ + if (m_editingSelection.isEmpty()) return false; + m_mousePos = e->pos(); + update(); + return true; +} + +bool +Pane::editSelectionEnd(QMouseEvent *) +{ + if (m_editingSelection.isEmpty()) return false; + + int offset = m_mousePos.x() - m_clickPos.x(); + Layer *layer = getSelectedLayer(); + + if (offset == 0 || !layer) { + m_editingSelection = Selection(); + return true; + } + + int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset; + int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset; + + long f0 = getFrameForX(p0); + long f1 = getFrameForX(p1); + + Selection newSelection(f0, f1); + + if (m_editingSelectionEdge == 0) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Drag Selection"), true); + + layer->moveSelection(m_editingSelection, f0); + + } else { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Resize Selection"), true); + + if (m_editingSelectionEdge < 0) { + f1 = m_editingSelection.getEndFrame(); + } else { + f0 = m_editingSelection.getStartFrame(); + } + + newSelection = Selection(f0, f1); + layer->resizeSelection(m_editingSelection, newSelection); + } + + m_manager->removeSelection(m_editingSelection); + m_manager->addSelection(newSelection); + + CommandHistory::getInstance()->endCompoundOperation(); + + m_editingSelection = Selection(); + return true; +} + +void +Pane::toolModeChanged() +{ + ViewManager::ToolMode mode = m_manager->getToolMode(); +// std::cerr << "Pane::toolModeChanged(" << mode << ")" << std::endl; + + switch (mode) { + + case ViewManager::NavigateMode: + setCursor(Qt::PointingHandCursor); + break; + + case ViewManager::SelectMode: + setCursor(Qt::ArrowCursor); + break; + + case ViewManager::EditMode: + setCursor(Qt::UpArrowCursor); + break; + + case ViewManager::DrawMode: + setCursor(Qt::CrossCursor); + break; +/* + case ViewManager::TextMode: + setCursor(Qt::IBeamCursor); + break; +*/ + } +} + +void +Pane::zoomWheelsEnabledChanged() +{ + updateHeadsUpDisplay(); + update(); +} + +void +Pane::viewZoomLevelChanged(View *v, unsigned long z, bool locked) +{ +// std::cerr << "Pane[" << this << "]::zoomLevelChanged (global now " +// << (m_manager ? m_manager->getGlobalZoom() : 0) << ")" << std::endl; + + View::viewZoomLevelChanged(v, z, locked); + + if (m_hthumb && !m_hthumb->isVisible()) return; + + if (v != this) { + if (!locked || !m_followZoom) return; + } + + if (m_manager && m_manager->getZoomWheelsEnabled()) { + updateHeadsUpDisplay(); + } +} + +void +Pane::propertyContainerSelected(View *v, PropertyContainer *pc) +{ + Layer *layer = 0; + + if (getLayerCount() > 0) { + layer = getLayer(getLayerCount() - 1); + disconnect(layer, SIGNAL(verticalZoomChanged()), + this, SLOT(verticalZoomChanged())); + } + + View::propertyContainerSelected(v, pc); + updateHeadsUpDisplay(); + + if (m_vthumb) { + RangeMapper *rm = 0; + if (layer) rm = layer->getNewVerticalZoomRangeMapper(); + if (rm) m_vthumb->setRangeMapper(rm); + } + + if (getLayerCount() > 0) { + layer = getLayer(getLayerCount() - 1); + connect(layer, SIGNAL(verticalZoomChanged()), + this, SLOT(verticalZoomChanged())); + } +} + +void +Pane::verticalZoomChanged() +{ + Layer *layer = 0; + + if (getLayerCount() > 0) { + + layer = getLayer(getLayerCount() - 1); + + if (m_vthumb && m_vthumb->isVisible()) { + m_vthumb->setValue(layer->getCurrentVerticalZoomStep()); + } + } +} + +void +Pane::updateContextHelp(const QPoint *pos) +{ + QString help = ""; + + if (m_clickedInRange) { + emit contextHelpChanged(""); + return; + } + + ViewManager::ToolMode mode = ViewManager::NavigateMode; + if (m_manager) mode = m_manager->getToolMode(); + + bool editable = false; + Layer *layer = getSelectedLayer(); + if (layer && layer->isLayerEditable()) { + editable = true; + } + + if (mode == ViewManager::NavigateMode) { + + help = tr("Click and drag to navigate"); + + } else if (mode == ViewManager::SelectMode) { + + if (!hasTopLayerTimeXAxis()) return; + + bool haveSelection = (m_manager && !m_manager->getSelections().empty()); + + if (haveSelection) { + if (editable) { + help = tr("Click and drag to select a range; hold Shift to avoid snapping to items; hold Ctrl for multi-select; middle-click and drag to navigate"); + } else { + help = tr("Click and drag to select a range; hold Ctrl for multi-select; middle-click and drag to navigate"); + } + + if (pos) { + bool closeToLeft = false, closeToRight = false; + Selection selection = getSelectionAt(pos->x(), closeToLeft, closeToRight); + if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { + + help = tr("Click and drag to move the selection boundary"); + } + } + } else { + if (editable) { + help = tr("Click and drag to select a range; hold Shift to avoid snapping to items; middle-click to navigate"); + } else { + help = tr("Click and drag to select a range; middle-click and drag to navigate"); + } + } + + } else if (mode == ViewManager::DrawMode) { + + //!!! could call through to a layer function to find out exact meaning + if (editable) { + help = tr("Click to add a new item in the active layer"); + } + + } else if (mode == ViewManager::EditMode) { + + //!!! could call through to layer + if (editable) { + help = tr("Click and drag an item in the active layer to move it"); + if (pos) { + bool closeToLeft = false, closeToRight = false; + Selection selection = getSelectionAt(pos->x(), closeToLeft, closeToRight); + if (!selection.isEmpty()) { + help = tr("Click and drag to move all items in the selected range"); + } + } + } + } + + emit contextHelpChanged(help); +} + +void +Pane::mouseEnteredWidget() +{ + QWidget *w = dynamic_cast(sender()); + if (!w) return; + + if (w == m_vpan) { + emit contextHelpChanged(tr("Click and drag to adjust the visible range of the vertical scale")); + } else if (w == m_vthumb) { + emit contextHelpChanged(tr("Click and drag to adjust the vertical zoom level")); + } else if (w == m_hthumb) { + emit contextHelpChanged(tr("Click and drag to adjust the horizontal zoom level")); + } else if (w == m_reset) { + emit contextHelpChanged(tr("Reset horizontal and vertical zoom levels to their defaults")); + } +} + +void +Pane::mouseLeftWidget() +{ + emit contextHelpChanged(""); +} + +QString +Pane::toXmlString(QString indent, QString extraAttributes) const +{ + return View::toXmlString + (indent, + QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3") + .arg(m_centreLineVisible).arg(height()).arg(extraAttributes)); +} + + diff -r 000000000000 -r fc9323a41f5a view/Pane.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/Pane.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,147 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PANE_H_ +#define _PANE_H_ + +#include +#include + +#include "base/ZoomConstraint.h" +#include "View.h" +#include "base/Selection.h" + +class QWidget; +class QPaintEvent; +class Layer; +class Thumbwheel; +class Panner; +class NotifyingPushButton; + +class Pane : public View +{ + Q_OBJECT + +public: + Pane(QWidget *parent = 0); + virtual QString getPropertyContainerIconName() const { return "pane"; } + + virtual bool shouldIlluminateLocalFeatures(const Layer *layer, + QPoint &pos) const; + virtual bool shouldIlluminateLocalSelection(QPoint &pos, + bool &closeToLeft, + bool &closeToRight) const; + + void setCentreLineVisible(bool visible); + bool getCentreLineVisible() const { return m_centreLineVisible; } + + virtual size_t getFirstVisibleFrame() const; + + virtual QImage *toNewImage(size_t f0, size_t f1); + virtual QImage *toNewImage() { return View::toNewImage(); } + virtual QSize getImageSize(size_t f0, size_t f1); + virtual QSize getImageSize() { return View::getImageSize(); } + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + +signals: + void paneInteractedWith(); + void rightButtonMenuRequested(QPoint position); + +public slots: + virtual void toolModeChanged(); + virtual void zoomWheelsEnabledChanged(); + virtual void viewZoomLevelChanged(View *v, unsigned long z, bool locked); + + virtual void horizontalThumbwheelMoved(int value); + virtual void verticalThumbwheelMoved(int value); + virtual void verticalZoomChanged(); + virtual void verticalPannerMoved(float x, float y, float w, float h); + virtual void editVerticalPannerExtents(); + + virtual void propertyContainerSelected(View *, PropertyContainer *pc); + + void mouseEnteredWidget(); + void mouseLeftWidget(); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void leaveEvent(QEvent *e); + virtual void wheelEvent(QWheelEvent *e); + virtual void resizeEvent(QResizeEvent *e); + + virtual bool render(QPainter &paint, int x0, size_t f0, size_t f1); + + Selection getSelectionAt(int x, bool &closeToLeft, bool &closeToRight) const; + + bool editSelectionStart(QMouseEvent *e); + bool editSelectionDrag(QMouseEvent *e); + bool editSelectionEnd(QMouseEvent *e); + bool selectionIsBeingEdited() const; + + void updateHeadsUpDisplay(); + void updateVerticalPanner(); + + bool canTopLayerMoveVertical(); + bool getTopLayerDisplayExtents(float &valueMin, float &valueMax, + float &displayMin, float &displayMax, + QString *unit = 0); + bool setTopLayerDisplayExtents(float displayMin, float displayMax); + + void dragTopLayer(QMouseEvent *e); + void dragExtendSelection(QMouseEvent *e); + void zoomToRegion(int x0, int y0, int x1, int y1); + void updateContextHelp(const QPoint *pos); + + bool m_identifyFeatures; + QPoint m_identifyPoint; + QPoint m_clickPos; + QPoint m_mousePos; + bool m_clickedInRange; + bool m_shiftPressed; + bool m_ctrlPressed; + bool m_navigating; + bool m_resizing; + size_t m_dragCentreFrame; + float m_dragStartMinValue; + bool m_centreLineVisible; + size_t m_selectionStartFrame; + Selection m_editingSelection; + int m_editingSelectionEdge; + mutable int m_scaleWidth; + + enum DragMode { + UnresolvedDrag, + VerticalDrag, + HorizontalDrag, + FreeDrag + }; + DragMode m_dragMode; + + QWidget *m_headsUpDisplay; + Panner *m_vpan; + Thumbwheel *m_hthumb; + Thumbwheel *m_vthumb; + NotifyingPushButton *m_reset; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a view/PaneStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/PaneStack.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,464 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PaneStack.h" + +#include "Pane.h" +#include "widgets/PropertyStack.h" +#include "layer/Layer.h" +#include "ViewManager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +//#define DEBUG_PANE_STACK 1 + +PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) : + QFrame(parent), + m_currentPane(0), + m_splitter(new QSplitter), + m_propertyStackStack(new QStackedWidget), + m_viewManager(viewManager), + m_layoutStyle(PropertyStackPerPaneLayout) +{ + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + m_splitter->setOrientation(Qt::Vertical); + m_splitter->setOpaqueResize(false); + + layout->addWidget(m_splitter); + layout->setStretchFactor(m_splitter, 1); + layout->addWidget(m_propertyStackStack); + m_propertyStackStack->hide(); + + setLayout(layout); +} + +Pane * +PaneStack::addPane(bool suppressPropertyBox) +{ + QFrame *frame = new QFrame; + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + layout->setSpacing(2); + + QLabel *currentIndicator = new QLabel(frame); + currentIndicator->setFixedWidth(QPainter(this).fontMetrics().width("x")); + layout->addWidget(currentIndicator); + layout->setStretchFactor(currentIndicator, 1); + currentIndicator->setScaledContents(true); + + Pane *pane = new Pane(frame); + pane->setViewManager(m_viewManager); + layout->addWidget(pane); + layout->setStretchFactor(pane, 10); + + QWidget *properties = 0; + if (suppressPropertyBox) { + properties = new QFrame(); + } else { + properties = new PropertyStack(frame, pane); + connect(properties, SIGNAL(propertyContainerSelected(View *, PropertyContainer *)), + this, SLOT(propertyContainerSelected(View *, PropertyContainer *))); + connect(properties, SIGNAL(viewSelected(View *)), + this, SLOT(viewSelected(View *))); + connect(properties, SIGNAL(contextHelpChanged(const QString &)), + this, SIGNAL(contextHelpChanged(const QString &))); + } + if (m_layoutStyle == PropertyStackPerPaneLayout) { + layout->addWidget(properties); + } else { + properties->setParent(m_propertyStackStack); + m_propertyStackStack->addWidget(properties); + } + layout->setStretchFactor(properties, 1); + + PaneRec rec; + rec.pane = pane; + rec.propertyStack = properties; + rec.currentIndicator = currentIndicator; + rec.frame = frame; + rec.layout = layout; + m_panes.push_back(rec); + + frame->setLayout(layout); + m_splitter->addWidget(frame); + + connect(pane, SIGNAL(propertyContainerAdded(PropertyContainer *)), + this, SLOT(propertyContainerAdded(PropertyContainer *))); + connect(pane, SIGNAL(propertyContainerRemoved(PropertyContainer *)), + this, SLOT(propertyContainerRemoved(PropertyContainer *))); + connect(pane, SIGNAL(paneInteractedWith()), + this, SLOT(paneInteractedWith())); + connect(pane, SIGNAL(rightButtonMenuRequested(QPoint)), + this, SLOT(rightButtonMenuRequested(QPoint))); + + if (!m_currentPane) { + setCurrentPane(pane); + } + + return pane; +} + +void +PaneStack::setPropertyStackMinWidth(int mw) +{ + for (std::vector::iterator i = m_panes.begin(); + i != m_panes.end(); ++i) { + i->propertyStack->setMinimumWidth(mw); + } + m_propertyStackMinWidth = mw; +} + +void +PaneStack::setLayoutStyle(LayoutStyle style) +{ + if (style == m_layoutStyle) return; + m_layoutStyle = style; + + std::vector::iterator i; + + switch (style) { + + case NoPropertyStacks: + case SinglePropertyStackLayout: + + for (i = m_panes.begin(); i != m_panes.end(); ++i) { + i->layout->removeWidget(i->propertyStack); + i->propertyStack->setParent(m_propertyStackStack); + m_propertyStackStack->addWidget(i->propertyStack); + } + m_propertyStackStack->setVisible(style != NoPropertyStacks); + break; + + case PropertyStackPerPaneLayout: + + for (i = m_panes.begin(); i != m_panes.end(); ++i) { + m_propertyStackStack->removeWidget(i->propertyStack); + i->propertyStack->setParent(i->frame); + i->layout->addWidget(i->propertyStack); + i->propertyStack->show(); + } + m_propertyStackStack->hide(); + break; + } +} + +Pane * +PaneStack::getPane(int n) +{ + return m_panes[n].pane; +} + +Pane * +PaneStack::getHiddenPane(int n) +{ + return m_hiddenPanes[n].pane; +} + +void +PaneStack::deletePane(Pane *pane) +{ + std::vector::iterator i; + bool found = false; + + for (i = m_panes.begin(); i != m_panes.end(); ++i) { + if (i->pane == pane) { + m_panes.erase(i); + found = true; + break; + } + } + + if (!found) { + + for (i = m_hiddenPanes.begin(); i != m_hiddenPanes.end(); ++i) { + if (i->pane == pane) { + m_hiddenPanes.erase(i); + found = true; + break; + } + } + + if (!found) { + std::cerr << "WARNING: PaneStack::deletePane(" << pane << "): Pane not found in visible or hidden panes, not deleting" << std::endl; + return; + } + } + + delete pane->parent(); + + if (m_currentPane == pane) { + if (m_panes.size() > 0) { + setCurrentPane(m_panes[0].pane); + } else { + setCurrentPane(0); + } + } +} + +int +PaneStack::getPaneCount() const +{ + return m_panes.size(); +} + +int +PaneStack::getHiddenPaneCount() const +{ + return m_hiddenPanes.size(); +} + +void +PaneStack::hidePane(Pane *pane) +{ + std::vector::iterator i = m_panes.begin(); + + while (i != m_panes.end()) { + if (i->pane == pane) { + + m_hiddenPanes.push_back(*i); + m_panes.erase(i); + + QWidget *pw = dynamic_cast(pane->parent()); + if (pw) pw->hide(); + + if (m_currentPane == pane) { + if (m_panes.size() > 0) { + setCurrentPane(m_panes[0].pane); + } else { + setCurrentPane(0); + } + } + + return; + } + ++i; + } + + std::cerr << "WARNING: PaneStack::hidePane(" << pane << "): Pane not found in visible panes" << std::endl; +} + +void +PaneStack::showPane(Pane *pane) +{ + std::vector::iterator i = m_hiddenPanes.begin(); + + while (i != m_hiddenPanes.end()) { + if (i->pane == pane) { + m_panes.push_back(*i); + m_hiddenPanes.erase(i); + QWidget *pw = dynamic_cast(pane->parent()); + if (pw) pw->show(); + + //!!! update current pane + + return; + } + ++i; + } + + std::cerr << "WARNING: PaneStack::showPane(" << pane << "): Pane not found in hidden panes" << std::endl; +} + +void +PaneStack::setCurrentPane(Pane *pane) // may be null +{ + if (m_currentPane == pane) return; + + std::vector::iterator i = m_panes.begin(); + + // We used to do this by setting the foreground and background + // role, but it seems the background role is ignored and the + // background drawn transparent in Qt 4.1 -- I can't quite see why + + QPixmap selectedMap(1, 1); + selectedMap.fill(QApplication::palette().color(QPalette::Foreground)); + + QPixmap unselectedMap(1, 1); + unselectedMap.fill(QApplication::palette().color(QPalette::Background)); + + bool found = false; + + while (i != m_panes.end()) { + if (i->pane == pane) { + i->currentIndicator->setPixmap(selectedMap); + if (m_layoutStyle != PropertyStackPerPaneLayout) { + m_propertyStackStack->setCurrentWidget(i->propertyStack); + } + found = true; + } else { + i->currentIndicator->setPixmap(unselectedMap); + } + ++i; + } + + if (found || pane == 0) { + m_currentPane = pane; + emit currentPaneChanged(m_currentPane); + } else { + std::cerr << "WARNING: PaneStack::setCurrentPane(" << pane << "): pane is not a visible pane in this stack" << std::endl; + } +} + +void +PaneStack::setCurrentLayer(Pane *pane, Layer *layer) // may be null +{ + setCurrentPane(pane); + + if (m_currentPane) { + + std::vector::iterator i = m_panes.begin(); + + while (i != m_panes.end()) { + + if (i->pane == pane) { + PropertyStack *stack = dynamic_cast + (i->propertyStack); + if (stack) { + if (stack->containsContainer(layer)) { + stack->setCurrentIndex(stack->getContainerIndex(layer)); + emit currentLayerChanged(pane, layer); + } else { + stack->setCurrentIndex + (stack->getContainerIndex + (pane->getPropertyContainer(0))); + emit currentLayerChanged(pane, 0); + } + } + break; + } + ++i; + } + } +} + +Pane * +PaneStack::getCurrentPane() +{ + return m_currentPane; +} + +void +PaneStack::propertyContainerAdded(PropertyContainer *) +{ + sizePropertyStacks(); +} + +void +PaneStack::propertyContainerRemoved(PropertyContainer *) +{ + sizePropertyStacks(); +} + +void +PaneStack::propertyContainerSelected(View *client, PropertyContainer *pc) +{ + std::vector::iterator i = m_panes.begin(); + + while (i != m_panes.end()) { + PropertyStack *stack = dynamic_cast(i->propertyStack); + if (stack && + stack->getClient() == client && + stack->containsContainer(pc)) { + setCurrentPane(i->pane); + break; + } + ++i; + } + + Layer *layer = dynamic_cast(pc); + if (layer) emit currentLayerChanged(m_currentPane, layer); + else emit currentLayerChanged(m_currentPane, 0); +} + +void +PaneStack::viewSelected(View *v) +{ + Pane *p = dynamic_cast(v); + if (p) setCurrentPane(p); +} + +void +PaneStack::paneInteractedWith() +{ + Pane *pane = dynamic_cast(sender()); + if (!pane) return; + setCurrentPane(pane); +} + +void +PaneStack::rightButtonMenuRequested(QPoint position) +{ + Pane *pane = dynamic_cast(sender()); + if (!pane) return; + emit rightButtonMenuRequested(pane, position); +} + +void +PaneStack::sizePropertyStacks() +{ + int maxMinWidth = 0; + + if (m_propertyStackMinWidth > 0) maxMinWidth = m_propertyStackMinWidth; + + for (size_t i = 0; i < m_panes.size(); ++i) { + if (!m_panes[i].propertyStack) continue; +#ifdef DEBUG_PANE_STACK + std::cerr << "PaneStack::sizePropertyStacks: " << i << ": min " + << m_panes[i].propertyStack->minimumSizeHint().width() << ", hint " + << m_panes[i].propertyStack->sizeHint().width() << ", current " + << m_panes[i].propertyStack->width() << std::endl; +#endif + + if (m_panes[i].propertyStack->sizeHint().width() > maxMinWidth) { + maxMinWidth = m_panes[i].propertyStack->sizeHint().width(); + } + } + +#ifdef DEBUG_PANE_STACK + std::cerr << "PaneStack::sizePropertyStacks: max min width " << maxMinWidth << std::endl; +#endif + +//#ifdef Q_WS_MAC + // This is necessary to compensate for cb->setMinimumSize(10, 10) + // in PropertyBox in the Mac version (to avoid a mysterious crash) + // ... no longer necessary with qt4.2 +// int setWidth = maxMinWidth * 3 / 2; +//#else + int setWidth = maxMinWidth; +//#endif + + m_propertyStackStack->setMaximumWidth(setWidth + 10); + + for (size_t i = 0; i < m_panes.size(); ++i) { + if (!m_panes[i].propertyStack) continue; + m_panes[i].propertyStack->setMinimumWidth(setWidth); + } + + emit propertyStacksResized(); +} + + diff -r 000000000000 -r fc9323a41f5a view/PaneStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/PaneStack.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,109 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PANESTACK_H_ +#define _PANESTACK_H_ + +#include + +class QWidget; +class QLabel; +class QStackedWidget; +class QSplitter; +class QHBoxLayout; +class View; +class Pane; +class Layer; +class ViewManager; +class PropertyContainer; +class PropertyStack; + +class PaneStack : public QFrame +{ + Q_OBJECT + +public: + PaneStack(QWidget *parent, ViewManager *viewManager); + + Pane *addPane(bool suppressPropertyBox = false); // I own the returned value + void deletePane(Pane *pane); // Deletes the pane, but _not_ its layers + + int getPaneCount() const; // Returns only count of visible panes + Pane *getPane(int n); // Of visible panes; I own the returned value + + void hidePane(Pane *pane); // Also removes pane from getPane/getPaneCount + void showPane(Pane *pane); // Returns pane to getPane/getPaneCount + + int getHiddenPaneCount() const; + Pane *getHiddenPane(int n); // I own the returned value + + void setCurrentPane(Pane *pane); + void setCurrentLayer(Pane *pane, Layer *layer); + Pane *getCurrentPane(); + + enum LayoutStyle { + NoPropertyStacks = 0, + SinglePropertyStackLayout = 1, + PropertyStackPerPaneLayout = 2 + }; + + LayoutStyle getLayoutStyle() const { return m_layoutStyle; } + void setLayoutStyle(LayoutStyle style); + + void setPropertyStackMinWidth(int mw); + +signals: + void currentPaneChanged(Pane *pane); + void currentLayerChanged(Pane *pane, Layer *layer); + void rightButtonMenuRequested(Pane *pane, QPoint position); + void propertyStacksResized(); + void contextHelpChanged(const QString &); + +public slots: + void propertyContainerAdded(PropertyContainer *); + void propertyContainerRemoved(PropertyContainer *); + void propertyContainerSelected(View *client, PropertyContainer *); + void viewSelected(View *v); + void paneInteractedWith(); + void rightButtonMenuRequested(QPoint); + +protected: + Pane *m_currentPane; + + struct PaneRec + { + Pane *pane; + QWidget *propertyStack; + QLabel *currentIndicator; + QFrame *frame; + QHBoxLayout *layout; + }; + + std::vector m_panes; + std::vector m_hiddenPanes; + + QSplitter *m_splitter; + QStackedWidget *m_propertyStackStack; + + ViewManager *m_viewManager; // I don't own this + int m_propertyStackMinWidth; + void sizePropertyStacks(); + + LayoutStyle m_layoutStyle; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a view/View.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/View.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,1821 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "View.h" +#include "layer/Layer.h" +#include "data/model/Model.h" +#include "base/ZoomConstraint.h" +#include "base/Profiler.h" + +#include "layer/TimeRulerLayer.h" //!!! damn, shouldn't be including that here +#include "data/model/PowerOfSqrtTwoZoomConstraint.h" //!!! likewise + +#include +#include +#include +#include +#include + +#include +#include +#include + +//#define DEBUG_VIEW_WIDGET_PAINT 1 + +using std::cerr; +using std::endl; + +View::View(QWidget *w, bool showProgress) : + QFrame(w), + m_centreFrame(0), + m_zoomLevel(1024), + m_followPan(true), + m_followZoom(true), + m_followPlay(PlaybackScrollPage), + m_playPointerFrame(0), + m_showProgress(showProgress), + m_cache(0), + m_cacheCentreFrame(0), + m_cacheZoomLevel(1024), + m_selectionCached(false), + m_deleting(false), + m_haveSelectedLayer(false), + m_manager(0), + m_propertyContainer(new ViewPropertyContainer(this)) +{ +} + +View::~View() +{ +// std::cerr << "View::~View(" << this << ")" << std::endl; + + m_deleting = true; + delete m_propertyContainer; +} + +PropertyContainer::PropertyList +View::getProperties() const +{ + PropertyContainer::PropertyList list; + list.push_back("Global Scroll"); + list.push_back("Global Zoom"); + list.push_back("Follow Playback"); + return list; +} + +QString +View::getPropertyLabel(const PropertyName &pn) const +{ + if (pn == "Global Scroll") return tr("Global Scroll"); + if (pn == "Global Zoom") return tr("Global Zoom"); + if (pn == "Follow Playback") return tr("Follow Playback"); + return ""; +} + +PropertyContainer::PropertyType +View::getPropertyType(const PropertyContainer::PropertyName &name) const +{ + if (name == "Global Scroll") return PropertyContainer::ToggleProperty; + if (name == "Global Zoom") return PropertyContainer::ToggleProperty; + if (name == "Follow Playback") return PropertyContainer::ValueProperty; + return PropertyContainer::InvalidProperty; +} + +int +View::getPropertyRangeAndValue(const PropertyContainer::PropertyName &name, + int *min, int *max, int *deflt) const +{ + if (deflt) *deflt = 1; + if (name == "Global Scroll") return m_followPan; + if (name == "Global Zoom") return m_followZoom; + if (name == "Follow Playback") { + if (min) *min = 0; + if (max) *max = 2; + if (deflt) *deflt = int(PlaybackScrollPage); + return int(m_followPlay); + } + if (min) *min = 0; + if (max) *max = 0; + if (deflt) *deflt = 0; + return 0; +} + +QString +View::getPropertyValueLabel(const PropertyContainer::PropertyName &name, + int value) const +{ + if (name == "Follow Playback") { + switch (value) { + default: + case 0: return tr("Scroll"); + case 1: return tr("Page"); + case 2: return tr("Off"); + } + } + return tr(""); +} + +void +View::setProperty(const PropertyContainer::PropertyName &name, int value) +{ + if (name == "Global Scroll") { + setFollowGlobalPan(value != 0); + } else if (name == "Global Zoom") { + setFollowGlobalZoom(value != 0); + } else if (name == "Follow Playback") { + switch (value) { + default: + case 0: setPlaybackFollow(PlaybackScrollContinuous); break; + case 1: setPlaybackFollow(PlaybackScrollPage); break; + case 2: setPlaybackFollow(PlaybackIgnore); break; + } + } +} + +size_t +View::getPropertyContainerCount() const +{ + return m_layers.size() + 1; // the 1 is for me +} + +const PropertyContainer * +View::getPropertyContainer(size_t i) const +{ + return (const PropertyContainer *)(((View *)this)-> + getPropertyContainer(i)); +} + +PropertyContainer * +View::getPropertyContainer(size_t i) +{ + if (i == 0) return m_propertyContainer; + return m_layers[i-1]; +} + +bool +View::getValueExtents(QString unit, float &min, float &max, bool &log) const +{ + bool have = false; + + for (LayerList::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + QString layerUnit; + float layerMin = 0.0, layerMax = 0.0; + float displayMin = 0.0, displayMax = 0.0; + bool layerLog = false; + + if ((*i)->getValueExtents(layerMin, layerMax, layerLog, layerUnit) && + layerUnit.toLower() == unit.toLower()) { + + if ((*i)->getDisplayExtents(displayMin, displayMax)) { + + min = displayMin; + max = displayMax; + log = layerLog; + have = true; + break; + + } else { + + if (!have || layerMin < min) min = layerMin; + if (!have || layerMax > max) max = layerMax; + if (layerLog) log = true; + have = true; + } + } + } + + return have; +} + +int +View::getTextLabelHeight(const Layer *layer, QPainter &paint) const +{ + std::map sortedLayers; + + for (LayerList::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + if ((*i)->needsTextLabelHeight()) { + sortedLayers[getObjectExportId(*i)] = *i; + } + } + + int y = 15 + paint.fontMetrics().ascent(); + + for (std::map::const_iterator i = sortedLayers.begin(); + i != sortedLayers.end(); ++i) { + if (i->second == layer) return y; + y += paint.fontMetrics().height(); + } + + return y; +} + +void +View::propertyContainerSelected(View *client, PropertyContainer *pc) +{ + if (client != this) return; + + if (pc == m_propertyContainer) { + if (m_haveSelectedLayer) { + m_haveSelectedLayer = false; + update(); + } + return; + } + + delete m_cache; + m_cache = 0; + + Layer *selectedLayer = 0; + + for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if (*i == pc) { + selectedLayer = *i; + m_layers.erase(i); + break; + } + } + + if (selectedLayer) { + m_haveSelectedLayer = true; + m_layers.push_back(selectedLayer); + update(); + } else { + m_haveSelectedLayer = false; + } +} + +void +View::toolModeChanged() +{ +// std::cerr << "View::toolModeChanged(" << m_manager->getToolMode() << ")" << std::endl; +} + +void +View::overlayModeChanged() +{ + delete m_cache; + m_cache = 0; + update(); +} + +void +View::zoomWheelsEnabledChanged() +{ + // subclass might override this +} + +long +View::getStartFrame() const +{ + size_t w2 = (width() / 2) * m_zoomLevel; + size_t frame = m_centreFrame; + if (frame >= w2) { + frame -= w2; + return (frame / m_zoomLevel * m_zoomLevel); + } else { + frame = w2 - frame; + frame = frame / m_zoomLevel * m_zoomLevel; + return -(long)frame - m_zoomLevel; + } +} + +size_t +View::getEndFrame() const +{ + return getFrameForX(width()) - 1; +} + +void +View::setStartFrame(long f) +{ + setCentreFrame(f + m_zoomLevel * (width() / 2)); +} + +bool +View::setCentreFrame(size_t f, bool e) +{ + bool changeVisible = false; + + if (m_centreFrame != f) { + + int formerPixel = m_centreFrame / m_zoomLevel; + + m_centreFrame = f; + + int newPixel = m_centreFrame / m_zoomLevel; + + if (newPixel != formerPixel) { + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cout << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << std::endl; +#endif + update(); + + changeVisible = true; + } + + if (e) emit centreFrameChanged(f, m_followPan, m_followPlay); + } + + return changeVisible; +} + +int +View::getXForFrame(long frame) const +{ + return (frame - getStartFrame()) / m_zoomLevel; +} + +long +View::getFrameForX(int x) const +{ + return (long(x) * long(m_zoomLevel)) + getStartFrame(); +} + +float +View::getYForFrequency(float frequency, + float minf, + float maxf, + bool logarithmic) const +{ + int h = height(); + + if (logarithmic) { + + static float lastminf = 0.0, lastmaxf = 0.0; + static float logminf = 0.0, logmaxf = 0.0; + + if (lastminf != minf) { + lastminf = (minf == 0.0 ? 1.0 : minf); + logminf = log10f(minf); + } + if (lastmaxf != maxf) { + lastmaxf = (maxf < lastminf ? lastminf : maxf); + logmaxf = log10f(maxf); + } + + if (logminf == logmaxf) return 0; + return h - (h * (log10f(frequency) - logminf)) / (logmaxf - logminf); + + } else { + + if (minf == maxf) return 0; + return h - (h * (frequency - minf)) / (maxf - minf); + } +} + +float +View::getFrequencyForY(int y, + float minf, + float maxf, + bool logarithmic) const +{ + int h = height(); + + if (logarithmic) { + + static float lastminf = 0.0, lastmaxf = 0.0; + static float logminf = 0.0, logmaxf = 0.0; + + if (lastminf != minf) { + lastminf = (minf == 0.0 ? 1.0 : minf); + logminf = log10f(minf); + } + if (lastmaxf != maxf) { + lastmaxf = (maxf < lastminf ? lastminf : maxf); + logmaxf = log10f(maxf); + } + + if (logminf == logmaxf) return 0; + return pow(10.f, logminf + ((logmaxf - logminf) * (h - y)) / h); + + } else { + + if (minf == maxf) return 0; + return minf + ((h - y) * (maxf - minf)) / h; + } +} + +int +View::getZoomLevel() const +{ +#ifdef DEBUG_VIEW_WIDGET_PAINT +// std::cout << "zoom level: " << m_zoomLevel << std::endl; +#endif + return m_zoomLevel; +} + +void +View::setZoomLevel(size_t z) +{ + if (m_zoomLevel != int(z)) { + m_zoomLevel = z; + emit zoomLevelChanged(z, m_followZoom); + update(); + } +} + +bool +View::hasLightBackground() const +{ + for (LayerList::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + if (!(*i)->hasLightBackground()) return false; + } + return true; +} + +View::LayerProgressBar::LayerProgressBar(QWidget *parent) : + QProgressBar(parent) +{ + QFont f(font()); + f.setPointSize(f.pointSize() * 8 / 10); + setFont(f); +} + +void +View::addLayer(Layer *layer) +{ + delete m_cache; + m_cache = 0; + + m_layers.push_back(layer); + + m_progressBars[layer] = new LayerProgressBar(this); + m_progressBars[layer]->setMinimum(0); + m_progressBars[layer]->setMaximum(100); + m_progressBars[layer]->setMinimumWidth(80); + m_progressBars[layer]->hide(); + + connect(layer, SIGNAL(layerParametersChanged()), + this, SLOT(layerParametersChanged())); + connect(layer, SIGNAL(layerParameterRangesChanged()), + this, SLOT(layerParameterRangesChanged())); + connect(layer, SIGNAL(layerNameChanged()), + this, SLOT(layerNameChanged())); + connect(layer, SIGNAL(modelChanged()), + this, SLOT(modelChanged())); + connect(layer, SIGNAL(modelCompletionChanged()), + this, SLOT(modelCompletionChanged())); + connect(layer, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(modelChanged(size_t, size_t))); + connect(layer, SIGNAL(modelReplaced()), + this, SLOT(modelReplaced())); + + update(); + + emit propertyContainerAdded(layer); +} + +void +View::removeLayer(Layer *layer) +{ + if (m_deleting) { + return; + } + + delete m_cache; + m_cache = 0; + + for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if (*i == layer) { + m_layers.erase(i); + if (m_progressBars.find(layer) != m_progressBars.end()) { + delete m_progressBars[layer]; + m_progressBars.erase(layer); + } + break; + } + } + + disconnect(layer, SIGNAL(layerParametersChanged()), + this, SLOT(layerParametersChanged())); + disconnect(layer, SIGNAL(layerParameterRangesChanged()), + this, SLOT(layerParameterRangesChanged())); + disconnect(layer, SIGNAL(layerNameChanged()), + this, SLOT(layerNameChanged())); + disconnect(layer, SIGNAL(modelChanged()), + this, SLOT(modelChanged())); + disconnect(layer, SIGNAL(modelCompletionChanged()), + this, SLOT(modelCompletionChanged())); + disconnect(layer, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(modelChanged(size_t, size_t))); + disconnect(layer, SIGNAL(modelReplaced()), + this, SLOT(modelReplaced())); + + update(); + + emit propertyContainerRemoved(layer); +} + +Layer * +View::getSelectedLayer() +{ + if (m_haveSelectedLayer && !m_layers.empty()) { + return getLayer(getLayerCount() - 1); + } else { + return 0; + } +} + +const Layer * +View::getSelectedLayer() const +{ + return const_cast(const_cast(this)->getSelectedLayer()); +} + +void +View::setViewManager(ViewManager *manager) +{ + if (m_manager) { + m_manager->disconnect(this, SLOT(globalCentreFrameChanged(unsigned long))); + m_manager->disconnect(this, SLOT(viewCentreFrameChanged(View *, unsigned long))); + m_manager->disconnect(this, SLOT(viewManagerPlaybackFrameChanged(unsigned long))); + m_manager->disconnect(this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); + m_manager->disconnect(this, SLOT(toolModeChanged())); + m_manager->disconnect(this, SLOT(selectionChanged())); + m_manager->disconnect(this, SLOT(overlayModeChanged())); + m_manager->disconnect(this, SLOT(zoomWheelsEnabledChanged())); + disconnect(m_manager, SLOT(viewCentreFrameChanged(unsigned long, bool, PlaybackFollowMode))); + disconnect(m_manager, SLOT(zoomLevelChanged(unsigned long, bool))); + } + + m_manager = manager; + + connect(m_manager, SIGNAL(globalCentreFrameChanged(unsigned long)), + this, SLOT(globalCentreFrameChanged(unsigned long))); + connect(m_manager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)), + this, SLOT(viewCentreFrameChanged(View *, unsigned long))); + connect(m_manager, SIGNAL(playbackFrameChanged(unsigned long)), + this, SLOT(viewManagerPlaybackFrameChanged(unsigned long))); + + connect(m_manager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)), + this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); + + connect(m_manager, SIGNAL(toolModeChanged()), + this, SLOT(toolModeChanged())); + connect(m_manager, SIGNAL(selectionChanged()), + this, SLOT(selectionChanged())); + connect(m_manager, SIGNAL(inProgressSelectionChanged()), + this, SLOT(selectionChanged())); + connect(m_manager, SIGNAL(overlayModeChanged()), + this, SLOT(overlayModeChanged())); + connect(m_manager, SIGNAL(zoomWheelsEnabledChanged()), + this, SLOT(zoomWheelsEnabledChanged())); + + connect(this, SIGNAL(centreFrameChanged(unsigned long, bool, + PlaybackFollowMode)), + m_manager, SLOT(viewCentreFrameChanged(unsigned long, bool, + PlaybackFollowMode))); + + connect(this, SIGNAL(zoomLevelChanged(unsigned long, bool)), + m_manager, SLOT(viewZoomLevelChanged(unsigned long, bool))); + + if (m_followPlay != PlaybackIgnore) { +// std::cerr << "View::setViewManager: setting centre frame to playback frame: " << m_manager->getPlaybackFrame() << std::endl; + setCentreFrame(m_manager->getPlaybackFrame(), false); + } else if (m_followPan) { +// std::cerr << "View::setViewManager: setting centre frame to global centre frame: " << m_manager->getGlobalCentreFrame() << std::endl; + setCentreFrame(m_manager->getGlobalCentreFrame(), false); + } + if (m_followZoom) setZoomLevel(m_manager->getGlobalZoom()); + + toolModeChanged(); +} + +void +View::setFollowGlobalPan(bool f) +{ + m_followPan = f; + emit propertyContainerPropertyChanged(m_propertyContainer); +} + +void +View::setFollowGlobalZoom(bool f) +{ + m_followZoom = f; + emit propertyContainerPropertyChanged(m_propertyContainer); +} + +void +View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style) +{ + if (style == OutlinedText) { + + QColor origPenColour = paint.pen().color(); + QColor penColour = origPenColour; + QColor surroundColour = Qt::white; //palette().background().color(); + + if (!hasLightBackground()) { + int h, s, v; + penColour.getHsv(&h, &s, &v); + penColour = QColor::fromHsv(h, s, 255 - v); + surroundColour = Qt::black; + } + + paint.setPen(surroundColour); + + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if (!(dx || dy)) continue; + paint.drawText(x + dx, y + dy, text); + } + } + + paint.setPen(penColour); + + paint.drawText(x, y, text); + + paint.setPen(origPenColour); + + } else { + + std::cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << std::endl; + } +} + +void +View::setPlaybackFollow(PlaybackFollowMode m) +{ + m_followPlay = m; + emit propertyContainerPropertyChanged(m_propertyContainer); +} + +void +View::modelChanged() +{ + QObject *obj = sender(); + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::modelChanged()" << std::endl; +#endif + + // If the model that has changed is not used by any of the cached + // layers, we won't need to recreate the cache + + bool recreate = false; + + bool discard; + LayerList scrollables = getScrollableBackLayers(false, discard); + for (LayerList::const_iterator i = scrollables.begin(); + i != scrollables.end(); ++i) { + if (*i == obj || (*i)->getModel() == obj) { + recreate = true; + break; + } + } + + if (recreate) { + delete m_cache; + m_cache = 0; + } + + checkProgress(obj); + + update(); +} + +void +View::modelChanged(size_t startFrame, size_t endFrame) +{ + QObject *obj = sender(); + + long myStartFrame = getStartFrame(); + size_t myEndFrame = getEndFrame(); + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::modelChanged(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << std::endl; +#endif + + if (myStartFrame > 0 && endFrame < size_t(myStartFrame)) { + checkProgress(obj); + return; + } + if (startFrame > myEndFrame) { + checkProgress(obj); + return; + } + + // If the model that has changed is not used by any of the cached + // layers, we won't need to recreate the cache + + bool recreate = false; + + bool discard; + LayerList scrollables = getScrollableBackLayers(false, discard); + for (LayerList::const_iterator i = scrollables.begin(); + i != scrollables.end(); ++i) { + if (*i == obj || (*i)->getModel() == obj) { + recreate = true; + break; + } + } + + if (recreate) { + delete m_cache; + m_cache = 0; + } + + if (long(startFrame) < myStartFrame) startFrame = myStartFrame; + if (endFrame > myEndFrame) endFrame = myEndFrame; + + checkProgress(obj); + + update(); +} + +void +View::modelCompletionChanged() +{ + QObject *obj = sender(); + checkProgress(obj); +} + +void +View::modelReplaced() +{ +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::modelReplaced()" << std::endl; +#endif + delete m_cache; + m_cache = 0; + + update(); +} + +void +View::layerParametersChanged() +{ + Layer *layer = dynamic_cast(sender()); + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View::layerParametersChanged()" << std::endl; +#endif + + delete m_cache; + m_cache = 0; + update(); + + if (layer) { + emit propertyContainerPropertyChanged(layer); + } +} + +void +View::layerParameterRangesChanged() +{ + Layer *layer = dynamic_cast(sender()); + if (layer) emit propertyContainerPropertyRangeChanged(layer); +} + +void +View::layerNameChanged() +{ + Layer *layer = dynamic_cast(sender()); + if (layer) emit propertyContainerNameChanged(layer); +} + +void +View::globalCentreFrameChanged(unsigned long f) +{ + if (m_followPan) { + setCentreFrame(f, false); + } +} + +void +View::viewCentreFrameChanged(View *, unsigned long ) +{ + // We do nothing with this, but a subclass might +} + +void +View::viewManagerPlaybackFrameChanged(unsigned long f) +{ + if (m_manager) { + if (sender() != m_manager) return; + } + + if (m_playPointerFrame == f) return; + bool visible = (getXForFrame(m_playPointerFrame) != getXForFrame(f)); + size_t oldPlayPointerFrame = m_playPointerFrame; + m_playPointerFrame = f; + if (!visible) return; + + switch (m_followPlay) { + + case PlaybackScrollContinuous: + if (QApplication::mouseButtons() == Qt::NoButton) { + setCentreFrame(f, false); + } + break; + + case PlaybackScrollPage: + { + int xold = getXForFrame(oldPlayPointerFrame); + update(xold - 1, 0, 3, height()); + + long w = getEndFrame() - getStartFrame(); + w -= w/5; + long sf = (f / w) * w - w/8; + + if (m_manager && + m_manager->isPlaying() && + m_manager->getPlaySelectionMode()) { + MultiSelection::SelectionList selections = m_manager->getSelections(); + if (!selections.empty()) { + size_t selectionStart = selections.begin()->getStartFrame(); + if (sf < long(selectionStart) - w / 10) { + sf = long(selectionStart) - w / 10; + } + } + } + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "PlaybackScrollPage: f = " << f << ", sf = " << sf << ", start frame " + << getStartFrame() << std::endl; +#endif + + // We don't consider scrolling unless the pointer is outside + // the clearly visible range already + + int xnew = getXForFrame(m_playPointerFrame); + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "xnew = " << xnew << ", width = " << width() << std::endl; +#endif + + if (xnew < width()/8 || xnew > (width()*7)/8) { + if (QApplication::mouseButtons() == Qt::NoButton) { + long offset = getFrameForX(width()/2) - getStartFrame(); + long newCentre = sf + offset; + bool changed = setCentreFrame(newCentre, false); + if (changed) { + xold = getXForFrame(oldPlayPointerFrame); + update(xold - 1, 0, 3, height()); + } + } + } + + update(xnew - 1, 0, 3, height()); + + break; + } + + case PlaybackIgnore: + if (long(f) >= getStartFrame() && f < getEndFrame()) { + update(); + } + break; + } +} + +void +View::viewZoomLevelChanged(View *p, unsigned long z, bool locked) +{ +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View[" << this << "]: viewZoomLevelChanged(" << p << ", " << z << ", " << locked << ")" << std::endl; +#endif + if (m_followZoom && p != this && locked) { + setZoomLevel(z); + } +} + +void +View::selectionChanged() +{ + if (m_selectionCached) { + delete m_cache; + m_cache = 0; + m_selectionCached = false; + } + update(); +} + +size_t +View::getFirstVisibleFrame() const +{ + long f0 = getStartFrame(); + size_t f = getModelsStartFrame(); + if (f0 < 0 || f0 < long(f)) return f; + return f0; +} + +size_t +View::getLastVisibleFrame() const +{ + size_t f0 = getEndFrame(); + size_t f = getModelsEndFrame(); + if (f0 > f) return f; + return f0; +} + +size_t +View::getModelsStartFrame() const +{ + bool first = true; + size_t startFrame = 0; + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + if ((*i)->getModel() && (*i)->getModel()->isOK()) { + + size_t thisStartFrame = (*i)->getModel()->getStartFrame(); + + if (first || thisStartFrame < startFrame) { + startFrame = thisStartFrame; + } + first = false; + } + } + return startFrame; +} + +size_t +View::getModelsEndFrame() const +{ + bool first = true; + size_t endFrame = 0; + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + if ((*i)->getModel() && (*i)->getModel()->isOK()) { + + size_t thisEndFrame = (*i)->getModel()->getEndFrame(); + + if (first || thisEndFrame > endFrame) { + endFrame = thisEndFrame; + } + first = false; + } + } + + if (first) return getModelsStartFrame(); + return endFrame; +} + +int +View::getModelsSampleRate() const +{ + //!!! Just go for the first, for now. If we were supporting + // multiple samplerates, we'd probably want to do frame/time + // conversion in the model + + //!!! nah, this wants to always return the sr of the main model! + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if ((*i)->getModel() && (*i)->getModel()->isOK()) { + return (*i)->getModel()->getSampleRate(); + } + } + return 0; +} + +bool +View::areLayersScrollable() const +{ + // True iff all views are scrollable + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if (!(*i)->isLayerScrollable(this)) return false; + } + return true; +} + +View::LayerList +View::getScrollableBackLayers(bool testChanged, bool &changed) const +{ + changed = false; + + // We want a list of all the scrollable layers that are behind the + // backmost non-scrollable layer. + + LayerList scrollables; + bool metUnscrollable = false; + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { +// std::cerr << "View::getScrollableBackLayers: calling isLayerDormant on layer " << *i << std::endl; +// std::cerr << "(name is " << (*i)->objectName().toStdString() << ")" +// << std::endl; +// std::cerr << "View::getScrollableBackLayers: I am " << this << std::endl; + if ((*i)->isLayerDormant(this)) continue; + if ((*i)->isLayerOpaque()) { + // You can't see anything behind an opaque layer! + scrollables.clear(); + if (metUnscrollable) break; + } + if (!metUnscrollable && (*i)->isLayerScrollable(this)) { + scrollables.push_back(*i); + } else { + metUnscrollable = true; + } + } + + if (testChanged && scrollables != m_lastScrollableBackLayers) { + m_lastScrollableBackLayers = scrollables; + changed = true; + } + return scrollables; +} + +View::LayerList +View::getNonScrollableFrontLayers(bool testChanged, bool &changed) const +{ + changed = false; + LayerList nonScrollables; + + // Everything in front of the first non-scrollable from the back + // should also be considered non-scrollable + + bool started = false; + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if ((*i)->isLayerDormant(this)) continue; + if (!started && (*i)->isLayerScrollable(this)) { + continue; + } + started = true; + if ((*i)->isLayerOpaque()) { + // You can't see anything behind an opaque layer! + nonScrollables.clear(); + } + nonScrollables.push_back(*i); + } + + if (testChanged && nonScrollables != m_lastNonScrollableBackLayers) { + m_lastNonScrollableBackLayers = nonScrollables; + changed = true; + } + + return nonScrollables; +} + +size_t +View::getZoomConstraintBlockSize(size_t blockSize, + ZoomConstraint::RoundingDirection dir) + const +{ + size_t candidate = blockSize; + bool haveCandidate = false; + + PowerOfSqrtTwoZoomConstraint defaultZoomConstraint; + + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint(); + if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint; + + size_t thisBlockSize = + zoomConstraint->getNearestBlockSize(blockSize, dir); + + // Go for the block size that's furthest from the one + // passed in. Most of the time, that's what we want. + if (!haveCandidate || + (thisBlockSize > blockSize && thisBlockSize > candidate) || + (thisBlockSize < blockSize && thisBlockSize < candidate)) { + candidate = thisBlockSize; + haveCandidate = true; + } + } + + return candidate; +} + +bool +View::areLayerColoursSignificant() const +{ + for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if ((*i)->isLayerColourSignificant()) return true; + if ((*i)->isLayerOpaque()) break; + } + return false; +} + +bool +View::hasTopLayerTimeXAxis() const +{ + LayerList::const_iterator i = m_layers.end(); + if (i == m_layers.begin()) return false; + --i; + return (*i)->hasTimeXAxis(); +} + +void +View::zoom(bool in) +{ + int newZoomLevel = m_zoomLevel; + + if (in) { + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, + ZoomConstraint::RoundDown); + } else { + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, + ZoomConstraint::RoundUp); + } + + if (newZoomLevel != m_zoomLevel) { + setZoomLevel(newZoomLevel); + } +} + +void +View::scroll(bool right, bool lots) +{ + long delta; + if (lots) { + delta = (getEndFrame() - getStartFrame()) / 2; + } else { + delta = (getEndFrame() - getStartFrame()) / 20; + } + if (right) delta = -delta; + + if (int(m_centreFrame) < delta) { + setCentreFrame(0); + } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) { + setCentreFrame(getModelsEndFrame()); + } else { + setCentreFrame(m_centreFrame - delta); + } +} + +void +View::checkProgress(void *object) +{ + if (!m_showProgress) return; + + int ph = height(); + + for (ProgressMap::const_iterator i = m_progressBars.begin(); + i != m_progressBars.end(); ++i) { + + if (i->first == object) { + + int completion = i->first->getCompletion(this); + + if (completion >= 100) { + + i->second->hide(); + + } else { + + i->second->setText(i->first->getPropertyContainerName()); + + i->second->setValue(completion); + i->second->move(0, ph - i->second->height()); + + i->second->show(); + i->second->update(); + + ph -= i->second->height(); + } + } else { + if (i->second->isVisible()) { + ph -= i->second->height(); + } + } + } +} + +void +View::paintEvent(QPaintEvent *e) +{ +// Profiler prof("View::paintEvent", false); +// std::cerr << "View::paintEvent: centre frame is " << m_centreFrame << std::endl; + + if (m_layers.empty()) { + QFrame::paintEvent(e); + return; + } + + // ensure our constraints are met + +/*!!! Should we do this only if we have layers that can't support other + zoom levels? + + m_zoomLevel = getZoomConstraintBlockSize(m_zoomLevel, + ZoomConstraint::RoundUp); +*/ + + QPainter paint; + bool repaintCache = false; + bool paintedCacheRect = false; + + QRect cacheRect(rect()); + + if (e) { + cacheRect &= e->rect(); +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "paint rect " << cacheRect.width() << "x" << cacheRect.height() + << ", my rect " << width() << "x" << height() << std::endl; +#endif + } + + QRect nonCacheRect(cacheRect); + + // If not all layers are scrollable, but some of the back layers + // are, we should store only those in the cache. + + bool layersChanged = false; + LayerList scrollables = getScrollableBackLayers(true, layersChanged); + LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged); + bool selectionCacheable = nonScrollables.empty(); + bool haveSelections = m_manager && !m_manager->getSelections().empty(); + bool selectionDrawn = false; + + // If all the non-scrollable layers are non-opaque, then we draw + // the selection rectangle behind them and cache it. If any are + // opaque, however, we can't cache. + // + if (!selectionCacheable) { + selectionCacheable = true; + for (LayerList::const_iterator i = nonScrollables.begin(); + i != nonScrollables.end(); ++i) { + if ((*i)->isLayerOpaque()) { + selectionCacheable = false; + break; + } + } + } + + if (selectionCacheable) { + QPoint localPos; + bool closeToLeft, closeToRight; + if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) { + selectionCacheable = false; + } + } + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: have " << scrollables.size() + << " scrollable back layers and " << nonScrollables.size() + << " non-scrollable front layers" << std::endl; + std::cerr << "haveSelections " << haveSelections << ", selectionCacheable " + << selectionCacheable << ", m_selectionCached " << m_selectionCached << std::endl; +#endif + + if (layersChanged || scrollables.empty() || + (haveSelections && (selectionCacheable != m_selectionCached))) { + delete m_cache; + m_cache = 0; + m_selectionCached = false; + } + + if (!scrollables.empty()) { + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << "): cache " << m_cache << ", cache zoom " + << m_cacheZoomLevel << ", zoom " << m_zoomLevel << std::endl; +#endif + + if (!m_cache || + m_cacheZoomLevel != m_zoomLevel || + width() != m_cache->width() || + height() != m_cache->height()) { + + // cache is not valid + + if (cacheRect.width() < width()/10) { + delete m_cache; + m_cache = 0; +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: small repaint, not bothering to recreate cache" << std::endl; +#endif + } else { + delete m_cache; + m_cache = new QPixmap(width(), height()); +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: recreated cache" << std::endl; +#endif + cacheRect = rect(); + repaintCache = true; + } + + } else if (m_cacheCentreFrame != m_centreFrame) { + + long dx = + getXForFrame(m_cacheCentreFrame) - + getXForFrame(m_centreFrame); + + if (dx > -width() && dx < width()) { +#if defined(Q_WS_WIN32) || defined(Q_WS_MAC) + // Copying a pixmap to itself doesn't work properly on Windows + // or Mac (it only works when moving in one direction) + static QPixmap *tmpPixmap = 0; + if (!tmpPixmap || + tmpPixmap->width() != width() || + tmpPixmap->height() != height()) { + delete tmpPixmap; + tmpPixmap = new QPixmap(width(), height()); + } + paint.begin(tmpPixmap); + paint.drawPixmap(0, 0, *m_cache); + paint.end(); + paint.begin(m_cache); + paint.drawPixmap(dx, 0, *tmpPixmap); + paint.end(); +#else + // But it seems to be fine on X11 + paint.begin(m_cache); + paint.drawPixmap(dx, 0, *m_cache); + paint.end(); +#endif + + if (dx < 0) { + cacheRect = QRect(width() + dx, 0, -dx, height()); + } else { + cacheRect = QRect(0, 0, dx, height()); + } +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: scrolled cache by " << dx << std::endl; +#endif + } else { + cacheRect = rect(); +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: scrolling too far" << std::endl; +#endif + } + repaintCache = true; + + } else { +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View(" << this << ")::paintEvent: cache is good" << std::endl; +#endif + paint.begin(this); + paint.drawPixmap(cacheRect, *m_cache, cacheRect); + paint.end(); + QFrame::paintEvent(e); + paintedCacheRect = true; + } + + m_cacheCentreFrame = m_centreFrame; + m_cacheZoomLevel = m_zoomLevel; + } + +#ifdef DEBUG_VIEW_WIDGET_PAINT +// std::cerr << "View(" << this << ")::paintEvent: cacheRect " << cacheRect << ", nonCacheRect " << (nonCacheRect | cacheRect) << ", repaintCache " << repaintCache << ", paintedCacheRect " << paintedCacheRect << std::endl; +#endif + + // Scrollable (cacheable) items first + + if (!paintedCacheRect) { + + if (repaintCache) paint.begin(m_cache); + else paint.begin(this); + + paint.setClipRect(cacheRect); + + if (hasLightBackground()) { + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + } else { + paint.setPen(Qt::black); + paint.setBrush(Qt::black); + } + paint.drawRect(cacheRect); + + paint.setPen(Qt::black); + paint.setBrush(Qt::NoBrush); + + for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) { + paint.setRenderHint(QPainter::Antialiasing, false); + paint.save(); + (*i)->paint(this, paint, cacheRect); + paint.restore(); + } + + if (haveSelections && selectionCacheable) { + drawSelections(paint); + m_selectionCached = repaintCache; + selectionDrawn = true; + } + + paint.end(); + + if (repaintCache) { + cacheRect |= (e ? e->rect() : rect()); + paint.begin(this); + paint.drawPixmap(cacheRect, *m_cache, cacheRect); + paint.end(); + } + } + + // Now non-cacheable items. We always need to redraw the + // non-cacheable items across at least the area we drew of the + // cacheable items. + + nonCacheRect |= cacheRect; + + paint.begin(this); + paint.setClipRect(nonCacheRect); + + if (scrollables.empty()) { + if (hasLightBackground()) { + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + } else { + paint.setPen(Qt::black); + paint.setBrush(Qt::black); + } + paint.drawRect(nonCacheRect); + } + + paint.setPen(Qt::black); + paint.setBrush(Qt::NoBrush); + + for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) { +// Profiler profiler2("View::paintEvent non-cacheable"); + (*i)->paint(this, paint, nonCacheRect); + } + + paint.end(); + + paint.begin(this); + if (e) paint.setClipRect(e->rect()); + if (!m_selectionCached) { + drawSelections(paint); + } + paint.end(); + + bool showPlayPointer = true; + if (m_followPlay == PlaybackScrollContinuous) { + showPlayPointer = false; + } else if (long(m_playPointerFrame) <= getStartFrame() || + m_playPointerFrame >= getEndFrame()) { + showPlayPointer = false; + } else if (m_manager && !m_manager->isPlaying()) { + if (m_playPointerFrame == getCentreFrame() && + m_followPlay != PlaybackIgnore) { + showPlayPointer = false; + } + } + + if (showPlayPointer) { + + paint.begin(this); + + int playx = getXForFrame(m_playPointerFrame); + + paint.setPen(Qt::black); + paint.drawLine(playx - 1, 0, playx - 1, height() - 1); + paint.drawLine(playx + 1, 0, playx + 1, height() - 1); + paint.drawPoint(playx, 0); + paint.drawPoint(playx, height() - 1); + paint.setPen(Qt::white); + paint.drawLine(playx, 1, playx, height() - 2); + + paint.end(); + } + + QFrame::paintEvent(e); +} + +void +View::drawSelections(QPainter &paint) +{ + if (!hasTopLayerTimeXAxis()) return; + + MultiSelection::SelectionList selections; + + if (m_manager) { + selections = m_manager->getSelections(); + if (m_manager->haveInProgressSelection()) { + bool exclusive; + Selection inProgressSelection = + m_manager->getInProgressSelection(exclusive); + if (exclusive) selections.clear(); + selections.insert(inProgressSelection); + } + } + + paint.save(); + + bool translucent = !areLayerColoursSignificant(); + + if (translucent) { + paint.setBrush(QColor(150, 150, 255, 80)); + } else { + paint.setBrush(Qt::NoBrush); + } + + int sampleRate = getModelsSampleRate(); + + QPoint localPos; + long illuminateFrame = -1; + bool closeToLeft, closeToRight; + + if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) { + illuminateFrame = getFrameForX(localPos.x()); + } + + const QFontMetrics &metrics = paint.fontMetrics(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + + int p0 = getXForFrame(i->getStartFrame()); + int p1 = getXForFrame(i->getEndFrame()); + + if (p1 < 0 || p0 > width()) continue; + +#ifdef DEBUG_VIEW_WIDGET_PAINT + std::cerr << "View::drawSelections: " << p0 << ",-1 [" << (p1-p0) << "x" << (height()+1) << "]" << std::endl; +#endif + + bool illuminateThis = + (illuminateFrame >= 0 && i->contains(illuminateFrame)); + + paint.setPen(QColor(150, 150, 255)); + + if (translucent && shouldLabelSelections()) { + paint.drawRect(p0, -1, p1 - p0, height() + 1); + } else { + // Make the top & bottom lines of the box visible if we + // are lacking some of the other visual cues. There's no + // particular logic to this, it's just a question of what + // I happen to think looks nice. + paint.drawRect(p0, 0, p1 - p0, height() - 1); + } + + if (illuminateThis) { + paint.save(); + if (hasLightBackground()) { + paint.setPen(QPen(Qt::black, 2)); + } else { + paint.setPen(QPen(Qt::white, 2)); + } + if (closeToLeft) { + paint.drawLine(p0, 1, p1, 1); + paint.drawLine(p0, 0, p0, height()); + paint.drawLine(p0, height() - 1, p1, height() - 1); + } else if (closeToRight) { + paint.drawLine(p0, 1, p1, 1); + paint.drawLine(p1, 0, p1, height()); + paint.drawLine(p0, height() - 1, p1, height() - 1); + } else { + paint.setBrush(Qt::NoBrush); + paint.drawRect(p0, 1, p1 - p0, height() - 2); + } + paint.restore(); + } + + if (sampleRate && shouldLabelSelections() && m_manager && + m_manager->shouldShowSelectionExtents()) { + + QString startText = QString("%1 / %2") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (i->getStartFrame(), sampleRate).toText(true))) + .arg(i->getStartFrame()); + + QString endText = QString(" %1 / %2") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (i->getEndFrame(), sampleRate).toText(true))) + .arg(i->getEndFrame()); + + QString durationText = QString("(%1 / %2) ") + .arg(QString::fromStdString + (RealTime::frame2RealTime + (i->getEndFrame() - i->getStartFrame(), sampleRate) + .toText(true))) + .arg(i->getEndFrame() - i->getStartFrame()); + + int sw = metrics.width(startText), + ew = metrics.width(endText), + dw = metrics.width(durationText); + + int sy = metrics.ascent() + metrics.height() + 4; + int ey = sy; + int dy = sy + metrics.height(); + + int sx = p0 + 2; + int ex = sx; + int dx = sx; + + if (sw + ew > (p1 - p0)) { + ey += metrics.height(); + dy += metrics.height(); + } + + if (ew < (p1 - p0)) { + ex = p1 - 2 - ew; + } + + if (dw < (p1 - p0)) { + dx = p1 - 2 - dw; + } + + paint.drawText(sx, sy, startText); + paint.drawText(ex, ey, endText); + paint.drawText(dx, dy, durationText); + } + } + + paint.restore(); +} + +bool +View::render(QPainter &paint, int xorigin, size_t f0, size_t f1) +{ + size_t x0 = f0 / m_zoomLevel; + size_t x1 = f1 / m_zoomLevel; + + size_t w = x1 - x0; + + size_t origCentreFrame = m_centreFrame; + + bool someLayersIncomplete = false; + + for (LayerList::iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + int c = (*i)->getCompletion(this); + if (c < 100) { + someLayersIncomplete = true; + break; + } + } + + if (someLayersIncomplete) { + + QProgressDialog progress(tr("Waiting for layers to be ready..."), + tr("Cancel"), 0, 100, this); + + int layerCompletion = 0; + + while (layerCompletion < 100) { + + for (LayerList::iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + int c = (*i)->getCompletion(this); + if (i == m_layers.begin() || c < layerCompletion) { + layerCompletion = c; + } + } + + if (layerCompletion >= 100) break; + + progress.setValue(layerCompletion); + qApp->processEvents(); + if (progress.wasCanceled()) { + update(); + return false; + } + + usleep(50000); + } + } + + QProgressDialog progress(tr("Rendering image..."), + tr("Cancel"), 0, w / width(), this); + + for (size_t x = 0; x < w; x += width()) { + + progress.setValue(x / width()); + qApp->processEvents(); + if (progress.wasCanceled()) { + m_centreFrame = origCentreFrame; + update(); + return false; + } + + m_centreFrame = f0 + (x + width()/2) * m_zoomLevel; + + QRect chunk(0, 0, width(), height()); + + if (hasLightBackground()) { + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + } else { + paint.setPen(Qt::black); + paint.setBrush(Qt::black); + } + + paint.drawRect(QRect(xorigin + x, 0, width(), height())); + + paint.setPen(Qt::black); + paint.setBrush(Qt::NoBrush); + + for (LayerList::iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + paint.setRenderHint(QPainter::Antialiasing, false); + + paint.save(); + paint.translate(xorigin + x, 0); + + std::cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << std::endl; + + (*i)->paint(this, paint, chunk); + + paint.restore(); + } + } + + m_centreFrame = origCentreFrame; + update(); + return true; +} + +QImage * +View::toNewImage() +{ + size_t f0 = getModelsStartFrame(); + size_t f1 = getModelsEndFrame(); + + return toNewImage(f0, f1); +} + +QImage * +View::toNewImage(size_t f0, size_t f1) +{ + size_t x0 = f0 / getZoomLevel(); + size_t x1 = f1 / getZoomLevel(); + + QImage *image = new QImage(x1 - x0, height(), QImage::Format_RGB32); + + QPainter *paint = new QPainter(image); + if (!render(*paint, 0, f0, f1)) { + delete paint; + delete image; + return 0; + } else { + delete paint; + return image; + } +} + +QSize +View::getImageSize() +{ + size_t f0 = getModelsStartFrame(); + size_t f1 = getModelsEndFrame(); + + return getImageSize(f0, f1); +} + +QSize +View::getImageSize(size_t f0, size_t f1) +{ + size_t x0 = f0 / getZoomLevel(); + size_t x1 = f1 / getZoomLevel(); + + return QSize(x1 - x0, height()); +} + +QString +View::toXmlString(QString indent, QString extraAttributes) const +{ + QString s; + + s += indent; + + s += QString("\n") + .arg(m_centreFrame) + .arg(m_zoomLevel) + .arg(m_followPan) + .arg(m_followZoom) + .arg(m_followPlay == PlaybackScrollContinuous ? "scroll" : + m_followPlay == PlaybackScrollPage ? "page" : "ignore") + .arg(extraAttributes); + + for (size_t i = 0; i < m_layers.size(); ++i) { + bool visible = !m_layers[i]->isLayerDormant(this); + s += m_layers[i]->toXmlString(indent + " ", + QString("visible=\"%1\"") + .arg(visible ? "true" : "false")); + } + + s += indent + "\n"; + + return s; +} + +ViewPropertyContainer::ViewPropertyContainer(View *v) : + m_v(v) +{ + connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, SIGNAL(propertyChanged(PropertyContainer::PropertyName))); +} + diff -r 000000000000 -r fc9323a41f5a view/View.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/View.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,389 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _VIEW_H_ +#define _VIEW_H_ + +#include +#include + +#include "base/ZoomConstraint.h" +#include "base/PropertyContainer.h" +#include "ViewManager.h" +#include "base/XmlExportable.h" + +// #define DEBUG_VIEW_WIDGET_PAINT 1 + +class Layer; +class ViewPropertyContainer; + +#include + +/** + * View is the base class of widgets that display one or more + * overlaid views of data against a horizontal time scale. + * + * A View may have any number of attached Layers, each of which + * is expected to have one data Model (although multiple views may + * share the same model). + * + * A View may be panned in time and zoomed, although the + * mechanisms for doing so (as well as any other operations and + * properties available) depend on the subclass. + */ + +class View : public QFrame, + public XmlExportable +{ + Q_OBJECT + +public: + /** + * Deleting a View does not delete any of its layers. They should + * be managed elsewhere (e.g. by the Document). + */ + virtual ~View(); + + /** + * Retrieve the first visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. The result may be negative. + */ + virtual long getStartFrame() const; + + /** + * Set the widget pan based on the given first visible frame. The + * frame value may be negative. + */ + virtual void setStartFrame(long); + + /** + * Return the centre frame of the visible widget. This is an + * exact value that does not depend on the zoom block size. Other + * frame values (start, end) are calculated from this based on the + * zoom and other factors. + */ + virtual size_t getCentreFrame() const { return m_centreFrame; } + + /** + * Set the centre frame of the visible widget. + */ + virtual void setCentreFrame(size_t f) { setCentreFrame(f, true); } + + /** + * Retrieve the last visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. + */ + virtual size_t getEndFrame() const; + + /** + * Return the pixel x-coordinate corresponding to a given sample + * frame (which may be negative). + */ + int getXForFrame(long frame) const; + + /** + * Return the closest frame to the given pixel x-coordinate. + */ + long getFrameForX(int x) const; + + /** + * Return the pixel y-coordinate corresponding to a given + * frequency, if the frequency range is as specified. This does + * not imply any policy about layer frequency ranges, but it might + * be useful for layers to match theirs up if desired. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + float getYForFrequency(float frequency, float minFreq, float maxFreq, + bool logarithmic) const; + + /** + * Return the closest frequency to the given pixel y-coordinate, + * if the frequency range is as specified. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + float getFrequencyForY(int y, float minFreq, float maxFreq, + bool logarithmic) const; + + /** + * Return the zoom level, i.e. the number of frames per pixel + */ + int getZoomLevel() const; + + /** + * Set the zoom level, i.e. the number of frames per pixel. The + * centre frame will be unchanged; the start and end frames will + * change. + */ + virtual void setZoomLevel(size_t z); + + /** + * Zoom in or out. + */ + virtual void zoom(bool in); + + /** + * Scroll left or right by a smallish or largish amount. + */ + virtual void scroll(bool right, bool lots); + + virtual void addLayer(Layer *v); + virtual void removeLayer(Layer *v); // does not delete the layer + virtual int getLayerCount() const { return m_layers.size(); } + + /** + * Return a layer, counted in stacking order. That is, layer 0 is + * the bottom layer and layer "getLayerCount()-1" is the top one. + */ + virtual Layer *getLayer(int n) { return m_layers[n]; } + + /** + * Return the layer last selected by the user. This is normally + * the top layer, the same as getLayer(getLayerCount()-1). + * However, if the user has selected the pane itself more recently + * than any of the layers on it, this function will return 0. It + * will also return 0 if there are no layers. + */ + virtual Layer *getSelectedLayer(); + virtual const Layer *getSelectedLayer() const; + + virtual void setViewManager(ViewManager *m); + virtual ViewManager *getViewManager() const { return m_manager; } + + virtual void setFollowGlobalPan(bool f); + virtual bool getFollowGlobalPan() const { return m_followPan; } + + virtual void setFollowGlobalZoom(bool f); + virtual bool getFollowGlobalZoom() const { return m_followZoom; } + + virtual bool hasLightBackground() const; + + enum TextStyle { + BoxedText, + OutlinedText + }; + + virtual void drawVisibleText(QPainter &p, int x, int y, + QString text, TextStyle style); + + virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const { + return false; + } + virtual bool shouldIlluminateLocalSelection(QPoint &, bool &, bool &) const { + return false; + } + + virtual void setPlaybackFollow(PlaybackFollowMode m); + virtual PlaybackFollowMode getPlaybackFollow() const { return m_followPlay; } + + typedef PropertyContainer::PropertyName PropertyName; + + // We implement the PropertyContainer API, although we don't + // actually subclass PropertyContainer. We have our own + // PropertyContainer that we can return on request that just + // delegates back to us. + virtual PropertyContainer::PropertyList getProperties() const; + virtual QString getPropertyLabel(const PropertyName &) const; + virtual PropertyContainer::PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max, int *deflt) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + virtual QString getPropertyContainerName() const { + return objectName(); + } + virtual QString getPropertyContainerIconName() const = 0; + + virtual size_t getPropertyContainerCount() const; + + virtual const PropertyContainer *getPropertyContainer(size_t i) const; + virtual PropertyContainer *getPropertyContainer(size_t i); + + // Render the contents on a wide canvas + virtual QImage *toNewImage(size_t f0, size_t f1); + virtual QImage *toNewImage(); + virtual QSize getImageSize(size_t f0, size_t f1); + virtual QSize getImageSize(); + + virtual int getTextLabelHeight(const Layer *layer, QPainter &) const; + + virtual bool getValueExtents(QString unit, float &min, float &max, + bool &log) const; + + virtual QString toXmlString(QString indent = "", + QString extraAttributes = "") const; + + // First frame actually in model, to right of scale, if present + virtual size_t getFirstVisibleFrame() const; + virtual size_t getLastVisibleFrame() const; + + size_t getModelsStartFrame() const; + size_t getModelsEndFrame() const; + +signals: + void propertyContainerAdded(PropertyContainer *pc); + void propertyContainerRemoved(PropertyContainer *pc); + void propertyContainerPropertyChanged(PropertyContainer *pc); + void propertyContainerPropertyRangeChanged(PropertyContainer *pc); + void propertyContainerNameChanged(PropertyContainer *pc); + void propertyChanged(PropertyContainer::PropertyName); + + void centreFrameChanged(unsigned long frame, + bool globalScroll, + PlaybackFollowMode followMode); + + void zoomLevelChanged(unsigned long, bool); + + void contextHelpChanged(const QString &); + +public slots: + virtual void modelChanged(); + virtual void modelChanged(size_t startFrame, size_t endFrame); + virtual void modelCompletionChanged(); + virtual void modelReplaced(); + virtual void layerParametersChanged(); + virtual void layerParameterRangesChanged(); + virtual void layerNameChanged(); + + virtual void globalCentreFrameChanged(unsigned long); + virtual void viewCentreFrameChanged(View *, unsigned long); + virtual void viewManagerPlaybackFrameChanged(unsigned long); + virtual void viewZoomLevelChanged(View *, unsigned long, bool); + + virtual void propertyContainerSelected(View *, PropertyContainer *pc); + + virtual void selectionChanged(); + virtual void toolModeChanged(); + virtual void overlayModeChanged(); + virtual void zoomWheelsEnabledChanged(); + +protected: + View(QWidget *, bool showProgress); + virtual void paintEvent(QPaintEvent *e); + virtual void drawSelections(QPainter &); + virtual bool shouldLabelSelections() const { return true; } + virtual bool render(QPainter &paint, int x0, size_t f0, size_t f1); + + typedef std::vector LayerList; + + int getModelsSampleRate() const; + bool areLayersScrollable() const; + LayerList getScrollableBackLayers(bool testChanged, bool &changed) const; + LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const; + size_t getZoomConstraintBlockSize(size_t blockSize, + ZoomConstraint::RoundingDirection dir = + ZoomConstraint::RoundNearest) const; + + // True if the top layer(s) use colours for meaningful things. If + // this is the case, selections will be shown using unfilled boxes + // rather than with a translucent fill. + bool areLayerColoursSignificant() const; + + // True if the top layer has a time axis on the x coordinate (this + // is generally the case except for spectrum/slice layers). It + // will not be possible to make or display selections if this is + // false. + bool hasTopLayerTimeXAxis() const; + + bool setCentreFrame(size_t f, bool doEmit); + + void checkProgress(void *object); + + size_t m_centreFrame; + int m_zoomLevel; + bool m_followPan; + bool m_followZoom; + PlaybackFollowMode m_followPlay; + size_t m_playPointerFrame; + bool m_lightBackground; + bool m_showProgress; + + QPixmap *m_cache; + size_t m_cacheCentreFrame; + int m_cacheZoomLevel; + bool m_selectionCached; + + bool m_deleting; + + LayerList m_layers; // I don't own these, but see dtor note above + bool m_haveSelectedLayer; + + // caches for use in getScrollableBackLayers, getNonScrollableFrontLayers + mutable LayerList m_lastScrollableBackLayers; + mutable LayerList m_lastNonScrollableBackLayers; + + class LayerProgressBar : public QProgressBar { + public: + LayerProgressBar(QWidget *parent); + virtual QString text() const { return m_text; } + virtual void setText(QString text) { m_text = text; } + protected: + QString m_text; + }; + + typedef std::map ProgressMap; + ProgressMap m_progressBars; // I own the ProgressBars + + ViewManager *m_manager; // I don't own this + ViewPropertyContainer *m_propertyContainer; // I own this +}; + + +// Use this for delegation, because we can't subclass from +// PropertyContainer (which is a QObject) ourselves because of +// ambiguity with QFrame parent + +class ViewPropertyContainer : public PropertyContainer +{ + Q_OBJECT + +public: + ViewPropertyContainer(View *v); + PropertyList getProperties() const { return m_v->getProperties(); } + QString getPropertyLabel(const PropertyName &n) const { + return m_v->getPropertyLabel(n); + } + PropertyType getPropertyType(const PropertyName &n) const { + return m_v->getPropertyType(n); + } + int getPropertyRangeAndValue(const PropertyName &n, int *min, int *max, + int *deflt) const { + return m_v->getPropertyRangeAndValue(n, min, max, deflt); + } + QString getPropertyValueLabel(const PropertyName &n, int value) const { + return m_v->getPropertyValueLabel(n, value); + } + QString getPropertyContainerName() const { + return m_v->getPropertyContainerName(); + } + QString getPropertyContainerIconName() const { + return m_v->getPropertyContainerIconName(); + } + +public slots: + virtual void setProperty(const PropertyName &n, int value) { + m_v->setProperty(n, value); + } + +protected: + View *m_v; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a view/ViewManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/ViewManager.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,442 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ViewManager.h" +#include "base/AudioPlaySource.h" +#include "data/model/Model.h" +#include "base/CommandHistory.h" +#include "View.h" +#include "system/System.h" + +#include + +#include + +//#define DEBUG_VIEW_MANAGER 1 + +ViewManager::ViewManager() : + m_playSource(0), + m_globalCentreFrame(0), + m_globalZoom(1024), + m_playbackFrame(0), + m_mainModelSampleRate(0), + m_lastLeft(0), + m_lastRight(0), + m_inProgressExclusive(true), + m_toolMode(NavigateMode), + m_playLoopMode(false), + m_playSelectionMode(false), + m_overlayMode(StandardOverlays), + m_zoomWheelsEnabled(true) +{ + QSettings settings; + settings.beginGroup("MainWindow"); + m_overlayMode = OverlayMode + (settings.value("overlay-mode", int(m_overlayMode)).toInt()); + m_zoomWheelsEnabled = + settings.value("zoom-wheels-enabled", m_zoomWheelsEnabled).toBool(); + settings.endGroup(); +/*!!! + connect(this, + SIGNAL(zoomLevelChanged(void *, unsigned long, bool)), + SLOT(considerZoomChange(void *, unsigned long, bool))); +*/ +} + +ViewManager::~ViewManager() +{ +} + +unsigned long +ViewManager::getGlobalCentreFrame() const +{ +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::getGlobalCentreFrame: returning " << m_globalCentreFrame << std::endl; +#endif + return m_globalCentreFrame; +} + +void +ViewManager::setGlobalCentreFrame(unsigned long f) +{ +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::setGlobalCentreFrame to " << f << std::endl; +#endif + m_globalCentreFrame = f; + emit globalCentreFrameChanged(f); +} + +unsigned long +ViewManager::getGlobalZoom() const +{ +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::getGlobalZoom: returning " << m_globalZoom << std::endl; +#endif + return m_globalZoom; +} + +unsigned long +ViewManager::getPlaybackFrame() const +{ + if (m_playSource && m_playSource->isPlaying()) { + m_playbackFrame = m_playSource->getCurrentPlayingFrame(); + } + return m_playbackFrame; +} + +void +ViewManager::setPlaybackFrame(unsigned long f) +{ + if (m_playbackFrame != f) { + m_playbackFrame = f; + emit playbackFrameChanged(f); + if (m_playSource && m_playSource->isPlaying()) { + m_playSource->play(f); + } + } +} + +bool +ViewManager::haveInProgressSelection() const +{ + return !m_inProgressSelection.isEmpty(); +} + +const Selection & +ViewManager::getInProgressSelection(bool &exclusive) const +{ + exclusive = m_inProgressExclusive; + return m_inProgressSelection; +} + +void +ViewManager::setInProgressSelection(const Selection &selection, bool exclusive) +{ + m_inProgressExclusive = exclusive; + m_inProgressSelection = selection; + if (exclusive) clearSelections(); + emit inProgressSelectionChanged(); +} + +void +ViewManager::clearInProgressSelection() +{ + m_inProgressSelection = Selection(); + emit inProgressSelectionChanged(); +} + +const MultiSelection & +ViewManager::getSelection() const +{ + return m_selections; +} + +const MultiSelection::SelectionList & +ViewManager::getSelections() const +{ + return m_selections.getSelections(); +} + +void +ViewManager::setSelection(const Selection &selection) +{ + MultiSelection ms(m_selections); + ms.setSelection(selection); + setSelections(ms); +} + +void +ViewManager::addSelection(const Selection &selection) +{ + MultiSelection ms(m_selections); + ms.addSelection(selection); + setSelections(ms); +} + +void +ViewManager::removeSelection(const Selection &selection) +{ + MultiSelection ms(m_selections); + ms.removeSelection(selection); + setSelections(ms); +} + +void +ViewManager::clearSelections() +{ + MultiSelection ms(m_selections); + ms.clearSelections(); + setSelections(ms); +} + +void +ViewManager::setSelections(const MultiSelection &ms) +{ + if (m_selections.getSelections() == ms.getSelections()) return; + SetSelectionCommand *command = new SetSelectionCommand(this, ms); + CommandHistory::getInstance()->addCommand(command); +} + +void +ViewManager::signalSelectionChange() +{ + emit selectionChanged(); +} + +ViewManager::SetSelectionCommand::SetSelectionCommand(ViewManager *vm, + const MultiSelection &ms) : + m_vm(vm), + m_oldSelection(vm->m_selections), + m_newSelection(ms) +{ +} + +ViewManager::SetSelectionCommand::~SetSelectionCommand() { } + +void +ViewManager::SetSelectionCommand::execute() +{ + m_vm->m_selections = m_newSelection; + m_vm->signalSelectionChange(); +} + +void +ViewManager::SetSelectionCommand::unexecute() +{ + m_vm->m_selections = m_oldSelection; + m_vm->signalSelectionChange(); +} + +QString +ViewManager::SetSelectionCommand::getName() const +{ + if (m_newSelection.getSelections().empty()) return tr("Clear Selection"); + else return tr("Select"); +} + +Selection +ViewManager::getContainingSelection(size_t frame, bool defaultToFollowing) const +{ + return m_selections.getContainingSelection(frame, defaultToFollowing); +} + +void +ViewManager::setToolMode(ToolMode mode) +{ + m_toolMode = mode; + + emit toolModeChanged(); +} + +void +ViewManager::setPlayLoopMode(bool mode) +{ + if (m_playLoopMode != mode) { + + m_playLoopMode = mode; + + emit playLoopModeChanged(); + emit playLoopModeChanged(mode); + } +} + +void +ViewManager::setPlaySelectionMode(bool mode) +{ + if (m_playSelectionMode != mode) { + + m_playSelectionMode = mode; + + emit playSelectionModeChanged(); + emit playSelectionModeChanged(mode); + } +} + +size_t +ViewManager::getPlaybackSampleRate() const +{ + if (m_playSource) { + return m_playSource->getSourceSampleRate(); + } + return 0; +} + +size_t +ViewManager::getOutputSampleRate() const +{ + if (m_playSource) { + return m_playSource->getTargetSampleRate(); + } + return 0; +} + +void +ViewManager::setAudioPlaySource(AudioPlaySource *source) +{ + if (!m_playSource) { + QTimer::singleShot(100, this, SLOT(checkPlayStatus())); + } + m_playSource = source; +} + +void +ViewManager::playStatusChanged(bool /* playing */) +{ + checkPlayStatus(); +} + +void +ViewManager::checkPlayStatus() +{ + if (m_playSource && m_playSource->isPlaying()) { + + float left = 0, right = 0; + if (m_playSource->getOutputLevels(left, right)) { + if (left != m_lastLeft || right != m_lastRight) { + emit outputLevelsChanged(left, right); + m_lastLeft = left; + m_lastRight = right; + } + } + + m_playbackFrame = m_playSource->getCurrentPlayingFrame(); + +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::checkPlayStatus: Playing, frame " << m_playbackFrame << ", levels " << m_lastLeft << "," << m_lastRight << std::endl; +#endif + + emit playbackFrameChanged(m_playbackFrame); + + QTimer::singleShot(20, this, SLOT(checkPlayStatus())); + + } else { + + QTimer::singleShot(100, this, SLOT(checkPlayStatus())); + + if (m_lastLeft != 0.0 || m_lastRight != 0.0) { + emit outputLevelsChanged(0.0, 0.0); + m_lastLeft = 0.0; + m_lastRight = 0.0; + } + +#ifdef DEBUG_VIEW_MANAGER +// std::cout << "ViewManager::checkPlayStatus: Not playing" << std::endl; +#endif + } +} + +bool +ViewManager::isPlaying() const +{ + return m_playSource && m_playSource->isPlaying(); +} + +void +ViewManager::viewCentreFrameChanged(unsigned long f, bool locked, + PlaybackFollowMode mode) +{ + View *v = dynamic_cast(sender()); + + if (locked) { + m_globalCentreFrame = f; + emit globalCentreFrameChanged(f); + } else { + if (v) emit viewCentreFrameChanged(v, f); + } + + if (mode == PlaybackIgnore) { + return; + } + + seek(f); +} + +void +ViewManager::seek(unsigned long f) +{ +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::seek(" << f << ")" << std::endl; +#endif + + if (m_playSource && m_playSource->isPlaying()) { + unsigned long playFrame = m_playSource->getCurrentPlayingFrame(); + unsigned long diff = max(f, playFrame) - min(f, playFrame); + if (diff > 20000) { + m_playbackFrame = f; + m_playSource->play(f); +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::considerSeek: reseeking from " << playFrame << " to " << f << std::endl; +#endif + emit playbackFrameChanged(f); + } + } else { + if (m_playbackFrame != f) { + m_playbackFrame = f; + emit playbackFrameChanged(f); + } + } +} + +void +ViewManager::viewZoomLevelChanged(unsigned long z, bool locked) +{ + View *v = dynamic_cast(sender()); + + if (!v) { + std::cerr << "ViewManager::viewZoomLevelChanged: WARNING: sender is not a view" << std::endl; + return; + } + +//!!! emit zoomLevelChanged(); + + if (locked) { + m_globalZoom = z; + } + +#ifdef DEBUG_VIEW_MANAGER + std::cout << "ViewManager::viewZoomLevelChanged(" << v << ", " << z << ", " << locked << ")" << std::endl; +#endif + + emit viewZoomLevelChanged(v, z, locked); +} + +void +ViewManager::setOverlayMode(OverlayMode mode) +{ + if (m_overlayMode != mode) { + m_overlayMode = mode; + emit overlayModeChanged(); + } + + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("overlay-mode", int(m_overlayMode)); + settings.endGroup(); +} + +void +ViewManager::setZoomWheelsEnabled(bool enabled) +{ + if (m_zoomWheelsEnabled != enabled) { + m_zoomWheelsEnabled = enabled; + emit zoomWheelsEnabledChanged(); + } + + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("zoom-wheels-enabled", m_zoomWheelsEnabled); + settings.endGroup(); +} + diff -r 000000000000 -r fc9323a41f5a view/ViewManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/ViewManager.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,256 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _VIEW_MANAGER_H_ +#define _VIEW_MANAGER_H_ + +#include +#include + +#include + +#include "base/Selection.h" +#include "base/Command.h" +#include "base/Clipboard.h" + +class AudioPlaySource; +class Model; + +enum PlaybackFollowMode { + PlaybackScrollContinuous, + PlaybackScrollPage, + PlaybackIgnore +}; + +class View; + +/** + * The ViewManager manages properties that may need to be synchronised + * between separate Views. For example, it handles signals associated + * with changes to the global pan and zoom, and it handles selections. + * + * Views should be implemented in such a way as to work + * correctly whether they are supplied with a ViewManager or not. + */ + +class ViewManager : public QObject +{ + Q_OBJECT + +public: + ViewManager(); + virtual ~ViewManager(); + + void setAudioPlaySource(AudioPlaySource *source); + + bool isPlaying() const; + + unsigned long getGlobalCentreFrame() const; + void setGlobalCentreFrame(unsigned long); + unsigned long getGlobalZoom() const; + + unsigned long getPlaybackFrame() const; + void setPlaybackFrame(unsigned long frame); + + bool haveInProgressSelection() const; + const Selection &getInProgressSelection(bool &exclusive) const; + void setInProgressSelection(const Selection &selection, bool exclusive); + void clearInProgressSelection(); + + const MultiSelection &getSelection() const; + + const MultiSelection::SelectionList &getSelections() const; + void setSelection(const Selection &selection); + void addSelection(const Selection &selection); + void removeSelection(const Selection &selection); + void clearSelections(); + + /** + * Return the selection that contains a given frame. + * If defaultToFollowing is true, and if the frame is not in a + * selected area, return the next selection after the given frame. + * Return the empty selection if no appropriate selection is found. + */ + Selection getContainingSelection(size_t frame, bool defaultToFollowing) const; + + Clipboard &getClipboard() { return m_clipboard; } + + enum ToolMode { + NavigateMode, + SelectMode, + EditMode, + DrawMode + }; + ToolMode getToolMode() const { return m_toolMode; } + void setToolMode(ToolMode mode); + + bool getPlayLoopMode() const { return m_playLoopMode; } + void setPlayLoopMode(bool on); + + bool getPlaySelectionMode() const { return m_playSelectionMode; } + void setPlaySelectionMode(bool on); + + /** + * The sample rate that is used for playback. This is usually the + * rate of the main model, but not always. Models whose rates + * differ from this will play back at the wrong speed -- there is + * no per-model resampler. + */ + size_t getPlaybackSampleRate() const; + + /** + * The sample rate of the audio output device. If the playback + * sample rate differs from this, everything will be resampled at + * the output stage. + */ + size_t getOutputSampleRate() const; + + /** + * The sample rate of the current main model. This may in theory + * differ from the playback sample rate, in which case even the + * main model will play at the wrong speed. + */ + size_t getMainModelSampleRate() const { return m_mainModelSampleRate; } + + void setMainModelSampleRate(size_t sr) { m_mainModelSampleRate = sr; } + + enum OverlayMode { + NoOverlays, + MinimalOverlays, + StandardOverlays, + AllOverlays + }; + void setOverlayMode(OverlayMode mode); + OverlayMode getOverlayMode() const { return m_overlayMode; } + + bool shouldShowCentreLine() const { + return m_overlayMode != NoOverlays; + } + bool shouldShowFrameCount() const { + return m_overlayMode != NoOverlays; + } + bool shouldShowDuration() const { + return m_overlayMode > MinimalOverlays; + } + bool shouldShowVerticalScale() const { + return m_overlayMode > MinimalOverlays; + } + bool shouldShowSelectionExtents() const { + return m_overlayMode > MinimalOverlays; + } + bool shouldShowLayerNames() const { + return m_overlayMode == AllOverlays; + } + bool shouldShowScaleGuides() const { + return m_overlayMode != NoOverlays; + } + + void setZoomWheelsEnabled(bool enable); + bool getZoomWheelsEnabled() const { return m_zoomWheelsEnabled; } + +signals: + /** Emitted when user causes the global centre frame to change. */ + void globalCentreFrameChanged(unsigned long frame); + + /** Emitted when user scrolls a view, but doesn't affect global centre. */ + void viewCentreFrameChanged(View *v, unsigned long frame); + + /** Emitted when a view zooms. */ + void viewZoomLevelChanged(View *v, unsigned long zoom, bool locked); + + /** Emitted when the playback frame changes. */ + void playbackFrameChanged(unsigned long frame); + + /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */ + void outputLevelsChanged(float left, float right); + + /** Emitted when the selection has changed. */ + void selectionChanged(); + + /** Emitted when the in-progress (rubberbanding) selection has changed. */ + void inProgressSelectionChanged(); + + /** Emitted when the tool mode has been changed. */ + void toolModeChanged(); + + /** Emitted when the play loop mode has been changed. */ + void playLoopModeChanged(); + void playLoopModeChanged(bool); + + /** Emitted when the play selection mode has been changed. */ + void playSelectionModeChanged(); + void playSelectionModeChanged(bool); + + /** Emitted when the overlay mode has been changed. */ + void overlayModeChanged(); + + /** Emitted when the zoom wheels have been toggled. */ + void zoomWheelsEnabledChanged(); + +public slots: + void viewCentreFrameChanged(unsigned long, bool, PlaybackFollowMode); + void viewZoomLevelChanged(unsigned long, bool); + +protected slots: + void checkPlayStatus(); + void playStatusChanged(bool playing); + void seek(unsigned long); +//!!! void considerZoomChange(void *, unsigned long, bool); + +protected: + AudioPlaySource *m_playSource; + unsigned long m_globalCentreFrame; + unsigned long m_globalZoom; + mutable unsigned long m_playbackFrame; + size_t m_mainModelSampleRate; + + float m_lastLeft; + float m_lastRight; + + MultiSelection m_selections; + Selection m_inProgressSelection; + bool m_inProgressExclusive; + + Clipboard m_clipboard; + + ToolMode m_toolMode; + + bool m_playLoopMode; + bool m_playSelectionMode; + + void setSelections(const MultiSelection &ms); + void signalSelectionChange(); + + class SetSelectionCommand : public Command + { + public: + SetSelectionCommand(ViewManager *vm, const MultiSelection &ms); + virtual ~SetSelectionCommand(); + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + protected: + ViewManager *m_vm; + MultiSelection m_oldSelection; + MultiSelection m_newSelection; + }; + + OverlayMode m_overlayMode; + bool m_zoomWheelsEnabled; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a view/svview.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/svview.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a view/view.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/view.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,26 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += xml + +TARGET = svview + +DEPENDPATH += . .. +INCLUDEPATH += . .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += Overview.h \ + Pane.h \ + PaneStack.h \ + View.h \ + ViewManager.h +SOURCES += Overview.cpp \ + Pane.cpp \ + PaneStack.cpp \ + View.cpp \ + ViewManager.cpp diff -r 000000000000 -r fc9323a41f5a widgets/AudioDial.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/AudioDial.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,546 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/** + * A rotary dial widget. + * + * Based on an original design by Thorsten Wilms. + * + * Implemented as a widget for the Rosegarden MIDI and audio sequencer + * and notation editor by Chris Cannam. + * + * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas + * and adapted for use in QSynth. + * + * Ported to Qt4 by Chris Cannam. + * + * This file copyright 2003-2006 Chris Cannam, copyright 2005 Pedro + * Lopez-Cabanillas, copyright 2006 Queen Mary, University of London. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. See the file + * COPYING included with this distribution for more information. + */ + +#include "AudioDial.h" + +#include "base/RangeMapper.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using std::endl; +using std::cerr; + + +//!!! Pedro updated his version to use my up/down response code from RG -- need to grab that code in preference to this version from Rui + + +//------------------------------------------------------------------------- +// AudioDial - Instance knob widget class. +// + +#define AUDIO_DIAL_MIN (0.25 * M_PI) +#define AUDIO_DIAL_MAX (1.75 * M_PI) +#define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) + + +//static int dialsExtant = 0; + + +// Constructor. +AudioDial::AudioDial(QWidget *parent) : + QDial(parent), + m_knobColor(Qt::black), + m_meterColor(Qt::white), + m_defaultValue(0), + m_mappedValue(0), + m_noMappedUpdate(false), + m_showTooltip(true), + m_rangeMapper(0) +{ + m_mouseDial = false; + m_mousePressed = false; +// ++dialsExtant; +} + + +// Destructor. +AudioDial::~AudioDial (void) +{ + delete m_rangeMapper; +// --dialsExtant; +} + + +void AudioDial::setRangeMapper(RangeMapper *mapper) +{ +// std::cerr << "AudioDial[" << this << "][\"" << objectName().toStdString() << "\"::setRangeMapper(" << mapper << ") [current is " << m_rangeMapper << "] (have " << dialsExtant << " dials extant)" << std::endl; + + if (m_rangeMapper == mapper) return; + + if (!m_rangeMapper && mapper) { + connect(this, SIGNAL(valueChanged(int)), + this, SLOT(updateMappedValue(int))); + } + + delete m_rangeMapper; + m_rangeMapper = mapper; + + updateMappedValue(value()); +} + + +void AudioDial::paintEvent(QPaintEvent *) +{ + QPainter paint; + + float angle = AUDIO_DIAL_MIN // offset + + (AUDIO_DIAL_RANGE * + (float(QDial::value() - QDial::minimum()) / + (float(QDial::maximum() - QDial::minimum())))); + int degrees = int(angle * 180.0 / M_PI); + + int ns = notchSize(); + int numTicks = 1 + (maximum() + ns - minimum()) / ns; + + QColor knobColor(m_knobColor); + if (knobColor == Qt::black) + knobColor = palette().background().color(); + + QColor meterColor(m_meterColor); + if (!isEnabled()) + meterColor = palette().mid().color(); + else if (m_meterColor == Qt::white) + meterColor = palette().highlight().color(); + + int m_size = width() < height() ? width() : height(); + int scale = 1; + int width = m_size - 2*scale; + + paint.begin(this); + paint.setRenderHint(QPainter::Antialiasing, true); + paint.translate(1, 1); + + QPen pen; + QColor c; + + // Knob body and face... + + c = knobColor; + pen.setColor(knobColor); + pen.setWidth(scale * 2); + pen.setCapStyle(Qt::FlatCap); + + paint.setPen(pen); + paint.setBrush(c); + + int indent = (int)(width * 0.15 + 1); + + paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent); + + pen.setWidth(3 * scale); + int pos = indent-1 + (width-2*indent) / 20; + int darkWidth = (width-2*indent) * 3 / 4; + while (darkWidth) { + c = c.light(102); + pen.setColor(c); + paint.setPen(pen); + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + if (!--darkWidth) break; + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + if (!--darkWidth) break; + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + ++pos; --darkWidth; + } + + // Tick notches... + + if ( notchesVisible() ) { +// std::cerr << "Notches visible" << std::endl; + pen.setColor(palette().dark().color()); + pen.setWidth(scale); + paint.setPen(pen); + for (int i = 0; i < numTicks; ++i) { + int div = numTicks; + if (div > 1) --div; + drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, + width, true); + } + } + + // The bright metering bit... + + c = meterColor; + pen.setColor(c); + pen.setWidth(indent); + paint.setPen(pen); + +// std::cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << std::endl; + + int arcLen = -(degrees - 45) * 16; + if (arcLen == 0) arcLen = -16; + + paint.drawArc(indent/2, indent/2, + width-indent, width-indent, (180 + 45) * 16, arcLen); + + paint.setBrush(Qt::NoBrush); + + // Shadowing... + + pen.setWidth(scale); + paint.setPen(pen); + + // Knob shadow... + + int shadowAngle = -720; + c = knobColor.dark(); + for (int arc = 120; arc < 2880; arc += 240) { + pen.setColor(c); + paint.setPen(pen); + paint.drawArc(indent, indent, + width-2*indent, width-2*indent, shadowAngle + arc, 240); + paint.drawArc(indent, indent, + width-2*indent, width-2*indent, shadowAngle - arc, 240); + c = c.light(110); + } + + // Scale shadow... + + shadowAngle = 2160; + c = palette().dark().color(); + for (int arc = 120; arc < 2880; arc += 240) { + pen.setColor(c); + paint.setPen(pen); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, shadowAngle + arc, 240); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, shadowAngle - arc, 240); + c = c.light(108); + } + + // Undraw the bottom part... + + pen.setColor(palette().background().color()); + pen.setWidth(scale * 4); + paint.setPen(pen); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, -45 * 16, -92 * 16); + + // Scale ends... + + pen.setColor(palette().dark().color()); + pen.setWidth(scale); + paint.setPen(pen); + for (int i = 0; i < numTicks; ++i) { + if (i != 0 && i != numTicks - 1) continue; + int div = numTicks; + if (div > 1) --div; + drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, + width, false); + } + + // Pointer notch... + + float hyp = float(width) / 2.0; + float len = hyp - indent; + --len; + + float x0 = hyp; + float y0 = hyp; + + float x = hyp - len * sin(angle); + float y = hyp + len * cos(angle); + + c = palette().dark().color(); + pen.setColor(isEnabled() ? c.dark(130) : c); + pen.setWidth(scale * 2); + paint.setPen(pen); + paint.drawLine(int(x0), int(y0), int(x), int(y)); + + paint.end(); +} + + +void AudioDial::drawTick(QPainter &paint, + float angle, int size, bool internal) +{ + float hyp = float(size) / 2.0; + float x0 = hyp - (hyp - 1) * sin(angle); + float y0 = hyp + (hyp - 1) * cos(angle); + +// cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl; + + if (internal) { + + float len = hyp / 4; + float x1 = hyp - (hyp - len) * sin(angle); + float y1 = hyp + (hyp - len) * cos(angle); + + paint.drawLine(int(x0), int(y0), int(x1), int(y1)); + + } else { + + float len = hyp / 4; + float x1 = hyp - (hyp + len) * sin(angle); + float y1 = hyp + (hyp + len) * cos(angle); + + paint.drawLine(int(x0), int(y0), int(x1), int(y1)); + } +} + + +void AudioDial::setKnobColor(const QColor& color) +{ + m_knobColor = color; + update(); +} + + +void AudioDial::setMeterColor(const QColor& color) +{ + m_meterColor = color; + update(); +} + + +void AudioDial::setMouseDial(bool mouseDial) +{ + m_mouseDial = mouseDial; +} + + +void AudioDial::setDefaultValue(int defaultValue) +{ + m_defaultValue = defaultValue; +} + + +void AudioDial::setValue(int value) +{ + QDial::setValue(value); + updateMappedValue(value); +} + + +void AudioDial::setMappedValue(float mappedValue) +{ + if (m_rangeMapper) { + int newPosition = m_rangeMapper->getPositionForValue(mappedValue); + bool changed = (m_mappedValue != mappedValue); + m_mappedValue = mappedValue; + m_noMappedUpdate = true; + std::cerr << "AudioDial::setMappedValue(" << mappedValue << "): new position is " << newPosition << std::endl; + if (newPosition != value()) { + setValue(newPosition); + } else if (changed) { + emit valueChanged(newPosition); + } + m_noMappedUpdate = false; + } else { + setValue(int(mappedValue)); + } +} + + +void AudioDial::setShowToolTip(bool show) +{ + m_showTooltip = show; + m_noMappedUpdate = true; + updateMappedValue(value()); + m_noMappedUpdate = false; +} + + +float AudioDial::mappedValue() const +{ + if (m_rangeMapper) { + std::cerr << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << std::endl; + return m_mappedValue; + } + return value(); +} + + +void AudioDial::updateMappedValue(int value) +{ + if (!m_noMappedUpdate) { + if (m_rangeMapper) { + m_mappedValue = m_rangeMapper->getValueForPosition(value); + } else { + m_mappedValue = value; + } + } + + if (m_showTooltip) { + QString name = objectName(); + QString unit = ""; + QString text; + if (m_rangeMapper) unit = m_rangeMapper->getUnit(); + if (name != "") { + text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + } else { + text = tr("%2%3").arg(m_mappedValue).arg(unit); + } + setToolTip(text); + } +} + + +// Alternate mouse behavior event handlers. +void AudioDial::mousePressEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mousePressEvent(mouseEvent); + } else if (mouseEvent->button() == Qt::MidButton || + ((mouseEvent->button() == Qt::LeftButton) && + (mouseEvent->modifiers() & Qt::ControlModifier))) { + int dv = m_defaultValue; + if (dv < minimum()) dv = minimum(); + if (dv > maximum()) dv = maximum(); + setValue(m_defaultValue); + } else if (mouseEvent->button() == Qt::LeftButton) { + m_mousePressed = true; + m_posMouse = mouseEvent->pos(); + } +} + + +void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent) +{ + //!!! needs a common base class with Thumbwheel + + if (m_mouseDial) { + QDial::mouseDoubleClickEvent(mouseEvent); + } else if (mouseEvent->button() != Qt::LeftButton) { + return; + } + + bool ok = false; + + if (m_rangeMapper) { + + float min = m_rangeMapper->getValueForPosition(minimum()); + float max = m_rangeMapper->getValueForPosition(maximum()); + + if (min > max) { + float tmp = min; + min = max; + max = tmp; + } + + QString unit = m_rangeMapper->getUnit(); + + QString text; + if (objectName() != "") { + if (unit != "") { + text = tr("New value for %1, from %2 to %3 %4:") + .arg(objectName()).arg(min).arg(max).arg(unit); + } else { + text = tr("New value for %1, from %2 to %3:") + .arg(objectName()).arg(min).arg(max); + } + } else { + if (unit != "") { + text = tr("Enter a new value from %1 to %2 %3:") + .arg(min).arg(max).arg(unit); + } else { + text = tr("Enter a new value from %1 to %2:") + .arg(min).arg(max); + } + } + + float newValue = QInputDialog::getDouble + (this, + tr("Enter new value"), + text, + m_mappedValue, + min, + max, + 4, + &ok); + + if (ok) { + setMappedValue(newValue); + } + + } else { + + int newPosition = QInputDialog::getInteger + (this, + tr("Enter new value"), + tr("Enter a new value from %1 to %2:") + .arg(minimum()).arg(maximum()), + value(), minimum(), maximum(), pageStep(), &ok); + + if (ok) { + setValue(newPosition); + } + } +} + + +void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mouseMoveEvent(mouseEvent); + } else if (m_mousePressed) { + const QPoint& posMouse = mouseEvent->pos(); + int v = QDial::value() + + (posMouse.x() - m_posMouse.x()) + + (m_posMouse.y() - posMouse.y()); + if (v > QDial::maximum()) + v = QDial::maximum(); + else + if (v < QDial::minimum()) + v = QDial::minimum(); + m_posMouse = posMouse; + QDial::setValue(v); + } +} + + +void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mouseReleaseEvent(mouseEvent); + } else if (m_mousePressed) { + m_mousePressed = false; + } +} + +void +AudioDial::enterEvent(QEvent *e) +{ + QDial::enterEvent(e); + emit mouseEntered(); +} + +void +AudioDial::leaveEvent(QEvent *e) +{ + QDial::enterEvent(e); + emit mouseLeft(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/AudioDial.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/AudioDial.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,146 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_DIAL_H_ +#define _AUDIO_DIAL_H_ + +/** + * A rotary dial widget. + * + * Based on an original design by Thorsten Wilms. + * + * Implemented as a widget for the Rosegarden MIDI and audio sequencer + * and notation editor by Chris Cannam. + * + * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas + * and adapted for use in QSynth. + * + * Ported to Qt4 by Chris Cannam. + * + * This file copyright 2003-2006 Chris Cannam, copyright 2005 Pedro + * Lopez-Cabanillas, copyright 2006 Queen Mary, University of London. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. See the file + * COPYING included with this distribution for more information. + */ + +#include +#include + +class RangeMapper; + +/** + * AudioDial is a nicer-looking QDial that by default reacts to mouse + * movement on horizontal and vertical axes instead of in a radial + * motion. Move the mouse up or right to increment the value, down or + * left to decrement it. AudioDial also responds to the mouse wheel. + * + * The programming interface for this widget is compatible with QDial, + * with the addition of properties for the knob colour and meter + * colour and a boolean property mouseDial that determines whether to + * respond to radial mouse motion in the same way as QDial (the + * default is no). + */ + +class AudioDial : public QDial +{ + Q_OBJECT + Q_PROPERTY( QColor knobColor READ getKnobColor WRITE setKnobColor ) + Q_PROPERTY( QColor meterColor READ getMeterColor WRITE setMeterColor ) + Q_PROPERTY( bool mouseDial READ getMouseDial WRITE setMouseDial ) + +public: + AudioDial(QWidget *parent = 0); + ~AudioDial(); + + const QColor& getKnobColor() const { return m_knobColor; } + const QColor& getMeterColor() const { return m_meterColor; } + bool getMouseDial() const { return m_mouseDial; } + + void setRangeMapper(RangeMapper *mapper); // I take ownership, will delete + const RangeMapper *rangeMapper() const { return m_rangeMapper; } + float mappedValue() const; + + void setShowToolTip(bool show); + +signals: + void mouseEntered(); + void mouseLeft(); + +public slots: + /** + * Set the colour of the knob. The default is to inherit the + * colour from the widget's palette. + */ + void setKnobColor(const QColor &color); + + /** + * Set the colour of the meter (the highlighted area around the + * knob that shows the current value). The default is to inherit + * the colour from the widget's palette. + */ + void setMeterColor(const QColor &color); + + /** + * Specify that the dial should respond to radial mouse movements + * in the same way as QDial. + */ + void setMouseDial(bool mouseDial); + + void setDefaultValue(int defaultValue); + + void setValue(int value); + + void setMappedValue(float mappedValue); + +protected: + void drawTick(QPainter &paint, float angle, int size, bool internal); + virtual void paintEvent(QPaintEvent *); + + // Alternate mouse behavior event handlers. + virtual void mousePressEvent(QMouseEvent *pMouseEvent); + virtual void mouseMoveEvent(QMouseEvent *pMouseEvent); + virtual void mouseReleaseEvent(QMouseEvent *pMouseEvent); + virtual void mouseDoubleClickEvent(QMouseEvent *pMouseEvent); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + +protected slots: + void updateMappedValue(int value); + +private: + QColor m_knobColor; + QColor m_meterColor; + + int m_defaultValue; + float m_mappedValue; + bool m_noMappedUpdate; + + // Alternate mouse behavior tracking. + bool m_mouseDial; + bool m_mousePressed; + QPoint m_posMouse; + + bool m_showTooltip; + + RangeMapper *m_rangeMapper; +}; + + +#endif // __AudioDial_h + +// end of AudioDial.h diff -r 000000000000 -r fc9323a41f5a widgets/Fader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Fader.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,300 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/** + * Horizontal audio fader and meter widget. + * + * Based on the vertical fader and meter widget from the Hydrogen drum + * machine. (Any poor taste that has crept in during the + * modifications for this application is entirely my own, however.) + * The following copyright notice applies to code from this file, and + * also to the files in icons/fader_*.png (also modified by me). --cc + */ + +/** + * Hydrogen + * Copyright(c) 2002-2005 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * http://www.hydrogen-music.org + */ + + +#include "Fader.h" + +#include "base/AudioLevel.h" + +#include +#include +#include +#include +#include +#include + +#include + +Fader::Fader(QWidget *parent, bool withoutKnob) : + QWidget(parent), + m_withoutKnob(withoutKnob), + m_value(1.0), + m_peakLeft(0.0), + m_peakRight(0.0), + m_mousePressed(false) +{ + setMinimumSize(116, 23); + setMaximumSize(116, 23); + resize(116, 23); + + QString background_path = ":/icons/fader_background.png"; + bool ok = m_back.load(background_path); + if (ok == false) { + std::cerr << "Fader: Error loading pixmap" << std::endl; + } + + QString leds_path = ":/icons/fader_leds.png"; + ok = m_leds.load(leds_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } + + QString knob_path = ":/icons/fader_knob.png"; + ok = m_knob.load(knob_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } + + QString clip_path = ":/icons/fader_knob_red.png"; + ok = m_clip.load(clip_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } +} + +Fader::~Fader() +{ + +} + +void +Fader::mouseMoveEvent(QMouseEvent *ev) +{ + if (ev->button() == Qt::MidButton) { + setValue(1.0); + emit valueChanged(1.0); + update(); + ev->accept(); + return; + } + if (!m_mousePressed) return; + + int x = ev->x(); + int diff = x - m_mousePressX; + if (diff == 0) return; + + int vx = AudioLevel::multiplier_to_fader + (m_mousePressValue, getMaxX(), AudioLevel::LongFader); + + vx += diff; + + if (vx > getMaxX()) vx = getMaxX(); + if (vx < 0) vx = 0; + + float fval = AudioLevel::fader_to_multiplier + (vx, getMaxX(), AudioLevel::LongFader); + + setValue(fval); + emit valueChanged(fval); + ev->accept(); +} + + +void +Fader::mouseReleaseEvent(QMouseEvent *ev) +{ + if (m_mousePressed) { + mouseMoveEvent(ev); + m_mousePressed = false; + } +} + +void +Fader::mouseDoubleClickEvent(QMouseEvent *) +{ + bool ok = false; + float min = AudioLevel::fader_to_dB + (0, getMaxX(), AudioLevel::LongFader); + float max = AudioLevel::fader_to_dB + (getMaxX(), getMaxX(), AudioLevel::LongFader); + float deft = AudioLevel::multiplier_to_dB(m_value); + + float dB = QInputDialog::getDouble + (this, + tr("Enter new fader level"), + tr("New fader level, from %1 to %2 dBFS:").arg(min).arg(max), + deft, min, max, 3, &ok); + + if (ok) { + float value = AudioLevel::dB_to_multiplier(dB); + setValue(value); + emit valueChanged(value); + update(); + } +} + +void +Fader::mousePressEvent(QMouseEvent *ev) +{ + if (ev->button() == Qt::MidButton || + ((ev->button() == Qt::LeftButton) && + (ev->modifiers() & Qt::ControlModifier))) { + setValue(1.0); + emit valueChanged(1.0); + update(); + return; + } + + if (ev->button() != Qt::LeftButton) return; + m_mousePressed = true; + m_mousePressX = ev->x(); + m_mousePressValue = getValue(); +} + + +void +Fader::wheelEvent(QWheelEvent *ev) +{ + ev->accept(); + + //!!! needs improvement + + if (ev->delta() > 0) { + setValue(m_value * 1.1); + } else { + setValue(m_value / 1.1); + } + + update(); + emit valueChanged(getValue()); +} + +void +Fader::enterEvent(QEvent *) +{ + emit mouseEntered(); +} + +void +Fader::leaveEvent(QEvent *) +{ + emit mouseLeft(); +} + +void +Fader::setValue(float v) +{ + float max = AudioLevel::dB_to_multiplier(10.0); + + if (v > max) { + v = max; + } else if (v < 0.0) { + v = 0.0; + } + + if (m_value != v) { + m_value = v; + float db = AudioLevel::multiplier_to_dB(m_value); + QString text; + if (db <= AudioLevel::DB_FLOOR) { + text = tr("Level: Off"); + } else { + text = tr("Level: %1%2.%3%4 dB") + .arg(db < 0.0 ? "-" : "") + .arg(abs(int(db))) + .arg(abs(int(db * 10.0) % 10)) + .arg(abs(int(db * 100.0) % 10)); + } + std::cerr << "Fader: setting tooltip to \"" << text.toStdString() << "\"" << std::endl; + QWidget::setToolTip(text); + update(); + } +} + + +float +Fader::getValue() +{ + return m_value; +} + + + +void +Fader::setPeakLeft(float peak) +{ + if (this->m_peakLeft != peak) { + this->m_peakLeft = peak; + update(); + } +} + + +void +Fader::setPeakRight(float peak) +{ + if (this->m_peakRight != peak) { + this->m_peakRight = peak; + update(); + } +} + + +void +Fader::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + // background + painter.drawPixmap(rect(), m_back, QRect(0, 0, 116, 23)); + + int offset_L = AudioLevel::multiplier_to_fader(m_peakLeft, 116, + AudioLevel::IEC268LongMeter); + + painter.drawPixmap(QRect(0, 0, offset_L, 11), m_leds, + QRect(0, 0, offset_L, 11)); + + int offset_R = AudioLevel::multiplier_to_fader(m_peakRight, 116, + AudioLevel::IEC268LongMeter); + + painter.drawPixmap(QRect(0, 11, offset_R, 11), m_leds, + QRect(0, 11, offset_R, 11)); + + if (m_withoutKnob == false) { + + static const uint knob_width = 29; + static const uint knob_height = 9; + + int x = AudioLevel::multiplier_to_fader(m_value, 116 - knob_width, + AudioLevel::LongFader); + + bool clipping = (m_peakLeft > 1.0 || m_peakRight > 1.0); + + painter.drawPixmap(QRect(x, 7, knob_width, knob_height), + clipping ? m_clip : m_knob, + QRect(0, 0, knob_width, knob_height)); + } +} + +int +Fader::getMaxX() const +{ + return 116 - 12; +} diff -r 000000000000 -r fc9323a41f5a widgets/Fader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Fader.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,87 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef FADER_H +#define FADER_H + +/** + * Horizontal audio fader and meter widget. + * Based on the vertical fader and meter widget from: + * + * Hydrogen + * Copyright(c) 2002-2005 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * http://www.hydrogen-music.org + */ + + +#include +#include + +#include +#include +#include +#include +#include + +class Fader : public QWidget +{ + Q_OBJECT + +public: + Fader(QWidget *parent, bool withoutKnob = false); + ~Fader(); + + void setValue(float newValue); + float getValue(); + + void setPeakLeft(float); + float getPeakLeft() { return m_peakLeft; } + + void setPeakRight(float); + float getPeakRight() { return m_peakRight; } + +signals: + void valueChanged(float); // 0.0 -> 1.0 + + void mouseEntered(); + void mouseLeft(); + +protected: + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseDoubleClickEvent(QMouseEvent *ev); + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mouseReleaseEvent(QMouseEvent *ev); + virtual void wheelEvent( QWheelEvent *ev ); + virtual void paintEvent(QPaintEvent *ev); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + int getMaxX() const; + + bool m_withoutKnob; + float m_value; + float m_peakLeft; + float m_peakRight; + + bool m_mousePressed; + int m_mousePressX; + float m_mousePressValue; + + QPixmap m_back; + QPixmap m_leds; + QPixmap m_knob; + QPixmap m_clip; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/ItemEditDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ItemEditDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,378 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ItemEditDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for FLT_MIN/MAX + + +ItemEditDialog::ItemEditDialog(size_t sampleRate, int options, + QString valueUnits, QWidget *parent) : + QDialog(parent), + m_sampleRate(sampleRate), + m_frameTimeSpinBox(0), + m_realTimeSecsSpinBox(0), + m_realTimeUSecsSpinBox(0), + m_frameDurationSpinBox(0), + m_realDurationSecsSpinBox(0), + m_realDurationUSecsSpinBox(0), + m_valueSpinBox(0), + m_textField(0) +{ + QGridLayout *grid = new QGridLayout; + setLayout(grid); + + QGroupBox *timeBox = 0; + QGroupBox *valueBox = 0; + QGridLayout *subgrid = 0; + + int row = 0, subrow = 0; + + size_t singleStep = RealTime::frame2RealTime(2, sampleRate).usec() - 1; + + if ((options & ShowTime) || (options & ShowDuration)) { + + timeBox = new QGroupBox; + timeBox->setTitle(tr("Timing")); + grid->addWidget(timeBox, row, 0); + + subgrid = new QGridLayout; + timeBox->setLayout(subgrid); + + ++row; + } + + if (options & ShowTime) { + + subgrid->addWidget(new QLabel(tr("Time:")), subrow, 0); + + m_frameTimeSpinBox = new QSpinBox; + m_frameTimeSpinBox->setMaximum(INT_MAX); + m_frameTimeSpinBox->setSuffix(tr(" frames")); + subgrid->addWidget(m_frameTimeSpinBox, subrow, 1, 1, 2); + connect(m_frameTimeSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(frameTimeChanged(int))); + + ++subrow; + + m_realTimeSecsSpinBox = new QSpinBox; + m_realTimeSecsSpinBox->setMaximum(999999); + m_realTimeSecsSpinBox->setSuffix(tr(" sec")); + subgrid->addWidget(m_realTimeSecsSpinBox, subrow, 1); + connect(m_realTimeSecsSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(realTimeSecsChanged(int))); + + m_realTimeUSecsSpinBox = new QSpinBox; + m_realTimeUSecsSpinBox->setMaximum(999999); + m_realTimeUSecsSpinBox->setSuffix(tr(" usec")); + m_realTimeUSecsSpinBox->setSingleStep(singleStep); + subgrid->addWidget(m_realTimeUSecsSpinBox, subrow, 2); + connect(m_realTimeUSecsSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(realTimeUSecsChanged(int))); + + ++subrow; + } + + if (options & ShowDuration) { + + subgrid->addWidget(new QLabel(tr("Duration:")), subrow, 0); + + m_frameDurationSpinBox = new QSpinBox; + m_frameDurationSpinBox->setMaximum(INT_MAX); + m_frameDurationSpinBox->setSuffix(tr(" frames")); + subgrid->addWidget(m_frameDurationSpinBox, subrow, 1, 1, 2); + connect(m_frameDurationSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(frameDurationChanged(int))); + + ++subrow; + + m_realDurationSecsSpinBox = new QSpinBox; + m_realDurationSecsSpinBox->setMaximum(999999); + m_realDurationSecsSpinBox->setSuffix(tr(" sec")); + subgrid->addWidget(m_realDurationSecsSpinBox, subrow, 1); + connect(m_realDurationSecsSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(realDurationSecsChanged(int))); + + m_realDurationUSecsSpinBox = new QSpinBox; + m_realDurationUSecsSpinBox->setMaximum(999999); + m_realDurationUSecsSpinBox->setSuffix(tr(" usec")); + m_realDurationUSecsSpinBox->setSingleStep(singleStep); + subgrid->addWidget(m_realDurationUSecsSpinBox, subrow, 2); + connect(m_realDurationUSecsSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(realDurationUSecsChanged(int))); + + ++subrow; + } + + if ((options & ShowValue) || (options & ShowText)) { + + valueBox = new QGroupBox; + valueBox->setTitle(tr("Properties")); + grid->addWidget(valueBox, row, 0); + + subgrid = new QGridLayout; + valueBox->setLayout(subgrid); + + ++row; + } + + subrow = 0; + + if (options & ShowValue) { + + subgrid->addWidget(new QLabel(tr("Value:")), subrow, 0); + + m_valueSpinBox = new QDoubleSpinBox; + m_valueSpinBox->setSuffix(QString(" %1").arg(valueUnits)); + m_valueSpinBox->setDecimals(10); + m_valueSpinBox->setMinimum(-1e100); + m_valueSpinBox->setMaximum(1e100); + connect(m_valueSpinBox, SIGNAL(valueChanged(double)), + this, SLOT(valueChanged(double))); + subgrid->addWidget(m_valueSpinBox, subrow, 1); + + ++subrow; + } + + if (options & ShowText) { + + subgrid->addWidget(new QLabel(tr("Text:")), subrow, 0); + + m_textField = new QLineEdit; + connect(m_textField, SIGNAL(textChanged(QString)), + this, SLOT(textChanged(QString))); + subgrid->addWidget(m_textField, subrow, 1); + + ++subrow; + } + + if (options & ShowText) { + m_textField->setFocus(Qt::OtherFocusReason); + } else if (options & ShowValue) { + m_valueSpinBox->setFocus(Qt::OtherFocusReason); + } + + QHBoxLayout *hbox = new QHBoxLayout; + grid->addLayout(hbox, row, 0, 1, 2); + + QPushButton *ok = new QPushButton(tr("OK")); + m_resetButton = new QPushButton(tr("Reset")); + QPushButton *cancel = new QPushButton(tr("Cancel")); + hbox->addStretch(10); + hbox->addWidget(ok); + hbox->addWidget(m_resetButton); + hbox->addWidget(cancel); + ok->setDefault(true); + connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); + connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); + m_resetButton->setEnabled(false); +} + +void +ItemEditDialog::setFrameTime(long frame) +{ + if (!m_frameTimeSpinBox) return; + + RealTime rt(RealTime::frame2RealTime(frame, m_sampleRate)); + m_realTimeSecsSpinBox->setValue(rt.sec); + m_realTimeUSecsSpinBox->setValue(rt.usec()); + m_frameTimeSpinBox->setValue(frame); + m_defaultFrame = frame; + m_resetButton->setEnabled(false); +} + +long +ItemEditDialog::getFrameTime() const +{ + return m_frameTimeSpinBox->value(); +} + +void +ItemEditDialog::setRealTime(RealTime rt) +{ + setFrameTime(RealTime::realTime2Frame(rt, m_sampleRate)); +} + +RealTime +ItemEditDialog::getRealTime() const +{ + return RealTime::frame2RealTime(getFrameTime(), m_sampleRate); +} + +void +ItemEditDialog::setFrameDuration(long duration) +{ + if (!m_frameDurationSpinBox) return; + + RealTime rt(RealTime::frame2RealTime(duration, m_sampleRate)); + m_realDurationSecsSpinBox->setValue(rt.sec); + m_realDurationUSecsSpinBox->setValue(rt.usec()); + m_frameDurationSpinBox->setValue(duration); + m_defaultDuration = duration; + m_resetButton->setEnabled(false); +} + +long +ItemEditDialog::getFrameDuration() const +{ + return m_frameDurationSpinBox->value(); +} + +void +ItemEditDialog::setRealDuration(RealTime rt) +{ + setFrameDuration(RealTime::realTime2Frame(rt, m_sampleRate)); +} + +RealTime +ItemEditDialog::getRealDuration() const +{ + return RealTime::frame2RealTime(getFrameDuration(), m_sampleRate); +} + +void +ItemEditDialog::setValue(float v) +{ + if (!m_valueSpinBox) return; + + m_valueSpinBox->setValue(v); + m_defaultValue = v; + m_resetButton->setEnabled(false); +} + +float +ItemEditDialog::getValue() const +{ + return m_valueSpinBox->value(); +} + +void +ItemEditDialog::setText(QString text) +{ + if (!m_textField) return; + + m_textField->setText(text); + m_defaultText = text; + m_resetButton->setEnabled(false); +} + +QString +ItemEditDialog::getText() const +{ + return m_textField->text(); +} + +void +ItemEditDialog::frameTimeChanged(int i) +{ + m_realTimeSecsSpinBox->blockSignals(true); + m_realTimeUSecsSpinBox->blockSignals(true); + + RealTime rt(RealTime::frame2RealTime(i, m_sampleRate)); + m_realTimeSecsSpinBox->setValue(rt.sec); + m_realTimeUSecsSpinBox->setValue(rt.usec()); + + m_realTimeSecsSpinBox->blockSignals(false); + m_realTimeUSecsSpinBox->blockSignals(false); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::realTimeSecsChanged(int i) +{ + RealTime rt = getRealTime(); + rt.sec = i; + size_t frame = RealTime::realTime2Frame(rt, m_sampleRate); + m_frameTimeSpinBox->setValue(frame); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::realTimeUSecsChanged(int i) +{ + RealTime rt = getRealTime(); + rt.nsec = i * 1000; + size_t frame = RealTime::realTime2Frame(rt, m_sampleRate); + m_frameTimeSpinBox->setValue(frame); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::frameDurationChanged(int i) +{ + m_realDurationSecsSpinBox->blockSignals(true); + m_realDurationUSecsSpinBox->blockSignals(true); + + RealTime rt(RealTime::frame2RealTime(i, m_sampleRate)); + m_realDurationSecsSpinBox->setValue(rt.sec); + m_realDurationUSecsSpinBox->setValue(rt.usec()); + + m_realDurationSecsSpinBox->blockSignals(false); + m_realDurationUSecsSpinBox->blockSignals(false); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::realDurationSecsChanged(int i) +{ + RealTime rt = getRealDuration(); + rt.sec = i; + size_t frame = RealTime::realTime2Frame(rt, m_sampleRate); + m_frameDurationSpinBox->setValue(frame); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::realDurationUSecsChanged(int i) +{ + RealTime rt = getRealDuration(); + rt.nsec = i * 1000; + size_t frame = RealTime::realTime2Frame(rt, m_sampleRate); + m_frameDurationSpinBox->setValue(frame); + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::valueChanged(double) +{ + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::textChanged(QString) +{ + m_resetButton->setEnabled(true); +} + +void +ItemEditDialog::reset() +{ + setFrameTime(m_defaultFrame); + setFrameDuration(m_defaultDuration); + setValue(m_defaultValue); + setText(m_defaultText); + m_resetButton->setEnabled(false); +} + diff -r 000000000000 -r fc9323a41f5a widgets/ItemEditDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ItemEditDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _ITEM_EDIT_DIALOG_H_ +#define _ITEM_EDIT_DIALOG_H_ + +#include +#include + +#include "base/RealTime.h" + +class QSpinBox; +class QDoubleSpinBox; +class QLineEdit; + +class ItemEditDialog : public QDialog +{ + Q_OBJECT + +public: + enum { + ShowTime = 1 << 0, + ShowDuration = 1 << 1, + ShowValue = 1 << 2, + ShowText = 1 << 3 + }; + + ItemEditDialog(size_t sampleRate, int options, QString valueUnits = "", + QWidget *parent = 0); + + void setFrameTime(long frame); + long getFrameTime() const; + + void setRealTime(RealTime rt); + RealTime getRealTime() const; + + void setFrameDuration(long frame); + long getFrameDuration() const; + + void setRealDuration(RealTime rt); + RealTime getRealDuration() const; + + void setValue(float value); + float getValue() const; + + void setText(QString text); + QString getText() const; + +protected slots: + void frameTimeChanged(int); + void realTimeSecsChanged(int); + void realTimeUSecsChanged(int); + void frameDurationChanged(int); + void realDurationSecsChanged(int); + void realDurationUSecsChanged(int); + void valueChanged(double); + void textChanged(QString); + void reset(); + +protected: + size_t m_sampleRate; + long m_defaultFrame; + long m_defaultDuration; + float m_defaultValue; + QString m_defaultText; + QSpinBox *m_frameTimeSpinBox; + QSpinBox *m_realTimeSecsSpinBox; + QSpinBox *m_realTimeUSecsSpinBox; + QSpinBox *m_frameDurationSpinBox; + QSpinBox *m_realDurationSecsSpinBox; + QSpinBox *m_realDurationUSecsSpinBox; + QDoubleSpinBox *m_valueSpinBox; + QLineEdit *m_textField; + QPushButton *m_resetButton; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/LEDButton.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LEDButton.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,349 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the KDE + libraries. Copyright (c) 1998-2004 Jörg Habenicht, Richard J + Moore, Chris Cannam and others, distributed under the GNU Lesser + General Public License. + + Ported to Qt4 by Chris Cannam. +*/ + + +#include "LEDButton.h" + +#include +#include +#include +#include + +#include + + +class LEDButton::LEDButtonPrivate +{ + friend class LEDButton; + + int dark_factor; + QColor offcolor; + QPixmap *off_map; + QPixmap *on_map; +}; + + +LEDButton::LEDButton(QWidget *parent) : + QWidget(parent), + led_state(true) +{ + QColor col(Qt::green); + d = new LEDButton::LEDButtonPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + setColor(col); +} + + +LEDButton::LEDButton(const QColor& col, QWidget *parent) : + QWidget(parent), + led_state(true) +{ + d = new LEDButton::LEDButtonPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + setColor(col); +} + +LEDButton::LEDButton(const QColor& col, bool state, QWidget *parent) : + QWidget(parent), + led_state(state) +{ + d = new LEDButton::LEDButtonPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + setColor(col); +} + +LEDButton::~LEDButton() +{ + delete d->off_map; + delete d->on_map; + delete d; +} + +void +LEDButton::mousePressEvent(QMouseEvent *e) +{ + std::cerr << "LEDButton(" << this << ")::mousePressEvent" << std::endl; + + if (e->buttons() & Qt::LeftButton) { + toggle(); + bool newState = state(); + std::cerr << "emitting new state " << newState << std::endl; + emit stateChanged(newState); + } +} + +void +LEDButton::enterEvent(QEvent *) +{ + emit mouseEntered(); +} + +void +LEDButton::leaveEvent(QEvent *) +{ + emit mouseLeft(); +} + +void +LEDButton::paintEvent(QPaintEvent *) +{ + QPainter paint; + QColor color; + QBrush brush; + QPen pen; + + // First of all we want to know what area should be updated + // Initialize coordinates, width, and height of the LED + int width = this->width(); + + // Make sure the LED is round! + if (width > this->height()) + width = this->height(); + width -= 2; // leave one pixel border + if (width < 0) + width = 0; + + QPixmap *tmpMap = 0; + + if (led_state) { + if (d->on_map) { + paint.begin(this); + paint.drawPixmap(0, 0, *d->on_map); + paint.end(); + return; + } + } else { + if (d->off_map) { + paint.begin(this); + paint.drawPixmap(0, 0, *d->off_map); + paint.end(); + return; + } + } + + int scale = 1; + width *= scale; + + tmpMap = new QPixmap(width, width); + tmpMap->fill(palette().background().color()); + paint.begin(tmpMap); + + paint.setRenderHint(QPainter::Antialiasing, true); + + // Set the color of the LED according to given parameters + color = (led_state) ? led_color : d->offcolor; + + // Set the brush to SolidPattern, this fills the entire area + // of the ellipse which is drawn first + brush.setStyle(Qt::SolidPattern); + brush.setColor(color); + paint.setBrush(brush); + + // Draws a "flat" LED with the given color: + paint.drawEllipse( scale, scale, width - scale*2, width - scale*2 ); + + // Draw the bright light spot of the LED now, using modified "old" + // painter routine taken from KDEUI´s LEDButton widget: + + // Setting the new width of the pen is essential to avoid "pixelized" + // shadow like it can be observed with the old LED code + pen.setWidth( 2 * scale ); + + // shrink the light on the LED to a size about 2/3 of the complete LED + int pos = width/5 + 1; + int light_width = width; + light_width *= 2; + light_width /= 3; + + // Calculate the LED´s "light factor": + int light_quote = (130*2/(light_width?light_width:1))+100; + + // Now draw the bright spot on the LED: + while (light_width) { + color = color.light( light_quote ); // make color lighter + pen.setColor( color ); // set color as pen color + paint.setPen( pen ); // select the pen for drawing + paint.drawEllipse( pos, pos, light_width, light_width ); // draw the ellipse (circle) + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + pos++; light_width--; + } + + // Drawing of bright spot finished, now draw a thin border + // around the LED which resembles a shadow with light coming + // from the upper left. + +// pen.setWidth( 2 * scale + 1 ); // ### shouldn't this value be smaller for smaller LEDs? + pen.setWidth(2 * scale); + brush.setStyle(Qt::NoBrush); + paint.setBrush(brush); // This avoids filling of the ellipse + + // Set the initial color value to colorGroup().light() (bright) and start + // drawing the shadow border at 45° (45*16 = 720). + + int angle = -720; + color = palette().light().color(); + + for (int arc = 120; arc < 2880; arc += 240) { + pen.setColor(color); + paint.setPen(pen); + int w = width - pen.width()/2 - scale + 1; + paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle + arc, 240); + paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle - arc, 240); + color = color.dark(110); //FIXME: this should somehow use the contrast value + } // end for ( angle = 720; angle < 6480; angle += 160 ) + + paint.end(); + // + // painting done + + QPixmap *&dest = led_state ? d->on_map : d->off_map; + + if (scale > 1) { + + QImage i = tmpMap->toImage(); + width /= scale; + delete tmpMap; + dest = new QPixmap(QPixmap::fromImage + (i.scaled(width, width, + Qt::KeepAspectRatio, + Qt::SmoothTransformation))); + + } else { + + dest = tmpMap; + } + + paint.begin(this); + paint.drawPixmap(0, 0, *dest); + paint.end(); +} + +bool +LEDButton::state() const +{ + return led_state; +} + +QColor +LEDButton::color() const +{ + return led_color; +} + +void +LEDButton::setState( bool state ) +{ + if (led_state != state) + { + led_state = state; + update(); + } +} + +void +LEDButton::toggleState() +{ + led_state = (led_state == true) ? false : true; + // setColor(led_color); + update(); +} + +void +LEDButton::setColor(const QColor& col) +{ + if(led_color!=col) { + led_color = col; + d->offcolor = col.dark(d->dark_factor); + delete d->on_map; + d->on_map = 0; + delete d->off_map; + d->off_map = 0; + update(); + } +} + +void +LEDButton::setDarkFactor(int darkfactor) +{ + if (d->dark_factor != darkfactor) { + d->dark_factor = darkfactor; + d->offcolor = led_color.dark(darkfactor); + update(); + } +} + +int +LEDButton::darkFactor() const +{ + return d->dark_factor; +} + +void +LEDButton::toggle() +{ + toggleState(); +} + +void +LEDButton::on() +{ + setState(true); +} + +void +LEDButton::off() +{ + setState(false); +} + +QSize +LEDButton::sizeHint() const +{ + return QSize(17, 17); +} + +QSize +LEDButton::minimumSizeHint() const +{ + return QSize(17, 17); +} + diff -r 000000000000 -r fc9323a41f5a widgets/LEDButton.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LEDButton.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,83 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +/* + This is a modified version of a source file from the KDE + libraries. Copyright (c) 1998-2004 Jörg Habenicht, Richard J + Moore and others, distributed under the GNU Lesser General Public + License. + + Ported to Qt4 by Chris Cannam. + + The original KDE widget comes in round and rectangular and flat, + raised, and sunken variants. This version retains only the round + sunken variant. This version also implements a simple button API. +*/ + +#ifndef _LED_BUTTON_H_ +#define _LED_BUTTON_H_ + +#include + +class QColor; + +class LEDButton : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(int darkFactor READ darkFactor WRITE setDarkFactor) + +public: + LEDButton(QWidget *parent = 0); + LEDButton(const QColor &col, QWidget *parent = 0); + LEDButton(const QColor& col, bool state, QWidget *parent = 0); + ~LEDButton(); + + bool state() const; + QColor color() const; + int darkFactor() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + +signals: + void stateChanged(bool); + + void mouseEntered(); + void mouseLeft(); + +public slots: + void toggle(); + void on(); + void off(); + + void setState(bool); + void toggleState(); + void setColor(const QColor& color); + void setDarkFactor(int darkfactor); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *); + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + + bool led_state; + QColor led_color; + + class LEDButtonPrivate; + LEDButtonPrivate *d; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/LayerTree.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LayerTree.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,268 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LayerTree.h" +#include "view/PaneStack.h" + +#include "view/Pane.h" +#include "layer/Layer.h" +#include "data/model/Model.h" + +#include + + +class ViewObjectAssoc : public QObject +{ +public: + ViewObjectAssoc(QObject *parent, View *v, QObject *o) : + QObject(parent), view(v), object(o) { + ++extantCount; + } + + virtual ~ViewObjectAssoc() { + std::cerr << "~ViewObjectAssoc (now " << --extantCount << " extant)" + << std::endl; + } + + View *view; + QObject *object; + + static int extantCount; +}; + +int ViewObjectAssoc::extantCount = 0; + + +LayerTreeModel::LayerTreeModel(PaneStack *stack, QObject *parent) : + QAbstractItemModel(parent), + m_stack(stack) +{ +} + +LayerTreeModel::~LayerTreeModel() +{ +} + +QVariant +LayerTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) return QVariant(); + if (role != Qt::DisplayRole) return QVariant(); + + std::cerr << "LayerTreeModel::data(" << &index << ", role " << role << ")" << std::endl; + + QObject *obj = static_cast(index.internalPointer()); + + PaneStack *paneStack = dynamic_cast(obj); + if (paneStack) { + std::cerr << "node is pane stack" << std::endl; + return QVariant("Pane stack"); + } + + Pane *pane = dynamic_cast(obj); + if (pane) { + // need index of pane in pane stack + for (int i = 0; i < m_stack->getPaneCount(); ++i) { + if (pane == m_stack->getPane(i)) { + std::cerr << "node is pane " << i << std::endl; + return QVariant(QString("Pane %1").arg(i + 1)); + } + } + return QVariant(); + } + + ViewObjectAssoc *assoc = dynamic_cast(obj); + if (assoc) { + std::cerr << "node is assoc" << std::endl; + Layer *layer = dynamic_cast(assoc->object); + if (layer) { + std::cerr << "with layer" << std::endl; + return QVariant(layer->objectName()); + } + Model *model = dynamic_cast(assoc->object); + if (model) { + std::cerr << "with model" << std::endl; + return QVariant(model->objectName()); + } + } + + return QVariant(); +} + +Qt::ItemFlags +LayerTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) return Qt::ItemIsEnabled; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant +LayerTreeModel::headerData(int section, + Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + if (section == 0) return QVariant(tr("Layer")); + else if (section == 1) return QVariant(tr("Model")); + } + + return QVariant(); +} + +QModelIndex +LayerTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + std::cerr << "LayerTreeModel::index(" << row << ", " << column << ", " + << &parent << ")" << std::endl; + + if (!parent.isValid()) { + // this is the pane stack + std::cerr << "parent invalid, returning pane stack as root" << std::endl; + if (column > 0) return QModelIndex(); + return createIndex(row, column, m_stack); + } + + QObject *obj = static_cast(parent.internalPointer()); + + PaneStack *paneStack = dynamic_cast(obj); + if (paneStack) { + if (column > 0) return QModelIndex(); + if (paneStack == m_stack && row < m_stack->getPaneCount()) { + std::cerr << "parent is pane stack, returning a pane" << std::endl; + return createIndex(row, column, m_stack->getPane(row)); + } + std::cerr << "parent is wrong pane stack, returning nothing" << std::endl; + return QModelIndex(); + } + + Pane *pane = dynamic_cast(obj); + if (pane) { + std::cerr << "parent is pane" << std::endl; + if (row < pane->getLayerCount()) { + Layer *layer = pane->getLayer(row); + if (column == 0) { + std::cerr << "parent is pane, returning layer" << std::endl; + ViewObjectAssoc *assoc = new ViewObjectAssoc + (const_cast(this), pane, layer); + return createIndex(row, column, assoc); + } else { + std::cerr << "parent is pane, column != 0, returning model" << std::endl; + ViewObjectAssoc *assoc = new ViewObjectAssoc + (const_cast(this), pane, layer->getModel()); + return createIndex(row, column, assoc); + } + } + } + + std::cerr << "unknown parent, returning nothing" << std::endl; + return QModelIndex(); +} + +QModelIndex +LayerTreeModel::parent(const QModelIndex &index) const +{ + std::cerr << "LayerTreeModel::parent(" << &index << ")" << std::endl; + + QObject *obj = static_cast(index.internalPointer()); + + PaneStack *paneStack = dynamic_cast(obj); + if (paneStack) { + std::cerr << "node is pane stack, returning no parent" << std::endl; + return QModelIndex(); + } + + Pane *pane = dynamic_cast(obj); + if (pane) { + std::cerr << "node is pane, returning pane stack as parent" << std::endl; + return createIndex(0, 0, m_stack); + } + + ViewObjectAssoc *assoc = dynamic_cast(obj); + if (assoc) { + View *view = assoc->view; + Pane *pane = dynamic_cast(view); + if (pane) { + // need index of pane in pane stack + for (int i = 0; i < m_stack->getPaneCount(); ++i) { + if (pane == m_stack->getPane(i)) { + std::cerr << "node is assoc, returning pane " << i << " as parent" << std::endl; + return createIndex(i, 0, pane); + } + } + } + std::cerr << "node is assoc, but no parent found" << std::endl; + return QModelIndex(); + } + + std::cerr << "unknown node" << std::endl; + return QModelIndex(); +} + +int +LayerTreeModel::rowCount(const QModelIndex &parent) const +{ + std::cerr << "LayerTreeModel::rowCount(" << &parent << ")" << std::endl; + + if (!parent.isValid()) { + std::cerr << "parent invalid, returning 1 for the pane stack" << std::endl; + return 1; // the pane stack + } + + QObject *obj = static_cast(parent.internalPointer()); + + PaneStack *paneStack = dynamic_cast(obj); + if (paneStack) { + if (paneStack == m_stack) { + std::cerr << "parent is pane stack, returning " + << m_stack->getPaneCount() << " panes" << std::endl; + return m_stack->getPaneCount(); + } else { + return 0; + } + } + + Pane *pane = dynamic_cast(obj); + if (pane) { + std::cerr << "parent is pane, returning " + << pane->getLayerCount() << " layers" << std::endl; + return pane->getLayerCount(); + } + + std::cerr << "parent unknown, returning 0" << std::endl; + return 0; +} + +int +LayerTreeModel::columnCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + std::cerr << "LayerTreeModel::columnCount: parent invalid, returning 2" << std::endl; + return 2; + } + + QObject *obj = static_cast(parent.internalPointer()); + + Pane *pane = dynamic_cast(obj); + if (pane) { + std::cerr << "LayerTreeModel::columnCount: pane, returning 2" << std::endl; + return 2; // layer and model + } + + std::cerr << "LayerTreeModel::columnCount: returning 1" << std::endl; + + return 1; +} + diff -r 000000000000 -r fc9323a41f5a widgets/LayerTree.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/LayerTree.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,53 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LAYER_TREE_H_ +#define _LAYER_TREE_H_ + +#include + +class PaneStack; +class View; +class Layer; + +class LayerTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + LayerTreeModel(PaneStack *stack, QObject *parent = 0); + virtual ~LayerTreeModel(); + + QVariant data(const QModelIndex &index, int role) const; + + Qt::ItemFlags flags(const QModelIndex &index) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + + QModelIndex parent(const QModelIndex &index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + +protected: + PaneStack *m_stack; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/ListInputDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ListInputDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,113 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ListInputDialog.h" + +#include +#include +#include +#include +#include +#include + +ListInputDialog::ListInputDialog(QWidget *parent, const QString &title, + const QString &labelText, const QStringList &list, + int current, Qt::WFlags f) : + QDialog(parent, f), + m_strings(list) +{ + setWindowTitle(title); + + QVBoxLayout *vbox = new QVBoxLayout(this); + + QLabel *label = new QLabel(labelText, this); + vbox->addWidget(label); + vbox->addStretch(1); + + int count = 0; + for (QStringList::const_iterator i = list.begin(); i != list.end(); ++i) { + QRadioButton *radio = new QRadioButton(*i); + if (current == count++) radio->setChecked(true); + m_radioButtons.push_back(radio); + vbox->addWidget(radio); + } + + vbox->addStretch(1); + + m_footnote = new QLabel; + vbox->addWidget(m_footnote); + m_footnote->hide(); + + QHBoxLayout *hbox = new QHBoxLayout; + vbox->addLayout(hbox, Qt::AlignRight); + + QPushButton *ok = new QPushButton(tr("OK"), this); + ok->setDefault(true); + + QPushButton *cancel = new QPushButton(tr("Cancel"), this); + + QSize bs = ok->sizeHint().expandedTo(cancel->sizeHint()); + ok->setFixedSize(bs); + cancel->setFixedSize(bs); + + hbox->addStretch(); + hbox->addWidget(ok); + hbox->addWidget(cancel); + + QObject::connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + QObject::connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); +} + +ListInputDialog::~ListInputDialog() +{ +} + +QString +ListInputDialog::getCurrentString() const +{ + for (size_t i = 0; i < m_radioButtons.size(); ++i) { + if (m_radioButtons[i]->isChecked()) { + return m_strings[i]; + } + } + return ""; +} + +void +ListInputDialog::setItemAvailability(int item, bool available) +{ + m_radioButtons[item]->setEnabled(available); +} + +void +ListInputDialog::setFootnote(QString footnote) +{ + m_footnote->setText(footnote); + m_footnote->show(); +} + +QString +ListInputDialog::getItem(QWidget *parent, const QString &title, + const QString &label, const QStringList &list, + int current, bool *ok, Qt::WFlags f) +{ + ListInputDialog dialog(parent, title, label, list, current, f); + + bool accepted = (dialog.exec() == QDialog::Accepted); + if (ok) *ok = accepted; + + return dialog.getCurrentString(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/ListInputDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ListInputDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LIST_INPUT_DIALOG_H_ +#define _LIST_INPUT_DIALOG_H_ + +#include +#include +#include + +#include + +class QRadioButton; +class QLabel; + +/** + * Like QInputDialog::getItem(), except that it offers the items as a + * set of radio buttons instead of in a single combo box. + */ + +class ListInputDialog : public QDialog +{ + Q_OBJECT + +public: + ListInputDialog(QWidget *parent, const QString &title, + const QString &label, const QStringList &list, + int current = 0, Qt::WFlags f = 0); + virtual ~ListInputDialog(); + + void setItemAvailability(int item, bool available); + void setFootnote(QString footnote); + + QString getCurrentString() const; + + static QString getItem(QWidget *parent, const QString &title, + const QString &label, const QStringList &list, + int current = 0, bool *ok = 0, Qt::WFlags f = 0); + +protected: + QStringList m_strings; + std::vector m_radioButtons; + QLabel *m_footnote; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingCheckBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingCheckBox.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,35 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NotifyingCheckBox.h" + +NotifyingCheckBox::~NotifyingCheckBox() +{ +} + +void +NotifyingCheckBox::enterEvent(QEvent *e) +{ + QCheckBox::enterEvent(e); + emit mouseEntered(); +} + +void +NotifyingCheckBox::leaveEvent(QEvent *e) +{ + QCheckBox::enterEvent(e); + emit mouseLeft(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingCheckBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingCheckBox.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,46 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTIFYING_CHECK_BOX_H_ +#define _NOTIFYING_CHECK_BOX_H_ + +#include + +/** + * Very trivial enhancement to QCheckBox to make it emit signals when + * the mouse enters and leaves (for context help). + */ + +class NotifyingCheckBox : public QCheckBox +{ + Q_OBJECT +public: + + NotifyingCheckBox(QWidget *parent = 0) : + QCheckBox(parent) { } + + virtual ~NotifyingCheckBox(); + +signals: + void mouseEntered(); + void mouseLeft(); + +protected: + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingComboBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingComboBox.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,35 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NotifyingComboBox.h" + +NotifyingComboBox::~NotifyingComboBox() +{ +} + +void +NotifyingComboBox::enterEvent(QEvent *e) +{ + QComboBox::enterEvent(e); + emit mouseEntered(); +} + +void +NotifyingComboBox::leaveEvent(QEvent *e) +{ + QComboBox::enterEvent(e); + emit mouseLeft(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingComboBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingComboBox.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,46 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTIFYING_COMBO_BOX_H_ +#define _NOTIFYING_COMBO_BOX_H_ + +#include + +/** + * Very trivial enhancement to QComboBox to make it emit signals when + * the mouse enters and leaves (for context help). + */ + +class NotifyingComboBox : public QComboBox +{ + Q_OBJECT +public: + + NotifyingComboBox(QWidget *parent = 0) : + QComboBox(parent) { } + + virtual ~NotifyingComboBox(); + +signals: + void mouseEntered(); + void mouseLeft(); + +protected: + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingPushButton.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingPushButton.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,35 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NotifyingPushButton.h" + +NotifyingPushButton::~NotifyingPushButton() +{ +} + +void +NotifyingPushButton::enterEvent(QEvent *e) +{ + QPushButton::enterEvent(e); + emit mouseEntered(); +} + +void +NotifyingPushButton::leaveEvent(QEvent *e) +{ + QPushButton::enterEvent(e); + emit mouseLeft(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingPushButton.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingPushButton.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,46 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTIFYING_PUSH_BUTTON_H_ +#define _NOTIFYING_PUSH_BUTTON_H_ + +#include + +/** + * Very trivial enhancement to QPushButton to make it emit signals when + * the mouse enters and leaves (for context help). + */ + +class NotifyingPushButton : public QPushButton +{ + Q_OBJECT +public: + + NotifyingPushButton(QWidget *parent = 0) : + QPushButton(parent) { } + + virtual ~NotifyingPushButton(); + +signals: + void mouseEntered(); + void mouseLeft(); + +protected: + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingTabBar.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingTabBar.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,52 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "NotifyingTabBar.h" + +#include + +NotifyingTabBar::NotifyingTabBar(QWidget *parent) : + QTabBar(parent) +{ +} + +NotifyingTabBar::~NotifyingTabBar() +{ +} + +void +NotifyingTabBar::mousePressEvent(QMouseEvent *e) +{ + int i = currentIndex(); + QTabBar::mousePressEvent(e); + if (currentIndex() == i) { + emit activeTabClicked(); + } +} + +void +NotifyingTabBar::enterEvent(QEvent *e) +{ + QTabBar::enterEvent(e); + emit mouseEntered(); +} + +void +NotifyingTabBar::leaveEvent(QEvent *e) +{ + QTabBar::enterEvent(e); + emit mouseLeft(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/NotifyingTabBar.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingTabBar.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,40 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _NOTIFYING_TAB_BAR_H_ +#define _NOTIFYING_TAB_BAR_H_ + +#include + +class NotifyingTabBar : public QTabBar +{ + Q_OBJECT + +public: + NotifyingTabBar(QWidget *parent = 0); + virtual ~NotifyingTabBar(); + +signals: + void mouseEntered(); + void mouseLeft(); + void activeTabClicked(); + +protected: + virtual void mousePressEvent(QMouseEvent *); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/Panner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Panner.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,275 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Panner.h" + +#include +#include +#include +#include + +#include +#include + +#include "system/System.h" + +Panner::Panner(QWidget *parent) : + QWidget(parent), + m_rectX(0), + m_rectY(0), + m_rectWidth(1), + m_rectHeight(1), + m_defaultCentreX(0), + m_defaultCentreY(0), + m_defaultsSet(false), + m_thumbColour(palette().highlightedText().color()), + m_backgroundAlpha(255), + m_thumbAlpha(255), + m_clicked(false) +{ +} + +Panner::~Panner() +{ +} + +void +Panner::setAlpha(int backgroundAlpha, int thumbAlpha) +{ + m_backgroundAlpha = backgroundAlpha; + m_thumbAlpha = thumbAlpha; +} + +void +Panner::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::MidButton || + ((e->button() == Qt::LeftButton) && + (e->modifiers() & Qt::ControlModifier))) { + resetToDefault(); + } else if (e->button() == Qt::LeftButton) { + m_clicked = true; + m_clickPos = e->pos(); + m_dragStartX = m_rectX; + m_dragStartY = m_rectY; + } +} + +void +Panner::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + return; + } + + emit doubleClicked(); +} + +void +Panner::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clicked) return; + + float dx = float(e->pos().x() - m_clickPos.x()) / float(width()); + float dy = float(e->pos().y() - m_clickPos.y()) / float(height()); + + m_rectX = m_dragStartX + dx; + m_rectY = m_dragStartY + dy; + + normalise(); + repaint(); + emit rectExtentsChanged(m_rectX, m_rectY, m_rectWidth, m_rectHeight); + emit rectCentreMoved(centreX(), centreY()); +} + +void +Panner::mouseReleaseEvent(QMouseEvent *e) +{ + if (!m_clicked) return; + + mouseMoveEvent(e); + m_clicked = false; +} + +void +Panner::wheelEvent(QWheelEvent *e) +{ + if (e->delta() < 0) { + m_rectY += 0.1; + } else { + m_rectY -= 0.1; + } + + normalise(); + emitAndUpdate(); +} + +void +Panner::enterEvent(QEvent *) +{ + emit mouseEntered(); +} + +void +Panner::leaveEvent(QEvent *) +{ + emit mouseLeft(); +} + +void +Panner::paintEvent(QPaintEvent *) +{ + QPainter paint(this); + paint.setRenderHint(QPainter::Antialiasing, false); + + QColor bg(palette().background().color()); + bg.setAlpha(m_backgroundAlpha); + + paint.setPen(palette().dark().color()); + paint.setBrush(bg); + paint.drawRect(0, 0, width(), height()); + + QColor hl(m_thumbColour); + hl.setAlpha(m_thumbAlpha); + + paint.setBrush(hl); + + paint.drawRect(lrintf(width() * m_rectX), + lrintf(height() * m_rectY), + lrintf(width() * m_rectWidth), + lrintf(height() * m_rectHeight)); +} + +void +Panner::normalise() +{ + if (m_rectWidth > 1.0) m_rectWidth = 1.0; + if (m_rectHeight > 1.0) m_rectHeight = 1.0; + if (m_rectX + m_rectWidth > 1.0) m_rectX = 1.0 - m_rectWidth; + if (m_rectX < 0) m_rectX = 0; + if (m_rectY + m_rectHeight > 1.0) m_rectY = 1.0 - m_rectHeight; + if (m_rectY < 0) m_rectY = 0; + + if (!m_defaultsSet) { + m_defaultCentreX = centreX(); + m_defaultCentreY = centreY(); + m_defaultsSet = true; + } +} + +void +Panner::emitAndUpdate() +{ + emit rectExtentsChanged(m_rectX, m_rectY, m_rectWidth, m_rectHeight); + emit rectCentreMoved(centreX(), centreY()); + update(); +} + +void +Panner::getRectExtents(float &x0, float &y0, float &width, float &height) +{ + x0 = m_rectX; + y0 = m_rectY; + width = m_rectWidth; + height = m_rectHeight; +} + +void +Panner::setRectExtents(float x0, float y0, float width, float height) +{ +// std::cerr << "Panner::setRectExtents(" << x0 << ", " << y0 << ", " +// << width << ", " << height << ")" << std::endl; + + if (m_rectX == x0 && + m_rectY == y0 && + m_rectWidth == width && + m_rectHeight == height) { + return; + } + + m_rectX = x0; + m_rectY = y0; + m_rectWidth = width; + m_rectHeight = height; + + normalise(); + emitAndUpdate(); +} + +void +Panner::setRectWidth(float width) +{ + if (m_rectWidth == width) return; + m_rectWidth = width; + normalise(); + emitAndUpdate(); +} + +void +Panner::setRectHeight(float height) +{ + if (m_rectHeight == height) return; + m_rectHeight = height; + normalise(); + emitAndUpdate(); +} + +void +Panner::setRectCentreX(float x) +{ + float x0 = x - m_rectWidth/2; + if (x0 == m_rectX) return; + m_rectX = x0; + normalise(); + emitAndUpdate(); +} + +void +Panner::setRectCentreY(float y) +{ + float y0 = y - m_rectHeight/2; + if (y0 == m_rectY) return; + m_rectY = y0; + normalise(); + emitAndUpdate(); +} + +QSize +Panner::sizeHint() const +{ + return QSize(30, 30); +} + +void +Panner::setDefaultRectCentre(float cx, float cy) +{ + m_defaultCentreX = cx; + m_defaultCentreY = cy; + m_defaultsSet = true; +} + +void +Panner::resetToDefault() +{ + float x0 = m_defaultCentreX - m_rectWidth/2; + float y0 = m_defaultCentreY - m_rectHeight/2; + if (x0 == m_rectX && y0 == m_rectY) return; + m_rectX = x0; + m_rectY = y0; + normalise(); + emitAndUpdate(); +} + + diff -r 000000000000 -r fc9323a41f5a widgets/Panner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Panner.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,136 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PANNER_H_ +#define _PANNER_H_ + +#include + +class Panner : public QWidget +{ + Q_OBJECT + +public: + Panner(QWidget *parent = 0); + virtual ~Panner(); + + void setDefaultRectCentre(float, float); + + void setThumbColour(QColor colour); + void setAlpha(int backgroundAlpha, int thumbAlpha); + + void getRectExtents(float &x0, float &y0, float &width, float &height); + + virtual QSize sizeHint() const; + +signals: + /** + * Emitted when the panned rectangle is dragged or otherwise + * moved. Arguments are x0, y0, width and height of the rectangle + * in the range 0 -> 1 as proportions of the width and height of + * the whole widget. + */ + void rectExtentsChanged(float, float, float, float); + + /** + * Emitted when the rectangle is dragged or otherwise moved (as + * well as extentsChanged). Arguments are the centre coordinates + * of the rectangle in the range 0 -> 1 as proportions of the + * width and height of the whole widget. + */ + void rectCentreMoved(float, float); + + /** + * Emitted when the panner is double-clicked (for the "customer" + * code to pop up a value editing dialog, for example). + */ + void doubleClicked(); + + void mouseEntered(); + void mouseLeft(); + +public slots: + /** + * Set the extents of the panned rectangle within the overall + * panner widget. Coordinates are in the range 0 -> 1 in both axes, + * with 0 at the top in the y axis. + */ + void setRectExtents(float x0, float y0, float width, float height); + + /** + * Set the width of the panned rectangle as a fraction (0 -> 1) of + * that of the whole panner widget. + */ + void setRectWidth(float width); + + /** + * Set the height of the panned rectangle as a fraction (0 -> 1) + * of that of the whole panner widget. + */ + void setRectHeight(float height); + + /** + * Set the location of the centre of the panned rectangle on the x + * axis, as a proportion (0 -> 1) of the width of the whole panner + * widget. + */ + void setRectCentreX(float x); + + /** + * Set the location of the centre of the panned rectangle on the y + * axis, as a proportion (0 -> 1) of the height of the whole panner + * widget. + */ + void setRectCentreY(float y); + + void resetToDefault(); + +protected: + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void wheelEvent(QWheelEvent *e); + virtual void paintEvent(QPaintEvent *e); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + void normalise(); + void emitAndUpdate(); + + float m_rectX; + float m_rectY; + float m_rectWidth; + float m_rectHeight; + + float m_defaultCentreX; + float m_defaultCentreY; + bool m_defaultsSet; + + QColor m_thumbColour; + int m_backgroundAlpha; + int m_thumbAlpha; + + float centreX() const { return m_rectX + m_rectWidth/2; } + float centreY() const { return m_rectY + m_rectHeight/2; } + + bool m_clicked; + QPoint m_clickPos; + float m_dragStartX; + float m_dragStartY; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/PluginParameterBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PluginParameterBox.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,369 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PluginParameterBox.h" + +#include "AudioDial.h" + +#include "plugin/PluginXml.h" + +#include "base/RangeMapper.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +PluginParameterBox::PluginParameterBox(Vamp::PluginBase *plugin, QWidget *parent) : + QFrame(parent), + m_plugin(plugin) +{ + m_layout = new QGridLayout; + setLayout(m_layout); + populate(); +} + +PluginParameterBox::~PluginParameterBox() +{ +} + +void +PluginParameterBox::populate() +{ + Vamp::PluginBase::ParameterList params = m_plugin->getParameterDescriptors(); + Vamp::PluginBase::ProgramList programs = m_plugin->getPrograms(); + + m_params.clear(); + + if (params.empty() && programs.empty()) { + m_layout->addWidget + (new QLabel(tr("This plugin has no adjustable parameters.")), + 0, 0); + } + + int offset = 0; + + if (!programs.empty()) { + + std::string currentProgram = m_plugin->getCurrentProgram(); + + QComboBox *programCombo = new QComboBox; + programCombo->setMaxVisibleItems(20); + + for (size_t i = 0; i < programs.size(); ++i) { + programCombo->addItem(programs[i].c_str()); + if (programs[i] == currentProgram) { + programCombo->setCurrentIndex(i); + } + } + + m_layout->addWidget(new QLabel(tr("Program")), 0, 0); + m_layout->addWidget(programCombo, 0, 1, 1, 2); + + connect(programCombo, SIGNAL(currentIndexChanged(const QString &)), + this, SLOT(programComboChanged(const QString &))); + + offset = 1; + } + + for (size_t i = 0; i < params.size(); ++i) { + + QString identifier = params[i].identifier.c_str(); + QString name = params[i].name.c_str(); + QString unit = params[i].unit.c_str(); + + float min = params[i].minValue; + float max = params[i].maxValue; + float deft = params[i].defaultValue; + float value = m_plugin->getParameter(params[i].identifier); + + float qtz = 0.0; + if (params[i].isQuantized) qtz = params[i].quantizeStep; + + std::vector valueNames = params[i].valueNames; + + // construct an integer range + + int imin = 0, imax = 100; + + if (qtz > 0.0) { + imax = int((max - min) / qtz); + } else { + qtz = (max - min) / 100.0; + } + + //!!! would be nice to ensure the default value corresponds to + // an integer! + + QLabel *label = new QLabel(name); + if (params[i].description != "") { + label->setToolTip(params[i].description.c_str()); + } + m_layout->addWidget(label, i + offset, 0); + + ParamRec rec; + rec.param = params[i]; + rec.dial = 0; + rec.spin = 0; + rec.check = 0; + rec.combo = 0; + + if (params[i].isQuantized && !valueNames.empty()) { + + QComboBox *combobox = new QComboBox; + combobox->setObjectName(identifier); + for (unsigned int j = 0; j < valueNames.size(); ++j) { + combobox->addItem(valueNames[j].c_str()); + if ((unsigned int)(lrintf(fabsf((value - min) / qtz))) == j) { + combobox->setCurrentIndex(j); + } + } + connect(combobox, SIGNAL(activated(int)), + this, SLOT(dialChanged(int))); + m_layout->addWidget(combobox, i + offset, 1, 1, 2); + rec.combo = combobox; + + } else if (min == 0.0 && max == 1.0 && qtz == 1.0) { + + QCheckBox *checkbox = new QCheckBox; + checkbox->setObjectName(identifier); + checkbox->setCheckState(value == 0.0 ? Qt::Unchecked : Qt::Checked); + connect(checkbox, SIGNAL(stateChanged(int)), + this, SLOT(checkBoxChanged(int))); + m_layout->addWidget(checkbox, i + offset, 2); + rec.check = checkbox; + + } else { + + AudioDial *dial = new AudioDial; + dial->setObjectName(name); + dial->setMinimum(imin); + dial->setMaximum(imax); + dial->setPageStep(1); + dial->setNotchesVisible((imax - imin) <= 12); + dial->setDefaultValue(lrintf((deft - min) / qtz)); + dial->setValue(lrintf((value - min) / qtz)); + dial->setFixedWidth(32); + dial->setFixedHeight(32); + dial->setRangeMapper(new LinearRangeMapper + (imin, imax, min, max, unit)); + dial->setShowToolTip(true); + connect(dial, SIGNAL(valueChanged(int)), + this, SLOT(dialChanged(int))); + m_layout->addWidget(dial, i + offset, 1); + + QDoubleSpinBox *spinbox = new QDoubleSpinBox; + spinbox->setObjectName(identifier); + spinbox->setMinimum(min); + spinbox->setMaximum(max); + spinbox->setSuffix(QString(" %1").arg(unit)); + spinbox->setSingleStep(qtz); + spinbox->setValue(value); + spinbox->setDecimals(4); + connect(spinbox, SIGNAL(valueChanged(double)), + this, SLOT(spinBoxChanged(double))); + m_layout->addWidget(spinbox, i + offset, 2); + rec.dial = dial; + rec.spin = spinbox; + } + + m_params[identifier] = rec; + m_nameMap[name] = identifier; + } +} + +void +PluginParameterBox::dialChanged(int ival) +{ + QObject *obj = sender(); + QString identifier = obj->objectName(); + + if (m_params.find(identifier) == m_params.end() && + m_nameMap.find(identifier) != m_nameMap.end()) { + identifier = m_nameMap[identifier]; + } + + if (m_params.find(identifier) == m_params.end()) { + std::cerr << "WARNING: PluginParameterBox::dialChanged: Unknown parameter \"" << identifier.toStdString() << "\"" << std::endl; + return; + } + + Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param; + + float min = params.minValue; + float max = params.maxValue; + + float newValue; + + float qtz = 0.0; + if (params.isQuantized) qtz = params.quantizeStep; + + AudioDial *ad = dynamic_cast(obj); + + if (ad && ad->rangeMapper()) { + + newValue = ad->mappedValue(); + if (newValue < min) newValue = min; + if (newValue > max) newValue = max; + if (qtz != 0.0) { + ival = lrintf((newValue - min) / qtz); + newValue = min + ival * qtz; + } + + } else { + if (qtz == 0.0) { + qtz = (max - min) / 100.0; + } + newValue = min + ival * qtz; + } + + QDoubleSpinBox *spin = m_params[identifier].spin; + if (spin) { + spin->blockSignals(true); + spin->setValue(newValue); + spin->blockSignals(false); + } + + m_plugin->setParameter(identifier.toStdString(), newValue); + + emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString()); +} + +void +PluginParameterBox::checkBoxChanged(int state) +{ + QObject *obj = sender(); + QString identifier = obj->objectName(); + + if (m_params.find(identifier) == m_params.end() && + m_nameMap.find(identifier) != m_nameMap.end()) { + identifier = m_nameMap[identifier]; + } + + if (m_params.find(identifier) == m_params.end()) { + std::cerr << "WARNING: PluginParameterBox::checkBoxChanged: Unknown parameter \"" << identifier.toStdString() << "\"" << std::endl; + return; + } + + Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param; + + if (state) m_plugin->setParameter(identifier.toStdString(), 1.0); + else m_plugin->setParameter(identifier.toStdString(), 0.0); + + emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString()); +} + +void +PluginParameterBox::spinBoxChanged(double value) +{ + QObject *obj = sender(); + QString identifier = obj->objectName(); + + if (m_params.find(identifier) == m_params.end() && + m_nameMap.find(identifier) != m_nameMap.end()) { + identifier = m_nameMap[identifier]; + } + + if (m_params.find(identifier) == m_params.end()) { + std::cerr << "WARNING: PluginParameterBox::spinBoxChanged: Unknown parameter \"" << identifier.toStdString() << "\"" << std::endl; + return; + } + + Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param; + + float min = params.minValue; + float max = params.maxValue; + + float qtz = 0.0; + if (params.isQuantized) qtz = params.quantizeStep; + + if (qtz > 0.0) { + int step = int((value - min) / qtz); + value = min + step * qtz; + } + + int imax = 100; + + if (qtz > 0.0) { + imax = int((max - min) / qtz); + } else { + qtz = (max - min) / 100.0; + } + + int ival = lrintf((value - min) / qtz); + + AudioDial *dial = m_params[identifier].dial; + if (dial) { + dial->blockSignals(true); + dial->setValue(ival); + dial->blockSignals(false); + } + + m_plugin->setParameter(identifier.toStdString(), value); + + emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString()); +} + +void +PluginParameterBox::programComboChanged(const QString &newProgram) +{ + m_plugin->selectProgram(newProgram.toStdString()); + + for (std::map::iterator i = m_params.begin(); + i != m_params.end(); ++i) { + + Vamp::PluginBase::ParameterDescriptor ¶m = i->second.param; + float value = m_plugin->getParameter(param.identifier); + + if (i->second.spin) { + i->second.spin->blockSignals(true); + i->second.spin->setValue(value); + i->second.spin->blockSignals(false); + } + + if (i->second.dial) { + + float min = param.minValue; + float max = param.maxValue; + + float qtz = 0.0; + if (param.isQuantized) qtz = param.quantizeStep; + + if (qtz == 0.0) { + qtz = (max - min) / 100.0; + } + + i->second.dial->blockSignals(true); + i->second.dial->setValue(lrintf((value - min) / qtz)); + i->second.dial->blockSignals(false); + } + + if (i->second.combo) { + i->second.combo->blockSignals(true); + i->second.combo->setCurrentIndex(lrintf(value)); + i->second.combo->blockSignals(false); + } + } + + emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString()); +} + diff -r 000000000000 -r fc9323a41f5a widgets/PluginParameterBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PluginParameterBox.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,70 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLUGIN_PARAMETER_BOX_H_ +#define _PLUGIN_PARAMETER_BOX_H_ + +#include "vamp-sdk/PluginBase.h" + +#include +#include + +#include "system/System.h" + +class AudioDial; +class QDoubleSpinBox; +class QCheckBox; +class QGridLayout; +class QComboBox; + +class PluginParameterBox : public QFrame +{ + Q_OBJECT + +public: + PluginParameterBox(Vamp::PluginBase *, QWidget *parent = 0); + ~PluginParameterBox(); + + Vamp::PluginBase *getPlugin() { return m_plugin; } + +signals: + void pluginConfigurationChanged(QString); + +protected slots: + void dialChanged(int); + void spinBoxChanged(double); + void checkBoxChanged(int); + void programComboChanged(const QString &); + +protected: + void populate(); + + QGridLayout *m_layout; + Vamp::PluginBase *m_plugin; + + struct ParamRec { + AudioDial *dial; + QDoubleSpinBox *spin; + QCheckBox *check; + QComboBox *combo; + Vamp::PluginBase::ParameterDescriptor param; + }; + + std::map m_params; + std::map m_nameMap; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/PluginParameterDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PluginParameterDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,524 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PluginParameterDialog.h" + +#include "PluginParameterBox.h" +#include "WindowTypeSelector.h" + +#include "vamp-sdk/Plugin.h" +#include "vamp-sdk/PluginHostAdapter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PluginParameterDialog::PluginParameterDialog(Vamp::PluginBase *plugin, + QWidget *parent) : + QDialog(parent), + m_plugin(plugin), + m_channel(-1), + m_stepSize(0), + m_blockSize(0), + m_windowType(HanningWindow), + m_parameterBox(0) +{ + setWindowTitle(tr("Plugin Parameters")); + + QGridLayout *grid = new QGridLayout; + setLayout(grid); + + QGroupBox *pluginBox = new QGroupBox; + pluginBox->setTitle(plugin->getType().c_str()); + grid->addWidget(pluginBox, 0, 0); + + QGridLayout *subgrid = new QGridLayout; + pluginBox->setLayout(subgrid); + + subgrid->setSpacing(0); + subgrid->setMargin(10); + + QFont boldFont(pluginBox->font()); + boldFont.setBold(true); + + QFont italicFont(pluginBox->font()); + italicFont.setItalic(true); + + QLabel *nameLabel = new QLabel(plugin->getName().c_str()); + nameLabel->setWordWrap(true); + nameLabel->setFont(boldFont); + + QLabel *makerLabel = new QLabel(plugin->getMaker().c_str()); + makerLabel->setWordWrap(true); + + QLabel *versionLabel = new QLabel(QString("%1") + .arg(plugin->getPluginVersion())); + versionLabel->setWordWrap(true); + + QLabel *copyrightLabel = new QLabel(plugin->getCopyright().c_str()); + copyrightLabel->setWordWrap(true); + +// QLabel *typeLabel = new QLabel(plugin->getType().c_str()); +// typeLabel->setWordWrap(true); +// typeLabel->setFont(boldFont); + + QLabel *descriptionLabel = 0; + if (plugin->getDescription() != "") { + descriptionLabel = new QLabel(plugin->getDescription().c_str()); + descriptionLabel->setWordWrap(true); + descriptionLabel->setFont(italicFont); + } + + int row = 0; + + QLabel *label = new QLabel(tr("Name:")); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subgrid->addWidget(label, row, 0); + subgrid->addWidget(nameLabel, row, 1); + row++; + + if (descriptionLabel) { +// label = new QLabel(tr("Description:")); +// label->setAlignment(Qt::AlignTop | Qt::AlignLeft); +// subgrid->addWidget(label, row, 0); + subgrid->addWidget(descriptionLabel, row, 1); + row++; + } + + Vamp::PluginHostAdapter *fePlugin = + dynamic_cast(m_plugin); + + if (fePlugin) { + label = new QLabel(tr("Version:")); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subgrid->addWidget(label, row, 0); + subgrid->addWidget(versionLabel, row, 1); + row++; + } + +// label = new QLabel(tr("Type:")); +// label->setAlignment(Qt::AlignTop | Qt::AlignLeft); +// subgrid->addWidget(label, row, 0); +// subgrid->addWidget(typeLabel, row, 1); +// row++; + + label = new QLabel(tr("Maker:")); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subgrid->addWidget(label, row, 0); + subgrid->addWidget(makerLabel, row, 1); + row++; + + label = new QLabel(tr("Copyright: ")); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subgrid->addWidget(label, row, 0); + subgrid->addWidget(copyrightLabel, row, 1); + row++; + + m_outputSpacer = new QLabel; + subgrid->addWidget(m_outputSpacer, row, 0); + m_outputSpacer->setFixedHeight(7); + m_outputSpacer->hide(); + row++; + + m_outputLabel = new QLabel(tr("Output:")); + m_outputLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subgrid->addWidget(m_outputLabel, row, 0); + m_outputValue = new QLabel; + m_outputValue->setFont(boldFont); + subgrid->addWidget(m_outputValue, row, 1); + m_outputLabel->hide(); + m_outputValue->hide(); + row++; + + m_outputDescription = new QLabel; + m_outputDescription->setFont(italicFont); + subgrid->addWidget(m_outputDescription, row, 1); + m_outputDescription->hide(); + row++; + + subgrid->setColumnStretch(1, 2); + + m_inputModelBox = new QGroupBox; + m_inputModelBox->setTitle(tr("Input Source")); + grid->addWidget(m_inputModelBox, 1, 0); + + m_inputModels = new QComboBox; + QHBoxLayout *inputLayout = new QHBoxLayout; + m_inputModelBox->setLayout(inputLayout); + inputLayout->addWidget(m_inputModels); + m_inputModelBox->hide(); + + QGroupBox *paramBox = new QGroupBox; + paramBox->setTitle(tr("Plugin Parameters")); + grid->addWidget(paramBox, 2, 0); + grid->setRowStretch(2, 10); + + QHBoxLayout *paramLayout = new QHBoxLayout; + paramLayout->setMargin(0); + paramBox->setLayout(paramLayout); + + QScrollArea *scroll = new QScrollArea; + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scroll->setFrameShape(QFrame::NoFrame); + paramLayout->addWidget(scroll); + + m_parameterBox = new PluginParameterBox(m_plugin); + connect(m_parameterBox, SIGNAL(pluginConfigurationChanged(QString)), + this, SIGNAL(pluginConfigurationChanged(QString))); + scroll->setWidget(m_parameterBox); + + m_advanced = new QFrame; + QVBoxLayout *advancedLayout = new QVBoxLayout; + advancedLayout->setMargin(0); + m_advanced->setLayout(advancedLayout); + grid->addWidget(m_advanced, 3, 0); + + m_channelBox = new QGroupBox; + m_channelBox->setTitle(tr("Channels")); + advancedLayout->addWidget(m_channelBox); + m_channelBox->setVisible(false); + m_haveChannelBoxData = false; + + m_windowBox = new QGroupBox; + m_windowBox->setTitle(tr("Processing")); + advancedLayout->addWidget(m_windowBox); + m_windowBox->setVisible(false); + m_haveWindowBoxData = false; + + QHBoxLayout *hbox = new QHBoxLayout; + grid->addLayout(hbox, 4, 0); + + m_advancedVisible = false; + + m_advancedButton = new QPushButton(tr("Advanced >>")); + m_advancedButton->setCheckable(true); + connect(m_advancedButton, SIGNAL(clicked()), this, SLOT(advancedToggled())); + + QSettings settings; + settings.beginGroup("PluginParameterDialog"); + m_advancedVisible = settings.value("advancedvisible", false).toBool(); + settings.endGroup(); + + m_advanced->setVisible(false); + + hbox->addWidget(m_advancedButton); + m_advancedButton->hide(); + + QPushButton *ok = new QPushButton(tr("OK")); + QPushButton *cancel = new QPushButton(tr("Cancel")); + hbox->addStretch(10); + hbox->addWidget(ok); + hbox->addWidget(cancel); + connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); + + setAdvancedVisible(m_advancedVisible); +} + +PluginParameterDialog::~PluginParameterDialog() +{ +} + + +void +PluginParameterDialog::setOutputLabel(QString text, + QString description) +{ + if (text == "") { + m_outputSpacer->hide(); + m_outputLabel->hide(); + m_outputValue->hide(); + m_outputDescription->hide(); + } else { + m_outputSpacer->show(); + m_outputValue->setText(text); + m_outputValue->setWordWrap(true); + m_outputDescription->setText(description); + m_outputLabel->show(); + m_outputValue->show(); + if (description != "") { + m_outputDescription->show(); + } else { + m_outputDescription->hide(); + } + } +} + +void +PluginParameterDialog::setChannelArrangement(int sourceChannels, + int targetChannels, + int defaultChannel) +{ + m_channel = defaultChannel; + + if (sourceChannels != targetChannels) { + + // At the moment we can only cope with the case where + // sourceChannels > targetChannels and targetChannels == 1 + + if (sourceChannels < targetChannels) { + + QMessageBox::warning + (parentWidget(), + tr("Channel mismatch"), + tr("This plugin requires at least %1 input channels, but only %2 %3 available. The plugin probably will not work correctly.").arg(targetChannels).arg(sourceChannels).arg(sourceChannels != 1 ? tr("are") : tr("is")), + QMessageBox::Ok, + QMessageBox::NoButton); + + } else { + + if (m_haveChannelBoxData) { + std::cerr << "WARNING: PluginParameterDialog::setChannelArrangement: Calling more than once on same dialog is not currently implemented" << std::endl; + return; + } + + QVBoxLayout *channelLayout = new QVBoxLayout; + m_channelBox->setLayout(channelLayout); + + if (targetChannels != 1) { + + channelLayout->addWidget + (new QLabel(tr("This plugin accepts no more than %1 input channels,\nbut %2 are available. Only the first %3 will be used.\n") + .arg(targetChannels) + .arg(sourceChannels) + .arg(targetChannels))); + + } else { + + channelLayout->addWidget(new QLabel(tr("This plugin only has a single channel input,\nbut the source has %1 channels.").arg(sourceChannels))); + + QComboBox *channelCombo = new QComboBox; + channelCombo->addItem(tr("Use mean of source channels")); + for (int i = 0; i < sourceChannels; ++i) { + channelCombo->addItem(tr("Use channel %1 only").arg(i + 1)); + } + + connect(channelCombo, SIGNAL(activated(int)), + this, SLOT(channelComboChanged(int))); + + channelLayout->addWidget(channelCombo); + } + + m_channelBox->setVisible(true); + m_haveChannelBoxData = true; + m_advancedButton->show(); + } + } + + setAdvancedVisible(m_advancedVisible); +} + +void +PluginParameterDialog::setShowProcessingOptions(bool showWindowSize, + bool showFrequencyDomainOptions) +{ + if (m_haveWindowBoxData) { + std::cerr << "WARNING: PluginParameterDialog::setShowProcessingOptions: Calling more than once on same dialog is not currently implemented" << std::endl; + return; + } + + if (showWindowSize) { + + Vamp::PluginHostAdapter *fePlugin = dynamic_cast(m_plugin); + int size = 1024; + int increment = 1024; + if (fePlugin) { + size = fePlugin->getPreferredBlockSize(); + std::cerr << "Feature extraction plugin \"" << fePlugin->getName() << "\" reports preferred block size as " << size << std::endl; + if (size == 0) size = 1024; + increment = fePlugin->getPreferredStepSize(); + if (increment == 0) { + if (fePlugin->getInputDomain() == Vamp::Plugin::TimeDomain) { + increment = size; + } else { + increment = size/2; + } + } + } + + QGridLayout *windowLayout = new QGridLayout; + m_windowBox->setLayout(windowLayout); + + if (showFrequencyDomainOptions) { + windowLayout->addWidget(new QLabel(tr("Window size:")), 0, 0); + } else { + windowLayout->addWidget(new QLabel(tr("Audio frames per block:")), 0, 0); + } + + std::cerr << "size: " << size << ", increment: " << increment << std::endl; + + QComboBox *blockSizeCombo = new QComboBox; + blockSizeCombo->setEditable(true); + bool found = false; + for (int i = 0; i < 14; ++i) { + int val = 1 << (i + 3); + blockSizeCombo->addItem(QString("%1").arg(val)); + if (val == size) { + blockSizeCombo->setCurrentIndex(i); + found = true; + } + } + if (!found) { + blockSizeCombo->addItem(QString("%1").arg(size)); + blockSizeCombo->setCurrentIndex(blockSizeCombo->count() - 1); + } + blockSizeCombo->setValidator(new QIntValidator(1, pow(2.0, 18), this)); + connect(blockSizeCombo, SIGNAL(editTextChanged(const QString &)), + this, SLOT(blockSizeComboChanged(const QString &))); + windowLayout->addWidget(blockSizeCombo, 0, 1); + + windowLayout->addWidget(new QLabel(tr("Window increment:")), 1, 0); + + QComboBox *incrementCombo = new QComboBox; + incrementCombo->setEditable(true); + found = false; + for (int i = 0; i < 14; ++i) { + int val = 1 << (i + 3); + incrementCombo->addItem(QString("%1").arg(val)); + if (val == increment) { + incrementCombo->setCurrentIndex(i); + found = true; + } + } + if (!found) { + incrementCombo->addItem(QString("%1").arg(increment)); + incrementCombo->setCurrentIndex(incrementCombo->count() - 1); + } + incrementCombo->setValidator(new QIntValidator(1, pow(2.0, 18), this)); + connect(incrementCombo, SIGNAL(editTextChanged(const QString &)), + this, SLOT(incrementComboChanged(const QString &))); + windowLayout->addWidget(incrementCombo, 1, 1); + + if (showFrequencyDomainOptions) { + + windowLayout->addWidget(new QLabel(tr("Window shape:")), 2, 0); + WindowTypeSelector *windowTypeSelector = new WindowTypeSelector; + connect(windowTypeSelector, SIGNAL(windowTypeChanged(WindowType)), + this, SLOT(windowTypeChanged(WindowType))); + windowLayout->addWidget(windowTypeSelector, 2, 1); + } + + m_windowBox->setVisible(true); + m_haveWindowBoxData = true; + m_advancedButton->show(); + } + + setAdvancedVisible(m_advancedVisible); +} + +void +PluginParameterDialog::setCandidateInputModels(const QStringList &models) +{ + m_inputModels->clear(); + m_inputModels->insertItems(0, models); + connect(m_inputModels, SIGNAL(activated(const QString &)), + this, SIGNAL(inputModelChanged(QString))); + m_inputModelBox->show(); +} + +QString +PluginParameterDialog::getInputModel() const +{ + return m_inputModels->currentText(); +} + +void +PluginParameterDialog::getProcessingParameters(size_t &blockSize) const +{ + blockSize = m_blockSize; + return; +} + +void +PluginParameterDialog::getProcessingParameters(size_t &stepSize, + size_t &blockSize, + WindowType &windowType) const +{ + stepSize = m_stepSize; + blockSize = m_blockSize; + windowType = m_windowType; + return; +} + +void +PluginParameterDialog::blockSizeComboChanged(const QString &text) +{ + m_blockSize = text.toInt(); + std::cerr << "Block size changed to " << m_blockSize << std::endl; +} + +void +PluginParameterDialog::incrementComboChanged(const QString &text) +{ + m_stepSize = text.toInt(); + //!!! rename increment to step size throughout + std::cerr << "Increment changed to " << m_stepSize << std::endl; +} + +void +PluginParameterDialog::windowTypeChanged(WindowType type) +{ + m_windowType = type; +} + +void +PluginParameterDialog::advancedToggled() +{ + setAdvancedVisible(!m_advancedVisible); +} + +void +PluginParameterDialog::setAdvancedVisible(bool visible) +{ + m_advanced->setVisible(visible); + + if (visible) { + m_advancedButton->setText(tr("Advanced <<")); + m_advancedButton->setChecked(true); + } else { + m_advancedButton->setText(tr("Advanced >>")); + m_advancedButton->setChecked(false); + } + + QSettings settings; + settings.beginGroup("PluginParameterDialog"); + settings.setValue("advancedvisible", visible); + settings.endGroup(); + +// std::cerr << "resize to " << sizeHint().width() << " x " << sizeHint().height() << std::endl; + + setMinimumHeight(sizeHint().height()); + adjustSize(); + + m_advancedVisible = visible; + +// if (visible) setMaximumHeight(sizeHint().height()); +// adjustSize(); +} + +void +PluginParameterDialog::channelComboChanged(int index) +{ + m_channel = index - 1; +} + diff -r 000000000000 -r fc9323a41f5a widgets/PluginParameterDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PluginParameterDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,112 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PLUGIN_PARAMETER_DIALOG_H_ +#define _PLUGIN_PARAMETER_DIALOG_H_ + +#include + +#include "base/Window.h" + +namespace Vamp { class PluginBase; } +class PluginParameterBox; +class QWidget; +class QPushButton; +class QLabel; +class QGroupBox; +class QComboBox; + +/** + * A dialog for editing the parameters of a given plugin, using a + * PluginParameterBox. This dialog does not contain any mechanism for + * selecting the plugin in the first place. Note that the dialog + * directly modifies the parameters of the plugin, so they will remain + * modified even if the dialog is then cancelled. + */ + +class PluginParameterDialog : public QDialog +{ + Q_OBJECT + +public: + PluginParameterDialog(Vamp::PluginBase *, QWidget *parent = 0); + ~PluginParameterDialog(); + + void setChannelArrangement(int sourceChannels, + int targetChannels, + int defaultChannel); + + void setOutputLabel(QString output, QString description); + + void setShowProcessingOptions(bool showWindowSize, + bool showFrequencyDomainOptions); + + void setCandidateInputModels(const QStringList &names); + + Vamp::PluginBase *getPlugin() { return m_plugin; } + + int getChannel() const { return m_channel; } + + QString getInputModel() const; + + //!!! merge with PluginTransform::ExecutionContext + + void getProcessingParameters(size_t &blockSize) const; + void getProcessingParameters(size_t &stepSize, size_t &blockSize, + WindowType &windowType) const; + +signals: + void pluginConfigurationChanged(QString); + void inputModelChanged(QString); + +protected slots: + void channelComboChanged(int); + void blockSizeComboChanged(const QString &); + void incrementComboChanged(const QString &); + void windowTypeChanged(WindowType type); + void advancedToggled(); + void setAdvancedVisible(bool); + +protected: + Vamp::PluginBase *m_plugin; + + int m_channel; + size_t m_stepSize; + size_t m_blockSize; + + WindowType m_windowType; + PluginParameterBox *m_parameterBox; + + QLabel *m_outputLabel; + QLabel *m_outputValue; + QLabel *m_outputDescription; + QLabel *m_outputSpacer; + + QGroupBox *m_channelBox; + bool m_haveChannelBoxData; + + QGroupBox *m_windowBox; + bool m_haveWindowBoxData; + + QGroupBox *m_inputModelBox; + QComboBox *m_inputModels; + + QPushButton *m_advancedButton; + QWidget *m_advanced; + bool m_advancedVisible; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/PropertyBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyBox.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,701 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PropertyBox.h" +#include "PluginParameterDialog.h" + +#include "system/System.h" +#include "base/PropertyContainer.h" +#include "base/PlayParameters.h" +#include "layer/Layer.h" +#include "base/UnitDatabase.h" +#include "base/RangeMapper.h" + +#include "plugin/RealTimePluginFactory.h" +#include "plugin/RealTimePluginInstance.h" +#include "plugin/PluginXml.h" + +#include "AudioDial.h" +#include "LEDButton.h" + +#include "NotifyingCheckBox.h" +#include "NotifyingComboBox.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +//#define DEBUG_PROPERTY_BOX 1 + +PropertyBox::PropertyBox(PropertyContainer *container) : + m_container(container), + m_showButton(0), + m_playButton(0) +{ +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "(\"" << + container->getPropertyContainerName().toStdString() << "\")]::PropertyBox" << std::endl; +#endif + + m_mainBox = new QVBoxLayout; + setLayout(m_mainBox); + +// m_nameWidget = new QLabel; +// m_mainBox->addWidget(m_nameWidget); +// m_nameWidget->setText(container->objectName()); + + m_mainWidget = new QWidget; + m_mainBox->addWidget(m_mainWidget); + m_mainBox->insertStretch(2, 10); + + m_viewPlayFrame = 0; + populateViewPlayFrame(); + + m_layout = new QGridLayout; + m_layout->setMargin(0); + m_mainWidget->setLayout(m_layout); + + PropertyContainer::PropertyList properties = m_container->getProperties(); + + blockSignals(true); + + size_t i; + + for (i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i]); + } + + blockSignals(false); + + m_layout->setRowStretch(m_layout->rowCount(), 10); + + connect(UnitDatabase::getInstance(), SIGNAL(unitDatabaseChanged()), + this, SLOT(unitDatabaseChanged())); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "]::PropertyBox returning" << std::endl; +#endif +} + +PropertyBox::~PropertyBox() +{ +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "]::~PropertyBox" << std::endl; +#endif +} + +void +PropertyBox::populateViewPlayFrame() +{ +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox(" << m_container << ")::populateViewPlayFrame" << std::endl; +#endif + + if (m_viewPlayFrame) { + delete m_viewPlayFrame; + m_viewPlayFrame = 0; + } + + if (!m_container) return; + + Layer *layer = dynamic_cast(m_container); + if (layer) { + disconnect(layer, SIGNAL(modelReplaced()), + this, SLOT(populateViewPlayFrame())); + connect(layer, SIGNAL(modelReplaced()), + this, SLOT(populateViewPlayFrame())); + } + + PlayParameters *params = m_container->getPlayParameters(); + if (!params && !layer) return; + + m_viewPlayFrame = new QFrame; + m_viewPlayFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + m_mainBox->addWidget(m_viewPlayFrame); + + QHBoxLayout *layout = new QHBoxLayout; + m_viewPlayFrame->setLayout(layout); + + layout->setMargin(layout->margin() / 2); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox::populateViewPlayFrame: container " << m_container << " (name " << m_container->getPropertyContainerName().toStdString() << ") params " << params << std::endl; +#endif + + if (layer) { + QLabel *showLabel = new QLabel(tr("Show")); + layout->addWidget(showLabel); + layout->setAlignment(showLabel, Qt::AlignVCenter); + + m_showButton = new LEDButton(Qt::blue); + layout->addWidget(m_showButton); + connect(m_showButton, SIGNAL(stateChanged(bool)), + this, SIGNAL(showLayer(bool))); + connect(m_showButton, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(m_showButton, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + layout->setAlignment(m_showButton, Qt::AlignVCenter); + } + + if (params) { + + QLabel *playLabel = new QLabel(tr("Play")); + layout->addWidget(playLabel); + layout->setAlignment(playLabel, Qt::AlignVCenter); + + m_playButton = new LEDButton(Qt::darkGreen); + m_playButton->setState(!params->isPlayMuted()); + layout->addWidget(m_playButton); + connect(m_playButton, SIGNAL(stateChanged(bool)), + params, SLOT(setPlayAudible(bool))); + connect(m_playButton, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(m_playButton, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + connect(params, SIGNAL(playAudibleChanged(bool)), + m_playButton, SLOT(setState(bool))); + layout->setAlignment(m_playButton, Qt::AlignVCenter); + + layout->insertStretch(-1, 10); + + if (params->getPlayPluginId() != "") { + QPushButton *pluginButton = new QPushButton(QIcon(":icons/faders.png"), ""); + pluginButton->setFixedWidth(24); + pluginButton->setFixedHeight(24); + layout->addWidget(pluginButton); + connect(pluginButton, SIGNAL(clicked()), + this, SLOT(editPlugin())); + } + + AudioDial *gainDial = new AudioDial; + layout->addWidget(gainDial); + gainDial->setMeterColor(Qt::darkRed); + gainDial->setMinimum(-50); + gainDial->setMaximum(50); + gainDial->setPageStep(1); + gainDial->setFixedWidth(24); + gainDial->setFixedHeight(24); + gainDial->setNotchesVisible(false); + gainDial->setDefaultValue(0); + gainDial->setObjectName(tr("Playback Gain")); + gainDial->setRangeMapper(new LinearRangeMapper + (-50, 50, -25, 25, tr("dB"))); + gainDial->setShowToolTip(true); + connect(gainDial, SIGNAL(valueChanged(int)), + this, SLOT(playGainDialChanged(int))); + connect(params, SIGNAL(playGainChanged(float)), + this, SLOT(playGainChanged(float))); + connect(this, SIGNAL(changePlayGain(float)), + params, SLOT(setPlayGain(float))); + connect(this, SIGNAL(changePlayGainDial(int)), + gainDial, SLOT(setValue(int))); + connect(gainDial, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(gainDial, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + layout->setAlignment(gainDial, Qt::AlignVCenter); + + AudioDial *panDial = new AudioDial; + layout->addWidget(panDial); + panDial->setMeterColor(Qt::darkGreen); + panDial->setMinimum(-50); + panDial->setMaximum(50); + panDial->setPageStep(1); + panDial->setFixedWidth(24); + panDial->setFixedHeight(24); + panDial->setNotchesVisible(false); + panDial->setToolTip(tr("Playback Pan / Balance")); + panDial->setDefaultValue(0); + panDial->setObjectName(tr("Playback Pan / Balance")); + panDial->setShowToolTip(true); + connect(panDial, SIGNAL(valueChanged(int)), + this, SLOT(playPanDialChanged(int))); + connect(params, SIGNAL(playPanChanged(float)), + this, SLOT(playPanChanged(float))); + connect(this, SIGNAL(changePlayPan(float)), + params, SLOT(setPlayPan(float))); + connect(this, SIGNAL(changePlayPanDial(int)), + panDial, SLOT(setValue(int))); + connect(panDial, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(panDial, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + layout->setAlignment(panDial, Qt::AlignVCenter); + + } else { + + layout->insertStretch(-1, 10); + } +} + +void +PropertyBox::updatePropertyEditor(PropertyContainer::PropertyName name, + bool rangeChanged) +{ + PropertyContainer::PropertyType type = m_container->getPropertyType(name); + int row = m_layout->rowCount(); + + int min = 0, max = 0, value = 0, deflt = 0; + value = m_container->getPropertyRangeAndValue(name, &min, &max, &deflt); + + bool have = (m_propertyControllers.find(name) != + m_propertyControllers.end()); + + QString groupName = m_container->getPropertyGroupName(name); + QString propertyLabel = m_container->getPropertyLabel(name); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this + << "(\"" << m_container->getPropertyContainerName().toStdString() + << "\")]"; + std::cerr << "::updatePropertyEditor(\"" << name.toStdString() << "\"):"; + std::cerr << " value " << value << ", have " << have << ", group \"" + << groupName.toStdString() << "\"" << std::endl; +#endif + + bool inGroup = (groupName != QString()); + + if (!have) { + if (inGroup) { + if (m_groupLayouts.find(groupName) == m_groupLayouts.end()) { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: adding label \"" << groupName.toStdString() << "\" and frame for group for \"" << name.toStdString() << "\"" << std::endl; +#endif + m_layout->addWidget(new QLabel(groupName, m_mainWidget), row, 0); + QFrame *frame = new QFrame(m_mainWidget); + m_layout->addWidget(frame, row, 1, 1, 2); + m_groupLayouts[groupName] = new QHBoxLayout; + m_groupLayouts[groupName]->setMargin(0); + frame->setLayout(m_groupLayouts[groupName]); + } + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: adding label \"" << propertyLabel.toStdString() << "\"" << std::endl; +#endif + m_layout->addWidget(new QLabel(propertyLabel, m_mainWidget), row, 0); + } + } + + switch (type) { + + case PropertyContainer::ToggleProperty: + { + NotifyingCheckBox *cb; + + if (have) { + cb = dynamic_cast(m_propertyControllers[name]); + assert(cb); + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new checkbox" << std::endl; +#endif + cb = new NotifyingCheckBox(); + cb->setObjectName(name); + connect(cb, SIGNAL(stateChanged(int)), + this, SLOT(propertyControllerChanged(int))); + connect(cb, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(cb, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + if (inGroup) { + cb->setToolTip(propertyLabel); + m_groupLayouts[groupName]->addWidget(cb); + } else { + m_layout->addWidget(cb, row, 1, 1, 2); + } + m_propertyControllers[name] = cb; + } + + if (cb->isChecked() != (value > 0)) { + cb->blockSignals(true); + cb->setChecked(value > 0); + cb->blockSignals(false); + } + break; + } + + case PropertyContainer::RangeProperty: + { + AudioDial *dial; + + if (have) { + dial = dynamic_cast(m_propertyControllers[name]); + assert(dial); + if (rangeChanged) { + dial->blockSignals(true); + dial->setMinimum(min); + dial->setMaximum(max); + dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); + dial->blockSignals(false); + } + + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new dial" << std::endl; +#endif + dial = new AudioDial(); + dial->setObjectName(name); + dial->setMinimum(min); + dial->setMaximum(max); + dial->setPageStep(1); + dial->setNotchesVisible((max - min) <= 12); + dial->setDefaultValue(deflt); + dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); + dial->setShowToolTip(true); + connect(dial, SIGNAL(valueChanged(int)), + this, SLOT(propertyControllerChanged(int))); + connect(dial, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(dial, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + + if (inGroup) { + dial->setFixedWidth(24); + dial->setFixedHeight(24); + m_groupLayouts[groupName]->addWidget(dial); + } else { + dial->setFixedWidth(32); + dial->setFixedHeight(32); + m_layout->addWidget(dial, row, 1); + QLabel *label = new QLabel(m_mainWidget); + connect(dial, SIGNAL(valueChanged(int)), + label, SLOT(setNum(int))); + label->setNum(value); + m_layout->addWidget(label, row, 2); + } + + m_propertyControllers[name] = dial; + } + + if (dial->value() != value) { + dial->blockSignals(true); + dial->setValue(value); + dial->blockSignals(false); + } + break; + } + + case PropertyContainer::ValueProperty: + case PropertyContainer::UnitsProperty: + { + NotifyingComboBox *cb; + + if (have) { + cb = dynamic_cast(m_propertyControllers[name]); + assert(cb); + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new combobox" << std::endl; +#endif + + cb = new NotifyingComboBox(); + cb->setObjectName(name); + cb->setDuplicatesEnabled(false); + } + + if (!have || rangeChanged) { + cb->blockSignals(true); + cb->clear(); + if (type == PropertyContainer::ValueProperty) { + for (int i = min; i <= max; ++i) { + cb->addItem(m_container->getPropertyValueLabel(name, i)); + } + cb->setEditable(false); + } else { + QStringList units = UnitDatabase::getInstance()->getKnownUnits(); + for (int i = 0; i < units.size(); ++i) { + cb->addItem(units[i]); + } + cb->setEditable(true); + } + cb->blockSignals(false); + } + + if (!have) { + connect(cb, SIGNAL(activated(int)), + this, SLOT(propertyControllerChanged(int))); + connect(cb, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(cb, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + + if (inGroup) { + cb->setToolTip(propertyLabel); + m_groupLayouts[groupName]->addWidget(cb); + } else { + m_layout->addWidget(cb, row, 1, 1, 2); + } + m_propertyControllers[name] = cb; + } + + cb->blockSignals(true); + if (type == PropertyContainer::ValueProperty) { + if (cb->currentIndex() != value) { + cb->setCurrentIndex(value); + } + } else { + QString unit = UnitDatabase::getInstance()->getUnitById(value); + if (cb->currentText() != unit) { + for (int i = 0; i < cb->count(); ++i) { + if (cb->itemText(i) == unit) { + cb->setCurrentIndex(i); + break; + } + } + } + } + cb->blockSignals(false); + +#ifdef Q_WS_MAC + // Crashes on startup without this, for some reason + cb->setMinimumSize(QSize(10, 10)); +#endif + + break; + } + + default: + break; + } +} + +void +PropertyBox::propertyContainerPropertyChanged(PropertyContainer *pc) +{ + if (pc != m_container) return; + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox::propertyContainerPropertyChanged" << std::endl; +#endif + + PropertyContainer::PropertyList properties = m_container->getProperties(); + size_t i; + + blockSignals(true); + + for (i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i]); + } + + blockSignals(false); +} + +void +PropertyBox::propertyContainerPropertyRangeChanged(PropertyContainer *) +{ + blockSignals(true); + + PropertyContainer::PropertyList properties = m_container->getProperties(); + for (size_t i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i], true); + } + + blockSignals(false); +} + +void +PropertyBox::unitDatabaseChanged() +{ + blockSignals(true); + + PropertyContainer::PropertyList properties = m_container->getProperties(); + for (size_t i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i]); + } + + blockSignals(false); +} + +void +PropertyBox::propertyControllerChanged(int value) +{ + QObject *obj = sender(); + QString name = obj->objectName(); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox::propertyControllerChanged(" << name.toStdString() + << ", " << value << ")" << std::endl; +#endif + + PropertyContainer::PropertyType type = m_container->getPropertyType(name); + + if (type == PropertyContainer::UnitsProperty) { + NotifyingComboBox *cb = dynamic_cast(obj); + if (cb) { + QString unit = cb->currentText(); + m_container->setPropertyWithCommand + (name, UnitDatabase::getInstance()->getUnitId(unit)); + } + } else if (type != PropertyContainer::InvalidProperty) { + m_container->setPropertyWithCommand(name, value); + } + + updateContextHelp(obj); +} + +void +PropertyBox::playGainChanged(float gain) +{ + int dialValue = lrint(log10(gain) * 20.0); + if (dialValue < -50) dialValue = -50; + if (dialValue > 50) dialValue = 50; + emit changePlayGainDial(dialValue); +} + +void +PropertyBox::playGainDialChanged(int dialValue) +{ + QObject *obj = sender(); + float gain = pow(10, float(dialValue) / 20.0); + emit changePlayGain(gain); + updateContextHelp(obj); +} + +void +PropertyBox::playPanChanged(float pan) +{ + int dialValue = lrint(pan * 50.0); + if (dialValue < -50) dialValue = -50; + if (dialValue > 50) dialValue = 50; + emit changePlayPanDial(dialValue); +} + +void +PropertyBox::playPanDialChanged(int dialValue) +{ + QObject *obj = sender(); + float pan = float(dialValue) / 50.0; + if (pan < -1.0) pan = -1.0; + if (pan > 1.0) pan = 1.0; + emit changePlayPan(pan); + updateContextHelp(obj); +} + +void +PropertyBox::editPlugin() +{ + //!!! should probably just emit and let something else do this + + PlayParameters *params = m_container->getPlayParameters(); + if (!params) return; + + QString pluginId = params->getPlayPluginId(); + QString configurationXml = params->getPlayPluginConfiguration(); + + RealTimePluginFactory *factory = + RealTimePluginFactory::instanceFor(pluginId); + if (!factory) return; + + RealTimePluginInstance *instance = + factory->instantiatePlugin(pluginId, 0, 0, 48000, 1024, 1); + if (!instance) return; + + PluginXml(instance).setParametersFromXml(configurationXml); + + PluginParameterDialog *dialog = new PluginParameterDialog(instance); + connect(dialog, SIGNAL(pluginConfigurationChanged(QString)), + this, SLOT(pluginConfigurationChanged(QString))); + + if (dialog->exec() == QDialog::Accepted) { + params->setPlayPluginConfiguration(PluginXml(instance).toXmlString()); + } else { + // restore in case we mucked about with the configuration + // as a consequence of signals from the dialog + params->setPlayPluginConfiguration(configurationXml); + } + + delete dialog; + delete instance; +} + +void +PropertyBox::pluginConfigurationChanged(QString configurationXml) +{ + PlayParameters *params = m_container->getPlayParameters(); + if (!params) return; + + params->setPlayPluginConfiguration(configurationXml); +} + +void +PropertyBox::layerVisibilityChanged(bool visible) +{ + if (m_showButton) m_showButton->setState(visible); +} + +void +PropertyBox::mouseEnteredWidget() +{ + updateContextHelp(sender()); +} + +void +PropertyBox::updateContextHelp(QObject *o) +{ + QWidget *w = dynamic_cast(o); + if (!w) return; + + if (!m_container) return; + QString cname = m_container->getPropertyContainerName(); + if (cname == "") return; + + QString wname = w->objectName(); + + QString extraText; + AudioDial *dial = dynamic_cast(w); + if (dial) { + float mv = dial->mappedValue(); + QString unit = ""; + if (dial->rangeMapper()) unit = dial->rangeMapper()->getUnit(); + if (unit != "") { + extraText = tr(" (current value: %1%2)").arg(mv).arg(unit); + } else { + extraText = tr(" (current value: %1)").arg(mv); + } + } + + if (w == m_showButton) { + emit contextHelpChanged(tr("Toggle Visibility of %1").arg(cname)); + } else if (w == m_playButton) { + emit contextHelpChanged(tr("Toggle Playback of %1").arg(cname)); + } else if (wname == "") { + return; + } else if (dynamic_cast(w)) { + emit contextHelpChanged(tr("Toggle %1 property of %2") + .arg(wname).arg(cname)); + } else { + emit contextHelpChanged(tr("Adjust %1 property of %2%3") + .arg(wname).arg(cname).arg(extraText)); + } +} + +void +PropertyBox::mouseLeftWidget() +{ + if (!(QApplication::mouseButtons() & Qt::LeftButton)) { + emit contextHelpChanged(""); + } +} + + diff -r 000000000000 -r fc9323a41f5a widgets/PropertyBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyBox.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PROPERTY_BOX_H_ +#define _PROPERTY_BOX_H_ + +#include "base/PropertyContainer.h" + +#include +#include + +class QLayout; +class QWidget; +class QGridLayout; +class QVBoxLayout; +class QLabel; +class LEDButton; + +class PropertyBox : public QFrame +{ + Q_OBJECT + +public: + PropertyBox(PropertyContainer *); + ~PropertyBox(); + + PropertyContainer *getContainer() { return m_container; } + +signals: + void changePlayGain(float); + void changePlayGainDial(int); + void changePlayPan(float); + void changePlayPanDial(int); + void showLayer(bool); + void contextHelpChanged(const QString &); + +public slots: + void propertyContainerPropertyChanged(PropertyContainer *); + void propertyContainerPropertyRangeChanged(PropertyContainer *); + void pluginConfigurationChanged(QString); + void layerVisibilityChanged(bool); + +protected slots: + void propertyControllerChanged(int); + + void playGainChanged(float); + void playGainDialChanged(int); + void playPanChanged(float); + void playPanDialChanged(int); + + void populateViewPlayFrame(); + + void unitDatabaseChanged(); + + void editPlugin(); + + void mouseEnteredWidget(); + void mouseLeftWidget(); + +protected: + void updatePropertyEditor(PropertyContainer::PropertyName, + bool rangeChanged = false); + void updateContextHelp(QObject *o); + + QLabel *m_nameWidget; + QWidget *m_mainWidget; + QGridLayout *m_layout; + PropertyContainer *m_container; + QFrame *m_viewPlayFrame; + QVBoxLayout *m_mainBox; + LEDButton *m_showButton; + LEDButton *m_playButton; + std::map m_groupLayouts; + std::map m_propertyControllers; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/PropertyStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyStack.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,248 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "PropertyStack.h" +#include "PropertyBox.h" +#include "base/PropertyContainer.h" +#include "view/View.h" +#include "layer/Layer.h" +#include "layer/LayerFactory.h" +#include "widgets/NotifyingTabBar.h" + +#include +#include + +#include + +//#define DEBUG_PROPERTY_STACK 1 + +PropertyStack::PropertyStack(QWidget *parent, View *client) : + QTabWidget(parent), + m_client(client) +{ + NotifyingTabBar *bar = new NotifyingTabBar(); + bar->setDrawBase(false); + + connect(bar, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredTabBar())); + connect(bar, SIGNAL(mouseLeft()), this, SLOT(mouseLeftTabBar())); + connect(bar, SIGNAL(activeTabClicked()), this, SLOT(activeTabClicked())); + + setTabBar(bar); + +#if (QT_VERSION >= 0x0402) + setElideMode(Qt::ElideNone); + tabBar()->setUsesScrollButtons(true); + tabBar()->setIconSize(QSize(16, 16)); +#endif + + repopulate(); + + connect(this, SIGNAL(currentChanged(int)), + this, SLOT(selectedContainerChanged(int))); + + connect(m_client, SIGNAL(propertyContainerAdded(PropertyContainer *)), + this, SLOT(propertyContainerAdded(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerRemoved(PropertyContainer *)), + this, SLOT(propertyContainerRemoved(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerPropertyChanged(PropertyContainer *)), + this, SLOT(propertyContainerPropertyChanged(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerPropertyRangeChanged(PropertyContainer *)), + this, SLOT(propertyContainerPropertyRangeChanged(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerNameChanged(PropertyContainer *)), + this, SLOT(propertyContainerNameChanged(PropertyContainer *))); + + connect(this, SIGNAL(propertyContainerSelected(View *, PropertyContainer *)), + m_client, SLOT(propertyContainerSelected(View *, PropertyContainer *))); +} + +void +PropertyStack::repopulate() +{ + blockSignals(true); + +#ifdef DEBUG_PROPERTY_STACK + std::cerr << "PropertyStack::repopulate" << std::endl; +#endif + + while (count() > 0) { + removeTab(0); + } + for (size_t i = 0; i < m_boxes.size(); ++i) { + delete m_boxes[i]; + } + m_boxes.clear(); + + for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) { + + PropertyContainer *container = m_client->getPropertyContainer(i); + QString name = container->getPropertyContainerName(); + + PropertyBox *box = new PropertyBox(container); + + connect(box, SIGNAL(showLayer(bool)), this, SLOT(showLayer(bool))); + connect(box, SIGNAL(contextHelpChanged(const QString &)), + this, SIGNAL(contextHelpChanged(const QString &))); + + Layer *layer = dynamic_cast(container); + if (layer) { + box->layerVisibilityChanged(!layer->isLayerDormant(m_client)); + } + + QString shortName = name; + + if (layer) { + shortName = LayerFactory::getInstance()->getLayerPresentationName + (LayerFactory::getInstance()->getLayerType(layer)); + } + + shortName = QString("&%1 %2").arg(i + 1).arg(shortName); + +#ifdef Q_WS_MAC + + // Qt 4.2 on OS/X doesn't show the icons in the tab bar, and + // I'm not sure why -- use labels instead + + addTab(box, shortName); + +#else + + // Icons on other platforms + + QString iconName = container->getPropertyContainerIconName(); + + QIcon icon(QString(":/icons/%1.png").arg(iconName)); + if (icon.isNull()) { + addTab(box, shortName); + } else { + addTab(box, icon, QString("&%1").arg(i + 1)); + setTabToolTip(i, name); + } + +#endif + + m_boxes.push_back(box); + } + + blockSignals(false); +} + +bool +PropertyStack::containsContainer(PropertyContainer *pc) const +{ + for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) { + PropertyContainer *container = m_client->getPropertyContainer(i); + if (pc == container) return true; + } + + return false; +} + +int +PropertyStack::getContainerIndex(PropertyContainer *pc) const +{ + for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) { + PropertyContainer *container = m_client->getPropertyContainer(i); + if (pc == container) return i; + } + + return false; +} + +void +PropertyStack::propertyContainerAdded(PropertyContainer *) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::propertyContainerRemoved(PropertyContainer *) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::propertyContainerPropertyChanged(PropertyContainer *pc) +{ + for (unsigned int i = 0; i < m_boxes.size(); ++i) { + if (pc == m_boxes[i]->getContainer()) { + m_boxes[i]->propertyContainerPropertyChanged(pc); + } + } +} + +void +PropertyStack::propertyContainerPropertyRangeChanged(PropertyContainer *pc) +{ + for (unsigned int i = 0; i < m_boxes.size(); ++i) { + if (pc == m_boxes[i]->getContainer()) { + m_boxes[i]->propertyContainerPropertyRangeChanged(pc); + } + } +} + +void +PropertyStack::propertyContainerNameChanged(PropertyContainer *) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::showLayer(bool show) +{ + QObject *obj = sender(); + + for (unsigned int i = 0; i < m_boxes.size(); ++i) { + if (obj == m_boxes[i]) { + Layer *layer = dynamic_cast(m_boxes[i]->getContainer()); + if (layer) { + layer->showLayer(m_client, show); + return; + } + } + } +} + +void +PropertyStack::selectedContainerChanged(int n) +{ + if (n >= int(m_boxes.size())) return; + emit propertyContainerSelected(m_client, m_boxes[n]->getContainer()); +} + +void +PropertyStack::mouseEnteredTabBar() +{ + emit contextHelpChanged(tr("Click to change the current active layer")); +} + +void +PropertyStack::mouseLeftTabBar() +{ + emit contextHelpChanged(""); +} + +void +PropertyStack::activeTabClicked() +{ + emit viewSelected(m_client); +} + diff -r 000000000000 -r fc9323a41f5a widgets/PropertyStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyStack.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,68 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PROPERTY_STACK_H_ +#define _PROPERTY_STACK_H_ + +#include +#include +#include + +class Layer; +class View; +class PropertyBox; +class PropertyContainer; + +class PropertyStack : public QTabWidget +{ + Q_OBJECT + +public: + PropertyStack(QWidget *parent, View *client); + + View *getClient() { return m_client; } + bool containsContainer(PropertyContainer *container) const; + int getContainerIndex(PropertyContainer *container) const; + +signals: + void viewSelected(View *client); + void propertyContainerSelected(View *client, PropertyContainer *container); + void contextHelpChanged(const QString &); + +public slots: + void propertyContainerAdded(PropertyContainer *); + void propertyContainerRemoved(PropertyContainer *); + void propertyContainerPropertyChanged(PropertyContainer *); + void propertyContainerPropertyRangeChanged(PropertyContainer *); + void propertyContainerNameChanged(PropertyContainer *); + + void showLayer(bool); + + void mouseEnteredTabBar(); + void mouseLeftTabBar(); + void activeTabClicked(); + +protected slots: + void selectedContainerChanged(int); + +protected: + View *m_client; + std::vector m_boxes; + + void repopulate(); + void updateValues(PropertyContainer *); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/RangeInputDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/RangeInputDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,134 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RangeInputDialog.h" + +#include +#include +#include +#include +#include + +RangeInputDialog::RangeInputDialog(QString title, QString message, + QString unit, float min, float max, + QWidget *parent) : + QDialog(parent) +{ + QGridLayout *grid = new QGridLayout; + setLayout(grid); + + setWindowTitle(title); + + QLabel *messageLabel = new QLabel; + messageLabel->setText(message); + grid->addWidget(messageLabel, 0, 0, 1, 5); + + m_rangeStart = new QDoubleSpinBox; + m_rangeStart->setDecimals(4); + m_rangeStart->setMinimum(min); + m_rangeStart->setMaximum(max); + m_rangeStart->setSuffix(unit); + grid->addWidget(m_rangeStart, 1, 1); + connect(m_rangeStart, SIGNAL(valueChanged(double)), + this, SLOT(rangeStartChanged(double))); + + grid->addWidget(new QLabel(tr(" to ")), 1, 2); + + m_rangeEnd = new QDoubleSpinBox; + m_rangeEnd->setDecimals(4); + m_rangeEnd->setMinimum(min); + m_rangeEnd->setMaximum(max); + m_rangeEnd->setSuffix(unit); + grid->addWidget(m_rangeEnd, 1, 3); + connect(m_rangeEnd, SIGNAL(valueChanged(double)), + this, SLOT(rangeEndChanged(double))); + + QHBoxLayout *hbox = new QHBoxLayout; + grid->addLayout(hbox, 2, 0, 1, 5); + + QPushButton *ok = new QPushButton(tr("OK"), this); + ok->setDefault(true); + + QPushButton *cancel = new QPushButton(tr("Cancel"), this); + + QSize bs = ok->sizeHint().expandedTo(cancel->sizeHint()); + ok->setFixedSize(bs); + cancel->setFixedSize(bs); + + hbox->addStretch(); + hbox->addWidget(ok); + hbox->addWidget(cancel); + + QObject::connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + QObject::connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); +} + +RangeInputDialog::~RangeInputDialog() +{ +} + +void +RangeInputDialog::getRange(float &min, float &max) +{ + min = float(m_rangeStart->value()); + max = float(m_rangeEnd->value()); + + if (min > max) { + float tmp = min; + min = max; + max = tmp; + } +} + +void +RangeInputDialog::setRange(float start, float end) +{ + if (start > end) { + float tmp = start; + start = end; + end = tmp; + } + + blockSignals(true); + m_rangeStart->setValue(start); + m_rangeEnd->setValue(end); + blockSignals(false); +} + +void +RangeInputDialog::rangeStartChanged(double min) +{ + double max = m_rangeEnd->value(); + if (min > max) { + double tmp = min; + min = max; + max = tmp; + } + emit rangeChanged(float(min), float(max)); +} + + +void +RangeInputDialog::rangeEndChanged(double max) +{ + double min = m_rangeStart->value(); + if (min > max) { + double tmp = min; + min = max; + max = tmp; + } + emit rangeChanged(float(min), float(max)); +} + diff -r 000000000000 -r fc9323a41f5a widgets/RangeInputDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/RangeInputDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RANGE_INPUT_DIALOG_H_ +#define _RANGE_INPUT_DIALOG_H_ + +#include +#include + +class QDoubleSpinBox; + +class RangeInputDialog : public QDialog +{ + Q_OBJECT + +public: + RangeInputDialog(QString title, QString message, QString unit, + float min, float max, QWidget *parent = 0); + virtual ~RangeInputDialog(); + + void getRange(float &start, float &end); + +signals: + void rangeChanged(float start, float end); + +public slots: + void setRange(float start, float end); + +protected slots: + void rangeStartChanged(double); + void rangeEndChanged(double); + +protected: + QDoubleSpinBox *m_rangeStart; + QDoubleSpinBox *m_rangeEnd; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/SubdividingMenu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/SubdividingMenu.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,280 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "SubdividingMenu.h" + +#include + +using std::set; +using std::map; + +SubdividingMenu::SubdividingMenu(size_t lowerLimit, size_t upperLimit, + QWidget *parent) : + QMenu(parent), + m_lowerLimit(lowerLimit ? lowerLimit : 14), + m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2), + m_entriesSet(false) +{ +} + +SubdividingMenu::SubdividingMenu(const QString &title, size_t lowerLimit, + size_t upperLimit, QWidget *parent) : + QMenu(title, parent), + m_lowerLimit(lowerLimit ? lowerLimit : 14), + m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2), + m_entriesSet(false) +{ +} + +SubdividingMenu::~SubdividingMenu() +{ + for (map::iterator i = m_pendingEntries.begin(); + i != m_pendingEntries.end(); ++i) { + delete i->second; + } +} + +void +SubdividingMenu::setEntries(const std::set &entries) +{ + m_entriesSet = true; + + size_t total = entries.size(); + + if (total < m_upperLimit) return; + + size_t count = 0; + QMenu *chunkMenu = new QMenu(); + chunkMenu->setTearOffEnabled(isTearOffEnabled()); + + QString firstNameInChunk; + QChar firstInitialInChunk; + bool discriminateStartInitial = false; + + for (set::const_iterator j = entries.begin(); + j != entries.end(); + ++j) { + +// std::cerr << "SubdividingMenu::setEntries: j -> " << j->toStdString() << std::endl; + + m_nameToChunkMenuMap[*j] = chunkMenu; + + set::const_iterator k = j; + ++k; + + QChar initial = (*j)[0]; + + if (count == 0) { + firstNameInChunk = *j; + firstInitialInChunk = initial; + } + +// std::cerr << "count = "<< count << ", upper limit = " << m_upperLimit << std::endl; + + bool lastInChunk = (k == entries.end() || + (count >= m_lowerLimit-1 && + (count == m_upperLimit || + (*k)[0] != initial))); + + ++count; + + if (lastInChunk) { + + bool discriminateEndInitial = (k != entries.end() && + (*k)[0] == initial); + + bool initialsEqual = (firstInitialInChunk == initial); + + QString from = QString("%1").arg(firstInitialInChunk); + if (discriminateStartInitial || + (discriminateEndInitial && initialsEqual)) { + from = firstNameInChunk.left(3); + } + + QString to = QString("%1").arg(initial); + if (discriminateEndInitial || + (discriminateStartInitial && initialsEqual)) { + to = j->left(3); + } + + QString menuText; + + if (from == to) menuText = from; + else menuText = tr("%1 - %2").arg(from).arg(to); + + discriminateStartInitial = discriminateEndInitial; + + chunkMenu->setTitle(menuText); + + QMenu::addMenu(chunkMenu); + + chunkMenu = new QMenu(); + chunkMenu->setTearOffEnabled(isTearOffEnabled()); + + count = 0; + } + } + + if (count == 0) delete chunkMenu; +} + +void +SubdividingMenu::entriesAdded() +{ + if (m_entriesSet) { + std::cerr << "ERROR: SubdividingMenu::entriesAdded: setEntries was also called -- should use one mechanism or the other, but not both" << std::endl; + return; + } + + set entries; + for (map::const_iterator i = m_pendingEntries.begin(); + i != m_pendingEntries.end(); ++i) { + entries.insert(i->first); + } + + setEntries(entries); + + for (map::iterator i = m_pendingEntries.begin(); + i != m_pendingEntries.end(); ++i) { + + QMenu *menu = dynamic_cast(i->second); + if (menu) { + addMenu(i->first, menu); + continue; + } + + QAction *action = dynamic_cast(i->second); + if (action) { + addAction(i->first, action); + continue; + } + } + + m_pendingEntries.clear(); +} + +void +SubdividingMenu::addAction(QAction *action) +{ + QString name = action->text(); + + if (!m_entriesSet) { + m_pendingEntries[name] = action; + return; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + QMenu::addAction(action); + return; + } + +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + m_nameToChunkMenuMap[name]->addAction(action); +} + +QAction * +SubdividingMenu::addAction(const QString &name) +{ + if (!m_entriesSet) { + QAction *action = new QAction(name, this); + m_pendingEntries[name] = action; + return action; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + return QMenu::addAction(name); + } + +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + return m_nameToChunkMenuMap[name]->addAction(name); +} + +void +SubdividingMenu::addAction(const QString &name, QAction *action) +{ + if (!m_entriesSet) { + m_pendingEntries[name] = action; + return; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + QMenu::addAction(action); + return; + } + +// std::cerr << "SubdividingMenu::addAction(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + m_nameToChunkMenuMap[name]->addAction(action); +} + +void +SubdividingMenu::addMenu(QMenu *menu) +{ + QString name = menu->title(); + + if (!m_entriesSet) { + m_pendingEntries[name] = menu; + return; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + QMenu::addMenu(menu); + return; + } + +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + m_nameToChunkMenuMap[name]->addMenu(menu); +} + +QMenu * +SubdividingMenu::addMenu(const QString &name) +{ + if (!m_entriesSet) { + QMenu *menu = new QMenu(name, this); + menu->setTearOffEnabled(isTearOffEnabled()); + m_pendingEntries[name] = menu; + return menu; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + return QMenu::addMenu(name); + } + +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + return m_nameToChunkMenuMap[name]->addMenu(name); +} + +void +SubdividingMenu::addMenu(const QString &name, QMenu *menu) +{ + if (!m_entriesSet) { + m_pendingEntries[name] = menu; + return; + } + + if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): not found in name-to-chunk map, adding to main menu" << std::endl; + QMenu::addMenu(menu); + return; + } + +// std::cerr << "SubdividingMenu::addMenu(" << name.toStdString() << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title().toStdString() << std::endl; + m_nameToChunkMenuMap[name]->addMenu(menu); +} + diff -r 000000000000 -r fc9323a41f5a widgets/SubdividingMenu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/SubdividingMenu.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,76 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SUBDIVIDING_MENU_H_ +#define _SUBDIVIDING_MENU_H_ + +#include + +#include +#include +#include + +/** + * A menu that divides its entries into submenus, alphabetically. For + * menus that may contain a very large or small number of named items + * (e.g. plugins). + * + * The menu needs to be told, before any of the actions are added, + * what the set of entry strings will be, so it can determine a + * reasonable categorisation. Do this by calling the setEntries() + * method. If it isn't practical to do this in advance, then add the + * entries and call entriesAdded() afterwards instead. + */ + +class SubdividingMenu : public QMenu +{ + Q_OBJECT + +public: + SubdividingMenu(size_t lowerLimit = 0, size_t upperLimit = 0, + QWidget *parent = 0); + SubdividingMenu(const QString &title, size_t lowerLimit = 0, + size_t upperLimit = 0, QWidget *parent = 0); + virtual ~SubdividingMenu(); + + void setEntries(const std::set &entries); + void entriesAdded(); + + // Action names and strings passed to addAction and addMenu must + // appear in the set previously given to setEntries. If you want + // to use a different string, use the two-argument method and pass + // the entry string (used to determine which submenu the action + // ends up on) as the first argument. + + virtual void addAction(QAction *); + virtual QAction *addAction(const QString &); + virtual void addAction(const QString &entry, QAction *); + + virtual void addMenu(QMenu *); + virtual QMenu *addMenu(const QString &); + virtual void addMenu(const QString &entry, QMenu *); + +protected: + std::map m_nameToChunkMenuMap; + + size_t m_lowerLimit; + size_t m_upperLimit; + + bool m_entriesSet; + std::map m_pendingEntries; +}; + +#endif + diff -r 000000000000 -r fc9323a41f5a widgets/Thumbwheel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Thumbwheel.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,562 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Thumbwheel.h" + +#include "base/RangeMapper.h" +#include "base/Profiler.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +Thumbwheel::Thumbwheel(Qt::Orientation orientation, + QWidget *parent) : + QWidget(parent), + m_min(0), + m_max(100), + m_default(50), + m_value(50), + m_mappedValue(50), + m_noMappedUpdate(false), + m_rotation(0.5), + m_orientation(orientation), + m_speed(1.0), + m_tracking(true), + m_showScale(true), + m_clicked(false), + m_atDefault(true), + m_clickRotation(m_rotation), + m_showTooltip(true), + m_rangeMapper(0) +{ +} + +Thumbwheel::~Thumbwheel() +{ + delete m_rangeMapper; +} + +void +Thumbwheel::setRangeMapper(RangeMapper *mapper) +{ + if (m_rangeMapper == mapper) return; + + if (!m_rangeMapper && mapper) { + connect(this, SIGNAL(valueChanged(int)), + this, SLOT(updateMappedValue(int))); + } + + delete m_rangeMapper; + m_rangeMapper = mapper; + + updateMappedValue(getValue()); +} + +void +Thumbwheel::setShowToolTip(bool show) +{ + m_showTooltip = show; + m_noMappedUpdate = true; + updateMappedValue(getValue()); + m_noMappedUpdate = false; +} + +void +Thumbwheel::setMinimumValue(int min) +{ + if (m_min == min) return; + + m_min = min; + if (m_max <= m_min) m_max = m_min + 1; + if (m_value < m_min) m_value = m_min; + if (m_value > m_max) m_value = m_max; + + m_rotation = float(m_value - m_min) / float(m_max - m_min); + update(); +} + +int +Thumbwheel::getMinimumValue() const +{ + return m_min; +} + +void +Thumbwheel::setMaximumValue(int max) +{ + if (m_max == max) return; + + m_max = max; + if (m_min >= m_max) m_min = m_max - 1; + if (m_value < m_min) m_value = m_min; + if (m_value > m_max) m_value = m_max; + + m_rotation = float(m_value - m_min) / float(m_max - m_min); + update(); +} + +int +Thumbwheel::getMaximumValue() const +{ + return m_max; +} + +void +Thumbwheel::setDefaultValue(int deft) +{ + if (m_default == deft) return; + + m_default = deft; + if (m_atDefault) { + setValue(m_default); + m_atDefault = true; // setValue unsets this + emit valueChanged(getValue()); + } +} + +void +Thumbwheel::setMappedValue(float mappedValue) +{ + if (m_rangeMapper) { + int newValue = m_rangeMapper->getPositionForValue(mappedValue); + bool changed = (m_mappedValue != mappedValue); + m_mappedValue = mappedValue; + m_noMappedUpdate = true; +// std::cerr << "Thumbwheel::setMappedValue(" << mappedValue << "): new value is " << newValue << " (visible " << isVisible() << ")" << std::endl; + if (newValue != getValue()) { + setValue(newValue); + changed = true; + } + if (changed) emit valueChanged(newValue); + m_noMappedUpdate = false; + } else { + int v = int(mappedValue); + if (v != getValue()) { + setValue(v); + emit valueChanged(v); + } + } +} + +int +Thumbwheel::getDefaultValue() const +{ + return m_default; +} + +void +Thumbwheel::setValue(int value) +{ +// std::cerr << "Thumbwheel::setValue(" << value << ") (from " << m_value +// << ", rotation " << m_rotation << ")" << " (visible " << isVisible() << ")" << std::endl; + + if (m_value != value) { + + m_atDefault = false; + + if (value < m_min) value = m_min; + if (value > m_max) value = m_max; + m_value = value; + } + + m_rotation = float(m_value - m_min) / float(m_max - m_min); + if (isVisible()) update(); +} + +void +Thumbwheel::resetToDefault() +{ + if (m_default == m_value) return; + setValue(m_default); + m_atDefault = true; + emit valueChanged(getValue()); +} + +int +Thumbwheel::getValue() const +{ + return m_value; +} + +float +Thumbwheel::getMappedValue() const +{ + if (m_rangeMapper) { +// std::cerr << "Thumbwheel::getMappedValue(): value = " << getValue() << ", mappedValue = " << m_mappedValue << std::endl; + return m_mappedValue; + } + return getValue(); +} + +void +Thumbwheel::updateMappedValue(int value) +{ + if (!m_noMappedUpdate) { + if (m_rangeMapper) { + m_mappedValue = m_rangeMapper->getValueForPosition(value); + } else { + m_mappedValue = value; + } + } + + if (m_showTooltip) { + QString name = objectName(); + QString unit = ""; + QString text; + if (m_rangeMapper) unit = m_rangeMapper->getUnit(); + if (name != "") { + text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + } else { + text = tr("%2%3").arg(m_mappedValue).arg(unit); + } + setToolTip(text); + } +} + +void +Thumbwheel::setSpeed(float speed) +{ + m_speed = speed; +} + +float +Thumbwheel::getSpeed() const +{ + return m_speed; +} + +void +Thumbwheel::setTracking(bool tracking) +{ + m_tracking = tracking; +} + +bool +Thumbwheel::getTracking() const +{ + return m_tracking; +} + +void +Thumbwheel::setShowScale(bool showScale) +{ + m_showScale = showScale; +} + +bool +Thumbwheel::getShowScale() const +{ + return m_showScale; +} + +void +Thumbwheel::enterEvent(QEvent *) +{ + emit mouseEntered(); +} + +void +Thumbwheel::leaveEvent(QEvent *) +{ + emit mouseLeft(); +} + +void +Thumbwheel::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::MidButton || + ((e->button() == Qt::LeftButton) && + (e->modifiers() & Qt::ControlModifier))) { + resetToDefault(); + } else if (e->button() == Qt::LeftButton) { + m_clicked = true; + m_clickPos = e->pos(); + m_clickRotation = m_rotation; + } +} + +void +Thumbwheel::mouseDoubleClickEvent(QMouseEvent *mouseEvent) +{ + //!!! needs a common base class with AudioDial (and Panner?) + + if (mouseEvent->button() != Qt::LeftButton) { + return; + } + + bool ok = false; + + if (m_rangeMapper) { + + float min = m_rangeMapper->getValueForPosition(m_min); + float max = m_rangeMapper->getValueForPosition(m_max); + + if (min > max) { + float tmp = min; + min = max; + max = tmp; + } + + QString unit = m_rangeMapper->getUnit(); + + QString text; + if (objectName() != "") { + if (unit != "") { + text = tr("New value for %1, from %2 to %3 %4:") + .arg(objectName()).arg(min).arg(max).arg(unit); + } else { + text = tr("New value for %1, from %2 to %3:") + .arg(objectName()).arg(min).arg(max); + } + } else { + if (unit != "") { + text = tr("Enter a new value from %1 to %2 %3:") + .arg(min).arg(max).arg(unit); + } else { + text = tr("Enter a new value from %1 to %2:") + .arg(min).arg(max); + } + } + + float newValue = QInputDialog::getDouble + (this, + tr("Enter new value"), + text, + m_mappedValue, + min, + max, + 4, + &ok); + + if (ok) { + setMappedValue(newValue); + } + + } else { + + int newValue = QInputDialog::getInteger + (this, + tr("Enter new value"), + tr("Enter a new value from %1 to %2:") + .arg(m_min).arg(m_max), + getValue(), m_min, m_max, 1, &ok); + + if (ok) { + setValue(newValue); + } + } +} + + +void +Thumbwheel::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clicked) return; + int dist = 0; + if (m_orientation == Qt::Horizontal) { + dist = e->x() - m_clickPos.x(); + } else { + dist = e->y() - m_clickPos.y(); + } + + float rotation = m_clickRotation + (m_speed * dist) / 100; + if (rotation < 0.f) rotation = 0.f; + if (rotation > 1.f) rotation = 1.f; + int value = lrintf(m_min + (m_max - m_min) * m_rotation); + if (value != m_value) { + setValue(value); + if (m_tracking) emit valueChanged(getValue()); + m_rotation = rotation; + } else if (fabsf(rotation - m_rotation) > 0.001) { + m_rotation = rotation; + repaint(); + } +} + +void +Thumbwheel::mouseReleaseEvent(QMouseEvent *e) +{ + if (!m_clicked) return; + bool reallyTracking = m_tracking; + m_tracking = true; + mouseMoveEvent(e); + m_tracking = reallyTracking; + m_clicked = false; +} + +void +Thumbwheel::wheelEvent(QWheelEvent *e) +{ + int step = lrintf(m_speed); + if (step == 0) step = 1; + + if (e->delta() > 0) { + setValue(m_value + step); + } else { + setValue(m_value - step); + } + + emit valueChanged(getValue()); +} + +void +Thumbwheel::paintEvent(QPaintEvent *) +{ + Profiler profiler("Thumbwheel::paintEvent", true); + + int bw = 3; + + QRect subclip; + if (m_orientation == Qt::Horizontal) { + subclip = QRect(bw, bw+1, width() - bw*2, height() - bw*2 - 2); + } else { + subclip = QRect(bw+1, bw, width() - bw*2 - 2, height() - bw*2); + } + + QPainter paint(this); + paint.fillRect(subclip, palette().background().color()); + + paint.setRenderHint(QPainter::Antialiasing, true); + + float w = width(); + float w0 = 0.5; + float w1 = w - 0.5; + + float h = height(); + float h0 = 0.5; + float h1 = h - 0.5; + + for (int i = bw-1; i >= 0; --i) { +// for (int i = 0; i < 1; ++i) { + + int grey = (i + 1) * (256 / (bw + 1)); + QColor fc = QColor(grey, grey, grey); + paint.setPen(fc); + + QPainterPath path; + + if (m_orientation == Qt::Horizontal) { + path.moveTo(w0 + i, h0 + i + 2); + path.quadTo(w/2, i * 1.25, w1 - i, h0 + i + 2); + path.lineTo(w1 - i, h1 - i - 2); + path.quadTo(w/2, h - i * 1.25, w0 + i, h1 - i - 2); + path.closeSubpath(); + } else { + path.moveTo(w0 + i + 2, h0 + i); + path.quadTo(i * 1.25, h/2, w0 + i + 2, h1 - i); + path.lineTo(w1 - i - 2, h1 - i); + path.quadTo(w - i * 1.25, h/2, w1 - i - 2, h0 + i); + path.closeSubpath(); + } + + paint.drawPath(path); + } + + paint.setClipRect(subclip); + + float radians = m_rotation * 1.5f * M_PI; + +// std::cerr << "value = " << m_value << ", min = " << m_min << ", max = " << m_max << ", rotation = " << rotation << std::endl; + + w = (m_orientation == Qt::Horizontal ? width() : height()) - bw*2; + + // total number of notches on the entire wheel + int notches = 25; + + // radius of the wheel including invisible part + int radius = int(w / 2 + 2); + + for (int i = 0; i < notches; ++i) { + + float a0 = (2.f * M_PI * i) / notches + radians; + float a1 = a0 + M_PI / (notches * 2); + float a2 = (2.f * M_PI * (i + 1)) / notches + radians; + + float depth = cosf((a0 + a2) / 2); + if (depth < 0) continue; + + float x0 = radius * sinf(a0) + w/2; + float x1 = radius * sinf(a1) + w/2; + float x2 = radius * sinf(a2) + w/2; + if (x2 < 0 || x0 > w) continue; + + if (x0 < 0) x0 = 0; + if (x2 > w) x2 = w; + + x0 += bw; + x1 += bw; + x2 += bw; + + int grey = lrintf(255 * depth); + QColor fc = QColor(grey, grey, grey); + QColor oc = palette().dark().color(); + + paint.setPen(oc); + paint.setBrush(fc); + + if (m_orientation == Qt::Horizontal) { + paint.drawRect(QRectF(x1, bw, x2 - x1, height() - bw*2)); + } else { + paint.drawRect(QRectF(bw, x1, width() - bw*2, x2 - x1)); + } + + if (m_showScale) { + + paint.setBrush(oc); + + float prop; + if (i >= notches / 4) { + prop = float(notches - (((i - float(notches) / 4.f) * 4.f) / 3.f)) + / notches; + } else { + prop = 0.f; + } + + if (m_orientation == Qt::Horizontal) { + paint.drawRect(QRectF(x1, height() - (height() - bw*2) * prop - bw, + x2 - x1, height() * prop)); + } else { + paint.drawRect(QRectF(bw, x1, (width() - bw*2) * prop, x2 - x1)); + } + } + + paint.setPen(oc); + paint.setBrush(palette().background().color()); + + if (m_orientation == Qt::Horizontal) { + paint.drawRect(QRectF(x0, bw, x1 - x0, height() - bw*2)); + } else { + paint.drawRect(QRectF(bw, x0, width() - bw*2, x1 - x0)); + } + } +} + +QSize +Thumbwheel::sizeHint() const +{ + if (m_orientation == Qt::Horizontal) { + return QSize(80, 12); + } else { + return QSize(12, 80); + } +} + diff -r 000000000000 -r fc9323a41f5a widgets/Thumbwheel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Thumbwheel.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,96 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _THUMBWHEEL_H_ +#define _THUMBWHEEL_H_ + +#include + +class RangeMapper; + +class Thumbwheel : public QWidget +{ + Q_OBJECT + +public: + Thumbwheel(Qt::Orientation orientation, QWidget *parent = 0); + virtual ~Thumbwheel(); + + int getMinimumValue() const; + int getMaximumValue() const; + int getDefaultValue() const; + float getSpeed() const; + bool getTracking() const; + bool getShowScale() const; + int getValue() const; + + void setRangeMapper(RangeMapper *mapper); // I take ownership, will delete + const RangeMapper *getRangeMapper() const { return m_rangeMapper; } + float getMappedValue() const; + + void setShowToolTip(bool show); + + QSize sizeHint() const; + +signals: + void valueChanged(int); + + void mouseEntered(); + void mouseLeft(); + +public slots: + void setMinimumValue(int min); + void setMaximumValue(int max); + void setDefaultValue(int deft); + void setSpeed(float speed); + void setTracking(bool tracking); + void setShowScale(bool show); + void setValue(int value); + void setMappedValue(float mappedValue); + void resetToDefault(); + +protected slots: + void updateMappedValue(int value); + +protected: + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void wheelEvent(QWheelEvent *e); + virtual void paintEvent(QPaintEvent *e); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + int m_min; + int m_max; + int m_default; + int m_value; + float m_mappedValue; + bool m_noMappedUpdate; + float m_rotation; + Qt::Orientation m_orientation; + float m_speed; + bool m_tracking; + bool m_showScale; + bool m_clicked; + bool m_atDefault; + QPoint m_clickPos; + float m_clickRotation; + bool m_showTooltip; + RangeMapper *m_rangeMapper; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/TipDialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/TipDialog.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,297 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TipDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +TipDialog::TipDialog(QWidget *parent, Qt::WFlags flags) : + QDialog(parent, flags), + m_tipNumber(0), + m_label(0), + m_caption(tr("Tip of the Day")) +{ + readTips(); + + QSettings settings; + settings.beginGroup("TipOfTheDay"); + + if (!settings.value("showonstartup", true).toBool()) return; + + m_tipNumber = settings.value("nexttip", 0).toInt(); + + setWindowTitle(m_caption); + + QGridLayout *grid = new QGridLayout; + setLayout(grid); + + QGroupBox *groupBox = new QGroupBox; +// groupBox->setTitle(m_caption); + grid->addWidget(groupBox, 0, 0); + + QGridLayout *subgrid = new QGridLayout; + groupBox->setLayout(subgrid); + + m_label = new QLabel; + subgrid->addWidget(m_label, 0, 0); + m_label->setWordWrap(true); + + QHBoxLayout *hbox = new QHBoxLayout; + grid->addLayout(hbox, 1, 0); + + QCheckBox *show = new QCheckBox(tr("Show tip on startup")); + hbox->addWidget(show); + + hbox->addSpacing(20); + hbox->addStretch(10); + + QPushButton *prev = new QPushButton(tr("<< Previous")); + hbox->addWidget(prev); + connect(prev, SIGNAL(clicked()), this, SLOT(previous())); + + QPushButton *next = new QPushButton(tr("Next >>")); + hbox->addWidget(next); + connect(next, SIGNAL(clicked()), this, SLOT(next())); + + QPushButton *close = new QPushButton(tr("Close")); + hbox->addWidget(close); + connect(close, SIGNAL(clicked()), this, SLOT(accept())); + + close->setDefault(true); + + showTip(); +} + +TipDialog::~TipDialog() +{ +} + +void +TipDialog::next() +{ + if (++m_tipNumber >= int(m_tips.size())) { + //!!! The tips file should define where we loop back to -- the + // first one at least is likely to be a generic welcome message + m_tipNumber = 0; + } + + showTip(); +} + +void +TipDialog::previous() +{ + if (--m_tipNumber < 0) { + m_tipNumber = m_tips.size() - 1; + } + + showTip(); +} + +void +TipDialog::readTips() +{ + std::cerr << "TipDialog::readTips" << std::endl; + + QString language = QLocale::system().name(); + QString filename = QString(":i18n/tips_%1.xml").arg(language); + + if (!QFileInfo(filename).exists()) { + + QString base = language.section('_', 0, 0); + filename = QString(":i18n/tips_%1.xml").arg(base); + + if (!QFileInfo(filename).exists()) { + + filename = QString(":i18n/tips.xml"); + + if (!QFileInfo(filename).exists()) return; + } + } + + QFile file(filename); + + std::cerr << "TipDialog::readTips from " << filename.toStdString() << std::endl; + + QXmlInputSource source(&file); + + TipFileParser parser(this); + parser.parse(source); +} + +void +TipDialog::showTip() +{ + if (m_tipNumber < int(m_tips.size())) { + std::cerr << "Tip " << m_tipNumber << " is: " << m_tips[m_tipNumber].toStdString() << std::endl; + m_label->setText(m_tips[m_tipNumber]); + } else { + accept(); + } + + int tn = m_tipNumber; + if (++tn >= int(m_tips.size())) tn = 0; //!!! as above + + QSettings settings; + settings.beginGroup("TipOfTheDay"); + settings.setValue("nexttip", tn); +} + +TipDialog::TipFileParser::TipFileParser(TipDialog *dialog) : + m_dialog(dialog), + m_inTip(false), + m_inText(false), + m_inHtml(false) +{ +} + +TipDialog::TipFileParser::~TipFileParser() +{ +} + +void +TipDialog::TipFileParser::parse(QXmlInputSource &source) +{ + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + reader.parse(source); +} + +bool +TipDialog::TipFileParser::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString name = qName.toLower(); + + std::cerr << "TipFileParser::startElement(" << name.toStdString() << ")" << std::endl; + + if (name == "tips") { + QString caption = attributes.value("caption"); + std::cerr << "TipFileParser::caption = " << caption.toStdString() << std::endl; + if (caption != "") m_dialog->m_caption = caption; + } else if (name == "tip") { + if (m_inTip) { + std::cerr << "WARNING: TipFileParser: nested elements" << std::endl; + } + m_inTip = true; + } else if (name == "text") { + if (m_inTip) { + m_inText = true; + std::cerr << "TipFileParser: adding new tip" << std::endl; + m_dialog->m_tips.push_back(""); + } else { + std::cerr << "WARNING: TipFileParser: outside element" << std::endl; + } + } else if (name == "html") { + if (m_inTip) { + m_inHtml = true; + std::cerr << "TipFileParser: adding new tip" << std::endl; + m_dialog->m_tips.push_back(""); + } else { + std::cerr << "WARNING: TipFileParser: outside element" << std::endl; + } + } else if (m_inHtml) { + m_dialog->m_tips[m_dialog->m_tips.size()-1] += "<" + qName; + for (int i = 0; i < attributes.count(); ++i) { + m_dialog->m_tips[m_dialog->m_tips.size()-1] += + " " + attributes.qName(i) + "=\"" + attributes.value(i) + "\""; + } + m_dialog->m_tips[m_dialog->m_tips.size()-1] += ">"; + } + + std::cerr << "TipFileParser::startElement done" << std::endl; + return true; +} + +bool +TipDialog::TipFileParser::endElement(const QString &, const QString &, + const QString &qName) +{ + QString name = qName.toLower(); + + if (name == "text") { + if (!m_inText) { + std::cerr << "WARNING: TipFileParser: without " << std::endl; + } + m_inText = false; + } else if (name == "html") { + if (!m_inHtml) { + std::cerr << "WARNING: TipFileParser: without " << std::endl; + } + m_inHtml = false; + } else if (name == "tip") { + if (m_inText) { + std::cerr << "WARNING: TipFileParser: without " << std::endl; + } else if (m_inHtml) { + std::cerr << "WARNING: TipFileParser: without " << std::endl; + } else if (!m_inTip) { + std::cerr << "WARNING: TipFileParser: without " << std::endl; + } + m_inTip = false; + } else if (m_inHtml) { + m_dialog->m_tips[m_dialog->m_tips.size()-1] += ""; + } + + return true; +} + +bool +TipDialog::TipFileParser::characters(const QString &text) +{ + std::cerr << "TipFileParser::characters(" << text.toStdString() << ")" << std::endl; + + if (m_inText || m_inHtml) { + m_dialog->m_tips[m_dialog->m_tips.size()-1] += text; + } + + return true; +} + +bool +TipDialog::TipFileParser::error(const QXmlParseException &exception) +{ + QString errorString = + QString("ERROR: TipFileParser: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << errorString.toStdString() << std::endl; + return QXmlDefaultHandler::error(exception); +} + +bool +TipDialog::TipFileParser::fatalError(const QXmlParseException &exception) +{ + QString errorString = + QString("FATAL ERROR: TipFileParser: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << errorString.toStdString() << std::endl; + return QXmlDefaultHandler::fatalError(exception); +} diff -r 000000000000 -r fc9323a41f5a widgets/TipDialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/TipDialog.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,83 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TIP_DIALOG_H_ +#define _TIP_DIALOG_H_ + +#include +#include +#include + +#include + +class QLabel; +class QXmlInputSource; + +class TipDialog : public QDialog +{ + Q_OBJECT + +public: + TipDialog(QWidget *parent = 0, Qt::WFlags flags = 0); + virtual ~TipDialog(); + + bool isOK() { return !m_tips.empty(); } + +protected slots: + void previous(); + void next(); + +protected: + int m_tipNumber; + QLabel *m_label; + QString m_caption; + + std::vector m_tips; + + void readTips(); + void showTip(); + + class TipFileParser : public QXmlDefaultHandler + { + public: + TipFileParser(TipDialog *dialog); + virtual ~TipFileParser(); + + void parse(QXmlInputSource &source); + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, + const QString &qName, + const QXmlAttributes& atts); + + virtual bool characters(const QString &); + + virtual bool endElement(const QString &namespaceURI, + const QString &localName, + const QString &qName); + + bool error(const QXmlParseException &exception); + bool fatalError(const QXmlParseException &exception); + + protected: + TipDialog *m_dialog; + + bool m_inTip; + bool m_inText; + bool m_inHtml; + }; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/WindowShapePreview.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/WindowShapePreview.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,208 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WindowShapePreview.h" + +#include +#include +#include +#include +#include +#include + +#include "data/fft/FFTapi.h" + +#include + +WindowShapePreview::WindowShapePreview(QWidget *parent) : + QFrame(parent), + m_windowType(WindowType(999)) +{ + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + setLayout(layout); + m_windowTimeExampleLabel = new QLabel; + m_windowFreqExampleLabel = new QLabel; + layout->addWidget(m_windowTimeExampleLabel); + layout->addWidget(m_windowFreqExampleLabel); +} + +WindowShapePreview::~WindowShapePreview() +{ +} + +void +WindowShapePreview::updateLabels() +{ + int step = 24; + int peak = 48; + int w = step * 4, h = 64; + WindowType type = m_windowType; + Window windower = Window(type, step * 2); + + QPixmap timeLabel(w, h + 1); + timeLabel.fill(Qt::white); + QPainter timePainter(&timeLabel); + + QPainterPath path; + + path.moveTo(0, h - peak + 1); + path.lineTo(w, h - peak + 1); + + timePainter.setPen(Qt::gray); + timePainter.setRenderHint(QPainter::Antialiasing, true); + timePainter.drawPath(path); + + path = QPainterPath(); + + //float acc[w]; + float *acc = (float*) malloc(w*sizeof(float)); + for (int i = 0; i < w; ++i) acc[i] = 0.f; + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < step * 2; ++i) { + acc[j * step + i] += windower.getValue(i); + } + } + for (int i = 0; i < w; ++i) { + int y = h - int(peak * acc[i] + 0.001) + 1; + if (i == 0) path.moveTo(i, y); + else path.lineTo(i, y); + } + + timePainter.drawPath(path); + timePainter.setRenderHint(QPainter::Antialiasing, false); + + path = QPainterPath(); + + timePainter.setPen(Qt::black); + + for (int i = 0; i < step * 2; ++i) { + int y = h - int(peak * windower.getValue(i) + 0.001) + 1; + if (i == 0) path.moveTo(i + step, float(y)); + else path.lineTo(i + step, float(y)); + } + + if (type == RectangularWindow) { + timePainter.drawPath(path); + path = QPainterPath(); + } + + timePainter.setRenderHint(QPainter::Antialiasing, true); + path.addRect(0, 0, w, h + 1); + timePainter.drawPath(path); + + QFont font; + font.setPixelSize(10); + font.setItalic(true); + timePainter.setFont(font); + QString label = tr("V / time"); + timePainter.drawText(w - timePainter.fontMetrics().width(label) - 4, + timePainter.fontMetrics().ascent() + 1, label); + + m_windowTimeExampleLabel->setPixmap(timeLabel); + + int fw = 100; + + QPixmap freqLabel(fw, h + 1); + freqLabel.fill(Qt::white); + QPainter freqPainter(&freqLabel); + path = QPainterPath(); + + int fftsize = 512; + + float *input = (float *)fftf_malloc(fftsize * sizeof(float)); + fftf_complex *output = + (fftf_complex *)fftf_malloc(fftsize * sizeof(fftf_complex)); + fftf_plan plan = fftf_plan_dft_r2c_1d(fftsize, input, output, + FFTW_ESTIMATE); + for (int i = 0; i < fftsize; ++i) input[i] = 0.f; + for (int i = 0; i < step * 2; ++i) { + input[fftsize/2 - step + i] = windower.getValue(i); + } + + fftf_execute(plan); + fftf_destroy_plan(plan); + + float maxdb = 0.f; + float mindb = 0.f; + bool first = true; + for (int i = 0; i < fftsize/2; ++i) { + float power = output[i][0] * output[i][0] + output[i][1] * output[i][1]; + float db = mindb; + if (power > 0) { + db = 20 * log10(power); + if (first || db > maxdb) maxdb = db; + if (first || db < mindb) mindb = db; + first = false; + } + } + + if (mindb > -80.f) mindb = -80.f; + + // -- no, don't use the actual mindb -- it's easier to compare + // plots with a fixed min value + mindb = -170.f; + + float maxval = maxdb + -mindb; + +// float ly = h - ((-80.f + -mindb) / maxval) * peak + 1; + + path.moveTo(0, h - peak + 1); + path.lineTo(fw, h - peak + 1); + + freqPainter.setPen(Qt::gray); + freqPainter.setRenderHint(QPainter::Antialiasing, true); + freqPainter.drawPath(path); + + path = QPainterPath(); + freqPainter.setPen(Qt::black); + +// std::cerr << "maxdb = " << maxdb << ", mindb = " << mindb << ", maxval = " <setPixmap(freqLabel); +} + +void +WindowShapePreview::setWindowType(WindowType type) +{ + if (m_windowType == type) return; + m_windowType = type; + updateLabels(); +} + diff -r 000000000000 -r fc9323a41f5a widgets/WindowShapePreview.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/WindowShapePreview.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,44 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WINDOW_SHAPE_PREVIEW_H_ +#define _WINDOW_SHAPE_PREVIEW_H_ + +#include + +#include "base/Window.h" + +class QLabel; + +class WindowShapePreview : public QFrame +{ + Q_OBJECT + +public: + WindowShapePreview(QWidget *parent = 0); + virtual ~WindowShapePreview(); + +public slots: + void setWindowType(WindowType type); + +protected: + QLabel *m_windowTimeExampleLabel; + QLabel *m_windowFreqExampleLabel; + WindowType m_windowType; + + void updateLabels(); +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/WindowTypeSelector.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/WindowTypeSelector.cpp Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,108 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "WindowTypeSelector.h" + +#include "WindowShapePreview.h" + +#include +#include + +#include "base/Preferences.h" + +WindowTypeSelector::WindowTypeSelector(WindowType defaultType, QWidget *parent) : + QFrame(parent), + m_windowType(WindowType(999)) +{ + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + setLayout(layout); + + // The WindowType enum is in rather a ragbag order -- reorder it here + // in a more sensible order + m_windows = new WindowType[9]; + m_windows[0] = HanningWindow; + m_windows[1] = HammingWindow; + m_windows[2] = BlackmanWindow; + m_windows[3] = BlackmanHarrisWindow; + m_windows[4] = NuttallWindow; + m_windows[5] = GaussianWindow; + m_windows[6] = ParzenWindow; + m_windows[7] = BartlettWindow; + m_windows[8] = RectangularWindow; + + Preferences *prefs = Preferences::getInstance(); + + m_windowShape = new WindowShapePreview; + + m_windowCombo = new QComboBox; + int min = 0, max = 0, deflt = 0, i = 0; + int window = int(defaultType); + if (window == 999) { + window = prefs->getPropertyRangeAndValue("Window Type", &min, &max, + &deflt); + } + int index = 0; + + for (i = 0; i <= 8; ++i) { + m_windowCombo->addItem(prefs->getPropertyValueLabel("Window Type", + m_windows[i])); + if (m_windows[i] == window) index = i; + } + + m_windowCombo->setCurrentIndex(index); + + layout->addWidget(m_windowShape); + layout->addWidget(m_windowCombo); + + connect(m_windowCombo, SIGNAL(currentIndexChanged(int)), + this, SLOT(windowIndexChanged(int))); + windowIndexChanged(index); +} + +WindowTypeSelector::~WindowTypeSelector() +{ + delete[] m_windows; +} + +WindowType +WindowTypeSelector::getWindowType() const +{ + return m_windowType; +} + +void +WindowTypeSelector::setWindowType(WindowType type) +{ + if (type == m_windowType) return; + int index; + for (index = 0; index <= 8; ++index) { + if (m_windows[index] == type) break; + } + if (index <= 8) m_windowCombo->setCurrentIndex(index); + m_windowType = type; + m_windowShape->setWindowType(m_windowType); +} + +void +WindowTypeSelector::windowIndexChanged(int index) +{ + WindowType type = m_windows[index]; + if (type == m_windowType) return; + m_windowType = type; + m_windowShape->setWindowType(m_windowType); + emit windowTypeChanged(type); +} + diff -r 000000000000 -r fc9323a41f5a widgets/WindowTypeSelector.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/WindowTypeSelector.h Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _WINDOW_TYPE_SELECTOR_H_ +#define _WINDOW_TYPE_SELECTOR_H_ + +#include + +#include "base/Window.h" + +class WindowShapePreview; +class QComboBox; + +class WindowTypeSelector : public QFrame +{ + Q_OBJECT + +public: + WindowTypeSelector(WindowType defaultType = WindowType(999), // 999 -> get from preferences + QWidget *parent = 0); + virtual ~WindowTypeSelector(); + + WindowType getWindowType() const; + +signals: + void windowTypeChanged(WindowType type); + +public slots: + void setWindowType(WindowType type); + +protected slots: + void windowIndexChanged(int index); + +protected: + QComboBox *m_windowCombo; + WindowShapePreview *m_windowShape; + WindowType *m_windows; + WindowType m_windowType; +}; + +#endif diff -r 000000000000 -r fc9323a41f5a widgets/svwidgets.vcproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/svwidgets.vcproj Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,908 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r fc9323a41f5a widgets/widgets.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/widgets.pro Fri May 11 09:08:14 2007 +0000 @@ -0,0 +1,58 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = vamp-hostsdk fftw3f +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += xml + +TARGET = svwidgets + +DEPENDPATH += . .. +INCLUDEPATH += . .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +# Input +HEADERS += AudioDial.h \ + Fader.h \ + ItemEditDialog.h \ + LayerTree.h \ + LEDButton.h \ + ListInputDialog.h \ + NotifyingCheckBox.h \ + NotifyingComboBox.h \ + NotifyingPushButton.h \ + NotifyingTabBar.h \ + Panner.h \ + PluginParameterBox.h \ + PluginParameterDialog.h \ + PropertyBox.h \ + PropertyStack.h \ + RangeInputDialog.h \ + SubdividingMenu.h \ + Thumbwheel.h \ + TipDialog.h \ + WindowShapePreview.h \ + WindowTypeSelector.h +SOURCES += AudioDial.cpp \ + Fader.cpp \ + ItemEditDialog.cpp \ + LayerTree.cpp \ + LEDButton.cpp \ + ListInputDialog.cpp \ + NotifyingCheckBox.cpp \ + NotifyingComboBox.cpp \ + NotifyingPushButton.cpp \ + NotifyingTabBar.cpp \ + Panner.cpp \ + PluginParameterBox.cpp \ + PluginParameterDialog.cpp \ + PropertyBox.cpp \ + PropertyStack.cpp \ + RangeInputDialog.cpp \ + SubdividingMenu.cpp \ + Thumbwheel.cpp \ + TipDialog.cpp \ + WindowShapePreview.cpp \ + WindowTypeSelector.cpp