packages feed

HarmTrace 1.0 → 2.0

raw patch · 56 files changed

+7561/−6340 lines, 56 filesdep +cmdargsdep +soxdep −regex-tdfadep ~basedep ~template-haskelldep ~uu-parsinglibbuild-type:Customsetup-changed

Dependencies added: cmdargs, sox

Dependencies removed: regex-tdfa

Dependency ranges changed: base, template-haskell, uu-parsinglib

Files

HarmTrace.cabal view
@@ -1,98 +1,169 @@-name:                   HarmTrace-version:                1.0-synopsis:               Harmony Analysis and Retrieval of Music-description:            HarmTrace: Harmony Analysis and Retrieval of Music -                        with Type-level Representations of Abstract-                        Chords Entities-                        .-                        We present HarmTrace, a system-                        for automatically analysing the harmony of music-                        sequences. HarmTrace is described in the paper:-                        .-                        * Jose Pedro Magalhaes and W. Bas de Haas.-                        /Experience Report: Functional Modelling of Musical Harmony./-                        International Conference on Functional Programming,-                        2011.-                        <http://dreixel.net/research/pdf/fmmh.pdf>--copyright:              (c) 2010--2011 Universiteit Utrecht-license:                GPL-3-license-file:           LICENSE-author:                 W. Bas de Haas and Jose Pedro Magalhaes-stability:              experimental-maintainer:             bash@cs.uu.nl, jpm@cs.uu.nl-homepage:               http://www.cs.uu.nl/wiki/GenericProgramming/HarmTrace-category:               Music-build-type:             Simple-cabal-version:          >= 1.6-tested-with:            GHC == 7.0.3, GHC == 7.2.1--extra-source-files:     README--executable harmtrace-  hs-source-dirs:       src-  other-modules:        HarmTrace.HarmTrace--                        HarmTrace.Audio.Annotations-                        HarmTrace.Audio.BeatChroma-                        HarmTrace.Audio.ChordTypes-                        HarmTrace.Audio.Evaluation-                        HarmTrace.Audio.Harmonize-                        HarmTrace.Audio.Key-                        HarmTrace.Audio.Parser-                        HarmTrace.Audio.Utils--                        HarmTrace.Base.MusicRep-                        HarmTrace.Base.Parsing-                        HarmTrace.Base.TypeLevel--                        HarmTrace.HAnTree.Binary-                        HarmTrace.HAnTree.HAn-                        HarmTrace.HAnTree.HAnParser-                        HarmTrace.HAnTree.PostProcess-                        HarmTrace.HAnTree.ToHAnTree-                        HarmTrace.HAnTree.Tree--                        HarmTrace.IO.Errors-                        HarmTrace.IO.Main-                        HarmTrace.IO.PrintTree--                        HarmTrace.Matching.Alignment-                        HarmTrace.Matching.HChord-                        HarmTrace.Matching.Sim-                        HarmTrace.Matching.SimpleChord-                        HarmTrace.Matching.Standard-                        HarmTrace.Matching.GuptaNishimura--                        HarmTrace.Models.Models-                        HarmTrace.Models.Parser--                        HarmTrace.Models.Jazz.Instances-                        HarmTrace.Models.Jazz.Main-                        HarmTrace.Models.Jazz.Model--                        HarmTrace.Models.Pop.Instances-                        HarmTrace.Models.Pop.Main-                        HarmTrace.Models.Pop.Model--                        HarmTrace.Tokenizer.Tokenizer-                        HarmTrace.Tokenizer.Tokens--  main-is:              Main.hs-  build-depends:        base >= 4.2 && < 4.5, template-haskell >=2.4 && <2.7,-                        mtl, directory, filepath, array, parallel >= 3,-                        Diff == 0.1.*, parseargs >= 0.1.3.2, -                        regex-tdfa == 1.1.*, process >= 1.0,-                        uu-parsinglib >= 2.7.1, ListLike >= 3.0.1,-                        vector >= 0.7, deepseq,-                        instant-generics >= 0.3.1 && < 0.4, binary == 0.5.*,-                        hmatrix >= 0.11.0.3, hmatrix-gsl-stats >= 0.1.2.9--  cpp-options:          -DAUDIO--  ghc-options:          -Wall-                        -O2 -fno-spec-constr-count -funbox-strict-fields-                        -fcontext-stack=50-                        -threaded -feager-blackholing -rtsopts--  ghc-prof-options:     -auto-all+name:                   HarmTrace
+version:                2.0
+synopsis:               Harmony Analysis and Retrieval of Music
+description:            HarmTrace: Harmony Analysis and Retrieval of Music 
+                        with Type-level Representations of Abstract
+                        Chords Entities
+                        .
+                        We present HarmTrace, a system
+                        for automatically analysing the harmony of music
+                        sequences. HarmTrace is described in the paper:
+                        .
+                        * Jose Pedro Magalhaes and W. Bas de Haas.
+                        /Experience Report: Functional Modelling of Musical Harmony./
+                        International Conference on Functional Programming,
+                        2011.
+                        <http://dreixel.net/research/pdf/fmmh.pdf>
+
+copyright:              (c) 2010--2012 Universiteit Utrecht,
+                                  2012 University of Oxford
+license:                GPL-3
+license-file:           LICENSE
+author:                 W. Bas de Haas and Jose Pedro Magalhaes
+stability:              experimental
+maintainer:             bash@cs.uu.nl, jpm@cs.ox.ac.uk
+homepage:               http://www.cs.uu.nl/wiki/GenericProgramming/HarmTrace
+category:               Music
+build-type:             Custom
+cabal-version:          >= 1.6
+tested-with:            GHC == 7.0.3, GHC == 7.2.1, GHC == 7.4.1
+
+extra-source-files:     README
+
+executable harmtrace
+  hs-source-dirs:       src
+  other-modules:        Constants
+
+                        HarmTrace.HarmTrace
+
+                        HarmTrace.Audio.ChordTypes
+                        HarmTrace.Audio.Annotate
+                        HarmTrace.Audio.AnnotationParser
+                        HarmTrace.Audio.DataParser
+                        HarmTrace.Audio.ChromaChord
+                        HarmTrace.Audio.ChromaKey
+                        HarmTrace.Audio.Key
+                        HarmTrace.Audio.Evaluation
+                        HarmTrace.Audio.Statistical
+
+                        HarmTrace.Base.MusicRep
+                        HarmTrace.Base.Parsing
+                        HarmTrace.Base.TypeLevel
+
+                        HarmTrace.HAnTree.Binary
+                        HarmTrace.HAnTree.HAn
+                        HarmTrace.HAnTree.HAnParser
+                        HarmTrace.HAnTree.PostProcess
+                        HarmTrace.HAnTree.ToHAnTree
+                        HarmTrace.HAnTree.Tree
+
+                        HarmTrace.IO.Errors
+                        HarmTrace.IO.Main
+                        HarmTrace.IO.PrintTree
+                        HarmTrace.IO.Common
+                        HarmTrace.IO.Match
+                        HarmTrace.IO.Parse
+                        HarmTrace.IO.Recognise
+                        HarmTrace.IO.FeatExtract
+                        HarmTrace.IO.Downsample
+                        HarmTrace.IO.BasePaths
+
+                        HarmTrace.Matching.Alignment
+                        HarmTrace.Matching.HChord
+                        HarmTrace.Matching.Sim
+                        HarmTrace.Matching.SimpleChord
+                        HarmTrace.Matching.Standard
+                        HarmTrace.Matching.GuptaNishimura
+
+                        HarmTrace.Models.Models
+                        HarmTrace.Models.Parser
+
+                        HarmTrace.Models.Jazz.Instances
+                        HarmTrace.Models.Jazz.Main
+                        HarmTrace.Models.Jazz.Model
+
+                        HarmTrace.Models.Pop.Instances
+                        HarmTrace.Models.Pop.Main
+                        HarmTrace.Models.Pop.Model
+
+                        HarmTrace.Tokenizer.Tokenizer
+                        HarmTrace.Tokenizer.Tokens
+
+  main-is:              Main.hs
+  build-depends:        base >= 4.2 && < 4.6, template-haskell >=2.4 && <2.8,
+                        mtl, directory, filepath, array, parallel >= 3,
+                        Diff == 0.1.*, cmdargs >= 0.9 && < 0.10, process >= 1.0,
+                        uu-parsinglib == 2.7.1, ListLike >= 3.0.1,
+                        vector >= 0.7, deepseq, sox >= 0.2.2.1,
+                        instant-generics >= 0.3.1 && < 0.4, binary == 0.5.*,
+                        hmatrix >= 0.11.0.3, hmatrix-gsl-stats >= 0.1.2.9
+
+  ghc-options:          -Wall
+                        -O2 -fno-spec-constr-count -funbox-strict-fields
+                        -fcontext-stack=50
+                        -threaded -feager-blackholing -rtsopts
+
+  ghc-prof-options:     -auto-all
+
+
+library
+-- Note the extreme duplication of information
+-- I don't think there's much we can do about that
+  build-depends:        base >= 4.2 && < 4.6, template-haskell >=2.4 && <2.8,
+                        mtl, directory, filepath, array, parallel >= 3,
+                        Diff == 0.1.*, parseargs >= 0.1.3.2, process >= 1.0,
+                        uu-parsinglib == 2.7.1, ListLike >= 3.0.1,
+                        vector >= 0.7, deepseq,
+                        instant-generics >= 0.3.1 && < 0.4, binary == 0.5.*,
+                        hmatrix >= 0.11.0.3, hmatrix-gsl-stats >= 0.1.2.9
+
+  ghc-options:          -Wall
+                        -O2 -fno-spec-constr-count -funbox-strict-fields
+                        -fcontext-stack=50
+                        -feager-blackholing -rtsopts
+
+  ghc-prof-options:     -auto-all
+
+  hs-source-dirs:       src
+
+  exposed-modules:      HarmTrace.Audio.ChordTypes
+                        HarmTrace.Audio.Annotate
+                        HarmTrace.Audio.AnnotationParser
+                        HarmTrace.Audio.DataParser
+                        HarmTrace.Audio.ChromaChord
+                        HarmTrace.Audio.ChromaKey
+                        HarmTrace.Audio.Key
+                        HarmTrace.Audio.Evaluation
+                        HarmTrace.Audio.Statistical
+
+                        HarmTrace.Base.MusicRep
+                        HarmTrace.Base.Parsing
+                        HarmTrace.Base.TypeLevel
+
+                        HarmTrace.HAnTree.Binary
+                        HarmTrace.HAnTree.HAn
+                        HarmTrace.HAnTree.HAnParser
+                        HarmTrace.HAnTree.PostProcess
+                        HarmTrace.HAnTree.ToHAnTree
+                        HarmTrace.HAnTree.Tree
+
+                        HarmTrace.Matching.Alignment
+                        HarmTrace.Matching.HChord
+                        HarmTrace.Matching.Sim
+                        HarmTrace.Matching.SimpleChord
+                        HarmTrace.Matching.Standard
+                        HarmTrace.Matching.GuptaNishimura
+
+                        HarmTrace.Models.Models
+                        HarmTrace.Models.Parser
+
+                        HarmTrace.Models.Jazz.Instances
+                        HarmTrace.Models.Jazz.Main
+                        HarmTrace.Models.Jazz.Model
+
+                        HarmTrace.Models.Pop.Instances
+                        HarmTrace.Models.Pop.Main
+                        HarmTrace.Models.Pop.Model
+
+                        HarmTrace.Tokenizer.Tokenizer
+                        HarmTrace.Tokenizer.Tokens
LICENSE view
@@ -1,674 +1,674 @@-                    GNU GENERAL PUBLIC LICENSE-                       Version 3, 29 June 2007-- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>- Everyone is permitted to copy and distribute verbatim copies- of this license document, but changing it is not allowed.--                            Preamble--  The GNU General Public License is a free, copyleft license for-software and other kinds of works.--  The licenses for most software and other practical works are designed-to take away your freedom to share and change the works.  By contrast,-the GNU General Public License is intended to guarantee your freedom to-share and change all versions of a program--to make sure it remains free-software for all its users.  We, the Free Software Foundation, use the-GNU General Public License for most of our software; it applies also to-any other work released this way by its authors.  You can apply it to-your programs, too.--  When we speak of free software, we are referring to freedom, not-price.  Our General Public Licenses are designed to make sure that you-have the freedom to distribute copies of free software (and charge for-them if you wish), that you receive source code or can get it if you-want it, that you can change the software or use pieces of it in new-free programs, and that you know you can do these things.--  To protect your rights, we need to prevent others from denying you-these rights or asking you to surrender the rights.  Therefore, you have-certain responsibilities if you distribute copies of the software, or if-you modify it: responsibilities to respect the freedom of others.--  For example, if you distribute copies of such a program, whether-gratis or for a fee, you must pass on to the recipients the same-freedoms that you received.  You must make sure that they, too, receive-or can get the source code.  And you must show them these terms so they-know their rights.--  Developers that use the GNU GPL protect your rights with two steps:-(1) assert copyright on the software, and (2) offer you this License-giving you legal permission to copy, distribute and/or modify it.--  For the developers' and authors' protection, the GPL clearly explains-that there is no warranty for this free software.  For both users' and-authors' sake, the GPL requires that modified versions be marked as-changed, so that their problems will not be attributed erroneously to-authors of previous versions.--  Some devices are designed to deny users access to install or run-modified versions of the software inside them, although the manufacturer-can do so.  This is fundamentally incompatible with the aim of-protecting users' freedom to change the software.  The systematic-pattern of such abuse occurs in the area of products for individuals to-use, which is precisely where it is most unacceptable.  Therefore, we-have designed this version of the GPL to prohibit the practice for those-products.  If such problems arise substantially in other domains, we-stand ready to extend this provision to those domains in future versions-of the GPL, as needed to protect the freedom of users.--  Finally, every program is threatened constantly by software patents.-States should not allow patents to restrict development and use of-software on general-purpose computers, but in those that do, we wish to-avoid the special danger that patents applied to a free program could-make it effectively proprietary.  To prevent this, the GPL assures that-patents cannot be used to render the program non-free.--  The precise terms and conditions for copying, distribution and-modification follow.--                       TERMS AND CONDITIONS--  0. Definitions.--  "This License" refers to version 3 of the GNU General Public License.--  "Copyright" also means copyright-like laws that apply to other kinds of-works, such as semiconductor masks.--  "The Program" refers to any copyrightable work licensed under this-License.  Each licensee is addressed as "you".  "Licensees" and-"recipients" may be individuals or organizations.--  To "modify" a work means to copy from or adapt all or part of the work-in a fashion requiring copyright permission, other than the making of an-exact copy.  The resulting work is called a "modified version" of the-earlier work or a work "based on" the earlier work.--  A "covered work" means either the unmodified Program or a work based-on the Program.--  To "propagate" a work means to do anything with it that, without-permission, would make you directly or secondarily liable for-infringement under applicable copyright law, except executing it on a-computer or modifying a private copy.  Propagation includes copying,-distribution (with or without modification), making available to the-public, and in some countries other activities as well.--  To "convey" a work means any kind of propagation that enables other-parties to make or receive copies.  Mere interaction with a user through-a computer network, with no transfer of a copy, is not conveying.--  An interactive user interface displays "Appropriate Legal Notices"-to the extent that it includes a convenient and prominently visible-feature that (1) displays an appropriate copyright notice, and (2)-tells the user that there is no warranty for the work (except to the-extent that warranties are provided), that licensees may convey the-work under this License, and how to view a copy of this License.  If-the interface presents a list of user commands or options, such as a-menu, a prominent item in the list meets this criterion.--  1. Source Code.--  The "source code" for a work means the preferred form of the work-for making modifications to it.  "Object code" means any non-source-form of a work.--  A "Standard Interface" means an interface that either is an official-standard defined by a recognized standards body, or, in the case of-interfaces specified for a particular programming language, one that-is widely used among developers working in that language.--  The "System Libraries" of an executable work include anything, other-than the work as a whole, that (a) is included in the normal form of-packaging a Major Component, but which is not part of that Major-Component, and (b) serves only to enable use of the work with that-Major Component, or to implement a Standard Interface for which an-implementation is available to the public in source code form.  A-"Major Component", in this context, means a major essential component-(kernel, window system, and so on) of the specific operating system-(if any) on which the executable work runs, or a compiler used to-produce the work, or an object code interpreter used to run it.--  The "Corresponding Source" for a work in object code form means all-the source code needed to generate, install, and (for an executable-work) run the object code and to modify the work, including scripts to-control those activities.  However, it does not include the work's-System Libraries, or general-purpose tools or generally available free-programs which are used unmodified in performing those activities but-which are not part of the work.  For example, Corresponding Source-includes interface definition files associated with source files for-the work, and the source code for shared libraries and dynamically-linked subprograms that the work is specifically designed to require,-such as by intimate data communication or control flow between those-subprograms and other parts of the work.--  The Corresponding Source need not include anything that users-can regenerate automatically from other parts of the Corresponding-Source.--  The Corresponding Source for a work in source code form is that-same work.--  2. Basic Permissions.--  All rights granted under this License are granted for the term of-copyright on the Program, and are irrevocable provided the stated-conditions are met.  This License explicitly affirms your unlimited-permission to run the unmodified Program.  The output from running a-covered work is covered by this License only if the output, given its-content, constitutes a covered work.  This License acknowledges your-rights of fair use or other equivalent, as provided by copyright law.--  You may make, run and propagate covered works that you do not-convey, without conditions so long as your license otherwise remains-in force.  You may convey covered works to others for the sole purpose-of having them make modifications exclusively for you, or provide you-with facilities for running those works, provided that you comply with-the terms of this License in conveying all material for which you do-not control copyright.  Those thus making or running the covered works-for you must do so exclusively on your behalf, under your direction-and control, on terms that prohibit them from making any copies of-your copyrighted material outside their relationship with you.--  Conveying under any other circumstances is permitted solely under-the conditions stated below.  Sublicensing is not allowed; section 10-makes it unnecessary.--  3. Protecting Users' Legal Rights From Anti-Circumvention Law.--  No covered work shall be deemed part of an effective technological-measure under any applicable law fulfilling obligations under article-11 of the WIPO copyright treaty adopted on 20 December 1996, or-similar laws prohibiting or restricting circumvention of such-measures.--  When you convey a covered work, you waive any legal power to forbid-circumvention of technological measures to the extent such circumvention-is effected by exercising rights under this License with respect to-the covered work, and you disclaim any intention to limit operation or-modification of the work as a means of enforcing, against the work's-users, your or third parties' legal rights to forbid circumvention of-technological measures.--  4. Conveying Verbatim Copies.--  You may convey verbatim copies of the Program's source code as you-receive it, in any medium, provided that you conspicuously and-appropriately publish on each copy an appropriate copyright notice;-keep intact all notices stating that this License and any-non-permissive terms added in accord with section 7 apply to the code;-keep intact all notices of the absence of any warranty; and give all-recipients a copy of this License along with the Program.--  You may charge any price or no price for each copy that you convey,-and you may offer support or warranty protection for a fee.--  5. Conveying Modified Source Versions.--  You may convey a work based on the Program, or the modifications to-produce it from the Program, in the form of source code under the-terms of section 4, provided that you also meet all of these conditions:--    a) The work must carry prominent notices stating that you modified-    it, and giving a relevant date.--    b) The work must carry prominent notices stating that it is-    released under this License and any conditions added under section-    7.  This requirement modifies the requirement in section 4 to-    "keep intact all notices".--    c) You must license the entire work, as a whole, under this-    License to anyone who comes into possession of a copy.  This-    License will therefore apply, along with any applicable section 7-    additional terms, to the whole of the work, and all its parts,-    regardless of how they are packaged.  This License gives no-    permission to license the work in any other way, but it does not-    invalidate such permission if you have separately received it.--    d) If the work has interactive user interfaces, each must display-    Appropriate Legal Notices; however, if the Program has interactive-    interfaces that do not display Appropriate Legal Notices, your-    work need not make them do so.--  A compilation of a covered work with other separate and independent-works, which are not by their nature extensions of the covered work,-and which are not combined with it such as to form a larger program,-in or on a volume of a storage or distribution medium, is called an-"aggregate" if the compilation and its resulting copyright are not-used to limit the access or legal rights of the compilation's users-beyond what the individual works permit.  Inclusion of a covered work-in an aggregate does not cause this License to apply to the other-parts of the aggregate.--  6. Conveying Non-Source Forms.--  You may convey a covered work in object code form under the terms-of sections 4 and 5, provided that you also convey the-machine-readable Corresponding Source under the terms of this License,-in one of these ways:--    a) Convey the object code in, or embodied in, a physical product-    (including a physical distribution medium), accompanied by the-    Corresponding Source fixed on a durable physical medium-    customarily used for software interchange.--    b) Convey the object code in, or embodied in, a physical product-    (including a physical distribution medium), accompanied by a-    written offer, valid for at least three years and valid for as-    long as you offer spare parts or customer support for that product-    model, to give anyone who possesses the object code either (1) a-    copy of the Corresponding Source for all the software in the-    product that is covered by this License, on a durable physical-    medium customarily used for software interchange, for a price no-    more than your reasonable cost of physically performing this-    conveying of source, or (2) access to copy the-    Corresponding Source from a network server at no charge.--    c) Convey individual copies of the object code with a copy of the-    written offer to provide the Corresponding Source.  This-    alternative is allowed only occasionally and noncommercially, and-    only if you received the object code with such an offer, in accord-    with subsection 6b.--    d) Convey the object code by offering access from a designated-    place (gratis or for a charge), and offer equivalent access to the-    Corresponding Source in the same way through the same place at no-    further charge.  You need not require recipients to copy the-    Corresponding Source along with the object code.  If the place to-    copy the object code is a network server, the Corresponding Source-    may be on a different server (operated by you or a third party)-    that supports equivalent copying facilities, provided you maintain-    clear directions next to the object code saying where to find the-    Corresponding Source.  Regardless of what server hosts the-    Corresponding Source, you remain obligated to ensure that it is-    available for as long as needed to satisfy these requirements.--    e) Convey the object code using peer-to-peer transmission, provided-    you inform other peers where the object code and Corresponding-    Source of the work are being offered to the general public at no-    charge under subsection 6d.--  A separable portion of the object code, whose source code is excluded-from the Corresponding Source as a System Library, need not be-included in conveying the object code work.--  A "User Product" is either (1) a "consumer product", which means any-tangible personal property which is normally used for personal, family,-or household purposes, or (2) anything designed or sold for incorporation-into a dwelling.  In determining whether a product is a consumer product,-doubtful cases shall be resolved in favor of coverage.  For a particular-product received by a particular user, "normally used" refers to a-typical or common use of that class of product, regardless of the status-of the particular user or of the way in which the particular user-actually uses, or expects or is expected to use, the product.  A product-is a consumer product regardless of whether the product has substantial-commercial, industrial or non-consumer uses, unless such uses represent-the only significant mode of use of the product.--  "Installation Information" for a User Product means any methods,-procedures, authorization keys, or other information required to install-and execute modified versions of a covered work in that User Product from-a modified version of its Corresponding Source.  The information must-suffice to ensure that the continued functioning of the modified object-code is in no case prevented or interfered with solely because-modification has been made.--  If you convey an object code work under this section in, or with, or-specifically for use in, a User Product, and the conveying occurs as-part of a transaction in which the right of possession and use of the-User Product is transferred to the recipient in perpetuity or for a-fixed term (regardless of how the transaction is characterized), the-Corresponding Source conveyed under this section must be accompanied-by the Installation Information.  But this requirement does not apply-if neither you nor any third party retains the ability to install-modified object code on the User Product (for example, the work has-been installed in ROM).--  The requirement to provide Installation Information does not include a-requirement to continue to provide support service, warranty, or updates-for a work that has been modified or installed by the recipient, or for-the User Product in which it has been modified or installed.  Access to a-network may be denied when the modification itself materially and-adversely affects the operation of the network or violates the rules and-protocols for communication across the network.--  Corresponding Source conveyed, and Installation Information provided,-in accord with this section must be in a format that is publicly-documented (and with an implementation available to the public in-source code form), and must require no special password or key for-unpacking, reading or copying.--  7. Additional Terms.--  "Additional permissions" are terms that supplement the terms of this-License by making exceptions from one or more of its conditions.-Additional permissions that are applicable to the entire Program shall-be treated as though they were included in this License, to the extent-that they are valid under applicable law.  If additional permissions-apply only to part of the Program, that part may be used separately-under those permissions, but the entire Program remains governed by-this License without regard to the additional permissions.--  When you convey a copy of a covered work, you may at your option-remove any additional permissions from that copy, or from any part of-it.  (Additional permissions may be written to require their own-removal in certain cases when you modify the work.)  You may place-additional permissions on material, added by you to a covered work,-for which you have or can give appropriate copyright permission.--  Notwithstanding any other provision of this License, for material you-add to a covered work, you may (if authorized by the copyright holders of-that material) supplement the terms of this License with terms:--    a) Disclaiming warranty or limiting liability differently from the-    terms of sections 15 and 16 of this License; or--    b) Requiring preservation of specified reasonable legal notices or-    author attributions in that material or in the Appropriate Legal-    Notices displayed by works containing it; or--    c) Prohibiting misrepresentation of the origin of that material, or-    requiring that modified versions of such material be marked in-    reasonable ways as different from the original version; or--    d) Limiting the use for publicity purposes of names of licensors or-    authors of the material; or--    e) Declining to grant rights under trademark law for use of some-    trade names, trademarks, or service marks; or--    f) Requiring indemnification of licensors and authors of that-    material by anyone who conveys the material (or modified versions of-    it) with contractual assumptions of liability to the recipient, for-    any liability that these contractual assumptions directly impose on-    those licensors and authors.--  All other non-permissive additional terms are considered "further-restrictions" within the meaning of section 10.  If the Program as you-received it, or any part of it, contains a notice stating that it is-governed by this License along with a term that is a further-restriction, you may remove that term.  If a license document contains-a further restriction but permits relicensing or conveying under this-License, you may add to a covered work material governed by the terms-of that license document, provided that the further restriction does-not survive such relicensing or conveying.--  If you add terms to a covered work in accord with this section, you-must place, in the relevant source files, a statement of the-additional terms that apply to those files, or a notice indicating-where to find the applicable terms.--  Additional terms, permissive or non-permissive, may be stated in the-form of a separately written license, or stated as exceptions;-the above requirements apply either way.--  8. Termination.--  You may not propagate or modify a covered work except as expressly-provided under this License.  Any attempt otherwise to propagate or-modify it is void, and will automatically terminate your rights under-this License (including any patent licenses granted under the third-paragraph of section 11).--  However, if you cease all violation of this License, then your-license from a particular copyright holder is reinstated (a)-provisionally, unless and until the copyright holder explicitly and-finally terminates your license, and (b) permanently, if the copyright-holder fails to notify you of the violation by some reasonable means-prior to 60 days after the cessation.--  Moreover, your license from a particular copyright holder is-reinstated permanently if the copyright holder notifies you of the-violation by some reasonable means, this is the first time you have-received notice of violation of this License (for any work) from that-copyright holder, and you cure the violation prior to 30 days after-your receipt of the notice.--  Termination of your rights under this section does not terminate the-licenses of parties who have received copies or rights from you under-this License.  If your rights have been terminated and not permanently-reinstated, you do not qualify to receive new licenses for the same-material under section 10.--  9. Acceptance Not Required for Having Copies.--  You are not required to accept this License in order to receive or-run a copy of the Program.  Ancillary propagation of a covered work-occurring solely as a consequence of using peer-to-peer transmission-to receive a copy likewise does not require acceptance.  However,-nothing other than this License grants you permission to propagate or-modify any covered work.  These actions infringe copyright if you do-not accept this License.  Therefore, by modifying or propagating a-covered work, you indicate your acceptance of this License to do so.--  10. Automatic Licensing of Downstream Recipients.--  Each time you convey a covered work, the recipient automatically-receives a license from the original licensors, to run, modify and-propagate that work, subject to this License.  You are not responsible-for enforcing compliance by third parties with this License.--  An "entity transaction" is a transaction transferring control of an-organization, or substantially all assets of one, or subdividing an-organization, or merging organizations.  If propagation of a covered-work results from an entity transaction, each party to that-transaction who receives a copy of the work also receives whatever-licenses to the work the party's predecessor in interest had or could-give under the previous paragraph, plus a right to possession of the-Corresponding Source of the work from the predecessor in interest, if-the predecessor has it or can get it with reasonable efforts.--  You may not impose any further restrictions on the exercise of the-rights granted or affirmed under this License.  For example, you may-not impose a license fee, royalty, or other charge for exercise of-rights granted under this License, and you may not initiate litigation-(including a cross-claim or counterclaim in a lawsuit) alleging that-any patent claim is infringed by making, using, selling, offering for-sale, or importing the Program or any portion of it.--  11. Patents.--  A "contributor" is a copyright holder who authorizes use under this-License of the Program or a work on which the Program is based.  The-work thus licensed is called the contributor's "contributor version".--  A contributor's "essential patent claims" are all patent claims-owned or controlled by the contributor, whether already acquired or-hereafter acquired, that would be infringed by some manner, permitted-by this License, of making, using, or selling its contributor version,-but do not include claims that would be infringed only as a-consequence of further modification of the contributor version.  For-purposes of this definition, "control" includes the right to grant-patent sublicenses in a manner consistent with the requirements of-this License.--  Each contributor grants you a non-exclusive, worldwide, royalty-free-patent license under the contributor's essential patent claims, to-make, use, sell, offer for sale, import and otherwise run, modify and-propagate the contents of its contributor version.--  In the following three paragraphs, a "patent license" is any express-agreement or commitment, however denominated, not to enforce a patent-(such as an express permission to practice a patent or covenant not to-sue for patent infringement).  To "grant" such a patent license to a-party means to make such an agreement or commitment not to enforce a-patent against the party.--  If you convey a covered work, knowingly relying on a patent license,-and the Corresponding Source of the work is not available for anyone-to copy, free of charge and under the terms of this License, through a-publicly available network server or other readily accessible means,-then you must either (1) cause the Corresponding Source to be so-available, or (2) arrange to deprive yourself of the benefit of the-patent license for this particular work, or (3) arrange, in a manner-consistent with the requirements of this License, to extend the patent-license to downstream recipients.  "Knowingly relying" means you have-actual knowledge that, but for the patent license, your conveying the-covered work in a country, or your recipient's use of the covered work-in a country, would infringe one or more identifiable patents in that-country that you have reason to believe are valid.--  If, pursuant to or in connection with a single transaction or-arrangement, you convey, or propagate by procuring conveyance of, a-covered work, and grant a patent license to some of the parties-receiving the covered work authorizing them to use, propagate, modify-or convey a specific copy of the covered work, then the patent license-you grant is automatically extended to all recipients of the covered-work and works based on it.--  A patent license is "discriminatory" if it does not include within-the scope of its coverage, prohibits the exercise of, or is-conditioned on the non-exercise of one or more of the rights that are-specifically granted under this License.  You may not convey a covered-work if you are a party to an arrangement with a third party that is-in the business of distributing software, under which you make payment-to the third party based on the extent of your activity of conveying-the work, and under which the third party grants, to any of the-parties who would receive the covered work from you, a discriminatory-patent license (a) in connection with copies of the covered work-conveyed by you (or copies made from those copies), or (b) primarily-for and in connection with specific products or compilations that-contain the covered work, unless you entered into that arrangement,-or that patent license was granted, prior to 28 March 2007.--  Nothing in this License shall be construed as excluding or limiting-any implied license or other defenses to infringement that may-otherwise be available to you under applicable patent law.--  12. No Surrender of Others' Freedom.--  If conditions are imposed on you (whether by court order, agreement or-otherwise) that contradict the conditions of this License, they do not-excuse you from the conditions of this License.  If you cannot convey a-covered work so as to satisfy simultaneously your obligations under this-License and any other pertinent obligations, then as a consequence you may-not convey it at all.  For example, if you agree to terms that obligate you-to collect a royalty for further conveying from those to whom you convey-the Program, the only way you could satisfy both those terms and this-License would be to refrain entirely from conveying the Program.--  13. Use with the GNU Affero General Public License.--  Notwithstanding any other provision of this License, you have-permission to link or combine any covered work with a work licensed-under version 3 of the GNU Affero General Public License into a single-combined work, and to convey the resulting work.  The terms of this-License will continue to apply to the part which is the covered work,-but the special requirements of the GNU Affero General Public License,-section 13, concerning interaction through a network will apply to the-combination as such.--  14. Revised Versions of this License.--  The Free Software Foundation may publish revised and/or new versions of-the GNU General Public License from time to time.  Such new versions will-be similar in spirit to the present version, but may differ in detail to-address new problems or concerns.--  Each version is given a distinguishing version number.  If the-Program specifies that a certain numbered version of the GNU General-Public License "or any later version" applies to it, you have the-option of following the terms and conditions either of that numbered-version or of any later version published by the Free Software-Foundation.  If the Program does not specify a version number of the-GNU General Public License, you may choose any version ever published-by the Free Software Foundation.--  If the Program specifies that a proxy can decide which future-versions of the GNU General Public License can be used, that proxy's-public statement of acceptance of a version permanently authorizes you-to choose that version for the Program.--  Later license versions may give you additional or different-permissions.  However, no additional obligations are imposed on any-author or copyright holder as a result of your choosing to follow a-later version.--  15. Disclaimer of Warranty.--  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.--  16. Limitation of Liability.--  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF-SUCH DAMAGES.--  17. Interpretation of Sections 15 and 16.--  If the disclaimer of warranty and limitation of liability provided-above cannot be given local legal effect according to their terms,-reviewing courts shall apply local law that most closely approximates-an absolute waiver of all civil liability in connection with the-Program, unless a warranty or assumption of liability accompanies a-copy of the Program in return for a fee.--                     END OF TERMS AND CONDITIONS--            How to Apply These Terms to Your New Programs--  If you develop a new program, and you want it to be of the greatest-possible use to the public, the best way to achieve this is to make it-free software which everyone can redistribute and change under these terms.--  To do so, attach the following notices to the program.  It is safest-to attach them to the start of each source file to most effectively-state the exclusion of warranty; and each file should have at least-the "copyright" line and a pointer to where the full notice is found.--    <one line to give the program's name and a brief idea of what it does.>-    Copyright (C) <year>  <name of author>--    This program is free software: you can redistribute it and/or modify-    it under the terms of the GNU General Public License as published by-    the Free Software Foundation, either version 3 of the License, or-    (at your option) any later version.--    This program is distributed in the hope that it will be useful,-    but WITHOUT ANY WARRANTY; without even the implied warranty of-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the-    GNU General Public License for more details.--    You should have received a copy of the GNU General Public License-    along with this program.  If not, see <http://www.gnu.org/licenses/>.--Also add information on how to contact you by electronic and paper mail.--  If the program does terminal interaction, make it output a short-notice like this when it starts in an interactive mode:--    <program>  Copyright (C) <year>  <name of author>-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.-    This is free software, and you are welcome to redistribute it-    under certain conditions; type `show c' for details.--The hypothetical commands `show w' and `show c' should show the appropriate-parts of the General Public License.  Of course, your program's commands-might be different; for a GUI interface, you would use an "about box".--  You should also get your employer (if you work as a programmer) or school,-if any, to sign a "copyright disclaimer" for the program, if necessary.-For more information on this, and how to apply and follow the GNU GPL, see-<http://www.gnu.org/licenses/>.--  The GNU General Public License does not permit incorporating your program-into proprietary programs.  If your program is a subroutine library, you-may consider it more useful to permit linking proprietary applications with-the library.  If this is what you want to do, use the GNU Lesser General-Public License instead of this License.  But first, please read+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
 <http://www.gnu.org/philosophy/why-not-lgpl.html>.
README view
@@ -1,23 +1,23 @@-HarmTrace (Harmony Analysis and Retrieval of Music with Type-level -Representations of Abstract Chords Entities) is a system for automatic harmony -analysis of music. It takes a sequence of chords as input and produces a harmony -analysis, which can be visualised as a tree. --Music theory has been essential in composing and performing music for centuries. -Within Western tonal music, from the early Baroque on to modern-day jazz and pop -music, the function of chords within a chord sequence can be explained by -harmony theory. Although Western tonal harmony theory is a thoroughly studied -area, formalising this theory is a hard problem. --With HarmTrace we have developed a formalisation of the rules of tonal harmony -as a Haskell (generalized) algebraic datatype. Given a sequence of chord labels, -the harmonic function of a chord in its tonal context is automatically derived. -For this, we use several advanced functional programming techniques, such as -type-level computations, datatype-generic programming, and error-correcting -parsers. Our functional model of harmony offers various benefits: it can be used -to define harmonic similarity measures and facilitate music retrieval, or it can -help musicologists in batch-analysing large corpora of digitised scores, for -instance. --More information about HarmTrace, including how to use and example output, is-available on its webpage: http://www.cs.uu.nl/wiki/GenericProgramming/HarmTrace+HarmTrace (Harmony Analysis and Retrieval of Music with Type-level 
+Representations of Abstract Chords Entities) is a system for automatic harmony 
+analysis of music. It takes a sequence of chords as input and produces a harmony 
+analysis, which can be visualised as a tree. 
+
+Music theory has been essential in composing and performing music for centuries. 
+Within Western tonal music, from the early Baroque on to modern-day jazz and pop 
+music, the function of chords within a chord sequence can be explained by 
+harmony theory. Although Western tonal harmony theory is a thoroughly studied 
+area, formalising this theory is a hard problem. 
+
+With HarmTrace we have developed a formalisation of the rules of tonal harmony 
+as a Haskell (generalized) algebraic datatype. Given a sequence of chord labels, 
+the harmonic function of a chord in its tonal context is automatically derived. 
+For this, we use several advanced functional programming techniques, such as 
+type-level computations, datatype-generic programming, and error-correcting 
+parsers. Our functional model of harmony offers various benefits: it can be used 
+to define harmonic similarity measures and facilitate music retrieval, or it can 
+help musicologists in batch-analysing large corpora of digitised scores, for 
+instance. 
+
+More information about HarmTrace, including how to use and example output, is
+available on its webpage: http://www.cs.uu.nl/wiki/GenericProgramming/HarmTrace
Setup.hs view
@@ -1,6 +1,19 @@-module Main (main) where--import Distribution.Simple--main :: IO ()-main = defaultMainWithHooks simpleUserHooks+module Main (main) where
+
+import Distribution.Simple
+import System.Cmd (system)
+
+main :: IO ()
+main = defaultMainWithHooks hooks
+  where hooks = simpleUserHooks { runTests = runTests'
+                                , preBuild = preBuild' }
+
+-- Runs the testsuite
+runTests' _ _ _ _ = system "shelltest tests -c -d" >> return ()
+
+-- Update the VERSION string before building
+--preBuild' :: Args -> BuildFlags -> IO HookedBuildInfo
+preBuild' a b = do -- First do our replacement
+                   system "cd src && runghc Replace.hs"
+                   -- Then go on with preBuild as usual
+                   preBuild simpleUserHooks a b
+ src/Constants.hs view
@@ -0,0 +1,140 @@+{-# OPTIONS_GHC -Wall #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  Constants
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Several constants used everywhere
+--------------------------------------------------------------------------------
+
+module Constants ( vERSION, cutOffProbability, maxProbChordListLength
+                 , maxSegmentSize, maxLProductSize, evaluationSampleRate
+                 , displaySampleRate, outputSampleRate, outputBitsPerSample
+                 , outputNrOfChannels, keyStr, chromaStr, beatStr
+                 , beatVampPath, chromaVampPath, keyVampPath
+                 , deleteDownsampledWav
+                 , defaultVampDir, defaultFeatDir , defaultOutDir  
+                 , modulationPenalty, minModulationLength ) where
+
+import HarmTrace.Audio.ChordTypes (NumData)
+
+--------------------------------------------------------------------------------
+-- Constants
+--------------------------------------------------------------------------------
+
+-- | Automatically generated
+vERSION :: String
+vERSION = gitVersion ++ " (master)"
+
+gitVersion :: String
+gitVersion = "HarmTrace-2.0"
+
+
+--------------------------------------------------------------------------------
+-- Chord Transcription Parameters
+--------------------------------------------------------------------------------
+
+-- | The cutOffProbability is the value that determines the length of the 
+-- probChord list (a sorted list with normalised distances to the chroma vector)
+cutOffProbability      :: NumData
+cutOffProbability      = 0.90
+
+maxProbChordListLength, maxSegmentSize, maxLProductSize :: Int
+-- | The maximum number if chords (the lenght) of a chord candidate list
+maxProbChordListLength = 6
+
+-- | The maximum size of a list of chord candidate lists (of which all possible
+-- combinations will be parsed). If, in the segmentation procedure, this 
+-- threshold is crossed an 'emergency split' will be made (see 
+-- Harmtrace.Audio.Annotate.emergencySplit for details)
+maxSegmentSize         = 12
+
+-- | Chord candidate list segments can contain long and short chord candidate 
+-- lists. This strongly influences the number of considered chord sequence 
+-- combinations, i.e. the LProductSize. If the number of possible combinations
+-- becomes greater than the 'maxLProductSize' also an 'emergency split' will
+-- be made, similar to maxSegmentSize.
+maxLProductSize        = 30
+
+--------------------------------------------------------------------------------
+-- Evaluation Parameters
+--------------------------------------------------------------------------------
+
+evaluationSampleRate, displaySampleRate :: NumData
+-- | The sample rate used in a normal (non-visual) comparison (in seconds).
+evaluationSampleRate = 0.01
+-- | The sample rate used when visually comparing a chord annotation with a 
+-- ground-truth annotation. Often a higher sample rate is preferred. Although
+-- one uses precision, the visual result is easier to read.
+displaySampleRate    = 0.3
+
+
+--------------------------------------------------------------------------------
+-- Feature Extraction and Downsampling Parameters
+--------------------------------------------------------------------------------
+
+outputSampleRate, outputBitsPerSample, outputNrOfChannels :: Int
+-- | Before feature extraction, all audio is downsampled to 'outputSampleRate' 
+-- Hz. N.B. If the sample rate is changed the VAMP plugin transforms should be
+-- updated accordingly (see 'beat', 'chroma', 'key').
+outputSampleRate    = 22050
+
+-- | Before feature extraction, all audio is converted to contain 
+-- 'outputBitsPerSample' bits per sample.
+outputBitsPerSample = 16
+
+-- | Before feature extraction, all audio is converted to contain a fixed number
+-- of channels, e.g. 1 for mono or 2 for stereo.
+outputNrOfChannels  = 1
+
+-- | Controls whether the downsampled wav file is deleted after feature 
+-- extraction.
+deleteDownsampledWav :: Bool
+deleteDownsampledWav = True
+
+-- | The strings that build up a audio feature file name
+keyStr, chromaStr, beatStr :: String
+chromaStr = "_vamp_nnls-chroma_nnls-chroma_bothchroma.csv"
+keyStr    = "_vamp_nnls-chroma_nnls-chroma_chroma.csv"
+-- beatStr   = "_vamp_qm-vamp-plugins_qm-tempotracker_beats.csv" 
+beatStr   = "_vamp_qm-vamp-plugins_qm-barbeattracker_beats.csv"
+
+beatVampPath, chromaVampPath, keyVampPath :: FilePath
+-- | specifying the filename of the beat tracker VAMP plugin transform file
+beatVampPath   = "beat-tracker-one.txt" 
+-- | specifying the filename of the NNLS chroma VAMP plugin transform file
+chromaVampPath = "both-chroma.txt" 
+-- | specifying the filename of the key-finding chroma VAMP plugin transform file
+keyVampPath    = "tuned-chroma.txt"
+             
+defaultVampDir, defaultOutDir, defaultFeatDir :: FilePath
+-- | The default base directory is the current directory
+defaultVampDir = ""
+defaultOutDir  = ""
+defaultFeatDir = ""
+
+--------------------------------------------------------------------------------
+-- Chroma key estimation
+--------------------------------------------------------------------------------
+
+-- | The penatly given in the cumulative key strength calculation. For every 
+-- beat the correlation between a Krumhansl profile and the current beat 
+-- synchronised chroma feature is calculated for all 24 keys at every beat. 
+-- An optimal path through this table can be defined as: 
+-- $M[i,j] = max \{ M[i-1,j] + K[i,j],
+--                  M[i-1,j] + K[i,k] + p, } 
+--                 where \{k \mid \forall x : K[i,x] \leq K[i,k]\}$
+-- where $p$ is the modulationPenalty, i.e. the penalty for changing the key
+-- at that particular beat.
+modulationPenalty :: NumData
+modulationPenalty   = 1.0
+
+-- | The minimal length in beats of a key segment
+minModulationLength :: Int
+minModulationLength = 16
+ src/HarmTrace/Audio/Annotate.hs view
@@ -0,0 +1,305 @@+{-# OPTIONS_GHC -Wall                     #-}
+{-# LANGUAGE GADTs                        #-}
+{-# LANGUAGE ScopedTypeVariables          #-}
+{-# LANGUAGE TupleSections                #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.Annotate
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Combining low-level features (VAMP plug-ins) with high-level
+-- knowledge (the HarmTrace harmony model)
+--------------------------------------------------------------------------------
+module HarmTrace.Audio.Annotate ( mptreeAnnotator, groupAnnotator
+                                , simpleAnnotator , putSegStats, preProcessData
+                                , preProcessKeyData ) where
+-- parameters
+import Constants ( maxSegmentSize, maxLProductSize)
+                 
+-- Audio Stuff
+import HarmTrace.Audio.ChromaChord ( createChordRanks, beatSync
+                                   , mergeByBeat, addBeatTimeStamp
+                                   , mergeAndTimeStamp)
+-- import HarmTrace.Audio.ChromaKey ( syncWithAnnKey)
+import HarmTrace.Audio.Key (getBeatSyncKeyFromChroma)
+import HarmTrace.Audio.ChordTypes
+
+-- Harmony Model stuff
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Models
+import HarmTrace.Models.Jazz.Main
+import HarmTrace.Models.Pop.Main
+import HarmTrace.Tokenizer.Tokens
+import HarmTrace.IO.Errors
+import HarmTrace.HAnTree.HAn (HAn)
+import HarmTrace.HAnTree.Tree (Tree, size, depth)
+import HarmTrace.HAnTree.ToHAnTree (GTree)
+import HarmTrace.HarmTrace
+
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.BasicInstances
+
+import System.IO (stdout,hFlush)
+import Data.List (sortBy, groupBy, zipWith4)
+import Text.Printf (printf)
+
+--------------------------------------------------------------------------------
+-- From chords with probabilities to a single chord, using harmony
+--------------------------------------------------------------------------------
+
+-- | MPTrEE (Model Propelled Transcription of Euphonic Enitities): 
+-- a sophisticated, harmony and beat informed chord annotator
+mptreeAnnotator :: GrammarEx -> Maybe [TimedData Key] -> AudioFeat 
+                 -> ChordBeatAnnotation
+mptreeAnnotator (GrammarEx g) k f = concatMap (harmonize g) (preProcessData k f)
+
+-- | preprocesses the raw audio data before using chord harmony model based
+-- chord selection. First, the beats and chroma are synchronised. Second, 
+-- chord candidate lists are created. Third, a beat time-stamp is added. Fourth,
+-- smart, beat informed grouping of the chord candidates is performed. Fifth,
+-- the chord candidate lists are grouped in segments based on the key (obtained
+-- as provided by the user or as derived from the audio data). Last, the 
+-- chord candidate lists are further segmented based on the occurrences of 
+-- I and V chords.
+preProcessData ::  Maybe [TimedData Key] -> AudioFeat -> [ProbChordSeg]
+preProcessData gtk (AudioFeat chrm beats afk) = 
+  segmentByTonic $ segmentByKey key . mergeByBeat . addBeatTimeStamp bt 
+                                    . createChordRanks $ beatSync bt chrm  where
+
+      -- if a ground-truth key annotations are provided, we use these 
+      -- annotations otherwise the key is derived from audio data
+      (bt, key) = case gtk of 
+         Nothing  -> preProcessKeyData chrm beats afk
+         (Just k) -> syncWithAnnKey k
+
+      -- syncronises the last beat and key frame to match the last chord frame
+      syncWithAnnKey :: [TimedData Key] -> ([BeatBar], [TimedData Key])
+      syncWithAnnKey keys = 
+        let endTime   = BeatBar (time $ last chrm, Four)
+            beats'    = takeWhile (< endTime) beats ++ [endTime]
+            -- filter the None keys
+            none (Key r _) = r /= Note Nothing N 
+            noNoneKey = (filter (none . getData) keys)
+            -- reset key start timestamp to 0.0 and end timestampt to chorma end
+            resetHead = setOnset (head noNoneKey) 0.0 : tail noNoneKey
+            (l,[lst]) = splitAt (length resetHead - 1) resetHead
+        in  (beats',l ++[setOffset lst (timeStamp endTime)])
+
+-- | preprocesses the audio data for automatic key detection.
+preProcessKeyData ::  [ChordinoLine] -> [BeatBar] -> [ChordinoLine] 
+                  -> ([BeatBar], [TimedData Key])  
+preProcessKeyData chrm beats afk =           
+  ( b, dumpBeats . mergeAndTimeStamp head b $ getBeatSyncKeyFromChroma b key ) 
+  where -- synchronise the last beat and key to match the last chord
+          endTime   = time $ last chrm
+          afk'      = takeWhile ((< endTime).time) afk 
+          (l,[lst]) = splitAt (length afk' - 1) afk'
+          end       = BeatBar (endTime, Four)
+          key       = l ++ [lst{time = endTime}]
+          b         = takeWhile (< end) beats ++ [end]
+
+-- reminder: ProbChordSeg = Segment Key [BeatTimedData [ProbChord]] 
+harmonize :: forall g. (GTree g) => Grammar g -> ProbChordSeg -> ChordBeatAnnotation
+harmonize g (Segment k cands) =
+  let isExpandable :: Bool
+      isExpandable = length (filter ((>1) . length) (map getData cands)) > 0
+      
+      myParse :: [ChordToken] -> (Tree HAn,[ChordToken],[Error Int])
+      myParse x =
+        let -- First, parse the tokens
+            res :: ([g],[Error Int])
+            res = case g of 
+                      Jazz -> parse_h ((,) <$> pJazz k <*> pEnd) (createStr 0 x)
+                      Pop  -> parse_h ((,) <$> pPop  k <*> pEnd) (createStr 0 x)
+            -- Build a ParseResult from that
+            pr = ParseResult u (concatMap chords x) (fst res) u u u (snd res) []
+            -- So that we can post-process it. Then extract the Tree HAn
+            t  = pieceTreeHAn (postProc [ RemovePDPT, MergeDelChords ]   pr)
+            u :: forall a. a
+            u  = error "harmonize: undefined placeholder evaluated"
+        -- Return the Tree HAn, the input tokens, and the errors
+        in (t, x, snd res)
+
+      -- To be improved
+      evaluateParse :: (Tree HAn,[ChordToken],[Error Int])
+                    -> (Tree HAn,[ChordToken],Float)
+      evaluateParse (ts,tokens,errors) = (ts,tokens,errorRatio errors tokens)
+
+      -- Generate, parse, and evaluate all possible sequences of chords
+      parseResults :: [(Tree HAn,[ChordToken],Float)]
+      parseResults = [ evaluateParse (myParse l)
+                     | l <- lProduct (map (map probChord . getData) cands) ]
+
+      -- From all possible parse trees, take the best one
+      select :: [(Tree HAn,[ChordToken],Float)] -> [ChordToken]
+      select = select1 . head
+             . groupBy (\(_,_,a) (_,_,b) -> a `compare` b == EQ)
+             . sortBy  (\(_,_,a) (_,_,b) -> a `compare` b)
+
+      -- These all have the same error ratio, so we sort them first by tree
+      -- size, then depth, and pick the first
+      select1 :: [(Tree HAn,[ChordToken],Float)] -> [ChordToken]
+      select1 = snd3 . head . sortBy cmp where
+        cmp (a,_,_) (b,_,_) = (size a, depth a) `compare` (size b, depth b)
+        snd3 (_,a,_) = a
+
+      probChord :: ProbChord -> ChordToken
+      probChord (ProbChord lab@(Chord r sh _add _on _dur) _p) = 
+        (ChordToken r' sh' [lab] NotParsed 1 0) where
+           r'  = if isNone r   then Note Nothing Imp else toScaleDegree k r
+           sh' = if sh == None then NoClass          else toClassType sh
+      
+      -- replaces the candidate list by the selected chord in a 
+      -- Timed datatype (either TimedData or BeatTimedData)
+     -- setBestChords :: Timed t => [ChordToken] -> [t ChordLabel]
+      setBestChords :: [ChordToken] -> [BeatTimedData ChordLabel]
+      setBestChords = zipWith setData cands . map (head . chords) 
+
+      -- if there is nothing to expand, do not parse
+  in if   isExpandable then setBestChords $ select parseResults 
+     else map pickHead cands
+
+pickHead :: Timed t => t [ProbChord] -> t ChordLabel
+pickHead tpc = setData tpc (chordLab . head $ getData tpc)
+     
+--------------------------------------------------------------------------------
+-- Segmentation functions
+--------------------------------------------------------------------------------
+-- Temporary test values
+{-
+test = segmentTonic testKey testSeq
+testKey = Key (Note Nothing C) MajMode
+testSeq = testChordG ++ testChordC ++ testChordC ++ testChordG ++ testChordG
+testChordC = [TimedData [ProbChord labC 1, ProbChord labG 0.5] 0 0]
+testChordG = [TimedData [ProbChord labG 1, ProbChord labC 0.5] 0 0]
+labC = Chord (Note Nothing C) Maj [] 0 0
+labG = Chord (Note Nothing G) Maj [] 0 0
+-}
+
+
+-- move to segmentations function in Harmonize?
+segmentByKey ::Timed t=>[t Key] -> [BeatTimedData [ProbChord]] -> [ProbChordSeg]
+segmentByKey []       _    = error "segmentByKey: empty key list"
+segmentByKey [k]      chds = [Segment (getData k) chds]
+segmentByKey (k : ks) chds = let (seg,cs) = span ((<= offset k) . offset) chds
+  in Segment (getData k) seg : segmentByKey ks cs
+
+-- Reminder: TimedData [ProbChord] == Block
+segmentByTonic :: [ProbChordSeg] -> [ProbChordSeg]
+segmentByTonic segs = concatMap emergencySplit $ concatMap split segs where
+  split :: ProbChordSeg -> [ProbChordSeg]
+  split (Segment key cs) = zipWith Segment (repeat key) (segmentTonic key cs)
+
+-- In case segments are just to big, even after segmenting on Tonic and Dominant
+-- split these segments into smaller segements recursively.
+emergencySplit :: ProbChordSeg -> [ProbChordSeg]
+emergencySplit (Segment k cs) = map (Segment k) (recSplit cs) where
+  -- recSplit :: [TimedData [a]] -> [[TimedData [a]]]
+  recSplit [] = []
+  recSplit b
+    |    blen               <= maxSegmentSize 
+      && snd (lProdStats b) <= maxLProductSize = [b]
+    | otherwise   = recSplit l ++ recSplit r
+      where blen  = length b
+            (l,r) = splitAt (blen `div` 2) b 
+  
+-- Break into segments according to the key
+-- segmentTonic :: Timed t => Key -> [t [ProbChord]] -> [[t [ProbChord]]]
+segmentTonic :: Key -> [BeatTimedData [ProbChord]] -> [[BeatTimedData [ProbChord]]]
+segmentTonic k cands = segment cands [] where 
+  segment []     []     = []
+  segment []     acc    = [reverse acc]
+  segment (c:cs) acc
+    | c' `isTonic` k || c' `isDom` k = reverse (c:acc) : segmentTonic k cs
+    | otherwise                      = segment cs (c:acc) where 
+        c' = getFstChord c
+
+-- Take the first chord (which is the one with the highest probability, since
+-- the list is sorted)
+getFstChord :: Timed t => t [ProbChord] -> ChordLabel
+getFstChord c = case getData c of
+                  []    -> error "getFstChord: empty list"
+                  (h:_) -> chordLab h -- only split on chords we are certain of
+                -- _   -> Chord (Note Nothing N) None [] 0 0 -- else return None
+
+-- Check if this chord label is the tonic
+isTonic :: ChordLabel -> Key -> Bool
+isTonic c (Key r m) = r == chordRoot c && m `eqMode` chordShorthand c
+
+-- Check if this chord label is the dominant
+-- JPM: I don't understand why this function looks so different from `isTonic`
+isDom :: ChordLabel -> Key -> Bool
+isDom (Chord (Note Nothing N) _ _ _ _) _ = False
+isDom c key       =    toScaleDegree key (chordRoot c) == Note Nothing V
+                    && MajMode `eqMode` chordShorthand c
+
+-- It is debatable how to implement this function, musically speaking. This
+-- is what I came up with, without thinking too much.
+eqMode :: Mode -> Shorthand -> Bool
+eqMode _ Sus4  = False
+eqMode _ Sus2  = False
+eqMode m sh    = m == toMode sh
+
+lProduct :: [[a]] -> [[a]]
+lProduct []    = []
+lProduct [l]   = [ [x] | x <- l ]
+lProduct (h:t) = concat [ map (x:) (lProduct t) | x <- h ]
+
+--------------------------------------------------------------------------------
+-- Some printing and statistics functions
+--------------------------------------------------------------------------------
+
+-- | prints Segmetation statistics
+putSegStats :: Maybe [TimedData Key] -> AudioFeat -> IO()
+putSegStats k af = mapM_ segmentStat $ preProcessData k af 
+
+segmentStat :: ProbChordSeg -> IO ()
+segmentStat s@(Segment k bs) = 
+  do putStr ("start: "  ++ (printf "%.3f" . onset  $ head bs))
+     putStr (", end: "  ++ (printf "%.3f" . offset $ last bs))
+     putStr (", key: "  ++ show k)
+     putStr (", probChords: " ++ show (length bs))
+     let (l, lpr) = lProdStats bs
+     putStr (", lists > 1: " ++ show l)
+     putStrLn (" lProduct: " ++ show lpr)
+     print s >> hFlush stdout
+
+-- Given a Block list this function returns the number of probChords with a 
+-- list > 1 (fst) and the lProduct size (snd)
+lProdStats :: Timed t => [t [a]] -> (Int, Int)
+lProdStats bs = (length l, lpr) where
+  l   = filter ((>1) . length ) (map getData bs)
+  lpr = foldr (\a b -> length a * b) 1 l
+
+--------------------------------------------------------------------------------
+-- A baseline chord label annotator
+--------------------------------------------------------------------------------
+
+-- | Creates an annotation out of a Chord candidate list by just picking the 
+-- first chord. This annotator does smart grouping 
+-- (see 'HarmTrace.Audio.ChromaChord.mergeByBeat').
+groupAnnotator :: GrammarEx -> Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation
+groupAnnotator _g _keyAnn (AudioFeat chrm beats _key ) = -- ignore key info
+  let endTime = BeatBar (time $ last chrm, Four)
+      beats'  = takeWhile (< endTime) beats ++ [endTime]
+  in map pickHead . mergeByBeat . addBeatTimeStamp beats' . createChordRanks 
+  $ beatSync beats' chrm  
+
+
+-- | The most simple annotator, no grouping, no matching, 
+-- just pick the best matching chord
+simpleAnnotator :: GrammarEx -> Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation
+simpleAnnotator _g _keyAnn (AudioFeat crm bts _key ) = -- ignore key
+  addTimeInfo . map (chordLab . head) . createChordRanks $ beatSync bts crm
+  where -- wraps a data types into 'BeatTimedData' datatype
+        addTimeInfo :: [a] -> [BeatTimedData a]
+        addTimeInfo blcks = zipWith4 BeatTimedData blcks bts' (0 : off) off 
+        
+        (off,bts') = unzip $ map beatBar bts
+
+ src/HarmTrace/Audio/AnnotationParser.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE FlexibleContexts #-}
+{-# OPTIONS_GHC -Wall         #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.AnnotationParser
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Parses textual ground-truth Chord annotations, such as the ones
+-- found at: <http://isophonics.net/content/reference-annotations>
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.AnnotationParser ( parseAnnotationData
+                                        , parseKeyAnnotationData
+                                        , preProcess) where
+
+import Data.Maybe (isJust,fromJust)
+
+import HarmTrace.Audio.DataParser (pNumData)
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Base.MusicRep
+import HarmTrace.Base.Parsing
+import HarmTrace.Tokenizer.Tokenizer ( parseDegrees, parseDegree
+                                     , parseRoot   , parseShorthand)
+
+-- perhaps this file should be moved to the tokeniser module, because it is 
+-- is very related to tokenising
+
+--------------------------------------------------------------------------------
+-- Harmonically analysing Chord Annotations
+--------------------------------------------------------------------------------
+
+-- | Preprocesses a chord annotation for analysing their harmonic structure by
+-- removing the time stamps and the None chords.
+preProcess  :: [TimedData ChordLabel] -> [ChordLabel]
+preProcess = preProcess' 0 
+preProcess' :: Int -> [TimedData ChordLabel] -> [ChordLabel]
+preProcess' _ [] = []
+preProcess' ix (TimedData c@(Chord r sh ad _loc dur) _on _off : ns) 
+  | isNoneChord c = preProcess' ix ns
+  | otherwise     = Chord r sh ad ix dur : preProcess' (ix+1) ns
+
+--------------------------------------------------------------------------------
+-- Chords
+--------------------------------------------------------------------------------
+-- | Parses a chord annotation.
+parseAnnotationData :: Parser ChordAnnotation
+parseAnnotationData = pListSep_ng pLineEnd pChordSegment <* pLineEnd
+
+pChordSegment :: Parser (TimedData ChordLabel)
+pChordSegment = timedData' <$> pNumData <* pSpaceTab
+                           <*> pNumData <* pSpaceTab <*> pChord
+
+
+pChord :: Parser ChordLabel
+pChord = f <$> parseRoot <*> pMaybe (pSym ':' *> parseShorthand)
+                         <*> (parseDegrees `opt` []) 
+                         -- there might be a basenote inversion annotation
+                         -- which we currently ignore
+                         <*> pMaybe (pSym '/' *> parseDegree) where
+           -- if there is no root note, there is no chord type
+           -- Chord root shorthand additions start duration
+  f r sh _ _ | r == Note Nothing N = Chord r None [] 0 1 
+             | isJust sh           = Chord r (fromJust sh) [] 0 1 
+             | otherwise           = Chord r Maj  [] 0 1 
+
+--------------------------------------------------------------------------------
+-- Keys
+--------------------------------------------------------------------------------
+-- | Parses a 'Key' annotation.
+parseKeyAnnotationData :: Parser [TimedData Key]
+parseKeyAnnotationData = pListSep_ng pLineEnd pKeySegment <* pLineEnd
+
+pKeySegment :: Parser (TimedData Key)
+pKeySegment = timedData'  <$> pNumData <*  pSpaceTab
+                          <*> pNumData <*  pSpaceTab 
+                          <*> (pKey    <|> pKeyNone)
+
+pKey :: Parser Key
+pKey = Key <$ pString "Key" <* pSpaceTab <*> parseRoot <*> pMode
+
+pKeyNone :: Parser Key
+pKeyNone = Key (Note Nothing N) MajMode <$ pString "Silence"
+
+pMode :: Parser Mode
+pMode  =     MajMode <$ pString ""
+         <|> MinMode <$ pString ":minor"
+         <|> MinMode <$ pString ":aeolian"
+         <|> MajMode <$ pString ":major"
+         <|> MinMode <$ pString ":dorian"
+         <|> MajMode <$ pString ":mixolydian" -- this must be solved differently
+         <|> MajMode <$ pString ":modal"      -- and this too....
+         
+
+--------------------------------------------------------------------------------
+-- General Parsers and Utils
+--------------------------------------------------------------------------------
+
+timedData' :: NumData -> NumData -> a -> TimedData a
+timedData' on off chrd = TimedData chrd on off
+
+pSpaceTab :: Parser Char
+pSpaceTab =  pSym ' ' <|> pSym '\t'
− src/HarmTrace/Audio/Annotations.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}
-{-# OPTIONS_GHC -Wall         #-}
-
-module HarmTrace.Audio.Annotations where
-
--- import Text.ParserCombinators.UU hiding (join)
--- import Text.ParserCombinators.UU.BasicInstances
--- import Data.ListLike.Base (ListLike)
-
-import Data.Maybe (isJust,fromJust)
-
-import HarmTrace.Audio.Parser
-import HarmTrace.Audio.ChordTypes
-import HarmTrace.Base.MusicRep
-import HarmTrace.Base.Parsing
-import HarmTrace.Tokenizer.Tokenizer (parseDegrees, parseDegree)
-
--- perhaps this file should be moved to the tokeniser module, because it is 
--- is very related to tokenising
-
---------------------------------------------------------------------------------
--- Harmonically analysing Chord Annotations
---------------------------------------------------------------------------------
-
-preProcess  :: [ChordSegment] -> [ChordLabel]
-preProcess = preProcess' 0 
-preProcess' :: Int -> [ChordSegment] -> [ChordLabel]
-preProcess' _ [] = []
-preProcess' ix ((TimedData c@(Chord r sh ad _loc dur) _on _off):ns) 
-  | isNoneChord c = preProcess' (ix) ns
-  | otherwise     = (Chord r sh ad ix dur) : preProcess' (ix+1) ns
-
---------------------------------------------------------------------------------
--- Chords
---------------------------------------------------------------------------------
-
-parseAnnotationData :: Parser ChordAnnotation
-parseAnnotationData = pListSep_ng pLineEnd pChordSegment <* pLineEnd
-
-pChordSegment :: Parser ChordSegment
-pChordSegment = timedData' <$> pNumData <* pSpaceTab
-                           <*> pNumData <* pSpaceTab <*> pChord
-
-
-pChord :: Parser ChordLabel
-pChord = f <$> parseRoot <*> pMaybe (pSym ':' *> parseShorthand)
-                         <*> (parseDegrees `opt` []) 
-                         -- there might be a basenote inversion annotation
-                         -- which we currently ignore
-                         <*> pMaybe (pSym '/' *> parseDegree) where
-           -- if there is no root note, there is no chord type
-           -- Chord root shorthand additions start duration
-  f r sh _ _ | r == (Note Nothing N) = Chord r None [] 0 1 
-             | isJust sh             = Chord r (fromJust sh) [] 0 1 
-             | otherwise             = Chord r Maj  [] 0 1 
-
---------------------------------------------------------------------------------
--- Keys
---------------------------------------------------------------------------------
-
-parseKeyAnnotationData :: Parser KeyAnnotation
-parseKeyAnnotationData = pListSep_ng pLineEnd pKeySegment <* pLineEnd
-
-pKeySegment :: Parser KeySegment
-pKeySegment = timedData'  <$> pNumData <*  pSpaceTab
-                          <*> pNumData <*  pSpaceTab 
-                          <*> (pKey    <|> pKeyNone)
-
-pKey :: Parser Key
-pKey = Key <$ pString "Key" <* pSpaceTab <*> parseRoot <*> pMode
-
-pKeyNone :: Parser Key
-pKeyNone = (Key (Note Nothing N) MajMode) <$ pString "Silence"
-
-pMode :: Parser Mode
-pMode  =     MajMode <$ pString ""
-         <|> MinMode <$ pString ":minor"
-         <|> MinMode <$ pString ":aeolian"
-         <|> MajMode <$ pString ":major"
-         <|> MinMode <$ pString ":dorian"
-         <|> MajMode <$ pString ":mixolydian" -- this must be solved differently
-         <|> MajMode <$ pString ":modal"      -- and this too....
-         
-
---------------------------------------------------------------------------------
--- General Parsers and Utils
---------------------------------------------------------------------------------
-
-timedData' :: NumData -> NumData -> a -> TimedData a
-timedData' on off chrd = TimedData chrd on off
-
-pSpaceTab :: Parser Char
-pSpaceTab =  pSym ' ' <|> pSym '\t'
-
--- begin stealing from Tokenizer.hs (added/changed some ad hoc instances)
-
-parseShorthand :: Parser Shorthand
-parseShorthand =     Maj      <$ pString "maj"
-                 <|> Min      <$ pString "min"
-                 <|> Dim      <$ pString "dim"
-                 <|> Aug      <$ pString "aug"
-                 <|> Maj7     <$ pString "maj7"
-                 <|> Min7     <$ pString "min7"
-                 <|> Sev      <$ pString "7"
-                 <|> Dim7     <$ pString "dim7"
-                 <|> HDim7    <$ pString "hdim" <* opt (pSym '7') '7'
-                 <|> MinMaj7  <$ pString "minmaj7"
-                 <|> Maj6     <$ pString "maj6"
-                 <|> Maj6     <$ pString "6"
-                 <|> Min6     <$ pString "min6"
-                 <|> Nin      <$ pString "9"
-                 <|> Maj9     <$ pString "maj9"
-                 <|> Min9     <$ pString "min9"
-                 <|> Maj      <$ pString ""-- this is not really nice, but works
-                 <|> Sus4     <$ pString "sus4"
-                 <|> Sus2     <$ pString "sus2"
-                 <?> "Shorthand"
-
-parseRoot :: Parser Root
-parseRoot =     Note Nothing   A  <$ pSym 'A'
-            <|> Note Nothing   B  <$ pSym 'B'
-            <|> Note Nothing   C  <$ pSym 'C'
-            <|> Note Nothing   D  <$ pSym 'D'
-            <|> Note Nothing   E  <$ pSym 'E'
-            <|> Note Nothing   F  <$ pSym 'F'
-            <|> Note Nothing   G  <$ pSym 'G'
-            <|> Note Nothing   N  <$ pSym 'N' -- for no chord
-            <|> Note (Just Fl) A <$ pString "Ab"
-            <|> Note (Just Fl) B <$ pString "Bb"
-            <|> Note (Just Fl) C <$ pString "Cb"
-            <|> Note (Just Fl) D <$ pString "Db"
-            <|> Note (Just Fl) E <$ pString "Eb"
-            <|> Note (Just Fl) F <$ pString "Fb"
-            <|> Note (Just Fl) G <$ pString "Gb"
-            <|> Note (Just Sh) A <$ pString "A#"
-            <|> Note (Just Sh) B <$ pString "B#"
-            <|> Note (Just Sh) C <$ pString "C#"
-            <|> Note (Just Sh) D <$ pString "D#"
-            <|> Note (Just Sh) E <$ pString "E#"
-            <|> Note (Just Sh) F <$ pString "F#"
-            <|> Note (Just Sh) G <$ pString "G#" <?> "Chord root"
-
--- end stealing from Tokenizer.hs
− src/HarmTrace/Audio/BeatChroma.hs
@@ -1,292 +0,0 @@-{-# OPTIONS_GHC -Wall         #-}-module HarmTrace.Audio.BeatChroma where--import HarmTrace.Audio.Parser-import HarmTrace.Audio.ChordTypes-import HarmTrace.Audio.Utils-import HarmTrace.Base.MusicRep---- import Text.Printf (printf)-import Data.List (sortBy, groupBy)-import Data.Ord (comparing)--import Numeric.LinearAlgebra -- http://hackage.haskell.org/package/hmatrix -import qualified Numeric.GSL.Statistics as GSL hiding (mean)------------------------------------------------------------------------------------- Local Parameters------------------------------------------------------------------------------------- The cutOffProbability is the value that determines the length of the --- probChord list (a sorted list with normalised distances to the chroma vector)-cutOffProbability      :: NumData-cutOffProbability      = 0.92---- The maximum number of probabilities-maxProbChordListLength :: Int-maxProbChordListLength = 6------------------------------------------------------------------------------------- Matching chords and chroma---------------------------------------------------------------------------------  --segmentByKey :: [TimedData Key] -> [TimedData [ProbChord]] -> [ProbChordSeg]-segmentByKey []       _    = error "segmentByKey: empty key list"-segmentByKey [k]      chds = [Segment (getData k) chds]-segmentByKey (k : ks) chds = let (seg,cs) = span ((<= (offset k)) . offset) chds-  in Segment (getData k) seg : segmentByKey ks cs-  -groupPChord :: [[ProbChord]] -> [[[ProbChord]]]-groupPChord = groupBy probChordEqPerm---- a function for comparing list of probchords, two list are considered equal--- if the chords and their order match, the probabilities are ignored-probChordEq :: [ProbChord] -> [ProbChord] -> Bool-probChordEq []     []     = True-probChordEq (p:pc) (q:qs) = chordLab p == chordLab q && probChordEq pc qs-probChordEq _      _      = False---- a function for comparing list of probchords, two list are considered equal--- if the chords and their order match, the probabilities are ignored-probChordEqPerm :: [ProbChord] -> [ProbChord] -> Bool-probChordEqPerm p q = eqPerm (map chordLab p) (map chordLab q)-eqPerm :: [ChordLabel] -> [ChordLabel] -> Bool-eqPerm []     _  = True-eqPerm (p:pc) qs = p `elem` qs && eqPerm pc qs--- eqPerm (p:pc) qs = ( qs == [noneLabel] || p `elem` qs ) && eqPerm pc qs---- syncronises the last beat and key frame to match the last chord frame-syncEndings ::  [ChordinoLine] -> [NumData] -> [ChordinoLine]-            -> ([ChordinoLine],   [NumData],   [ChordinoLine])-syncEndings chrm beat key = -  let endTime   = time $ last chrm-      key'      = takeWhile ((< endTime).time) key -      (l,[lst]) = splitAt ((length key') - 1) key'-  in (chrm, takeWhile (< endTime) beat ++ [endTime]-          , l                          ++ [lst{time = endTime}])---- creates "timed" blocks, given beat and beat aligned data --- (used by simpleannotator)-addTimeInfo :: BeatTrackerData -> [a] -> [TimedData a]-addTimeInfo offs blcks = zipWith3 TimedData blcks ons offs where-  ons = 0 : offs ------------------------------------------------------------------------------------- Matrix Functions for matching chords--------------------------------------------------------------------------------- --createChordRanks :: BeatChroma -> [[ProbChord]]-createChordRanks = -  map (selectTop . normalize .sortTake . matchCDictionary) -      . meanBeatSyncVectors where-    sortTake, normalize :: [ProbChord] -> [ProbChord]-    sortTake          = take maxProbChordListLength . sortBy (comparing prob)-    normalize l@(h:_) = map (\(ProbChord c p) -> (ProbChord c (prob h / p))) l -    normalize []      = []-    selectTop l -- selects the everything with a propability > x-      | null s       = none -      -- so far, this had not possitive effect-      -- | length s > maxProbChordListLength = none -      | otherwise    = s  -          where s    = takeWhile ((> cutOffProbability) . prob) l -                none = [ProbChord noneLabel 0.0]---- takes the mean of every "beat block" and these Vectors as one Matrix--- Each row of this matrix corresponds to the chroma within one beat-meanBeatSyncVectors :: BeatChroma -> [Vector NumData] -- [[ChordinoLine]]-meanBeatSyncVectors = map mean . beatSyncMatrix---- takes the median of every "beat block" and these Vectors as one Matrix--- Each row of this matrix corresponds to the chroma within one beat--- N.B. does not perform as well as meanBeatSyncVectors--- medianBeatSyncVectors :: BeatChroma -> [Vector NumData]--- medianBeatSyncVectors = -   -- map (fromList . map GSL.median . toColumns) . beatSyncMatrix- --- creates a list of matrices, in which each matrix corresponds to the--- collection of chroma vectors within one beat (drop the time stamp)-beatSyncMatrix :: BeatChroma -> [Matrix NumData]-beatSyncMatrix dat = map (dropColumns 1 . toChromaMatrix) dat---- converst a ChordinoData into a Matrix-toChromaMatrix :: ChordinoData -> Matrix NumData-toChromaMatrix = fromLists . map mergeLine where-  mergeLine :: ChordinoLine -> [NumData]-  mergeLine (ChordinoLine tm bs tb) = tm : bs ++ tb    -  --- matches all 12 tranposisition of a chord structure with a chorma vector  -matchCDictionary :: Vector NumData -> [ProbChord]-matchCDictionary v = map (matchStruct v) chordDictionary---- calculate an Euclidean (PNorm2) norm-matchStruct :: Vector NumData -> ChordCand -> ProbChord-matchStruct chroma cc@(ChordCand r sh _cs) = -  -- Chord root shorthand degrees description_str repetitions-  ProbChord (Chord r sh [] 0 1) (pnorm PNorm2 (chroma - bassTrebStruct cc))-            -- (correlation chroma (bassTrebStruct cc))---- creates a "matchable" 24D vector given a ChordStruct-bassTrebStruct :: ChordCand -> Vector NumData-bassTrebStruct (ChordCand _r _sh cs) = fromList (cs ++ cs)--- does not seem to improve performance-{-  | sh == None  = fromList (cs ++ cs)-  | otherwise   = fromList (bass ++ cs) where-    bass          = (f before) ++ 1 : (f $ tail after)-    (before,after)= splitAt (diaNatToSemi r) cs-    f             = map (* 0.5)--}                    ------------------------------------------------------------------------------------ The Chord Dictionary------------------------------------------------------------------------------------- the chord dictionary of all chords that are matched-chordDictionary :: [ChordCand]-chordDictionary = concatMap transpose12 [minBound..] -- all shorthands :-)-  --- takes a ChordStruct and returns a list containing the 12--- tranposed verions (including the original) of the ChordStruct  -transpose12 :: Shorthand -> [ChordCand]-transpose12 shand -  | null cstruct  = []-  | shand == None = [ChordCand (Note Nothing N) None cstruct]-  | otherwise     = transpose12' 11 shand cstruct where-      cstruct              = (shortHandToChordStruct shand)-      transpose12' :: Int -> Shorthand -> ChordStruct -> [ChordCand]-      transpose12' 0 sh cs = [ChordCand (head chromaPC) sh cs]-      transpose12' n sh cs = ChordCand (chromaPC!!(12-n)) sh (shift n cs) -                             : transpose12' (n-1) sh cs--shortHandToChordStruct :: Shorthand -> ChordStruct-shortHandToChordStruct sh = case sh of -  --        0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11-  --        C,  Db, D,  Eb, E,  F,  F#, G,  Ab, A,  Bb,  B-  Maj   -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,   0 ] -  Min   -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  0,  0,   0 ]-  -- HDim7 -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  1,   0 ]   -  -- Dim   -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,   0 ]   -  -- Maj6  -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  1,  0,   0 ] -  Sev   -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  1,   0 ] -  Dim7  -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,   0 ] -  -- Maj7  -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,   1 ] -  -- Min7  -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  0,  1,   0 ]-  -- Min6  -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  1,  0,   0 ]-  -- Sus4  -> [1,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,   0 ] -  -  None  -> [0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0 ] -  -- None -> [1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,   1 ] -  _     -> []-  -- none-  -- all-  -- ....-         ------------------------------------------------------------------------------------ Chroma key estimation----------------------------------------------------------------------------------         -beatSyncKeyStrenth :: BeatTrackerData -> ChordinoData -> [[NumData]]-beatSyncKeyStrenth bts key = -  map matchKeyProfiles . meanBeatSyncVectors {- . mergeBeats 8 -} $ beatSync bts key--nanToZero :: RealFloat a => a -> a-nanToZero n = if isNaN n then 0 else n---- printKeyStrength :: BeatTrackerData -> ChordinoData -> IO ()--- printKeyStrength bts chrm = -  -- let showLn :: [NumData] -> IO ()-      -- showLn x = do mapM_ (putStr . printf "%.2f ") x ; putStr "\n"-  -- in mapM_ showLn $ beatSyncKeyStrenth bts chrm-      -matchKeyProfiles :: Vector NumData -> [NumData]--- matchKeyProfiles chroma = map (\x -> pnorm PNorm2 (chroma - x)) allKeyProfiles-matchKeyProfiles crm = map (nanToZero . GSL.correlation crm) allKeyProfiles--allKeyProfiles :: [Vector NumData]-allKeyProfiles = map (fromList . keyToProfile) keyMap   -  -keyToProfile :: Key -> [NumData]          -keyToProfile (Key root m) = reverseShift (diaNatToSemi root) (selectProfile m) --selectProfile :: Mode -> [NumData]-selectProfile MajMode = krumhanslProfCMaj-selectProfile MinMode = krumhanslProfCMin          -          -krumhanslProfCMaj, krumhanslProfCMin :: [NumData]-krumhanslProfCMaj = -  [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]-krumhanslProfCMin = -  [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]-  ------------------------------------------------------------------------------------ key strength Matrix Computations----------------------------------------------------------------------------------                             -toBeatSyncKey :: BeatTrackerData -> KeyStrengthData -> [Key]-toBeatSyncKey bts key = -  medianFilter 4 . map ((!!) keyMap . maxIndex) . meanBeatSyncVectors -  $ beatSync bts key---- returns a (median based) key label given a Nx24 key strength matrix-findKeyIndex :: Matrix NumData -> Key-findKeyIndex = (!!) keyMap . median .  map maxIndex . toRows--toKeyMatrix :: KeyStrengthData -> Matrix NumData-toKeyMatrix = fromLists . map mergeKeyData where-  mergeKeyData :: ChordinoLine -> [NumData]-  mergeKeyData (ChordinoLine _ ma mi) = ma ++ mi---- findKeyLab :: Matrix NumData -> Key--- findKeyLab m = keyMap !! (findKeyIndex m)---- the chroma map uses a circle of fifths based ordering-keyMap :: [Key]-keyMap =  [ Key (Note (Just Sh) F) MajMode -- "F#" 6-          , Key (Note Nothing   B) MajMode -- "B"  11-          , Key (Note Nothing   E) MajMode -- "E"  4-          , Key (Note Nothing   A) MajMode -- "A"  9-          , Key (Note Nothing   D) MajMode -- "D"  2-          , Key (Note Nothing   G) MajMode -- "G"  7-          , Key (Note Nothing   C) MajMode -- "C"  0-          , Key (Note Nothing   F) MajMode -- "F"  5-          , Key (Note (Just Fl) B) MajMode -- "Bb" 10-          , Key (Note (Just Fl) E) MajMode -- "Eb" 3-          , Key (Note (Just Fl) A) MajMode -- "Ab" 8-          , Key (Note (Just Fl) D) MajMode -- "Db" 1-          , Key (Note (Just Fl) E) MinMode -- "Ebm" 3-          , Key (Note (Just Sh) G) MinMode -- "G#m" 8-          , Key (Note (Just Sh) C) MinMode -- "C#m" 1-          , Key (Note (Just Sh) F) MinMode -- "F#m" 6-          , Key (Note Nothing   B) MinMode -- "Bm"  11-          , Key (Note Nothing   E) MinMode -- "Em"  4-          , Key (Note Nothing   A) MinMode -- "Am"  9-          , Key (Note Nothing   D) MinMode -- "Dm"  2-          , Key (Note Nothing   G) MinMode -- "Gm"  7-          , Key (Note Nothing   C) MinMode -- "Cm"  0-          , Key (Note Nothing   F) MinMode -- "Fm"  5-          , Key (Note (Just Fl) B) MinMode -- "Bbm" 10-          ]------------------------------------------------------------------------------------- key annotation preparation------------------------------------------------------------------------------------- syncronises the last beat and key frame to match the last chord frame-syncWithAnnKey ::  [ChordinoLine] -> [NumData] -> [TimedData Key] -               -> ([NumData], [TimedData Key])-syncWithAnnKey chrm beat key = -  let endTime   = time $ last chrm -      beat'     = takeWhile (< endTime) beat ++ [endTime]-      none (Key r _) = r /= Note Nothing N -- filter the None keys-      noNoneKey = (filter (none . getData) key)-      -- reset key start timestamp to 0.0 and end timestampt to chorma end-      resetHead = (head noNoneKey) {onset = 0.0} : tail noNoneKey-      (l,[lst]) = splitAt ((length resetHead) - 1) resetHead-      -- key' = sampleAt beat' (l ++[lst{offset = endTime}])-  in  (beat',l ++[lst{offset = endTime}])-  ------------------------------------------------------------------------------------ general Matrix stuff------------------------------------------------------------------------------------- given a matrix, calculates the mean vector -mean :: (Product t, Fractional t) => Matrix t -> Vector t-mean a = constant (recip . fromIntegral . rows $ a) (rows a) <> a
src/HarmTrace/Audio/ChordTypes.hs view
@@ -1,102 +1,297 @@-{-# LANGUAGE TypeSynonymInstances             #-}-{-# LANGUAGE FlexibleInstances                #-}--module HarmTrace.Audio.ChordTypes where-             -import HarmTrace.Base.MusicRep-import Text.Printf (printf)-import Control.DeepSeq------------------------------------------------------------------------------------- High-level structure------------------------------------------------------------------------------------- the standard evaluation format of a chord annotation consists of a--- list with chords and segment boundaries-type ChordAnnotation = [ChordSegment]--type ChordSegment = TimedData ChordLabel--type KeyAnnotation = [KeySegment]- -type KeySegment = TimedData Key--type Block = TimedData [ProbChord]--data TimedData a = TimedData {getData :: a, onset :: NumData, offset :: NumData}---- clusering propchords in a collection of chords that share a key-data ProbChordSeg = Segment { segKey    :: Key -                            , segChords :: [TimedData [ProbChord]] }-  --- combining a chord with a probability-data ProbChord = ProbChord {chordLab :: ChordLabel, prob :: NumData}---- a chord candidate: an intermediate datatype that matches shorthand, --- chord structure and root note-data ChordCand = ChordCand Root Shorthand ChordStruct      -  -type ChordStruct = [NumData] ---- an iterable list of Roots-chromaPC ::[Root]  -chromaPC = [ Note Nothing   C-           , Note (Just Fl) D-           , Note Nothing   D-           , Note (Just Fl) E-           , Note Nothing   E-           , Note Nothing   F-           , Note (Just Sh) F-           , Note Nothing   G-           , Note (Just Fl) A-           , Note Nothing   A-           , Note (Just Fl) B-           , Note Nothing   B-           ]------------------------------------------------------------------------------------- NFData instances--------------------------------------------------------------------------------- ---- Simplified-instance NFData ChordSegment where-  rnf (TimedData a b c) = a `seq` rnf b `seq` rnf c------------------------------------------------------------------------------------- Instances of high-level datastructure--------------------------------------------------------------------------------- --instance Show (ProbChord) where -  show (ProbChord (Chord r sh _ _ _) p) = -    show r ++ ':' : show sh ++ ':' : printf "%.2f" p  --instance Show a => Show (TimedData a) where -  show (TimedData bk s l) = show bk ++ " (" ++ show s ++ ':' : show l ++ ")\n"--instance Show ProbChordSeg where-  show pc = concatMap (\x -> (show $ segKey pc) ++ ' ' : show x) (segChords pc)-                         ------------------------------------------------------------------------------------ numerical data representation-----------------------------------------------------------------------------------data AudioFeat = AudioFeat String ChordinoData BeatTrackerData KeyStrengthData -instance Show AudioFeat where -  show (AudioFeat idStr _ _ _) = idStr--type ChordinoData = [ChordinoLine]  -data ChordinoLine = ChordinoLine -  { time ::  NumData -  , bass :: [NumData]   -- each of the lists has always 12 elements -  , treb :: [NumData]   -- A, Bb, B, C, Db, D, Eb, E, F, F#, G, Ab -  } deriving (Eq, Show) -- and is shifted 3 positions to match C, Db, .., B-  -type KeyStrengthData = ChordinoData  --type BeatTrackerData = [NumData] -- deriving (Eq, Show)  --type NumData = Double--type BeatChroma = [[ChordinoLine]] -- one list per beat---- data TimeChroma = TimeChroma {stamp :: NumData, croma :: [NumData]}+{-# LANGUAGE TypeSynonymInstances             #-}
+{-# LANGUAGE FlexibleInstances                #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.ChordTypes
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: A set of types and classes for representing musical chords 
+-- recognised from an arbitrary audio source.
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.ChordTypes (
+  -- * The 'Timed' class
+    Timed (..)
+  
+  -- * Datatypes
+  -- ** Types for representing Chords and their probabilities
+  , ChordBeatAnnotation 
+  , ChordAnnotation 
+  , ProbChordSeg (..)
+  , ProbChord (..)
+  , ChordCand (..)
+  
+  -- ** Representing musical time
+  , TimedData (..)
+  , BeatTimedData (..)
+  -- , beatTimedData
+  , Beat (..)
+  , BeatBar (..)
+  , BeatBarTrackData 
+  , NumData 
+  
+  -- ** Representing raw audio data 
+  , AudioFeat (..)
+  , ChordinoData 
+  , ChordinoLine (..)
+  , KeyStrengthData 
+  , BeatTrackerData 
+  , BeatChroma 
+  , ChordStruct 
+
+  -- * Functions
+  -- ** Type conversion and other utilities
+  , getBeatTrack
+  , getBeat 
+  , setBeat 
+  , nextBeat
+  , prevBeat 
+  , dumpBeats
+  , dumpBeat
+  , timeStamp
+  , beat 
+
+  -- ** miscellaneous
+  , chromaPC
+)where
+             
+import HarmTrace.Base.MusicRep
+import Text.Printf (printf)
+import Control.DeepSeq
+
+--------------------------------------------------------------------------------
+-- High-level structure
+--------------------------------------------------------------------------------
+
+-- | Represents a chord transcription, similar to 'ChordAnnotation', but 
+-- 'ChordBeatAnnotation' also contains 'Beat' information.
+type ChordBeatAnnotation = [BeatTimedData ChordLabel]
+
+-- | A chord annotation consists of a
+-- list with chords and segment boundaries.
+type ChordAnnotation = [TimedData ChordLabel]
+
+data TimedData a = TimedData a NumData NumData
+
+-- | A datatype that wraps around an arbitrary datatype, adding (in this order)
+-- a 'Beat', an onset, and an offset.
+data BeatTimedData a = BeatTimedData a Beat NumData NumData
+
+-- | An alternative constructor for a BeatTimedData using two BeatBar datatypes
+-- instead of a 'Beat' and two 'NumData's.
+-- beatTimedData :: a -> BeatBar -> BeatBar -> BeatTimedData a
+-- beatTimedData a on off = let (onnum,onbt) = beatBar on 
+                         -- in  BeatTimedData a onbt onnum (fst $ beatBar off)
+
+-- | Clustering 'ProbChord's in a collection of chords that share a key
+data ProbChordSeg = Segment { segKey    :: Key 
+                            , segChords :: [BeatTimedData [ProbChord]] }
+  
+-- | Combines a 'ChordLabel' with a probability.
+data ProbChord = ProbChord {chordLab :: ChordLabel, prob :: NumData}
+
+-- | A chord candidate: an intermediate datatype that matches shorthand, 
+-- chord structure and root note (plus inversion)
+data ChordCand = ChordCand { originalRootCC   :: Root
+                           , inversionRootCC  :: Root
+                           , shorthardCC      :: Shorthand
+                           , chordStructCC    :: ChordStruct }
+  deriving Show
+  
+type ChordStruct = [NumData] 
+
+-- | For now, we fix the number of available beats to four, because this is also
+-- hard-coded into the bar and beat-tracker.
+data Beat = One | Two | Three | Four deriving (Eq, Enum)
+
+-- | An iterable list of Roots
+chromaPC ::[Root]  
+chromaPC = [ Note Nothing   C
+           , Note (Just Fl) D
+           , Note Nothing   D
+           , Note (Just Fl) E
+           , Note Nothing   E
+           , Note Nothing   F
+           , Note (Just Sh) F
+           , Note Nothing   G
+           , Note (Just Fl) A
+           , Note Nothing   A
+           , Note (Just Fl) B
+           , Note Nothing   B
+           ]
+
+-- | 'Timed' provides an interface for datatypes that add (musical) time 
+-- information to other datatypes. Hence, it allows for accessing the fields
+-- of 'TimedData' and 'BeatTimedData' via the same interface.
+class Timed t where
+  -- | Returns the contained datatype 
+  getData   :: t a -> a
+  -- | Returns the onset time stamp
+  onset     :: t a -> NumData
+  -- | Returns the offset time stamp
+  offset    :: t a -> NumData
+  -- | wraps a datatype in 't'
+  setData   :: t a -> b       -> t b
+  -- | Sets the onset time stamp
+  setOnset  :: t a -> NumData -> t a
+  -- | Sets the offset time stamp
+  setOffset :: t a -> NumData -> t a
+           
+instance Timed TimedData where
+  getData   (TimedData d _  _  ) = d
+  onset     (TimedData _ on _  ) = on
+  offset    (TimedData _ _  off) = off
+  setData   (TimedData _ on off) d   = TimedData d on off
+  setOnset  (TimedData d _  off) on  = TimedData d on off
+  setOffset (TimedData d on _  ) off = TimedData d on off
+  
+instance Timed BeatTimedData where
+  getData   (BeatTimedData d _ _  _  ) = d
+  onset     (BeatTimedData _ _ on _  ) = on
+  offset    (BeatTimedData _ _ _  off) = off
+  setData   (BeatTimedData _ b on off) d   = BeatTimedData d b on off
+  setOnset  (BeatTimedData d b _  off) on  = BeatTimedData d b on off
+  setOffset (BeatTimedData d b on _  ) off = BeatTimedData d b on off
+
+--------------------------------------------------------------------------------
+-- NFData instances
+-------------------------------------------------------------------------------- 
+
+-- Simplified
+instance NFData (TimedData ChordLabel) where
+  rnf (TimedData a b c) = a `seq` rnf b `seq` rnf c
+
+instance NFData Beat where  
+  rnf One   = ()
+  rnf Two   = ()
+  rnf Three = ()
+  rnf Four  = ()
+
+--------------------------------------------------------------------------------
+-- Instances of high-level data structure
+-------------------------------------------------------------------------------- 
+
+instance Eq (ProbChord) where
+  a == b = chordLab a == chordLab b
+
+-- TODO remove line-endings from show instances
+
+instance Show (ProbChord) where 
+  show (ProbChord (Chord r sh _ _ _) p) = 
+    show r ++ ':' : show sh ++ ':' : printf "%.2f" p  
+
+instance Show a => Show (TimedData a) where 
+  show (TimedData bk s l) = show bk ++ " (" ++ show s ++ ':' : show l ++ ")\n"
+
+instance Show ProbChordSeg where
+  show pc = concatMap (\x -> show (segKey pc) ++ ' ' : show x) (segChords pc)
+
+instance Show Beat where
+  show One   = "1"
+  show Two   = "2"
+  show Three = "3"
+  show Four  = "4"
+
+instance Show BeatBar where
+  show = show . beatBar
+  
+instance Show a => Show (BeatTimedData a) where
+  show (BeatTimedData dat bt on off) = 
+    show bt ++ ';' : show dat ++ ';' : show on ++ ';' : show off ++ "\n"
+    
+--------------------------------------------------------------------------------
+-- numerical data representation
+--------------------------------------------------------------------------------
+
+-- | Groups the three types of VAMP plug-in data: 'ChordinoData', 
+-- 'BeatBarTrackData', and 'KeyStrengthData'. See for more information:
+--
+-- * <http://www.vamp-plugins.org>
+--
+-- * <http://isophonics.net/nnls-chroma>
+--
+-- * <http://omras2.org/SonicAnnotator>
+data AudioFeat = AudioFeat { getChroma :: ChordinoData 
+                           , getBeats  :: BeatBarTrackData
+                           , getKeys   :: KeyStrengthData }
+
+type ChordinoData = [ChordinoLine]  
+
+-- | Represents two chroma features and a time stamp. 
+data ChordinoLine = ChordinoLine 
+  { 
+  -- | Returns the time stamp of the chroma features
+  time ::  NumData 
+  -- | Returns the bass chroma feature
+  , bass :: [NumData]   -- each of the lists has always 12 elements 
+  -- | Returns the treble chroma feature
+  , treb :: [NumData]   -- A, Bb, B, C, Db, D, Eb, E, F, F#, G, Ab 
+  } deriving (Eq, Show) -- and is shifted 3 positions to match C, Db, .., B
+  
+type KeyStrengthData = ChordinoData  
+
+type BeatTrackerData = [NumData]
+
+newtype BeatBar = BeatBar {beatBar :: (NumData, Beat)} deriving Eq
+
+type BeatBarTrackData = [BeatBar]
+
+-- | A type synonym is defined for our main numerical representation, this 
+-- allows us to easily change the precision.
+type NumData = Double
+
+type BeatChroma = [[ChordinoLine]] -- one list per beat
+
+-- we compare based on the timestamp only
+instance Ord BeatBar where
+  compare (BeatBar (b1,_)) (BeatBar (b2,_)) = compare b1 b2
+
+--------------------------------------------------------------------------------
+-- Some type conversion utilities
+--------------------------------------------------------------------------------
+
+-- | Converts  'BeatBarTrackData' into 'BeatTrackerData'
+getBeatTrack :: BeatBarTrackData -> BeatTrackerData
+getBeatTrack = map (fst . beatBar)
+
+-- | Provides access to the 'Beat' field of a 'BeatTimedData'. The other fields
+-- should be accessed by the methods of the 'Timed' class.
+getBeat :: BeatTimedData a -> Beat
+getBeat (BeatTimedData _ b _ _) = b
+
+-- | Adds 'Beat' information to a 'Timed' datatype
+setBeat :: Timed t => t a -> Beat -> BeatTimedData a
+setBeat tdat bt = BeatTimedData (getData tdat) bt (onset tdat) (offset tdat)
+
+nextBeat, prevBeat :: Beat -> Beat 
+-- | returns the next beat, e.g. @ nextBeat Two = Three @. 
+-- Following the (current) definition of 'Beat', we still assume 4/4, in the 
+-- future this function should also have the meter as an argument.
+nextBeat Four = One
+nextBeat b    = succ b
+
+-- | returns the previous 'Beat', similar to 'prevBeat'.
+prevBeat One  = Four
+prevBeat b    = pred b
+
+-- | Converts a list of 'BeatTimedData's into a list of 'TimedData's
+dumpBeats :: [BeatTimedData a] -> [TimedData a]
+dumpBeats = map dumpBeat
+
+-- | Converts a 'BeatTimedData' into a 'TimedData'
+dumpBeat :: BeatTimedData a -> TimedData a
+dumpBeat (BeatTimedData dat _bt on off) = TimedData dat on off
+
+-- | Returns the time stamp of a 'BeatBar'
+timeStamp :: BeatBar -> NumData
+timeStamp = fst . beatBar
+
+-- | Returns the 'Beat' of a 'BeatBar'
+beat :: BeatBar -> Beat
+beat = snd . beatBar
+ src/HarmTrace/Audio/ChromaChord.hs view
@@ -0,0 +1,268 @@+{-# OPTIONS_GHC -Wall         #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.ChromaChord
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Recognise audio chroma vectors into textual chord descriptions.
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.ChromaChord ( createChordRanks, beatSync
+                                   , mergeByBeat, addBeatTimeStamp
+                                   , mergeAndTimeStamp, meanBeatSyncVectors
+                                   , module Numeric.LinearAlgebra
+                                   , module Numeric.GSL.Statistics 
+                                   ) where
+
+import Constants (maxProbChordListLength, cutOffProbability)
+                                   
+import HarmTrace.Audio.DataParser (shift)
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Base.MusicRep
+
+-- import Text.Printf (printf)
+import Data.List (sortBy,find) -- , elemIndices, minimumBy)
+import Data.Maybe (isJust, fromJust, mapMaybe)
+import Data.Ord (comparing)
+-- import Data.Function (on)
+
+-- http://hackage.haskell.org/package/hmatrix
+import Numeric.LinearAlgebra hiding (find)
+-- import qualified Numeric.GSL.Statistics as GSL hiding (mean)
+import Numeric.GSL.Statistics  hiding (mean)
+
+--------------------------------------------------------------------------------
+-- Matching chords and chroma
+--------------------------------------------------------------------------------  
+
+-- | Wraps Chord candidate lists into a 'BeatTimedData' structure
+addBeatTimeStamp :: [BeatBar] -> [[a]] -> [BeatTimedData [a]]
+addBeatTimeStamp = beatTime (BeatBar (0,Four)) where
+
+  beatTime :: BeatBar ->  [BeatBar] -> [[a]] -> [BeatTimedData [a]]
+  beatTime _  [] [] = []
+  beatTime (BeatBar (on, onbt)) (next@(BeatBar (off, _)) : bs) (x : xs) =
+    BeatTimedData x onbt on off : beatTime next bs xs
+  beatTime _  _  _  = error "addBeatTimeStamp:: asynchronous beats and data"
+
+-- | Given a list of beats, a list of grouped data items, and a merging function
+-- 'mergeAndTimeStamp' returns a list of 'BeatTimedData'. Before wrapping the
+-- the grouped data items, e.g. chord candidates, the list is reduced by the 
+-- provided merging function.
+mergeAndTimeStamp  ::([a] -> a)-> [BeatBar] -> [[a]] -> [BeatTimedData a]
+mergeAndTimeStamp f = merge (BeatBar (0,Four)) where
+  -- merge :: BeatBar -> [BeatBar] -> [[a]] -> [BeatTimedData a]
+  merge  _                   []    []       = []
+  merge (BeatBar (on, bt)) beats (x : xs) = 
+    let (off : rest) = drop (length x -1) beats
+    in  BeatTimedData (f x) bt on (timeStamp off) : merge off rest xs
+  merge _ _ _ = error "mergeAndTimeStamp: asynchronous beats and data"
+
+-- | Merges chord segments, adding a bias toward merging at the first
+-- and the third 'Beat' (specified by 'canMerge').
+mergeByBeat :: [BeatTimedData [ProbChord]] -> [BeatTimedData [ProbChord]]
+mergeByBeat [] = []
+mergeByBeat [a] = [a]
+mergeByBeat (x:y:xs) 
+  | canMerge (getBeat x) (getBeat y) && isJust xy = mergeByBeat (fromJust xy:xs)
+  | otherwise                                     = x : mergeByBeat (y:xs) where
+      xy = merge x y  
+      -- merges two Timed 'ProbChord's using intersectPC (currently in Utils.hs)
+      merge :: BeatTimedData [ProbChord] -> BeatTimedData [ProbChord] 
+            -> Maybe (BeatTimedData [ProbChord])
+      merge a b 
+        | not $ null m = Just (BeatTimedData m (getBeat a) (onset a) (offset b))
+        | otherwise    = Nothing
+          where m = intersectPC (getData a) (getData b)
+      -- specifies which combinations of beats are allowed to merge
+      canMerge :: Beat -> Beat -> Bool
+      canMerge One   _ = True
+      canMerge Three _ = True
+      canMerge _     _ = False
+
+
+intersectPC :: [ProbChord] -> [ProbChord] -> [ProbChord]
+intersectPC a b = let (a',b') = order a b 
+                  in mapMaybe (findAndMergePC a') b' where
+  -- N.B. the probabilities are not divided by their length so > 1,
+  -- due to the addition
+  findAndMergePC :: [ProbChord] -> ProbChord -> Maybe ProbChord
+  findAndMergePC pcs pc = case find (== pc) pcs of
+    (Just pc') -> Just (ProbChord (chordLab pc) (prob pc + prob pc'))  
+    Nothing    -> Nothing
+
+  -- takes two lists and returns a tuple where the first element is the smallest
+  -- and the second element is the largest of the two lists
+  order  :: [a] -> [a] -> ([a],[a])
+  order x y
+    | length x <= length y = (x,y)
+    | otherwise            = (y,x)
+
+-- | Synchronises the 'ChordinoData' with the list of beats 
+-- by grouping the 'ChordinoLines' of the 'ChordinoData' in separate lists.
+beatSync :: BeatBarTrackData -> ChordinoData -> BeatChroma
+beatSync _   []     = []
+beatSync bt (cd:cs) = beatSync' (getBeatTrack bt) [cd] (cd:cs) where
+  beatSync' :: [NumData] -> [ChordinoLine] -> [ChordinoLine] -> [[ChordinoLine]]
+  beatSync' _  _   [] = []
+  beatSync' [] _   c  = [c]
+  beatSync' (b:bs) prv c -- we also store the previous group in case beat < time
+    | null x    = prv : beatSync' bs prv xs 
+    | otherwise = x   : beatSync' bs x   xs 
+        where (x, xs) = span ((>=) b . time) c
+
+--------------------------------------------------------------------------------
+-- Matrix Functions for matching chords
+-------------------------------------------------------------------------------- 
+
+-- | Having a matrix of beat-synchronised bass and treble chromagrams and a 
+-- chord dictionary, the probability of a chord sounding at a particular beat is
+-- estimated by calculating the Euclidean distance between the chord structures 
+-- and the chroma feature. These distances are calculated for every chord 
+-- candidate at every beat. Next, we sort the chord candidates by descending 
+-- Euclidean distance. To obtain a relative measure of the fit 
+-- between a chord candidate and the chroma vector in the range [0,1],
+-- the distances are normalised by dividing them by distance of the best 
+-- matching chord candidate. 
+createChordRanks :: BeatChroma -> [[ProbChord]]
+createChordRanks = 
+  map (selectTop . normalize . sortTake . matchCDictionary). meanBeatSyncVectors 
+    where
+    sortTake, normalize :: [ProbChord] -> [ProbChord]
+    sortTake          = take maxProbChordListLength . sortBy (comparing prob)
+
+    normalize l@(h:_) = let ph = prob h in map (\p -> p{prob = ph / prob p }) l 
+    normalize []      = []
+    
+    selectTop l -- selects the everything with a probability > x
+      | null s       = none 
+      -- so far, this had not positive effect
+      --   length s > maxProbChordListLength = none 
+      | otherwise    = s
+          where s    = takeWhile ((> cutOffProbability) . prob) l 
+                none = [ProbChord noneLabel 0.0]
+
+-- takes the mean of every "beat block" and these Vectors as one Matrix
+-- Each row of this matrix corresponds to the chroma within one beat
+meanBeatSyncVectors :: BeatChroma -> [Vector NumData] -- [[ChordinoLine]]
+meanBeatSyncVectors = map mean . beatSyncMatrix
+
+-- takes the median of every "beat block" and these Vectors as one Matrix
+-- Each row of this matrix corresponds to the chroma within one beat
+-- N.B. does not perform as well as meanBeatSyncVectors
+-- medianBeatSyncVectors :: BeatChroma -> [Vector NumData]
+-- medianBeatSyncVectors = 
+   -- map (fromList . map GSL.median . toColumns) . beatSyncMatrix
+ 
+-- creates a list of matrices, in which each matrix corresponds to the
+-- collection of chroma vectors within one beat (drop the time stamp)
+beatSyncMatrix :: BeatChroma -> [Matrix NumData]
+beatSyncMatrix = map (dropColumns 1 . toChromaMatrix)
+
+-- converts a ChordinoData into a Matrix
+toChromaMatrix :: ChordinoData -> Matrix NumData
+toChromaMatrix = fromLists . map mergeLine where
+  mergeLine :: ChordinoLine -> [NumData]
+  mergeLine (ChordinoLine tm bs tb) = tm : bs ++ tb    
+  
+-- matches all transpositions of a chord structure with a chroma vector
+matchCDictionary :: Vector NumData -> [ProbChord]
+matchCDictionary v = map (matchStruct v) chordDictionary
+
+-- calculate an Euclidean (PNorm2) norm 
+-- I also tried using the maximum norm, but this gave inferior results
+matchStruct :: Vector NumData -> ChordCand -> ProbChord
+matchStruct chroma (ChordCand r _ir None cs) = 
+  ProbChord (Chord r None [] 0 1) (pnorm PNorm2 (chroma - fromList (cs ++ cs)))
+matchStruct chroma (ChordCand r _ir sh cs) = 
+  -- Chord root shorthand degrees description_str repetitions
+  ProbChord (Chord r sh [] 0 1) (sqrt (bss * bss + treble * treble))
+    where treble = pnorm PNorm2 (subVector 12 12 chroma - fromList cs)
+          -- (_ir,bss) = minimumBy (compare `on` snd) . map matchInv $ rootInvs cs   
+          (_ir,bss) = matchInv r   
+
+          -- calculates the euclidean distance between the bass chromagram
+          -- and all bass note inversions.
+          matchInv :: Root -> (Root, NumData)
+          matchInv ir = (ir, pnorm PNorm2 (subVector 0 12 chroma - bcs)) where
+            bcs         = fromList (pre ++ [1] ++ tail post)
+            (pre, post) = splitAt (toSemitone ir) (shortHandToCS None)
+
+          -- For a given chord structure, compute all possible 
+          -- roots for inversions
+          -- rootInvs :: ChordStruct -> [Root]
+          -- rootInvs cs = map iThRoot (elemIndices 1 cs)
+          
+--------------------------------------------------------------------------------
+-- The Chord Dictionary
+--------------------------------------------------------------------------------
+
+-- the chord dictionary of all chords that are matched
+chordDictionary :: [ChordCand]
+chordDictionary = concatMap transpose12 [minBound..] -- all shorthands :-)
+  
+-- takes a ChordStruct and returns a list containing the 12 transposed versions
+-- (including the original) of the ChordStruct, times the number of possible
+-- inversions (3 or 4, depending on the number of notes in the shorthand).
+transpose12 :: Shorthand -> [ChordCand]
+transpose12 sh 
+  | null cstruct = []
+  | sh == None   = [ChordCand (Note Nothing N) (Note Nothing N) None cstruct]
+  | otherwise    = concatMap mkChordCands [0..11]
+
+  where
+      cstruct :: ChordStruct
+      cstruct = shortHandToCS sh
+
+      mkChordCands :: Int -> [ChordCand]
+      mkChordCands n = [ ChordCand r r sh shiftedCS]
+                       where shiftedCS = shift (12-n) cstruct
+                             r         = iThRoot n
+      
+-- Get the i-th chord root, for 0 <= i < 12
+-- For i >= 12, the result is given modulo 12
+-- JPM: I actually think this is not necessary because we know that
+-- 0 <= i < 12, but it makes things safer anyway
+iThRoot :: Int -> Root
+iThRoot n | n < 0            = error "iThRoot: negative index"
+          | n >= 0 && n < 12 = chromaPC !! n
+          | otherwise        = iThRoot (n `mod` 12)
+
+shortHandToCS :: Shorthand -> ChordStruct
+shortHandToCS sh = case sh of 
+  --        0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11
+  --        C,  Db, D,  Eb, E,  F,  F#, G,  Ab, A,  Bb,  B
+  Maj   -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,   0 ] 
+  Min   -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  0,  0,   0 ]
+  -- Dim   -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,   0 ]   
+  -- HDim7 -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  1,   0 ]   
+  -- Dim   -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,   0 ]   
+  -- Maj6  -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  1,  0,   0 ] 
+  Sev   -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  1,   0 ] 
+  -- Dim7  -> [1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,   0 ] 
+  -- Maj7  -> [1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,   1 ] 
+  -- Min7  -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  0,  1,   0 ]
+  -- Min6  -> [1,  0,  0,  1,  0,  0,  0,  1,  0,  1,  0,   0 ]
+  -- Sus4  -> [1,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,   0 ] 
+  
+  None  -> [0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0 ] 
+  -- None -> [1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,   1 ] 
+  _     -> []
+  -- none
+  -- all
+  -- ....
+
+--------------------------------------------------------------------------------
+-- general Matrix stuff
+--------------------------------------------------------------------------------
+
+-- given a matrix, calculates the mean vector 
+mean :: (Product t, Fractional t) => Matrix t -> Vector t
+mean a = constant (recip . fromIntegral . rows $ a) (rows a) <> a
+         
+ src/HarmTrace/Audio/ChromaKey.hs view
@@ -0,0 +1,89 @@+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.ChromaKey 
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Low-processing of chroma features for key-finding
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.ChromaKey ( beatSyncKeyStrength, keyMap ) where
+
+import HarmTrace.Audio.ChromaChord ( Vector, fromList, correlation
+                                   , beatSync, meanBeatSyncVectors )
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Base.MusicRep
+
+
+--------------------------------------------------------------------------------
+-- Chroma key estimation
+--------------------------------------------------------------------------------
+
+-- | Calculates the beat synchronised key strenght for all 
+-- 24 keys (ordered by 'KeyMap').
+beatSyncKeyStrength :: BeatBarTrackData -> ChordinoData -> [[NumData]]
+beatSyncKeyStrength bts key = 
+  map matchKeyProfiles . meanBeatSyncVectors $ beatSync bts key
+
+nanToZero :: RealFloat a => a -> a
+nanToZero n = if isNaN n then 0 else n
+      
+matchKeyProfiles :: Vector NumData -> [NumData]
+-- matchKeyProfiles chroma = map (\x -> pnorm PNorm2 (chroma - x)) allKeyProfiles
+-- matchKeyProfiles crm = map (nanToZero . GSL.correlation crm) allKeyProfiles
+matchKeyProfiles crm = map (nanToZero . correlation crm) allKeyProfiles
+
+allKeyProfiles :: [Vector NumData]
+allKeyProfiles = map (fromList . keyToProfile) keyMap   
+  
+keyToProfile :: Key -> [NumData]          
+keyToProfile (Key root m) = reverseShift (toSemitone root) (selectProfile m) 
+  where reverseShift :: Int -> [a] -> [a]
+        reverseShift p l = b ++ a where (a,b) = splitAt (length l - p) l     
+
+selectProfile :: Mode -> [NumData]
+selectProfile MajMode = krumhanslProfCMaj
+selectProfile MinMode = krumhanslProfCMin          
+          
+krumhanslProfCMaj, krumhanslProfCMin :: [NumData]
+krumhanslProfCMaj = 
+  [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]
+krumhanslProfCMin = 
+  [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]
+  
+--------------------------------------------------------------------------------
+-- key strength Matrix Computations
+--------------------------------------------------------------------------------
+
+-- | A key chroma map using a circle of fifths based ordering.
+keyMap :: [Key]
+keyMap =  [ Key (Note (Just Sh) F) MajMode -- "F#" 6
+          , Key (Note Nothing   B) MajMode -- "B"  11
+          , Key (Note Nothing   E) MajMode -- "E"  4
+          , Key (Note Nothing   A) MajMode -- "A"  9
+          , Key (Note Nothing   D) MajMode -- "D"  2
+          , Key (Note Nothing   G) MajMode -- "G"  7
+          , Key (Note Nothing   C) MajMode -- "C"  0
+          , Key (Note Nothing   F) MajMode -- "F"  5
+          , Key (Note (Just Fl) B) MajMode -- "Bb" 10
+          , Key (Note (Just Fl) E) MajMode -- "Eb" 3
+          , Key (Note (Just Fl) A) MajMode -- "Ab" 8
+          , Key (Note (Just Fl) D) MajMode -- "Db" 1
+          , Key (Note (Just Fl) E) MinMode -- "Ebm" 3
+          , Key (Note (Just Sh) G) MinMode -- "G#m" 8
+          , Key (Note (Just Sh) C) MinMode -- "C#m" 1
+          , Key (Note (Just Sh) F) MinMode -- "F#m" 6
+          , Key (Note Nothing   B) MinMode -- "Bm"  11
+          , Key (Note Nothing   E) MinMode -- "Em"  4
+          , Key (Note Nothing   A) MinMode -- "Am"  9
+          , Key (Note Nothing   D) MinMode -- "Dm"  2
+          , Key (Note Nothing   G) MinMode -- "Gm"  7
+          , Key (Note Nothing   C) MinMode -- "Cm"  0
+          , Key (Note Nothing   F) MinMode -- "Fm"  5
+          , Key (Note (Just Fl) B) MinMode -- "Bbm" 10
+          ]
+ src/HarmTrace/Audio/DataParser.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE FlexibleContexts #-}
+{-# OPTIONS_GHC -Wall         #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.DataParser
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Basic parsers for parsing VAMP csv files.
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.DataParser (
+  -- * Parsing beat data
+    parseBeatData
+  , parseBeatBarData
+  , pBeat
+  -- * Parsing chromagram data
+  , parseChordinoData
+  , parseChromaData
+  -- * Basic parsers
+  , pNumData
+  , pComma
+  , pParentheticalString
+  , pQuotedString
+  , pLabel
+  -- * Utilities
+  , shift
+) where
+
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Base.Parsing hiding (pComma,pQuotedString,pParentheticalString)
+
+--------------------------------------------------------------------------------
+-- data parsers
+--------------------------------------------------------------------------------
+
+-- | Parsing beat time stamps.
+parseBeatData :: Parser BeatTrackerData
+parseBeatData = pListSep_ng pLineEnd pLine <* pLineEnd where
+  pLine = opt pLabel "" *> pNumData <* opt (pComma *> pQuotedString) ""
+
+-- | Parses 'BeatBar' data.
+parseBeatBarData :: Parser BeatBarTrackData
+parseBeatBarData = pListSep_ng pLineEnd pLine <* pLineEnd where
+  pLine = curry BeatBar <$> (opt pLabel "" *> pNumData ) 
+                        <*> (pComma *> pBeat)
+-- | Parses a 'Beat'.
+pBeat :: Parser Beat
+pBeat = toBeat <$> pQuotedString where
+
+  toBeat :: String -> Beat
+  toBeat "1" = One
+  toBeat "2" = Two
+  toBeat "3" = Three
+  toBeat "4" = Four
+  toBeat b   = error ("HarmTrace.Audio.Parser.toBeat: unknown beat " ++ b)
+
+-- | Chroma parsing.
+parseChordinoData :: Parser ChordinoData
+parseChordinoData =  pListSep_ng pLineEnd pChordinoLine <* pLineEnd where
+  pChordinoLine = const convert <$> opt pLabel ""
+                                <*> pList1Sep (pSym ',') pNumData 
+  convert :: [NumData] -> ChordinoLine -- shift the chorma to match C .. B
+  convert l | length l == 25 = ChordinoLine h (shift 3 a) (shift 3 b)
+            | otherwise = error ("parseChordinoData: Wrong list length of " 
+                                 ++ show (length l))
+            where (h:t) = l
+                  (a,b) = splitAt 12 t
+
+-- rotates the elements in the list with n positions
+shift :: Int -> [a] -> [a]
+shift p l = b ++ a where (a,b) = splitAt p l           
+
+-- Parsing 12 dimentional chroma vectors for key-finding.
+parseChromaData :: Parser [ChordinoLine]
+parseChromaData =  pListSep_ng pLineEnd pCrmLine <* pLineEnd where
+  pCrmLine = convert <$> (opt pLabel "" *> pList1Sep (pSym ',') pNumData)
+  -- This is a bit of a hack, but I do not want to rewrite all the functions 
+  -- again for a very similar data type that only has one 12-dim chroma vector
+  convert :: [NumData] -> ChordinoLine   
+  convert l | length t == 12 = ChordinoLine h (shift 3 t) [] -- hence we make this []
+            | otherwise = error ("parseChromaData: Wrong list length of " 
+                                 ++ show (length l))
+            where (h:t) = l
+                  
+                  
+--------------------------------------------------------------------------------
+-- Basic parsers
+--------------------------------------------------------------------------------
+                  
+pNumData :: Parser NumData
+pNumData = pDoubleRaw
+
+pComma :: Parser Char
+pComma = pSym ','
+
+pParentheticalString :: Char -> Parser String
+pParentheticalString d = pSym d *> pList pNonQuoteVChar <* pSym d where
+  pNonQuoteVChar = pSatisfy (\c -> visibleChar c && c /= d) 
+                   (Insertion ("Character in a string set off from main text" ++
+                    "by delimiter, e.g. double-quotes or comment token") 'y' 5)
+  visibleChar c = '\032' <= c && c <= '\126'
+
+pQuotedString :: Parser String
+pQuotedString = pParentheticalString '"'
+
+pLabel :: Parser String
+pLabel = (pQuotedString `opt` "") <* pComma
src/HarmTrace/Audio/Evaluation.hs view
@@ -1,98 +1,118 @@-module HarmTrace.Audio.Evaluation where--import HarmTrace.Audio.ChordTypes-import HarmTrace.Audio.Utils -import HarmTrace.Audio.Key(getBeatSyncKeyFromChroma)-import HarmTrace.Audio.BeatChroma ( createChordRanks, groupPChord, syncEndings)-import HarmTrace.Base.MusicRep --import Data.List (genericLength, zipWith5)-import Text.Printf(printf)-import System.IO (stdout,hFlush)------------------------------------------------------------------------------------- Evaluation Parameters------------------------------------------------------------------------------------- this functions determines when two chords are considered the same-eqFunc :: ChordLabel -> ChordLabel -> Bool-eqFunc = chordTriadEq--sampleRate, displaySampleRate :: NumData--- the sample rate used in a normal (non-visual) comparison (in seconds)-sampleRate        = 0.01--- the sample rate used when visually comparing a chord annotation with a --- ground-truth annotation. Often a higher sample rate is prefered. Although--- one uses precision, the visual result is easier to read-displaySampleRate = 0.3------------------------------------------------------------------------------------- Evaluation function-----------------------------------------------------------------------------------chordClassEq :: ChordLabel -> ChordLabel -> Bool-chordClassEq (Chord (Note Nothing N) None _ _ _)-             (Chord (Note Nothing N) None _ _ _)       = True-chordClassEq (Chord (Note Nothing N) None _ _ _) _     = False-chordClassEq _     (Chord (Note Nothing N) None _ _ _) = False-chordClassEq (Chord r1 sh1 _ _ _) (Chord r2 sh2 _ _ _) =-  (diaNatToSemi r1) == (diaNatToSemi r2) && (toClassType sh1 == toClassType sh2)--chordTriadEq :: ChordLabel -> ChordLabel -> Bool-chordTriadEq (Chord (Note Nothing N) None _ _ _)-             (Chord (Note Nothing N) None _ _ _)       = True-chordTriadEq (Chord (Note Nothing N) None _ _ _) _     = False-chordTriadEq _     (Chord (Note Nothing N) None _ _ _) = False-chordTriadEq (Chord r1 sh1 _ _ _) (Chord r2 sh2 _ _ _) =-  (diaNatToSemi r1) == (diaNatToSemi r2) && (toMode sh1 == toMode sh2)---- calculates the relative correct overlap, which is the recall--- of matching frames, and defined as the nr of matching frames (sampled at--- an 10 milisecond interval) divided by all frames-relCorrectOverlap :: ChordAnnotation -> ChordAnnotation -> Double-relCorrectOverlap a b =-    (foldl countMatch 0 (zipWith eqFunc sama samb)) / tot where-    sama = sample a-    samb = sample b-    tot  = max (genericLength sama) (genericLength samb)---- does the same thing as relCorrectOverlap, but it also prints the--- chords and uses a lower sample rate-printRelCorrectOverlap :: (AudioFeat -> ChordAnnotation) -> AudioFeat-                       -> ChordAnnotation -> IO Double-printRelCorrectOverlap annotator af@(AudioFeat _idStr chrm' beat' key') gt = do-  let (chrm, beat, key) = syncEndings chrm' beat' key'-      keys  = mergeAndTimeStamp head  beat $ getBeatSyncKeyFromChroma beat key   -      blks  = mergeAndTimeStamp avgPC beat . groupPChord-            . createChordRanks $ beatSync beat chrm-      -- sample the info for printing and evaluation-      samaf = sampleWith displaySampleRate (annotator af)-      samgt = sampleWith displaySampleRate gt-      sambk = sampleWith displaySampleRate blks-      samk  = sampleWith displaySampleRate keys--      tot   = max (genericLength samaf) (genericLength samgt)-      showEq m = if m then "==" else "/=" -      printEval :: NumData -> ChordLabel -> ChordLabel -> Key -> [ProbChord] -                -> IO Bool-      printEval t g a b c = -         do putStrLn (printf "%.2f" t ++ '\t' : showEq equal ++ '\t' : show g-                      ++ '\t' : show a ++ '\t' : show b ++ '\t' : show c)-                      >> hFlush stdout-            return equal where equal = g `eqFunc` a-  putStrLn "time\tmatch\tGT\t\tMPTREE\tkey\toptional chords"-  m <- sequence (zipWith5 printEval [0.0,displaySampleRate ..] -                                    samgt samaf samk sambk)-  return ((foldl countMatch 0 m) / tot)--countMatch :: Double -> Bool -> Double -countMatch x y | y         = succ x -- count the number of matching frames-               | otherwise = x---- given a chord annotation sample the chord label at every 10 ms-sample :: [TimedData a] -> [a]-sample = sampleWith sampleRate---- like sample, but takes a sample rate (seconds :: Float) as argument-sampleWith :: NumData -> [TimedData a] -> [a]-sampleWith rate =  sampleAt [0.00, rate .. ] +--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.Evaluation
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Basic parsers for parsing VAMP csv files.
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.Evaluation (
+    relCorrectOverlap, printRelCorrectOverlap
+  ) where
+
+import Constants
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Audio.Annotate (preProcessData, preProcessKeyData)
+import HarmTrace.Base.MusicRep 
+
+import Data.List (genericLength, zipWith5)
+import Text.Printf(printf)
+import System.IO (stdout,hFlush)
+
+-- TODO this is a parameter and should some how be integrated into Constants.hs
+-- this functions determines when two chords are considered the same
+eqFunc :: ChordLabel -> ChordLabel -> Bool
+eqFunc = chordTriadEq
+
+--------------------------------------------------------------------------------
+-- Evaluation functions
+--------------------------------------------------------------------------------
+
+-- chordClassEq :: ChordLabel -> ChordLabel -> Bool
+-- chordClassEq (Chord (Note Nothing N) None _ _ _)
+             -- (Chord (Note Nothing N) None _ _ _)       = True
+-- chordClassEq (Chord (Note Nothing N) None _ _ _) _     = False
+-- chordClassEq _     (Chord (Note Nothing N) None _ _ _) = False
+-- chordClassEq (Chord r1 sh1 _ _ _) (Chord r2 sh2 _ _ _) =
+  -- (toSemitone r1) == (toSemitone r2) && (toClassType sh1 == toClassType sh2)
+
+chordTriadEq :: ChordLabel -> ChordLabel -> Bool
+chordTriadEq (Chord (Note Nothing N) None _ _ _)
+             (Chord (Note Nothing N) None _ _ _)       = True
+chordTriadEq (Chord (Note Nothing N) None _ _ _) _     = False
+chordTriadEq _     (Chord (Note Nothing N) None _ _ _) = False
+chordTriadEq (Chord r1 sh1 _ _ _) (Chord r2 sh2 _ _ _) =
+  toSemitone r1 == toSemitone r2 && toMode sh1 == toMode sh2
+
+-- | Calculates the relative correct overlap, which is the recall
+-- of matching frames, and defined as the nr of matching frames (sampled at
+-- an 10 milisecond interval) divided by all frames.
+relCorrectOverlap :: ChordAnnotation -> ChordAnnotation -> Double
+relCorrectOverlap a b =
+    foldl countMatch 0 (zipWith eqFunc sama samb) / tot where
+    sama = sample a
+    samb = sample b
+    tot  = max (genericLength sama) (genericLength samb)
+
+-- | does the same thing as relCorrectOverlap, but it also prints the
+-- chords and uses a lower sample rate. N.B. the number output by 
+-- 'printRelCorrectOverlap' might differ from the output of 
+-- 'relCorrectOverlap', because a different sample rate might be used (see
+-- 'Constants').
+printRelCorrectOverlap :: (AudioFeat -> ChordBeatAnnotation) -> AudioFeat
+                       -> ChordAnnotation -> IO Double
+printRelCorrectOverlap annotator af@(AudioFeat chrm btbar afk) gt = do
+  let keys = snd $ preProcessKeyData chrm btbar afk
+      -- BUG:  now alswo when we are evaluating a simple annotator grouping is 
+      --       is displayed, this is wrong. printRelCorrectOverlap should
+      --       be independend of the kind of annotator.
+      blks :: [BeatTimedData [ProbChord]]
+      blks  = concatMap segChords $ preProcessData Nothing af
+
+      -- sample the info for printing and evaluation
+      samaf = sampleWith displaySampleRate (annotator af)
+      samgt = sampleWith displaySampleRate gt
+      sambk = sampleWith displaySampleRate blks
+      samk  = sampleWith displaySampleRate keys
+
+      tot   = max (genericLength samaf) (genericLength samgt)
+      showEq m = if m then "==" else "/=" 
+      printEval :: NumData -> ChordLabel -> ChordLabel -> Key -> [ProbChord] 
+                -> IO Bool
+      printEval t g a b c = 
+         do putStrLn (printf "%.2f" t ++ '\t' : showEq equal ++ '\t' : show g
+                      ++ '\t' : show a ++ '\t' : show b ++ '\t' : show c)
+                      >> hFlush stdout
+            return equal where equal = g `eqFunc` a
+  putStrLn "time\tmatch\tGT\t\tMPTREE\tkey\toptional chords"
+  m <- sequence (zipWith5 printEval [0.0,displaySampleRate ..] 
+                                    samgt samaf samk sambk)
+  return (foldl countMatch 0 m / tot)
+
+countMatch :: Double -> Bool -> Double 
+countMatch x y | y         = succ x -- count the number of matching frames
+               | otherwise = x
+
+-- given a chord annotation sample the chord label at every 10 ms
+sample :: Timed t => [t a] -> [a]
+sample = sampleWith evaluationSampleRate
+
+-- like sample, but takes a sample rate (seconds :: Float) as argument
+sampleWith :: Timed t => NumData -> [t a] -> [a]
+sampleWith rate =  sampleAt [0.00, rate .. ] 
+
+        
+-- samples at specific points in time, specified in a list
+sampleAt :: Timed t => [NumData] -> [t a] -> [a]
+sampleAt  _  [] = [] -- below, will never occur
+sampleAt []  _  = error "Harmtrace.Audio.Evaluation: No sampling grid specified" 
+sampleAt (t:ts) (c:cs)
+  | t <= offset c = getData c : sampleAt ts (c:cs)
+  | otherwise     = sampleAt (t:ts) cs         
+
+    
− src/HarmTrace/Audio/Harmonize.hs
@@ -1,256 +0,0 @@-{-# OPTIONS_GHC -Wall                     #-}-{-# LANGUAGE GADTs                        #-}-{-# LANGUAGE ScopedTypeVariables          #-}-{-# LANGUAGE TupleSections                #-}--module HarmTrace.Audio.Harmonize ( harmonyAnnotator, headAnnotator-                                 , simpleAnnotator , putSegStats) where--import HarmTrace.Audio.BeatChroma( createChordRanks, syncWithAnnKey, addTimeInfo-                                 , groupPChord, segmentByKey , syncEndings)-import HarmTrace.Audio.Key (getBeatSyncKeyFromChroma)-import HarmTrace.Audio.Utils -import HarmTrace.Audio.ChordTypes-import HarmTrace.Base.MusicRep--import HarmTrace.Models.Models-import HarmTrace.Models.Jazz.Main-import HarmTrace.Models.Pop.Main-import HarmTrace.Tokenizer.Tokens-import HarmTrace.IO.Errors--import HarmTrace.HAnTree.HAn (HAn)-import HarmTrace.HAnTree.Tree (Tree, size, depth)-import HarmTrace.HAnTree.ToHAnTree (GTree)--- import HarmTrace.HAnTree.PostProcess (PPOption (..))-import HarmTrace.HarmTrace--import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.BasicInstances--import System.IO (stdout,hFlush)-import Data.List (sortBy, groupBy)-import Text.Printf (printf)------------------------------------------------------------------------------------- Local Parameters-----------------------------------------------------------------------------------maxSegmentSize, maxLProductSize :: Int-maxSegmentSize  = 8-maxLProductSize = 20------------------------------------------------------------------------------------- From chords with probabilities to a single chord, using harmony----------------------------------------------------------------------------------       -harmonyAnnotator :: GrammarEx -> Maybe [TimedData Key] -> AudioFeat -                 -> ChordAnnotation-harmonyAnnotator (GrammarEx g) mk af = case mk of -  Nothing  -> concatMap (harmonize g) (preProcessData af)-  (Just k) -> concatMap (harmonize g) (preProcessDataWithKey k af)--preProcessDataWithKey :: [TimedData Key] -> AudioFeat -> [ProbChordSeg]-preProcessDataWithKey key' (AudioFeat _idStr chrm beat' _keystrength) = -  let (beat,key) = syncWithAnnKey chrm beat' key'-      chdgrp     = mergeAndTimeStamp avgPC beat . groupPChord-                 . createChordRanks $ beatSync beat chrm-  in segmentByTonic $ segmentByKey key chdgrp-  -preProcessData :: AudioFeat -> [ProbChordSeg]-preProcessData (AudioFeat _idStr chrm' beat' key') = -  let (chrm, beat, key) = syncEndings chrm' beat' key'-      keygrp = mergeAndTimeStamp head beat $ getBeatSyncKeyFromChroma beat key   -      chdgrp = mergeAndTimeStamp avgPC beat . groupPChord-             . createChordRanks $ beatSync beat chrm-  in segmentByTonic $ segmentByKey keygrp chdgrp--harmonize :: forall g. (GTree g) => Grammar g -> ProbChordSeg -> ChordAnnotation-harmonize g (Segment k bs) =-  let isExpandable :: Bool-      isExpandable = (length $ filter ((>1) . length) (map getData bs)) > 0-      -      pickHead :: Block -> ChordSegment-      pickHead tpc = tpc {getData = chordLab . head $ getData tpc}-      -      myParse :: [ChordToken] -> (Tree HAn,[ChordToken],[Error Int])-      myParse x =-        let -- First, parse the tokens-            res :: ([g],[Error Int])-            res = case g of -                      Jazz -> parse_h ((,) <$> pJazz k <*> pEnd) (createStr 0 x)-                      Pop  -> parse_h ((,) <$> pPop  k <*> pEnd) (createStr 0 x)-            -- Build a ParseResult from that-            pr = ParseResult u (concatMap chords x) (fst res) u u u (snd res) []-            -- So that we can post-process it. Then extract the Tree HAn-            t  = pieceTreeHAn (postProc [ RemovePDPT, MergeDelChords ]   pr)-            u :: forall a. a-            u  = error "harmonize: undefined placeholder evaluated"-        -- Return the Tree HAn, the input tokens, and the errors-        in (t, x, snd res)--      -- To be improved-      evaluateParse :: (Tree HAn,[ChordToken],[Error Int])-                    -> (Tree HAn,[ChordToken],Float)-      evaluateParse (ts,tokens,errors) = (ts,tokens,errorRatio errors tokens)--      -- Generate, parse, and evaluate all possible sequences of chords-      parseResults :: [(Tree HAn,[ChordToken],Float)]-      parseResults = [ evaluateParse (myParse l)-                     | l <- lProduct (map (map probChord . getData) bs) ]--      -- From all possible parse trees, take the best one-      select :: [(Tree HAn,[ChordToken],Float)] -> [ChordToken]-      select = select1 . head-             . groupBy (\(_,_,a) (_,_,b) -> a `compare` b == EQ)-             . sortBy  (\(_,_,a) (_,_,b) -> a `compare` b)--      -- These all have the same error ratio, so we sort them first by tree-      -- size, then depth, and pick the first-      select1 :: [(Tree HAn,[ChordToken],Float)] -> [ChordToken]-      select1 = snd3 . head . sortBy cmp where-        cmp (a,_,_) (b,_,_) = (size a, depth a) `compare` (size b, depth b)-        snd3 (_,a,_) = a--      probChord :: ProbChord -> ChordToken-      probChord (ProbChord lab@(Chord r sh _add _on _dur) _p) = -        (ChordToken r' sh' [lab] NotParsed 1 0) where-           r'  = if isNone r   then Note Nothing Imp else toScaleDegree k r-           sh' = if sh == None then NoClass          else toClassType sh-      -      -- store on and offsets for merging after parsing-      ons, offs :: [NumData]-      ons  = map onset  bs-      offs = map offset bs--      mergeTimeStamp :: [ChordToken] -> [ChordSegment]-      mergeTimeStamp cs = zipWith3 TimedData (map (head . chords) cs) ons offs--      -- if there is nothing to expand, do not parse-  in if   isExpandable then mergeTimeStamp $ select parseResults -     else map pickHead bs------------------------------------------------------------------------------------- Segmentation functions------------------------------------------------------------------------------------ Temporary test values-{--test = segmentTonic testKey testSeq-testKey = Key (Note Nothing C) MajMode-testSeq = testChordG ++ testChordC ++ testChordC ++ testChordG ++ testChordG-testChordC = [TimedData [ProbChord labC 1, ProbChord labG 0.5] 0 0]-testChordG = [TimedData [ProbChord labG 1, ProbChord labC 0.5] 0 0]-labC = Chord (Note Nothing C) Maj [] 0 0-labG = Chord (Note Nothing G) Maj [] 0 0--}---- Reminder: TimedData [ProbChord] == Block-segmentByTonic :: [ProbChordSeg] -> [ProbChordSeg]-segmentByTonic segs = concatMap emergencySplit $ concatMap split segs where-  split :: ProbChordSeg -> [ProbChordSeg]-  split (Segment key cs) = zipWith Segment (repeat key) (segmentTonic key cs)---- In case segments are just to big, even after segmenting on Tonic and Dominant--- split these segments into smaller segements recursively.-emergencySplit :: ProbChordSeg -> [ProbChordSeg]-emergencySplit (Segment k cs) = map (Segment k) (recSplit cs)-recSplit :: [TimedData [a]] -> [[TimedData [a]]]-recSplit [] = []-recSplit b-  |     blen                <= maxSegmentSize -    && (snd $ lProdStats b) <= maxLProductSize = [b]-  | otherwise   = recSplit l ++ recSplit r-    where blen  = length b-          (l,r) = splitAt (blen `div` 2) b -  --- Break into segments according to the key-segmentTonic :: Key -> [Block] -> [[Block]]-segmentTonic k' cs' = segmentTonic' k' cs' [] where -  segmentTonic' _ []     []     = []-  segmentTonic' _ []     acc    = [reverse acc]-  segmentTonic' k (c:cs) acc-    | c' `isTonic` k || c' `isDom` k = reverse (c:acc) : segmentTonic k cs-    | otherwise                      = segmentTonic' k cs (c:acc) where -        c' = getFstChord c---- Take the first chord (which is the one with the highest probability, since--- the list is sorted)-getFstChord :: TimedData [ProbChord] -> ChordLabel-getFstChord c = case getData c of-                  []    -> error "getFstChord: empty list"-                  (h:_) -> chordLab h -- only split on chords we are certain of-                -- _   -> Chord (Note Nothing N) None [] 0 0 -- else return None---- Check if this chord label is the tonic-isTonic :: ChordLabel -> Key -> Bool-isTonic c (Key r m) = r == chordRoot c && m `eqMode` (chordShorthand c)---- Check if this chord label is the dominant--- JPM: I don't understand why this function looks so different from `isTonic`-isDom :: ChordLabel -> Key -> Bool-isDom (Chord (Note Nothing N) _ _ _ _) _ = False-isDom c key       =    toScaleDegree key (chordRoot c) == Note Nothing V-                    && MajMode `eqMode` (chordShorthand c)---- It is debatable how to implement this function, musically speaking. This--- is what I came up with, without thinking too much.-eqMode :: Mode -> Shorthand -> Bool-eqMode _ Sus4  = False-eqMode _ Sus2  = False-eqMode m sh    = m == toMode sh--lProduct :: [[a]] -> [[a]]-lProduct []    = []-lProduct [l]   = [ [x] | x <- l ]-lProduct (h:t) = concat [ map (x:) (lProduct t) | x <- h ]------------------------------------------------------------------------------------- Some printing and statistics functions------------------------------------------------------------------------------------- prints Segmetation statistics-putSegStats :: Maybe [TimedData Key] -> AudioFeat -> IO()-putSegStats mk af = case mk of-  Nothing  -> mapM_ segmentStat $ preProcessData af-  (Just k) -> mapM_ segmentStat $ preProcessDataWithKey k af --segmentStat :: ProbChordSeg -> IO ()-segmentStat s@(Segment k bs) = -  do putStr ("start: " ++ (printf "%.3f" . onset  $ head bs))-     putStr (", end: "  ++ (printf "%.3f" . offset $ last bs))-     putStr (", key: "  ++ show k)-     putStr (", probChords: " ++ show (length bs))-     let (l, lpr) = lProdStats bs-     putStr (", lists > 1: "  ++ (show l) )-     putStrLn (" lProduct: " ++ show lpr)-     print s >> hFlush stdout---- Given a Block list this function returns the number of probChords with a --- list > 1 (fst) and the lProduct size (snd)-lProdStats :: [TimedData [a]] -> (Int, Int)-lProdStats bs = (length l, lpr) where-  l   = filter ((>1) . length ) (map getData bs)-  lpr = foldr (\a b -> (length  a) * b) 1 l------------------------------------------------------------------------------------- A baseline chord label annotator------------------------------------------------------------------------------------- Creates an annotation out of a block list, by just picking the first chord--- This annotator does smart grouping-headAnnotator :: Maybe [TimedData Key] -> AudioFeat -> ChordAnnotation-headAnnotator _keyAnn (AudioFeat _idStr chrm beat _key ) = -- ignore key info-  let endTime = time $ last chrm-      bts     = takeWhile (< endTime) beat ++ [endTime]-  in map headChrd . mergeAndTimeStamp avgPC bts . groupPChord . createChordRanks -  $ beatSync bts chrm where -    headChrd :: TimedData [ProbChord] -> TimedData ChordLabel-    headChrd td = td {getData = chordLab . head $ getData td}---- The most simple annotator, no grouping, no matching, --- just pick the best matching chord-simpleAnnotator :: Maybe [TimedData Key] -> AudioFeat -> ChordAnnotation-simpleAnnotator _keyAnn (AudioFeat _idStr chrm beat _key ) = -- ignore key-  addTimeInfo beat . map (chordLab . head) . createChordRanks-  $ beatSync beat chrm-  
src/HarmTrace/Audio/Key.hs view
@@ -1,95 +1,127 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wall         #-}--module HarmTrace.Audio.Key where--import HarmTrace.Audio.ChordTypes-import HarmTrace.Audio.Utils-import HarmTrace.Audio.BeatChroma ( beatSyncKeyStrenth, keyMap)-import HarmTrace.Base.MusicRep--import Prelude as P hiding ( map, length, head, last, mapM, mapM_, max-                           , maximum, reverse, tail, null, concatMap )---- N.B. Vector inside this module refers to a different type than Vector---      in the HarmTrace.Audio.BeatChroma module-import Data.Vector as V -import qualified Data.List as L--import Data.Ord (comparing)-import Text.Printf (printf)------------------------------------------------------------------------------------- Chroma key estimation----------------------------------------------------------------------------------modulPenalty :: NumData-modulPenalty = 1.0--getBeatSyncKeyFromChroma :: [NumData] -> [ChordinoLine] -> [[Key]]-getBeatSyncKeyFromChroma bts key = -  groupKeys . getKeyFromTable $ selectKey bts key--selectKey :: [NumData] -> [ChordinoLine] ->  Vector (Vector (Int, NumData))-selectKey _bts  []  = empty -selectKey  []  _key = empty-selectKey  bts  key = k where-  -- start by calculating the beat synchronised key strenght for all -  -- 24 keys (ordered by HarmTrace.Audio.BeatChroma.keyMap)-  m :: Vector (Vector NumData)-  m = fromList . L.map fromList $ beatSyncKeyStrenth bts key-  -  -- calculate for every beat the maximum key (the index) and the -  -- profile correlation (snd)-  maxima :: Vector (Int, NumData)-  {-# INLINE maxima  #-}-  maxima = map (\x -> (maxIndex x, maximum x)) m-  -  -- we fill a beat x 24 table and store the cumulative key strength.-  -- we can chose to stay in the current key or we can modulate which is-  -- penalised by modulPenalty, we also store the index so we can follow-  -- the path back to the first beat-  fill :: Int -> Int -> (Int, NumData)-  {-# INLINE fill  #-}-  fill 0 j = (j, (m!0)!j)-  fill i j = let (mj, mv) = maxima!i -- current max-                 noModul  = (j , (snd ((k!(i-1))!j)) + ((m!i)!j))-                 modul    = (mj, (snd ((k!(i-1))!j)) + mv - modulPenalty)-             in max2 modul noModul --  k = generate (length m) (generate 24 . fill)--max2 :: (Int, NumData) -> (Int, NumData) -> (Int, NumData)-{-# INLINE max2  #-}-max2 t1@(_, s1) t2@(_, s2) = if s1 > s2 then t1 else t2--getKeyFromTable :: Vector (Vector (Int, NumData)) -> [Key]-getKeyFromTable k = L.map ((!!) keyMap) (L.reverse yek) where-  yek   = collectMax (fst $ maximumBy (comparing snd) (last k)) (reverse k)-  -- given the table calulated with selectKey, this function calculates -  -- the actual key assignment for every beat-  collectMax :: Int -> Vector (Vector (Int, NumData)) -> [Int]-  collectMax startj l -    | null l     = []-    | otherwise  = fst ((head l) ! startj) : collectMax m (tail l) where-        m = fst $ maximumBy (comparing snd) (head l)--printKeyTable :: [NumData] -> [ChordinoLine] -> IO ()-printKeyTable bts chrm = -  let showLn :: Vector (Int, NumData) -> IO ()-      showLn x = do mapM_ (\(i,f) -> putStr (printf "(%d, %.2f)" i f)) x-                    putStr "\n"-  in mapM_ showLn $ selectKey bts chrm --naiveBeatSyncKey :: BeatTrackerData -> [ChordinoLine] -> [Key]-naiveBeatSyncKey bts key = -  L.map (((!!) keyMap) . maxListIndex) $ beatSyncKeyStrenth bts key------------------------------------------------------------------------------------- key strengthpParsing-----------------------------------------------------------------------------------groupKeys :: [Key] -> [[Key]]-groupKeys ks = L.group . L.concat $ groupMinSize 16 (getGlobalKey ks) ks --getGlobalKey :: [Key] -> Key-getGlobalKey = mode+{-# LANGUAGE FlexibleContexts #-}
+{-# OPTIONS_GHC -Wall         #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.Key
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Key-finding from musical audio.
+--------------------------------------------------------------------------------
+
+module HarmTrace.Audio.Key (getBeatSyncKeyFromChroma) where
+
+import HarmTrace.Audio.ChordTypes
+import HarmTrace.Audio.Statistical (groupMinSize, mode)
+import HarmTrace.Audio.ChromaKey ( beatSyncKeyStrength, keyMap)
+import HarmTrace.Base.MusicRep
+import Constants (minModulationLength, modulationPenalty)
+
+import Prelude as P hiding ( map, length, head, last, mapM, mapM_, max
+                           , maximum, reverse, tail, null, concatMap )
+
+-- N.B. Vector inside this module refers to a different type than Vector
+--      in the HarmTrace.Audio.ChromaChord module
+import Data.Vector as V 
+import qualified Data.List as L
+import Control.Arrow ((&&&))
+import Data.Ord (comparing)
+
+--------------------------------------------------------------------------------
+-- Chroma key estimation
+--------------------------------------------------------------------------------
+
+-- | Returns a 'Key' key assignment, given 'Beat' and chroma information.
+getBeatSyncKeyFromChroma :: [BeatBar] -> [ChordinoLine] -> [[Key]]
+getBeatSyncKeyFromChroma bts key = 
+  groupKeys . getKeyFromTable $ selectKey bts key
+
+selectKey :: [BeatBar] -> [ChordinoLine] ->  Vector (Vector (Int, NumData))
+selectKey _bts  []  = empty 
+selectKey  []  _key = empty
+selectKey  bts  key = k where
+  -- start by calculating the beat synchronised key strenght for all 
+  -- 24 keys (ordered by HarmTrace.Audio.ChromaKey.keyMap)
+  m :: Vector (Vector NumData)
+  m = fromList . L.map fromList $ beatSyncKeyStrength bts key
+  
+  -- calculate for every beat the maximum key (the index) and the 
+  -- profile correlation (snd)
+  maxima :: Vector (Int, NumData)
+  {-# INLINE maxima  #-}
+  -- maxima = map (\x -> (maxIndex x, maximum x)) m
+  maxima = map (maxIndex &&& maximum) m
+  
+  -- we fill a beat x 24 table and store the cumulative key strength.
+  -- we can chose to stay in the current key or we can modulate which is
+  -- penalised by modulationPenalty, we also store the index so we can follow
+  -- the path back to the first beat
+  fill :: Int -> Int -> (Int, NumData)
+  {-# INLINE fill  #-}
+  fill 0 j = (j, (m!0)!j)
+  fill i j = let (mj, mv) = maxima!i -- current max
+                 noModul  = (j , snd ((k!(i-1))!j) + ((m!i)!j))
+                 modul    = (mj, snd ((k!(i-1))!j) + mv - modulationPenalty)
+                 -- Reviewer #3 writes that the line above is incorrect. He/She 
+                 -- suggests the line below:
+                 -- modul    = (mj, (snd ((k!(i-1))!mj)) +mv -modulationPenalty)
+                 -- Reviewer #3 is wrong: k!(i-1))!j represents the cumulative
+                 -- key-strength *up to* the modulation at k!i from k!i on, the
+                 -- new key, represehtned by mj, should be used to calculate
+                 -- the cumulative key strength at the next beat. 
+                 -- However, we should evaluate the key finding independently. 
+             in max2 modul noModul 
+
+  k = generate (length m) (generate 24 . fill)
+
+max2 :: (Int, NumData) -> (Int, NumData) -> (Int, NumData)
+{-# INLINE max2  #-}
+max2 t1@(_, s1) t2@(_, s2) = if s1 > s2 then t1 else t2
+
+-- given the cumulative key strength tabel, this function returns the beat-wise
+-- key assignments
+getKeyFromTable :: Vector (Vector (Int, NumData)) -> [Key]
+getKeyFromTable k = L.map (keyMap !!) (L.reverse yek) where
+  yek   = collectMax (fst $ maximumBy (comparing snd) (last k)) (reverse k)
+  -- given the table calulated with selectKey, this function calculates 
+  -- the actual key assignment for every beat
+  collectMax :: Int -> Vector (Vector (Int, NumData)) -> [Int]
+  collectMax startj l 
+    | null l     = []
+    | otherwise  = fst (head l ! startj) : collectMax m (tail l) where
+        m = fst $ maximumBy (comparing snd) (head l)
+
+-- debugging:
+-- printKeyTable :: [BeatBar] -> [ChordinoLine] -> IO ()
+-- printKeyTable bts chrm = 
+  -- let showLn :: Vector (Int, NumData) -> IO ()
+      -- showLn x = do mapM_ (\(i,f) -> putStr (printf "(%d, %.2f)" i f)) x
+                    -- putStr "\n"
+  -- in mapM_ showLn $ selectKey bts chrm 
+
+-- naiveBeatSyncKey :: BeatBarTrackData -> [ChordinoLine] -> [Key]
+-- naiveBeatSyncKey bts key = 
+  -- L.map (((!!) keyMap) . maxListIndex) $ beatSyncKeyStrenth bts key
+
+--------------------------------------------------------------------------------
+-- key strengthpParsing
+--------------------------------------------------------------------------------
+
+-- Smooths and groups the key changes. Given a list of beat-wise key assignments
+-- the similar keys are grouped, but a new group, i.e. modulation, has to have 
+-- the minimum size of 16 beats. If this is not the case, the key assignments
+-- will be replaced by the global key.
+-- See HarmTrace.Audio.Statistical.groupMinSize
+groupKeys :: [Key] -> [[Key]]
+groupKeys ks = L.group . L.concat $
+               groupMinSize minModulationLength (getGlobalKey ks) ks
+
+-- Selects the key that is most prominent.
+getGlobalKey :: [Key] -> Key
+getGlobalKey = mode
+
− src/HarmTrace/Audio/Parser.hs
@@ -1,79 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wall         #-}--module HarmTrace.Audio.Parser where--import HarmTrace.Audio.ChordTypes-import HarmTrace.Base.Parsing hiding (pComma,pQuotedString,pParentheticalString)------------------------------------------------------------------------------------- data parsers------------------------------------------------------------------------------------- Beat Parsing                                  -parseBeatData :: Parser BeatTrackerData-parseBeatData = pListSep_ng pLineEnd pLine <* pLineEnd where-  pLine = opt pLabel "" *> pNumData <* opt (pComma *> pQuotedString) ""---- Chroma parsing                  -parseChordinoData :: Parser ChordinoData-parseChordinoData =  pListSep_ng pLineEnd pChordinoLine <* pLineEnd where-  pChordinoLine = const convert <$> opt pLabel ""-                                <*> pList1Sep (pSym ',') pNumData -  convert :: [NumData] -> ChordinoLine -- shift the chorma to match C .. B-  convert l | length l == 25 = ChordinoLine h (shift 3 a) (shift 3 b)-            | otherwise = error ("Wrong list length of " ++ show (length l))-            where (h:t) = l-                  (a,b) = splitAt 12 t---- rotates the elements in the list with n positions-shift :: Int -> [a] -> [a]-shift p l = b ++ a where (a,b) = splitAt p l                 --reverseShift :: Int -> [a] -> [a]-reverseShift p l = b ++ a where (a,b) = splitAt ((length l) - p) l                 ---- key parsing-parseKeyStrengthData :: Parser KeyStrengthData  -parseKeyStrengthData =  pListSep_ng pLineEnd pKeyLine <* pLineEnd where-  pKeyLine = convert <$> pList1Sep (pSym ',') pNumData  -  convert :: [NumData] -> ChordinoLine-  convert l | length l == 26 = ChordinoLine h a (tail b)-            | otherwise = error ("Wrong list length of " ++ show (length l))-            where (h:t) = l-                  (a,b) = splitAt 12 t  ---- key 12 dim vector chroms-parseChromaData :: Parser [ChordinoLine]-parseChromaData =  pListSep_ng pLineEnd pCrmLine <* pLineEnd where-  pCrmLine = convert <$> pList1Sep (pSym ',') pNumData  -  -- This is a bit of a hack, but I do not want to rewrite all the functions -  -- again for a very similar data type that only has one 12-dim chroma vector-  convert :: [NumData] -> ChordinoLine   -  convert l | length t == 12 = ChordinoLine h (shift 3 t) [] -- hence we make this []-            | otherwise = error ("Wrong list length of " ++ show (length l))-            where (h:t) = l-                  -                  ------------------------------------------------------------------------------------ Basic parsers----------------------------------------------------------------------------------                  -pNumData :: Parser NumData-pNumData = pDoubleRaw--pComma :: Parser Char-pComma = pSym ','--pParentheticalString :: Char -> Parser String-pParentheticalString d = pSym d *> pList pNonQuoteVChar <* pSym d where-  pNonQuoteVChar = pSatisfy (\c -> visibleChar c && c /= d) -                   (Insertion ("Character in a string set off from main text" ++-                    "by delimiter, e.g. double-quotes or comment token") 'y' 5)-  visibleChar c = '\032' <= c && c <= '\126'--pQuotedString :: Parser String-pQuotedString = pParentheticalString '"'--pLabel :: Parser String-pLabel = (pQuotedString `opt` "") <* pComma
+ src/HarmTrace/Audio/Statistical.hs view
@@ -0,0 +1,83 @@+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Audio.Statistical
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Some statistical functions used in processing audio.
+--------------------------------------------------------------------------------
+module HarmTrace.Audio.Statistical where
+
+import HarmTrace.Audio.ChordTypes
+
+import Data.List ( genericLength, tails
+                 , inits,maximumBy, sort, group)
+import Data.Ord (comparing)
+
+--------------------------------------------------------------------------------
+-- Statistical functions
+--------------------------------------------------------------------------------
+
+-- | Similar to group, but in case a group is smaller than s, 
+-- the group is filled with the same number of a's:
+-- 
+-- >>> groupMinSize 3 0 [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
+-- [[0],[0,0],[0,0,0],[4,4,4,4],[5,5,5,5,5]]
+groupMinSize :: Eq a => Int -> a -> [a] -> [[a]]
+groupMinSize _ _  []    =  []
+groupMinSize s a (x:xs) =  grp : groupMinSize s a zs
+   where (ys,zs) = span (== x) xs
+         lys = length ys
+         grp = if length ys >= s then x:ys else replicate (lys + 1) a    
+
+-- | Returns the mean of list.
+listMean :: [NumData] -> NumData
+listMean a = sum a  / genericLength a
+  
+-- | a median filter: see <http://en.wikipedia.org/wiki/Median_filter>.
+medianFilter :: Ord a => Int -> [a] -> [a]
+medianFilter wsize l = map mode $ getWindows wsize l
+  
+-- | Returns a list with all "sliding windows" of a particular size.
+-- The left and right edge of the list are filled with the first and last (size 
+-- /2) items, respectively and the remainder is filled with the mode/median of
+-- the complete list.
+getWindows :: Ord a => Int -> [a] -> [[a]]
+getWindows size l = lbor ++ mid ++ rbor
+  where mid  = takeWhile (hasSize size) . map (take size) $ tails l
+        gmed = mode l
+        ls   = size `div` 2
+        rs   = size - ls - 1
+        lbor = reverse $ map (fillWith size gmed) 
+                             (reverse . dropWhile null . inits $ take ls l)
+        rbor = map (reverse . fillWith size gmed) 
+                   (takeWhile (not . null) . tails . take rs $ reverse l)
+        -- in general (length l) < x, but this is guaranteed within this let
+        fillWith ::  Int -> a -> [a] -> [a]
+        fillWith x a lt = replicate (x - length l) a ++ lt
+
+hasSize :: Int -> [a] -> Bool
+hasSize s l = length l >= s
+  
+-- | The mode: the element that occurs most often in the collection.
+mode   :: Ord a => [a] -> a
+mode [] = error "Key.hs: mode called on []"
+mode l  = head . maximumBy (comparing length) . group $ sort l 
+  
+-- | Returns the median of a list.
+median   :: Ord a => [a] -> a -- the median not good for keys... 
+median [] = error "Key.hs: median called on []"
+median l  = sort l !! (length l `div` 2)  
+
+-- | Returns the index of th maximal element of a list.
+maxListIndex :: Ord a => [a] -> Int
+maxListIndex = fst . maxPair where
+  maxPair :: Ord a => [a] -> (Int,a)
+  maxPair = maximumBy (comparing snd) . zip [0..] 
+
+
− src/HarmTrace/Audio/Utils.hs
@@ -1,121 +0,0 @@-module HarmTrace.Audio.Utils where--import HarmTrace.Audio.ChordTypes--import Data.List (genericLength, transpose, tails, inits,maximumBy, sort, group)-import Data.Ord (comparing)------------------------------------------------------------------------------------- Some utility functions used by other functions throughout the Audio module------------------------------------------------------------------------------------- returns the last timestamp--- getLastFrameTimeStamp :: [ChordinoLine] -> NumData--- getLastFrameTimeStamp = time . last---- given a list of beats, a list of grouped data items, and a merging function--- mergeAndTimeStamp returns a list of TimedData items of which these--- data items are grouped with the provided merging function-mergeAndTimeStamp  ::([a] -> a)-> [NumData] -> [[a]] -> [TimedData a]-mergeAndTimeStamp f = mergeAndTimeStamp' f 0-mergeAndTimeStamp' ::([a] -> a)-> NumData -> [NumData] -> [[a]] -> [TimedData a]-mergeAndTimeStamp' _      _   []    []       = []-mergeAndTimeStamp' mergef ons beats (x : xs) = -  let (offs : rest) = drop ((length x) -1) beats-  in  TimedData (mergef x) ons offs : mergeAndTimeStamp' mergef offs rest xs-mergeAndTimeStamp' _ _ _ _ = -  error "mergeAndTimeStamp: asynchronous beats and data"-  --- a function for merging identical lists of ProbChords. Equallity of the chords--- in the lists is assumed and the probabilities are averaged-avgPC :: [[ProbChord]] -> [ProbChord]-avgPC [[a]] = [a]-avgPC pcs   = let cs = map chordLab $ head pcs -                  ps = map listMean . transpose $ map (map prob) pcs-              in  zipWith ProbChord cs ps  ---- similarly to group, but in case a group is smaller than s the group is filled--- with the same number of a's--- ?> groupMinSize 3 0 [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]--- [[0],[0,0],[0,0,0],[4,4,4,4],[5,5,5,5,5]]-groupMinSize :: Eq a => Int -> a -> [a] -> [[a]]-groupMinSize _ _  []    =  []-groupMinSize s a (x:xs) =  grp : groupMinSize s a zs-   where (ys,zs) = span (== x) xs-         lys = length ys-         grp = if length ys >= s then x:ys else replicate (lys + 1) a-         --- samples at specific points in time, specified in a list-sampleAt :: [NumData] -> [TimedData a] -> [a]-sampleAt  _     [] = []-sampleAt []     a  = map getData a -- will never occur, return without sampling-sampleAt (t:ts) (c:cs)-  | t <= offset c = getData c : sampleAt ts (c:cs)-  | otherwise     = sampleAt (t:ts) cs         ------------------------------------------------------------------------------------- Statistical functions----------------------------------------------------------------------------------              -listMean :: [NumData] -> NumData-listMean a = sum a  / genericLength a-  --- a median filter: see http://en.wikipedia.org/wiki/Median_filter-medianFilter :: Ord a => Int -> [a] -> [a]-medianFilter wsize l = map mode $ getWindows wsize l-  --- returns a list with all "sliding windows" of size wsize  --- the left and right edge of the list are with the first and last (size /2)--- items, respectively and the remainder is filled with the mode/median of--- the complete list-getWindows :: Ord a => Int -> [a] -> [[a]]-getWindows size l = lbor ++ mid ++ rbor-  where mid  = takeWhile (hasSize size) . map (take size) $ tails l-        gmed = mode l-        ls   = size `div` 2-        rs   = size - ls - 1-        lbor = reverse $ map (fillWith size gmed) -                             (reverse . dropWhile (null) . inits $ take ls l)-        rbor = map (reverse . fillWith size gmed) -                   (takeWhile (not . null) . tails . take rs $ reverse l)-        -- in general (length l) < x, but this is guaranteed within this let-        fillWith ::  Int -> a -> [a] -> [a]-        fillWith x a lt = replicate (x - (length l)) a ++ lt--hasSize :: Int -> [a] -> Bool-hasSize s l = length l >= s-  --- the mode: the element that occurs most often in the collection -mode   :: Ord a => [a] -> a-mode [] = error "Key.hs: mode called on []"-mode l  = head . maximumBy (comparing length) . group $ sort l -  --- the median: not good for keys... -median   :: Ord a => [a] -> a-median [] = error "Key.hs: median called on []"-median l  = sort l !! (length l `div` 2)  --maxListIndex :: Ord a => [a] -> Int-maxListIndex = fst . maxPair where-  maxPair :: Ord a => [a] -> (Int,a)-  maxPair = maximumBy (comparing snd) . zip [0..] ------------------------------------------------------------------------------------- Making beat synchronized chroma----------------------------------------------------------------------------------mergeBeats :: Int -> [[a]] -> [[a]]-mergeBeats _ [] = []-mergeBeats n l  = let (a,b) = splitAt n l in concat a : mergeBeats n b---- synchronizes the ChordinoData to the list of beats in the BeatTrackerData--- by grouping the ChordinoLines of the ChordinoData in separate lists-beatSync :: BeatTrackerData -> ChordinoData -> BeatChroma-beatSync _   []     = []-beatSync bt (cd:cs) = beatSync' bt [cd] (cd:cs) where-  beatSync' :: [NumData] -> [ChordinoLine] -> [ChordinoLine] -> [[ChordinoLine]]-  beatSync' _  _   [] = []-  beatSync' [] _   c  = [c]-  beatSync' (b:bs) prv c -- we also store the previous group in case beat < time-    | null x    = prv : beatSync' bs prv xs -    | otherwise = x   : beatSync' bs x   xs -        where (x, xs) = span ((>=) b . time) c
src/HarmTrace/Base/MusicRep.hs view
@@ -1,268 +1,265 @@-{-# LANGUAGE TemplateHaskell                #-}-{-# LANGUAGE EmptyDataDecls                 #-}-{-# LANGUAGE TypeFamilies                   #-}-{-# LANGUAGE GADTs                          #-}--module HarmTrace.Base.MusicRep where-  -import Data.Maybe-import Data.List (elemIndex, intersperse, intercalate)  -import Control.DeepSeq-import HarmTrace.HAnTree.Binary-import Generics.Instant.TH-import Data.Binary-  ------------------------------------------------------------------------------------ Representing musical information at the value level------------------------------------------------------------------------------------- Keys (at the value level)-data Key  = Key { keyRoot :: Root, keyMode :: Mode } deriving (Eq, Ord)-data Mode = MajMode | MinMode deriving (Eq, Ord)--instance NFData Mode where-  rnf MinMode = ()-  rnf MajMode = ()-  -type ChordLabel   = Chord Root-type ChordDegree  = Chord ScaleDegree---- the representation for a single tokenized chord -data Chord a = Chord { chordRoot        :: a-                     , chordShorthand   :: Shorthand-                     , chordAdditions   :: [Addition]-                     , getLoc           :: Int -- the index of the chord  -                     , duration         :: Int -- in the list of tokens-                     }--data Class = Class ClassType Shorthand--data ClassType = MajClass | MinClass | DomClass | DimClass | NoClass-  deriving (Eq)--data Shorthand = -- Triad chords-                 Maj | Min | Dim | Aug-                 -- Seventh chords-               | Maj7 | Min7 | Sev | Dim7 | HDim7 | MinMaj7-                 -- Sixth chords-               | Maj6 | Min6-                 -- Extended chords-               | Nin | Maj9 | Min9-                 -- Suspended chords-               | Sus4 | Sus2-                 -- In some cases there is no chord a certain position-                 -- This is especially important for the chroma processing-               | None-  deriving (Show, Eq, Enum, Bounded) ----- Key relative scale degrees to abstract from the absolute Root notes-type ScaleDegree = Note DiatonicDegree--data DiatonicDegree = I | II | III | IV | V | VI | VII | Imp-  deriving (Show, Eq, Enum, Ord, Bounded)---- Representing absolute root notes  -type Root = Note DiatonicNatural-  -data DiatonicNatural =  C | D | E | F | G | A | B | N -- N is for no root -  deriving (Show, Eq, Enum, Ord, Bounded)-  --- Intervals for additonal chord notes    -type Addition = Note Interval-  -data Interval = I1  | I2  | I3  | I4 | I5 | I6 | I7 | I8 | I9 | I10 -              | I11 | I12 | I13 -  deriving (Eq, Enum, Ord, Bounded)     -  -data Note a = Note (Maybe Modifier) a   deriving (Eq, Ord) -  -data Modifier = Sh | Fl | SS | FF -- Sharp, flat, double sharp, double flat-  deriving (Eq, Ord)------------------------------------------------------------------------------------- Instances for the general music datatypes---------------------------------------------------------------------------------   --instance Show Key where-  show (Key r m) = show r ++ show m-    -instance Show Mode where-  show MajMode = ""-  show MinMode = "m"  -  -instance Eq a => Eq (Chord a) where-  (Chord ra sha dega _loc _d) == (Chord rb shb degb _locb _db) -     = ra == rb && sha == shb && dega == degb -  -instance (Show a) => Show (Chord a) where-  show (Chord r sh deg _loc _d) =  show r ++ ':' : show sh -                            ++ (if not (null deg) then showAdds deg else "")-                            -- ++ '_' : show loc ++ ':' : show d-    -showAdds :: Show a => [a] -> String                                -showAdds x = '(' : intercalate "," (map show x) ++ ")"-     -instance Show Class where show (Class ct _) = show ct-                            -instance Show ClassType where-  show MajClass = ""-  show MinClass = "m"-  show DomClass = "7"-  show DimClass = "0"-  show NoClass  = "N"--instance (Show a) => Show (Note a) where-  show (Note m interval) = show interval ++ maybe "" show m--instance Show Interval where-  show a = show . ((!!) ([1..13]::[Integer])) -                . fromJust $ elemIndex a [minBound..]-   -  -instance Show Modifier where -  show Sh = "#"-  show Fl = "b"-  show SS = "##"-  show FF = "bb"     ---- for showing additional additions-showAdditions :: [Addition] -> String-showAdditions a -  | null a    = ""-  | otherwise = "(" ++ concat (intersperse ","  (map show a)) ++ ")"           -  ------------------------------------------------------------------------------------ Utils      -----------------------------------------------------------------------------------isNone :: Root -> Bool-isNone (Note _ N) = True-isNone  _         = False--noneLabel :: ChordLabel-noneLabel = (Chord (Note Nothing N) None [] 0 0)--isNoneChord :: ChordLabel -> Bool-isNoneChord (Chord (Note _ N) _ _ _ _) = True-isNoneChord (Chord _ None _ _ _)       = True-isNoneChord _                          = False--toClassType :: Shorthand -> ClassType-toClassType sh -  | sh `elem` [Maj,Maj7,Maj6,Maj9,MinMaj7,Sus4,Sus2] = MajClass-  | sh `elem` [Min,Min7,Min6,Min9,HDim7] = MinClass-  | sh `elem` [Sev,Nin,Aug] = DomClass-  | sh `elem` [Dim,Dim7] = DimClass -  | otherwise = error ("toClassType: unknown shorthand: " ++ show sh)   --toMode :: Shorthand -> Mode     -toMode Maj     = MajMode-toMode Min     = MinMode-toMode Dim     = MinMode-toMode Aug     = MajMode-toMode Maj7    = MajMode-toMode Min7    = MinMode-toMode Sev     = MajMode-toMode Dim7    = MinMode-toMode HDim7   = MinMode-toMode MinMaj7 = MinMode-toMode Maj6    = MajMode -toMode Min6    = MinMode-toMode Nin     = MajMode-toMode Maj9    = MajMode-toMode Min9    = MinMode-toMode Sus4    = MinMode -- for now-toMode _       = MinMode -- should not happen-  ------------------------------------------------------------------------------------ Value Level Scale Degree Transposition--------------------------------------------------------------------------------- -    --- Chord root shorthand degrees location duration-toChordDegree :: Key -> ChordLabel -> ChordDegree-toChordDegree k (Chord r sh degs loc d) = -                 Chord (toScaleDegree k r) sh degs loc d    -    -toScaleDegree :: Key -> Root -> ScaleDegree-toScaleDegree _ n@(Note _ N) = -  error ("HarmTrace.Base.MusicRep.toScaleDegree: cannot transpose" ++ show n)-toScaleDegree (Key kr _) cr  = -- Note Nothing I-  scaleDegrees!!(((diaNatToSemi cr) - (diaNatToSemi kr)) `mod` 12)---- transposes a degree with sem semitones up-transposeSem :: ScaleDegree -> Int -> ScaleDegree-transposeSem deg sem = scaleDegrees!!((sem + (diaDegToSemi deg)) `mod` 12) where---- gives the semitone value [0,11] of a Degree, e.g. F# = 6-diaDegToSemi :: ScaleDegree -> Int-diaDegToSemi n@(Note _ Imp) = -  error ("HarmTrace.Base.MusicRep.diaDegToSemi: no semitone for" ++ show n)-diaDegToSemi   (Note m deg) = -  ([0,2,4,5,7,9,11] !! (fromJust $ elemIndex deg [minBound..])) + (modToSemi m) -  -diaNatToSemi :: Root -> Int-diaNatToSemi n@(Note _ N  ) = -  error ("HarmTrace.Base.MusicRep.diaDegToSemi: no semitone for" ++ show n)-diaNatToSemi   (Note m nat) = -  ([0,2,4,5,7,9,11] !! (fromJust $ elemIndex nat [minBound..])) + (modToSemi m) ----- transforms type-level modifiers to semitones (Int values)-modToSemi :: Maybe Modifier -> Int-modToSemi  Nothing  =  0-modToSemi (Just Sh) =  1-modToSemi (Just Fl) = -1-modToSemi (Just SS) =  2-modToSemi (Just FF) = -2-           -scaleDegrees ::[ScaleDegree]  -scaleDegrees = [ Note  Nothing   I-               , Note  (Just Fl) II-               , Note  Nothing   II-               , Note  (Just Fl) III-               , Note  Nothing   III-               , Note  Nothing   IV-               , Note  (Just Sh) IV-               , Note  Nothing   V-               , Note  (Just Fl) VI-               , Note  Nothing   VI-               , Note  (Just Fl) VII-               , Note  Nothing   VII-               ]------------------------------------------------------------------------------------- Binary instances-----------------------------------------------------------------------------------deriveAllL [''Note, ''DiatonicDegree-           , ''Mode, ''Chord, ''DiatonicNatural, ''ClassType-           , ''Modifier, ''Shorthand, ''Interval]--instance (Binary a) => Binary (Note a) where-  put = putDefault-  get = getDefault-instance Binary DiatonicDegree where-  put = putDefault-  get = getDefault-instance Binary Mode where-  put = putDefault-  get = getDefault-instance (Binary a) => Binary (Chord a) where-  put = putDefault-  get = getDefault-instance Binary DiatonicNatural where-  put = putDefault-  get = getDefault-instance Binary ClassType where-  put = putDefault-  get = getDefault-instance Binary Modifier where-  put = putDefault-  get = getDefault-instance Binary Shorthand where-  put = putDefault-  get = getDefault-instance Binary Interval where-  put = putDefault-  get = getDefault+{-# LANGUAGE TemplateHaskell                #-}
+{-# LANGUAGE EmptyDataDecls                 #-}
+{-# LANGUAGE TypeFamilies                   #-}
+{-# LANGUAGE GADTs                          #-}
+
+module HarmTrace.Base.MusicRep where
+  
+import Data.Maybe
+import Data.List (elemIndex, intersperse, intercalate)  
+import Control.DeepSeq
+import HarmTrace.HAnTree.Binary
+import Generics.Instant.TH
+import Data.Binary
+  
+--------------------------------------------------------------------------------
+-- Representing musical information at the value level
+--------------------------------------------------------------------------------
+
+-- Keys (at the value level)
+data Key  = Key { keyRoot :: Root, keyMode :: Mode } deriving (Eq, Ord)
+data Mode = MajMode | MinMode deriving (Eq, Ord)
+
+instance NFData Mode where
+  rnf MinMode = ()
+  rnf MajMode = ()
+  
+type ChordLabel   = Chord Root
+type ChordDegree  = Chord ScaleDegree
+
+-- the representation for a single tokenized chord 
+data Chord a = Chord { chordRoot        :: a
+                     , chordShorthand   :: Shorthand
+                     , chordAdditions   :: [Addition]
+                     , getLoc           :: Int -- the index of the chord  
+                     , duration         :: Int -- in the list of tokens
+                     }
+
+data Class = Class ClassType Shorthand
+
+data ClassType = MajClass | MinClass | DomClass | DimClass | NoClass
+  deriving (Eq)
+
+data Shorthand = -- Triad chords
+                 Maj | Min | Dim | Aug
+                 -- Seventh chords
+               | Maj7 | Min7 | Sev | Dim7 | HDim7 | MinMaj7
+                 -- Sixth chords
+               | Maj6 | Min6
+                 -- Extended chords
+               | Nin | Maj9 | Min9
+                 -- Suspended chords
+               | Sus4 | Sus2
+                 -- In some cases there is no chord a certain position
+                 -- This is especially important for the chroma processing
+               | None
+  deriving (Show, Eq, Enum, Bounded) 
+
+
+-- Key relative scale degrees to abstract from the absolute Root notes
+type ScaleDegree = Note DiatonicDegree
+
+data DiatonicDegree = I | II | III | IV | V | VI | VII | Imp
+  deriving (Show, Eq, Enum, Ord, Bounded)
+
+-- Representing absolute root notes  
+type Root = Note DiatonicNatural
+  
+data DiatonicNatural =  C | D | E | F | G | A | B | N -- N is for no root 
+  deriving (Show, Eq, Enum, Ord, Bounded)
+  
+-- Intervals for additonal chord notes    
+type Addition = Note Interval
+  
+data Interval = I1  | I2  | I3  | I4 | I5 | I6 | I7 | I8 | I9 | I10 
+              | I11 | I12 | I13 
+  deriving (Eq, Enum, Ord, Bounded)     
+  
+data Note a = Note (Maybe Modifier) a   deriving (Eq, Ord) 
+  
+data Modifier = Sh | Fl | SS | FF -- Sharp, flat, double sharp, double flat
+  deriving (Eq, Ord)
+
+--------------------------------------------------------------------------------
+-- Instances for the general music datatypes
+--------------------------------------------------------------------------------   
+
+instance Show Key where
+  show (Key r m) = show r ++ show m
+    
+instance Show Mode where
+  show MajMode = ""
+  show MinMode = "m"  
+  
+instance Eq a => Eq (Chord a) where
+  (Chord ra sha dega _loc _d) == (Chord rb shb degb _locb _db) 
+     = ra == rb && sha == shb && dega == degb 
+  
+instance (Show a) => Show (Chord a) where
+  show (Chord r sh deg _loc _d) =  show r ++ ':' : show sh 
+                            ++ (if not (null deg) then showAdds deg else "")
+                            -- ++ '_' : show loc ++ ':' : show d
+
+showAdds :: Show a => [a] -> String                                
+showAdds x = '(' : intercalate "," (map show x) ++ ")"
+     
+instance Show Class where show (Class ct _) = show ct
+                            
+instance Show ClassType where
+  show MajClass = ""
+  show MinClass = "m"
+  show DomClass = "7"
+  show DimClass = "0"
+  show NoClass  = "N"
+
+instance (Show a) => Show (Note a) where
+  show (Note m interval) = show interval ++ maybe "" show m
+
+instance Show Interval where
+  show a = show . ((!!) ([1..13]::[Integer])) 
+                . fromJust $ elemIndex a [minBound..]
+   
+  
+instance Show Modifier where 
+  show Sh = "#"
+  show Fl = "b"
+  show SS = "##"
+  show FF = "bb"     
+
+-- for showing additional additions
+showAdditions :: [Addition] -> String
+showAdditions a 
+  | null a    = ""
+  | otherwise = "(" ++ concat (intersperse ","  (map show a)) ++ ")"           
+  
+--------------------------------------------------------------------------------
+-- Utils      
+--------------------------------------------------------------------------------
+
+isNone :: Root -> Bool
+isNone (Note _ N) = True
+isNone  _         = False
+
+noneLabel :: ChordLabel
+noneLabel = (Chord (Note Nothing N) None [] 0 0)
+
+isNoneChord :: ChordLabel -> Bool
+isNoneChord (Chord (Note _ N) _ _ _ _) = True
+isNoneChord (Chord _ None _ _ _)       = True
+isNoneChord _                          = False
+
+toClassType :: Shorthand -> ClassType
+toClassType sh 
+  | sh `elem` [Maj,Maj7,Maj6,Maj9,MinMaj7,Sus4,Sus2] = MajClass
+  | sh `elem` [Min,Min7,Min6,Min9,HDim7] = MinClass
+  | sh `elem` [Sev,Nin,Aug] = DomClass
+  | sh `elem` [Dim,Dim7] = DimClass 
+  | otherwise = error 
+      ("HarmTrace.Base.MusicRep.toClassType: unknown shorthand: " ++ show sh)
+
+toMode :: Shorthand -> Mode     
+toMode Maj     = MajMode
+toMode Min     = MinMode
+toMode Dim     = MinMode
+toMode Aug     = MajMode
+toMode Maj7    = MajMode
+toMode Min7    = MinMode
+toMode Sev     = MajMode
+toMode Dim7    = MinMode
+toMode HDim7   = MinMode
+toMode MinMaj7 = MinMode
+toMode Maj6    = MajMode 
+toMode Min6    = MinMode
+toMode Nin     = MajMode
+toMode Maj9    = MajMode
+toMode Min9    = MinMode
+toMode Sus4    = MinMode -- for now
+toMode Sus2    = MinMode -- for now
+toMode m       = error 
+   ("HarmTrace.Base.MusicRep.toMode: unknown shorthand: " ++ show m)
+  
+--------------------------------------------------------------------------------
+-- Value Level Scale Degree Transposition
+-------------------------------------------------------------------------------- 
+    
+-- Chord root shorthand degrees location duration
+toChordDegree :: Key -> ChordLabel -> ChordDegree
+toChordDegree k (Chord r sh degs loc d) = 
+                 Chord (toScaleDegree k r) sh degs loc d    
+    
+toScaleDegree :: Key -> Root -> ScaleDegree
+toScaleDegree _ n@(Note _ N) = 
+  error ("HarmTrace.Base.MusicRep.toScaleDegree: cannot transpose" ++ show n)
+toScaleDegree (Key kr _) cr  = -- Note Nothing I
+  scaleDegrees!!(((toSemitone cr) - (toSemitone kr)) `mod` 12)
+
+-- transposes a degree with sem semitones up
+transposeSem :: ScaleDegree -> Int -> ScaleDegree
+transposeSem deg sem = scaleDegrees!!((sem + (toSemitone deg)) `mod` 12) where
+
+-- gives the semitone value [0,11] of a Degree, e.g. F# = 6  
+toSemitone :: (Show a, Enum a) => Note a -> Int
+toSemitone (Note m p)
+  | ix > 6    = error ("HarmTrace.Base.MusicRep.toSemitone: no semitone for"
+                        ++ show (Note m p))
+  | otherwise = ([0,2,4,5,7,9,11] !! ix) + modToSemi m where
+    ix = fromEnum p
+
+-- transforms type-level modifiers to semitones (Int values)
+modToSemi :: Maybe Modifier -> Int
+modToSemi  Nothing  =  0
+modToSemi (Just Sh) =  1
+modToSemi (Just Fl) = -1
+modToSemi (Just SS) =  2
+modToSemi (Just FF) = -2
+           
+scaleDegrees ::[ScaleDegree]  
+scaleDegrees = [ Note  Nothing   I
+               , Note  (Just Fl) II
+               , Note  Nothing   II
+               , Note  (Just Fl) III
+               , Note  Nothing   III
+               , Note  Nothing   IV
+               , Note  (Just Sh) IV
+               , Note  Nothing   V
+               , Note  (Just Fl) VI
+               , Note  Nothing   VI
+               , Note  (Just Fl) VII
+               , Note  Nothing   VII
+               ]
+
+--------------------------------------------------------------------------------
+-- Binary instances
+--------------------------------------------------------------------------------
+
+deriveAllL [''Note, ''DiatonicDegree
+           , ''Mode, ''Chord, ''DiatonicNatural, ''ClassType
+           , ''Modifier, ''Shorthand, ''Interval]
+
+instance (Binary a) => Binary (Note a) where
+  put = putDefault
+  get = getDefault
+instance Binary DiatonicDegree where
+  put = putDefault
+  get = getDefault
+instance Binary Mode where
+  put = putDefault
+  get = getDefault
+instance (Binary a) => Binary (Chord a) where
+  put = putDefault
+  get = getDefault
+instance Binary DiatonicNatural where
+  put = putDefault
+  get = getDefault
+instance Binary ClassType where
+  put = putDefault
+  get = getDefault
+instance Binary Modifier where
+  put = putDefault
+  get = getDefault
+instance Binary Shorthand where
+  put = putDefault
+  get = getDefault
+instance Binary Interval where
+  put = putDefault
+  get = getDefault
src/HarmTrace/Base/Parsing.hs view
@@ -1,41 +1,47 @@-{-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wall         #-}--module HarmTrace.Base.Parsing ( parseData, parseDataWithErrors-                              , pString, pLineEnd-                              , module Data.ListLike.Base-                              , module Text.ParserCombinators.UU -                              , module Text.ParserCombinators.UU.Utils-                              , module Text.ParserCombinators.UU.BasicInstances-                              ) where--import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.Utils hiding (pSpaces)-import Text.ParserCombinators.UU.BasicInstances hiding (IsLocationUpdatedBy)-import Data.ListLike.Base (ListLike)------------------------------------------------------------------------------------- A collection of parsing functions used by parsers throughout the project---------------------------------------------------------------------------------     ---- toplevel parsers-parseData :: (ListLike s a, Show a) => P (Str a s LineColPos) b -> s -> b-parseData p inp = fst ( parseDataWithErrors p inp )--parseDataWithErrors :: (ListLike s a, Show a) -                    =>  P (Str a s LineColPos) b -> s -> (b, [Error LineColPos])-parseDataWithErrors p inp = (parse ( (,) <$> p <*> pEnd) -                             (createStr (LineColPos 0 0 0) inp))-                                                 --- parses specific string-pString :: (ListLike state a, IsLocationUpdatedBy loc a, Show a, Eq a) -        => [a] -> P (Str a state loc) [a]-pString s = foldr (\a b -> (:) <$> a <*> b) (pure []) (map pSym s)                             ---- parses whitespace (@pedro: should probably not contain '\n')--- pSpaces :: Parser Char--- pSpaces = pAnySym [' ','\n','\t']---- parses UNIX and DOS/WINDOWS line endings-pLineEnd :: Parser String-pLineEnd  = pString "\n" <|> pString "\r\n" <|> pString " " <|> pString "\t"  +{-# LANGUAGE FlexibleContexts #-}
+{-# OPTIONS_GHC -Wall         #-}
+
+module HarmTrace.Base.Parsing ( parseData, parseDataWithErrors
+                              , pString, pLineEnd, pManyTill
+                              , module Data.ListLike.Base
+                              , module Text.ParserCombinators.UU 
+                              , module Text.ParserCombinators.UU.Utils
+                              , module Text.ParserCombinators.UU.BasicInstances
+                              ) where
+
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.Utils hiding (pSpaces)
+import Text.ParserCombinators.UU.BasicInstances hiding (IsLocationUpdatedBy)
+import Data.ListLike.Base (ListLike)
+
+--------------------------------------------------------------------------------
+-- A collection of parsing functions used by parsers throughout the project
+--------------------------------------------------------------------------------     
+
+-- toplevel parsers
+parseData :: (ListLike s a, Show a) => P (Str a s LineColPos) b -> s -> b
+parseData p inp = fst ( parseDataWithErrors p inp )
+
+parseDataWithErrors :: (ListLike s a, Show a) 
+                    =>  P (Str a s LineColPos) b -> s -> (b, [Error LineColPos])
+parseDataWithErrors p inp = (parse ( (,) <$> p <*> pEnd) 
+                             (createStr (LineColPos 0 0 0) inp))
+                                                 
+-- parses specific string
+pString :: (ListLike state a, IsLocationUpdatedBy loc a, Show a, Eq a) 
+        => [a] -> P (Str a state loc) [a]
+pString s = foldr (\a b -> (:) <$> a <*> b) (pure []) (map pSym s)                             
+
+-- parses whitespace (@pedro: should probably not contain '\n')
+-- pSpaces :: Parser Char
+-- pSpaces = pAnySym [' ','\n','\t']
+
+-- parses UNIX and DOS/WINDOWS line endings
+pLineEnd :: Parser String
+pLineEnd  = pString "\n" <|> pString "\r\n" <|> pString " " <|> pString "\t"  
+
+pManyTill :: P st a -> P st b -> P st [a]
+pManyTill p end = [] <$ end 
+                  <<|> 
+                  (:) <$> p <*> pManyTill p end
+
src/HarmTrace/Base/TypeLevel.hs view
@@ -1,81 +1,81 @@-{-# LANGUAGE EmptyDataDecls           #-}-{-# LANGUAGE KindSignatures           #-}-{-# LANGUAGE TypeFamilies             #-}-{-# LANGUAGE UndecidableInstances     #-}-{-# LANGUAGE ScopedTypeVariables      #-}-{-# LANGUAGE DeriveDataTypeable       #-}--module HarmTrace.Base.TypeLevel (-      Su, Ze -    , T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10-    , T11, T12, T13, T14, T15, T16, T17, T18, T19, T20-    , ToNat(..)-  ) where--import Data.Typeable----- Type level peano naturals-data Su :: * -> *  deriving Typeable-data Ze :: *       deriving Typeable---- Some shorthands-type T0 = Ze-type T1 = Su T0-type T2 = Su T1-type T3 = Su T2-type T4 = Su T3-type T5 = Su T4-type T6 = Su T5-type T7 = Su T6-type T8 = Su T7-type T9 = Su T8-type T10 = Su T9-type T11 = Su T10-type T12 = Su T11-type T13 = Su T12-type T14 = Su T13-type T15 = Su T14-type T16 = Su T15-type T17 = Su T16-type T18 = Su T17-type T19 = Su T18-type T20 = Su T19--class ToNat n where-  toNat :: n -> Int--instance ToNat Ze where toNat _ = 0-instance (ToNat n) => ToNat (Su n) where toNat _ = 1 + toNat (undefined :: n)-{---- Below is some experimentation...---- A degree has a distance to root in semi-tones (n in T0..T11) and a --- class (major or minor)-data Degree n cls---- Transposing is a bit like addition...-type family Transpose m n--- ... but we normalize at the end to stay within T0..T11-type instance Transpose m T0 = Norm m-type instance Transpose m (Su n) = Transpose (Su m) n---- Normalizing is the same as subtracting T12, but only if we can. Else we keep--- the type unchanged.-type Norm m = Sub m T12 m---- Subtraction with an extra type for failure-type family Sub m n fail--- Inductive case-type instance Sub (Su m) (Su n) fail = Sub m n fail--- Base case, subtraction succeeded-type instance Sub m T0 fail = m--- Base case, subtraction failed-type instance Sub T0 (Su n) fail = fail---- A secondary dominant is a transposition by 7 semi-tones-type SD deg = Transpose deg T7---- A tritone substitution is a transposition by 6 semi-tones-type TS deg = Transpose deg T6--}+{-# LANGUAGE EmptyDataDecls           #-}
+{-# LANGUAGE KindSignatures           #-}
+{-# LANGUAGE TypeFamilies             #-}
+{-# LANGUAGE UndecidableInstances     #-}
+{-# LANGUAGE ScopedTypeVariables      #-}
+{-# LANGUAGE DeriveDataTypeable       #-}
+
+module HarmTrace.Base.TypeLevel (
+      Su, Ze 
+    , T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
+    , T11, T12, T13, T14, T15, T16, T17, T18, T19, T20
+    , ToNat(..)
+  ) where
+
+import Data.Typeable
+
+
+-- Type level peano naturals
+data Su :: * -> *  deriving Typeable
+data Ze :: *       deriving Typeable
+
+-- Some shorthands
+type T0 = Ze
+type T1 = Su T0
+type T2 = Su T1
+type T3 = Su T2
+type T4 = Su T3
+type T5 = Su T4
+type T6 = Su T5
+type T7 = Su T6
+type T8 = Su T7
+type T9 = Su T8
+type T10 = Su T9
+type T11 = Su T10
+type T12 = Su T11
+type T13 = Su T12
+type T14 = Su T13
+type T15 = Su T14
+type T16 = Su T15
+type T17 = Su T16
+type T18 = Su T17
+type T19 = Su T18
+type T20 = Su T19
+
+class ToNat n where
+  toNat :: n -> Int
+
+instance ToNat Ze where toNat _ = 0
+instance (ToNat n) => ToNat (Su n) where toNat _ = 1 + toNat (undefined :: n)
+{-
+-- Below is some experimentation...
+
+-- A degree has a distance to root in semi-tones (n in T0..T11) and a 
+-- class (major or minor)
+data Degree n cls
+
+-- Transposing is a bit like addition...
+type family Transpose m n
+-- ... but we normalize at the end to stay within T0..T11
+type instance Transpose m T0 = Norm m
+type instance Transpose m (Su n) = Transpose (Su m) n
+
+-- Normalizing is the same as subtracting T12, but only if we can. Else we keep
+-- the type unchanged.
+type Norm m = Sub m T12 m
+
+-- Subtraction with an extra type for failure
+type family Sub m n fail
+-- Inductive case
+type instance Sub (Su m) (Su n) fail = Sub m n fail
+-- Base case, subtraction succeeded
+type instance Sub m T0 fail = m
+-- Base case, subtraction failed
+type instance Sub T0 (Su n) fail = fail
+
+-- A secondary dominant is a transposition by 7 semi-tones
+type SD deg = Transpose deg T7
+
+-- A tritone substitution is a transposition by 6 semi-tones
+type TS deg = Transpose deg T6
+-}
src/HarmTrace/HAnTree/Binary.hs view
@@ -1,59 +1,59 @@-{-# LANGUAGE GADTs                    #-}-{-# LANGUAGE FlexibleInstances        #-}-{-# LANGUAGE FlexibleContexts         #-}-{-# LANGUAGE TypeOperators            #-}---- Generic Binary--module HarmTrace.HAnTree.Binary where--import Control.Monad (liftM, liftM2)-import Data.Binary-import Generics.Instant---class GBinary a where-  gput :: a -> Put-  gget :: Get a---instance GBinary U where-  gput _ = return ()-  gget = return U--instance (GBinary a) => GBinary (CEq c p p a) where-  gput (C a) = gput a-  gget = liftM C gget-{--instance (GBinary a) => GBinary (CEq c p q a) where-  gput _ = return ()-  gget = error "gget: CEq impossible"--}-instance (GBinary a, GBinary b) => GBinary (a :+: b) where-  gput (L a) = put (0 :: Word8) >> gput a-  gput (R a) = put (1 :: Word8) >> gput a-  gget = do t <- get :: Get Word8-            case t of-              0 -> liftM L gget-              1 -> liftM R gget-              _ -> error "gget: :+: impossible"--instance (GBinary a, GBinary b) => GBinary (a :*: b) where-  gput (a :*: b) = gput a >> gput b-  gget = liftM2 (:*:) gget gget--instance (Binary a) => GBinary (Rec a) where-  gput (Rec a) = put a-  gget = liftM Rec get--instance (Binary a) => GBinary (Var a) where-  gput (Var a) = put a-  gget = liftM Var get----- Default implementations-getDefault :: (Representable a, GBinary (Rep a)) => Get a-getDefault = fmap to gget--putDefault :: (Representable a, GBinary (Rep a)) => a -> Put-putDefault = gput . from+{-# LANGUAGE GADTs                    #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE FlexibleContexts         #-}
+{-# LANGUAGE TypeOperators            #-}
+
+-- Generic Binary
+
+module HarmTrace.HAnTree.Binary where
+
+import Control.Monad (liftM, liftM2)
+import Data.Binary
+import Generics.Instant
+
+
+class GBinary a where
+  gput :: a -> Put
+  gget :: Get a
+
+
+instance GBinary U where
+  gput _ = return ()
+  gget = return U
+
+instance (GBinary a) => GBinary (CEq c p p a) where
+  gput (C a) = gput a
+  gget = liftM C gget
+{-
+instance (GBinary a) => GBinary (CEq c p q a) where
+  gput _ = return ()
+  gget = error "gget: CEq impossible"
+-}
+instance (GBinary a, GBinary b) => GBinary (a :+: b) where
+  gput (L a) = put (0 :: Word8) >> gput a
+  gput (R a) = put (1 :: Word8) >> gput a
+  gget = do t <- get :: Get Word8
+            case t of
+              0 -> liftM L gget
+              1 -> liftM R gget
+              _ -> error "gget: :+: impossible"
+
+instance (GBinary a, GBinary b) => GBinary (a :*: b) where
+  gput (a :*: b) = gput a >> gput b
+  gget = liftM2 (:*:) gget gget
+
+instance (Binary a) => GBinary (Rec a) where
+  gput (Rec a) = put a
+  gget = liftM Rec get
+
+instance (Binary a) => GBinary (Var a) where
+  gput (Var a) = put a
+  gget = liftM Var get
+
+
+-- Default implementations
+getDefault :: (Representable a, GBinary (Rep a)) => Get a
+getDefault = fmap to gget
+
+putDefault :: (Representable a, GBinary (Rep a)) => a -> Put
+putDefault = gput . from
src/HarmTrace/HAnTree/HAn.hs view
@@ -1,244 +1,244 @@-{-# LANGUAGE TemplateHaskell                #-}-{-# LANGUAGE EmptyDataDecls                 #-}-{-# LANGUAGE TypeFamilies                   #-}-{-# LANGUAGE GADTs                          #-}--module HarmTrace.HAnTree.HAn where --import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens-import HarmTrace.HAnTree.Binary--import Generics.Instant.TH-import Control.DeepSeq-import Data.Binary------------------------------------------------------------------------------------- Datatypes for representing Harmonic Analyses (at the value level)------------------------------------------------------------------------------------- H_armonic An_alysis wrapper datatype, the Int represents the duration-data HAn   = HAn      !Int !String -           | HAnFunc  !HFunc-           | HAnPrep  !Prep-           | HAnTrans !Trans -           | HAnChord !ChordToken -           -            -- duration Mode constructor_ix specials-data HFunc = Ton !Int !Mode !Int !(Maybe Spec)-           | Dom !Int !Mode !Int !(Maybe Spec) -           | Sub !Int !Mode !Int !(Maybe Spec) -           | P -           | PD-           | PT       -  -data Spec  = Blues | MinBorrow | Parallel          -  deriving Eq       ---- Preparations, like secondary dominants etc. that cause a "split" in the tree  -data Prep  = SecDom   !Int !ScaleDegree -- "V/X"-           | SecMin   !Int !ScaleDegree -- "v/X"           -           | DiatDom  !Int !ScaleDegree -- "Vd"-           | NoPrep---- Scalde degree transformations, e.g. tritone substitutions etc.-data Trans = Trit     !Int !ScaleDegree -- "bII/X"    -           | DimTrit  !Int !ScaleDegree -- "bIIb9/X"    -           | DimTrans !Int !ScaleDegree -- "VII0"      -           | NoTrans------------------------------------------------------------------------------------- Binary instances-----------------------------------------------------------------------------------deriveAllL [ ''HAn, ''Trans, ''Prep, ''HFunc, ''Spec]--instance Binary HAn where-  put = putDefault-  get = getDefault-instance Binary Trans where-  put = putDefault-  get = getDefault-instance Binary Prep where-  put = putDefault-  get = getDefault-instance Binary HFunc where-  put = putDefault-  get = getDefault-instance Binary Spec where-  put = putDefault-  get = getDefault------------------------------------------------------------------------------------- NFData instances-----------------------------------------------------------------------------------instance NFData HAn where-  rnf (HAn d s   ) = rnf d `seq` rnf s-  rnf (HAnFunc  a) = rnf a-  rnf (HAnTrans a) = rnf a-  rnf (HAnPrep  a) = rnf a-  rnf (HAnChord a) = seq a ()--instance NFData HFunc where-  rnf (Ton a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d-  rnf (Dom a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d-  rnf (Sub a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d-  rnf P             = ()-  rnf PD            = ()-  rnf PT            = ()-  -instance NFData Prep where-  rnf (SecDom i d) = rnf i `seq` d `seq` ()-  rnf (SecMin i d) = rnf i `seq` d `seq` ()-  rnf (DiatDom i d) = rnf i `seq` d `seq` ()-  rnf NoPrep = ()-  -instance NFData Trans where  -  rnf (Trit   i d) = rnf i `seq` d `seq` ()-  rnf (DimTrit  i d) = rnf i `seq` d `seq` ()-  rnf (DimTrans i d) = rnf i `seq` d `seq` ()-  rnf NoTrans = ()-  -instance NFData Spec where-  rnf Blues     = ()-  rnf MinBorrow = ()-  rnf Parallel  = ()-  ------------------------------------------------------------------------------------ Durations set and get instances---------------------------------------------------------------------------------  ---- Yes, I know these can be generic functions, but with my current generic --- programming skils it is faster two write them by hand.-class GetDur a where  -  getDur :: a -> Int-  -instance GetDur HAn where-  getDur (HAn  d  _s) = d-  getDur (HAnFunc  a) = getDur a  -  getDur (HAnPrep  a) = getDur a  -  getDur (HAnTrans a) = getDur a  -  getDur (HAnChord a) = dur a-  -instance GetDur HFunc where-  getDur (Ton i _ _ _) = i-  getDur (Dom i _ _ _) = i-  getDur (Sub i _ _ _) = i-  getDur _             = 0-  -instance GetDur Prep where-  getDur (SecDom i _) = i-  getDur (SecMin i _) = i-  getDur (DiatDom  i _) = i-  getDur NoPrep      = 0--instance GetDur Trans where  -  getDur (Trit   i _) = i-  getDur (DimTrit  i _) = i-  getDur (DimTrans i _) = i-  getDur NoTrans = 0--instance GetDur (Chord a) where-  getDur = duration- -class SetDur a where  -  setDur :: a -> Int -> a-  -instance SetDur HAn where-  setDur (HAn  _ s)   i = (HAn  i s) -  setDur (HAnFunc  a) i = (HAnFunc (setDur a i)) -  setDur (HAnTrans a) i = (HAnTrans (setDur a i)) -  setDur a           _i = a-  -instance SetDur HFunc where-  setDur (Ton _d m i s) d = (Ton d m i s)-  setDur (Dom _d m i s) d = (Dom d m i s)-  setDur (Sub _d m i s) d = (Sub d m i s)-  setDur a _  = a-  -instance SetDur Prep where-  setDur (SecDom   _d sd) d = (SecDom   d sd)-  setDur (SecMin   _d sd) d = (SecMin   d sd)-  setDur (DiatDom  _d sd) d = (DiatDom  d sd)-  setDur NoPrep _ = NoPrep-  -instance SetDur Trans where  -  setDur (Trit     _d sd) d = (Trit     d sd)-  setDur (DimTrit  _d sd) d = (DimTrit  d sd)-  setDur (DimTrans _d sd) d = (DimTrans d sd)-  setDur NoTrans _ = NoTrans- ------------------------------------------------------------------------------------ Eq instances---------------------------------------------------------------------------------  - -instance Eq HAn where -  (HAn _ s)        == (HAn _ s2)        = s     == s2-  (HAnChord chord) == (HAnChord chord2) = chord == chord2-  (HAnFunc  hfunk) == (HAnFunc  hfunk2) = hfunk == hfunk2-  (HAnTrans trans) == (HAnTrans trans2) = trans == trans2-  _ == _ = False-   -instance Eq HFunc where-  -- ignore duration for now-  (Ton _ b c d) == (Ton _ b2 c2 d2) = b == b2 && c == c2 && d == d2 -  (Dom _ b c d) == (Dom _ b2 c2 d2) = b == b2 && c == c2 && d == d2-  (Sub _ b c d) == (Sub _ b2 c2 d2) = b == b2 && c == c2 && d == d2-  P             == P   = True         -  PD            == PD  = True         -  PT            == PT  = True    -  _             == _   = False   -  -instance Eq Prep where-  (SecDom   _dur sd) == (SecDom   _dur2 sd2) = sd == sd2 -  (SecMin   _dur sd) == (SecMin   _dur2 sd2) = sd == sd2 -  (DiatDom  _dur sd) == (DiatDom  _dur2 sd2) = sd == sd2 -  NoPrep             == NoPrep = True-  _                  == _      = False-  -instance Eq Trans where  -  (Trit     _dur sd) == (Trit     _dur2 sd2) = sd == sd2 -  (DimTrit  _dur sd) == (DimTrit  _dur2 sd2) = sd == sd2 -  (DimTrans _dur sd) == (DimTrans _dur2 sd2) = sd == sd2 -  NoTrans           == NoTrans = True-  _                 == _       = False    ------------------------------------------------------------------------------------- Eq and Show instances----------------------------------------------------------------------------------  -instance Show Prep where-  show (SecDom   l d) = "V/"     ++ show d ++ '_' : show l-  show (SecMin   l d) = "v/"     ++ show d ++ '_' : show l-  show (DiatDom  l d) = "Vd/"++ show d ++ '_' : show l -  show NoPrep = "np"--instance Show Trans where  -  show (Trit     l d) = "IIb/"   ++ show d ++ '_' : show l-  show (DimTrit  l d) = "IIb9b/" ++ show d ++ '_' : show l-  show (DimTrans l d) = show d   ++ "0"    ++ '_' : show l-  show (NoTrans)      = "nt"--instance Show HAn where -  show (HAn l con)     = con ++ "_s"  ++ '_' : show l-  show (HAnChord chord) = show chord -  show (HAnFunc  hfunk) = show hfunk -  show (HAnTrans trans) = show trans -  show (HAnPrep  prep ) = show prep-  -instance Show HFunc where-  show (Ton l mode i s) = "T" ++ show mode       ++ '_' : show i -                              ++ maybe "" show s ++ '_' : show l-  show (Dom l mode i s) = "D" ++ show mode       ++ '_' : show i -                              ++ maybe "" show s ++ '_' : show l-  show (Sub l mode i s) = "S" ++ show mode       ++ '_' : show i -                              ++ maybe "" show s ++ '_' : show l-  show (P )             = "Piece"-  show (PT)             = "PT"-  show (PD)             = "PD" --instance Show Spec where-  show Blues     = "bls" -  show MinBorrow = "bor"+{-# LANGUAGE TemplateHaskell                #-}
+{-# LANGUAGE EmptyDataDecls                 #-}
+{-# LANGUAGE TypeFamilies                   #-}
+{-# LANGUAGE GADTs                          #-}
+
+module HarmTrace.HAnTree.HAn where 
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens
+import HarmTrace.HAnTree.Binary
+
+import Generics.Instant.TH
+import Control.DeepSeq
+import Data.Binary
+
+--------------------------------------------------------------------------------
+-- Datatypes for representing Harmonic Analyses (at the value level)
+--------------------------------------------------------------------------------
+
+-- H_armonic An_alysis wrapper datatype, the Int represents the duration
+data HAn   = HAn      !Int !String 
+           | HAnFunc  !HFunc
+           | HAnPrep  !Prep
+           | HAnTrans !Trans 
+           | HAnChord !ChordToken 
+           
+            -- duration Mode constructor_ix specials
+data HFunc = Ton !Int !Mode !Int !(Maybe Spec)
+           | Dom !Int !Mode !Int !(Maybe Spec) 
+           | Sub !Int !Mode !Int !(Maybe Spec) 
+           | P 
+           | PD
+           | PT       
+  
+data Spec  = Blues | MinBorrow | Parallel          
+  deriving Eq       
+
+-- Preparations, like secondary dominants etc. that cause a "split" in the tree  
+data Prep  = SecDom   !Int !ScaleDegree -- "V/X"
+           | SecMin   !Int !ScaleDegree -- "v/X"           
+           | DiatDom  !Int !ScaleDegree -- "Vd"
+           | NoPrep
+
+-- Scalde degree transformations, e.g. tritone substitutions etc.
+data Trans = Trit     !Int !ScaleDegree -- "bII/X"    
+           | DimTrit  !Int !ScaleDegree -- "bIIb9/X"    
+           | DimTrans !Int !ScaleDegree -- "VII0"      
+           | NoTrans
+
+--------------------------------------------------------------------------------
+-- Binary instances
+--------------------------------------------------------------------------------
+
+deriveAllL [ ''HAn, ''Trans, ''Prep, ''HFunc, ''Spec]
+
+instance Binary HAn where
+  put = putDefault
+  get = getDefault
+instance Binary Trans where
+  put = putDefault
+  get = getDefault
+instance Binary Prep where
+  put = putDefault
+  get = getDefault
+instance Binary HFunc where
+  put = putDefault
+  get = getDefault
+instance Binary Spec where
+  put = putDefault
+  get = getDefault
+
+--------------------------------------------------------------------------------
+-- NFData instances
+--------------------------------------------------------------------------------
+
+instance NFData HAn where
+  rnf (HAn d s   ) = rnf d `seq` rnf s
+  rnf (HAnFunc  a) = rnf a
+  rnf (HAnTrans a) = rnf a
+  rnf (HAnPrep  a) = rnf a
+  rnf (HAnChord a) = seq a ()
+
+instance NFData HFunc where
+  rnf (Ton a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
+  rnf (Dom a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
+  rnf (Sub a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
+  rnf P             = ()
+  rnf PD            = ()
+  rnf PT            = ()
+  
+instance NFData Prep where
+  rnf (SecDom i d) = rnf i `seq` d `seq` ()
+  rnf (SecMin i d) = rnf i `seq` d `seq` ()
+  rnf (DiatDom i d) = rnf i `seq` d `seq` ()
+  rnf NoPrep = ()
+  
+instance NFData Trans where  
+  rnf (Trit   i d) = rnf i `seq` d `seq` ()
+  rnf (DimTrit  i d) = rnf i `seq` d `seq` ()
+  rnf (DimTrans i d) = rnf i `seq` d `seq` ()
+  rnf NoTrans = ()
+  
+instance NFData Spec where
+  rnf Blues     = ()
+  rnf MinBorrow = ()
+  rnf Parallel  = ()
+  
+--------------------------------------------------------------------------------
+-- Durations set and get instances
+--------------------------------------------------------------------------------  
+
+-- Yes, I know these can be generic functions, but with my current generic 
+-- programming skils it is faster two write them by hand.
+class GetDur a where  
+  getDur :: a -> Int
+  
+instance GetDur HAn where
+  getDur (HAn  d  _s) = d
+  getDur (HAnFunc  a) = getDur a  
+  getDur (HAnPrep  a) = getDur a  
+  getDur (HAnTrans a) = getDur a  
+  getDur (HAnChord a) = dur a
+  
+instance GetDur HFunc where
+  getDur (Ton i _ _ _) = i
+  getDur (Dom i _ _ _) = i
+  getDur (Sub i _ _ _) = i
+  getDur _             = 0
+  
+instance GetDur Prep where
+  getDur (SecDom i _) = i
+  getDur (SecMin i _) = i
+  getDur (DiatDom  i _) = i
+  getDur NoPrep      = 0
+
+instance GetDur Trans where  
+  getDur (Trit   i _) = i
+  getDur (DimTrit  i _) = i
+  getDur (DimTrans i _) = i
+  getDur NoTrans = 0
+
+instance GetDur (Chord a) where
+  getDur = duration
+ 
+class SetDur a where  
+  setDur :: a -> Int -> a
+  
+instance SetDur HAn where
+  setDur (HAn  _ s)   i = (HAn  i s) 
+  setDur (HAnFunc  a) i = (HAnFunc (setDur a i)) 
+  setDur (HAnTrans a) i = (HAnTrans (setDur a i)) 
+  setDur a           _i = a
+  
+instance SetDur HFunc where
+  setDur (Ton _d m i s) d = (Ton d m i s)
+  setDur (Dom _d m i s) d = (Dom d m i s)
+  setDur (Sub _d m i s) d = (Sub d m i s)
+  setDur a _  = a
+  
+instance SetDur Prep where
+  setDur (SecDom   _d sd) d = (SecDom   d sd)
+  setDur (SecMin   _d sd) d = (SecMin   d sd)
+  setDur (DiatDom  _d sd) d = (DiatDom  d sd)
+  setDur NoPrep _ = NoPrep
+  
+instance SetDur Trans where  
+  setDur (Trit     _d sd) d = (Trit     d sd)
+  setDur (DimTrit  _d sd) d = (DimTrit  d sd)
+  setDur (DimTrans _d sd) d = (DimTrans d sd)
+  setDur NoTrans _ = NoTrans
+ 
+--------------------------------------------------------------------------------
+-- Eq instances
+--------------------------------------------------------------------------------  
+ 
+instance Eq HAn where 
+  (HAn _ s)        == (HAn _ s2)        = s     == s2
+  (HAnChord chord) == (HAnChord chord2) = chord == chord2
+  (HAnFunc  hfunk) == (HAnFunc  hfunk2) = hfunk == hfunk2
+  (HAnTrans trans) == (HAnTrans trans2) = trans == trans2
+  _ == _ = False
+   
+instance Eq HFunc where
+  -- ignore duration for now
+  (Ton _ b c d) == (Ton _ b2 c2 d2) = b == b2 && c == c2 && d == d2 
+  (Dom _ b c d) == (Dom _ b2 c2 d2) = b == b2 && c == c2 && d == d2
+  (Sub _ b c d) == (Sub _ b2 c2 d2) = b == b2 && c == c2 && d == d2
+  P             == P   = True         
+  PD            == PD  = True         
+  PT            == PT  = True    
+  _             == _   = False   
+  
+instance Eq Prep where
+  (SecDom   _dur sd) == (SecDom   _dur2 sd2) = sd == sd2 
+  (SecMin   _dur sd) == (SecMin   _dur2 sd2) = sd == sd2 
+  (DiatDom  _dur sd) == (DiatDom  _dur2 sd2) = sd == sd2 
+  NoPrep             == NoPrep = True
+  _                  == _      = False
+  
+instance Eq Trans where  
+  (Trit     _dur sd) == (Trit     _dur2 sd2) = sd == sd2 
+  (DimTrit  _dur sd) == (DimTrit  _dur2 sd2) = sd == sd2 
+  (DimTrans _dur sd) == (DimTrans _dur2 sd2) = sd == sd2 
+  NoTrans           == NoTrans = True
+  _                 == _       = False    
+
+--------------------------------------------------------------------------------
+-- Eq and Show instances
+--------------------------------------------------------------------------------
+  
+instance Show Prep where
+  show (SecDom   l d) = "V/"     ++ show d ++ '_' : show l
+  show (SecMin   l d) = "v/"     ++ show d ++ '_' : show l
+  show (DiatDom  l d) = "Vd/"++ show d ++ '_' : show l 
+  show NoPrep = "np"
+
+instance Show Trans where  
+  show (Trit     l d) = "IIb/"   ++ show d ++ '_' : show l
+  show (DimTrit  l d) = "IIb9b/" ++ show d ++ '_' : show l
+  show (DimTrans l d) = show d   ++ "0"    ++ '_' : show l
+  show (NoTrans)      = "nt"
+
+instance Show HAn where 
+  show (HAn l con)     = con ++ "_s"  ++ '_' : show l
+  show (HAnChord chord) = show chord 
+  show (HAnFunc  hfunk) = show hfunk 
+  show (HAnTrans trans) = show trans 
+  show (HAnPrep  prep ) = show prep
+  
+instance Show HFunc where
+  show (Ton l mode i s) = "T" ++ show mode       ++ '_' : show i 
+                              ++ maybe "" show s ++ '_' : show l
+  show (Dom l mode i s) = "D" ++ show mode       ++ '_' : show i 
+                              ++ maybe "" show s ++ '_' : show l
+  show (Sub l mode i s) = "S" ++ show mode       ++ '_' : show i 
+                              ++ maybe "" show s ++ '_' : show l
+  show (P )             = "Piece"
+  show (PT)             = "PT"
+  show (PD)             = "PD" 
+
+instance Show Spec where
+  show Blues     = "bls" 
+  show MinBorrow = "bor"
   show Parallel  = "par"
src/HarmTrace/HAnTree/HAnParser.hs view
@@ -1,43 +1,43 @@-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}-{-# LANGUAGE FlexibleContexts #-}--module HarmTrace.HAnTree.HAnParser where--import HarmTrace.Base.Parsing-import HarmTrace.Base.MusicRep (Mode(..))-import HarmTrace.HAnTree.HAn--import Data.Maybe (isJust, fromJust)------------------------------------------------------------------------------------ A Small Parser for Parsing MIR constructors---------------------------------------------------------------------------------     ---- this top-level function parses a constructor name and returns the --- corresponding HAn data type. N.B. we can implement the catch all--- case as a parser, because it accepts everything and one will get an --- "ambiguous parser?" error.s-parseHAn :: ListLike state Char => state -> HAn                          -parseHAn inp -  | isJust a  = fromJust a-  | otherwise = parseData (HAn 1 <$> pAnyStr) inp where -- catch all case-      a = parseData (pMaybe $ HAnFunc  <$> pHFunc) inp--        -pHFunc :: Parser HFunc-pHFunc =    Ton 1 <$ pSym 'T' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec-        <|> Dom 1 <$ pSym 'D' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec-        <|> Sub 1 <$ pSym 'S' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec-        <|> PD    <$ pString "PD" -        <|> PT    <$ pString "PT" --pMode :: Parser Mode-pMode =     MinMode <$ pSym 'm'-        <|> MajMode <$ pString "" --pSpec :: Parser Spec       -pSpec =     MinBorrow <$ pString "_bor" -        <|> Blues     <$ pString "_bls" -        <|> Parallel  <$ pString "_par" -     -pAnyStr :: Parser String        +{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+module HarmTrace.HAnTree.HAnParser where
+
+import HarmTrace.Base.Parsing
+import HarmTrace.Base.MusicRep (Mode(..))
+import HarmTrace.HAnTree.HAn
+
+import Data.Maybe (isJust, fromJust)
+--------------------------------------------------------------------------------
+-- A Small Parser for Parsing MIR constructors
+--------------------------------------------------------------------------------     
+
+-- this top-level function parses a constructor name and returns the 
+-- corresponding HAn data type. N.B. we can implement the catch all
+-- case as a parser, because it accepts everything and one will get an 
+-- "ambiguous parser?" error.s
+parseHAn :: ListLike state Char => state -> HAn                          
+parseHAn inp 
+  | isJust a  = fromJust a
+  | otherwise = parseData (HAn 1 <$> pAnyStr) inp where -- catch all case
+      a = parseData (pMaybe $ HAnFunc  <$> pHFunc) inp
+
+        
+pHFunc :: Parser HFunc
+pHFunc =    Ton 1 <$ pSym 'T' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec
+        <|> Dom 1 <$ pSym 'D' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec
+        <|> Sub 1 <$ pSym 'S' <*> pMode <* pSym '_' <*> pInteger <*> pMaybe pSpec
+        <|> PD    <$ pString "PD" 
+        <|> PT    <$ pString "PT" 
+
+pMode :: Parser Mode
+pMode =     MinMode <$ pSym 'm'
+        <|> MajMode <$ pString "" 
+
+pSpec :: Parser Spec       
+pSpec =     MinBorrow <$ pString "_bor" 
+        <|> Blues     <$ pString "_bls" 
+        <|> Parallel  <$ pString "_par" 
+     
+pAnyStr :: Parser String        
 pAnyStr =  pAtMost 15 pAscii
src/HarmTrace/HAnTree/PostProcess.hs view
@@ -1,109 +1,108 @@-module HarmTrace.HAnTree.PostProcess ( PPOption(..)-                                     , expandChordDurations-                                     , removePDPT, removeInsertions-                                     , mergeDelChords ) where--import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens as CT-import HarmTrace.HAnTree.HAn-import HarmTrace.HAnTree.Tree--import Data.List(partition, find)-import Data.Maybe (isJust, fromJust)--- import Debug.Trace---- Parser stuff-import Text.ParserCombinators.UU.BasicInstances as PC----- Optional post-processing operations   -data PPOption  = RemoveInsertions | RemovePDPT -               | MergeDelChords   | ExpandChordDurations -- | ... ?-  deriving (Eq)---- propagates the durations of the chords up into the tree-expandChordDurations :: Tree HAn -> Tree HAn-expandChordDurations (Node h [] a) = (Node h [] a)  where-expandChordDurations (Node h cs a) = (Node (setDur h d) cs' a)  where-     cs' = map expandChordDurations cs-     d   = sum $ map (getDur . getLabel) cs'---- removes some nodes from the tree structure that are not important for--- similarity estimation-removePDPT :: Tree HAn -> Tree HAn-removePDPT = removeBy (\l -> l `elem`  [(HAnFunc PD), (HAnFunc PT)])---- Removes the HAn Nodes that were inserted by the parsing process-removeInsertions :: Tree HAn -> Tree HAn-removeInsertions = head . fst . remIns--remIns :: Tree HAn -> ([Tree HAn], Bool)-remIns l@(Node han [ ] _ ) = if isInserted han then ([],True) else ([l],False)-remIns   (Node han  cn pn) = ([Node han (concat trees) pn], False) where-  (trees,_ ) = unzip . filter (not . snd) $ map remIns cn-  --- returns True if a HAn is Inserted-isInserted :: HAn -> Bool-isInserted (HAnChord (ChordToken _ _ _ CT.Inserted _ _)) = True-isInserted _                                             = False------------------------------------------------------------------------------------- PostProcessing a Tree HAn with the chords deleted by the parser------------------------------------------------------------------------------------- top level function for merging deleted chords--- TODO: could be made to work on [ChordToken] instead of [ChordLabel]-mergeDelChords :: Key -> [Error Int] -> [ChordLabel] -> Tree HAn -> Tree HAn-mergeDelChords key pErr tok tree = -  head $ mergeDelChords' key (groupNeighbours (filterErrorPos pErr tok)) [tree] -  --- N.B. there is a bug in this function: if the first chords is deleted --- it is not placed back because there is no chord in the tree before--- the deleted chord.---- merges the deleted chords back into the parsed Tree HAn-mergeDelChords' :: Key -> [[ChordLabel]] -> [Tree HAn] -> [Tree HAn]-mergeDelChords' _key [] tree = tree-mergeDelChords' _key _  []    = []-mergeDelChords'  key d (i@(Node (HAnChord c) _ _):ts)-  | status c == CT.Inserted = i : mergeDelChords' key d ts-  | isJust m  = i : (toDelHAn key $ fromJust m) ++ mergeDelChords' key d ts-  | otherwise = i : mergeDelChords' key d ts-  where m = find (\x -> (getLoc . last $ chords c) + 1 == (getLoc $ head x)) d-mergeDelChords'  key chrds (Node han cs pn : ts) =-  Node han (mergeDelChords' key chrds cs) pn : mergeDelChords' key chrds ts---- transforms a (deleted) chord into a Tree HAn data type-toDelHAn :: Key -> [ChordLabel] -> [Tree HAn]-toDelHAn key m = map f m where-  f c@(Chord r sh _add _loc d) = (Node (HAnChord-    (ChordToken (toScaleDegree key r) (toClassType sh) [c] CT.Deleted 1 d))-    [] Nothing)---- returns the deleted chords, given a list of errors and the input tokes-filterErrorPos :: [Error Int] -> [Chord a] -> [Chord a]-filterErrorPos e c = filter (\x -> getLoc x `elem` dels) chrds ++ cDelsAtEnd-  where-  (delsAtEnd, dels) = partition (== (-1)) . map gPos $ filter f e-  (chrds,cDelsAtEnd) = splitAt (length c - length delsAtEnd) c-  gPos (PC.Inserted _ p _) = p-  gPos (PC.Deleted  _ p _) = p-  gPos (DeletedAtEnd _)    = (-1)-  gPos (Replaced _ _ p _)  = p-  f    (PC.Inserted _ _ _) = False-  f    (PC.Deleted  _ _ _) = True-  f    (DeletedAtEnd _)    = True-  f    (Replaced _ _ _ _)  = False---- groups the deleted chord tokens that are neighbours, if we were not--- grouping chords but Integers, a result could look like:--- groupNeighbours [1,2,7,8,9,11,13,16,17] = [[1,2],[7,8,9],[11],[13],[16,17]]-groupNeighbours :: [Chord a] -> [[Chord a]]-groupNeighbours []     = []-groupNeighbours (x:xs) = let (grp,tl) = get x xs in grp : groupNeighbours tl--- splits a list into a list with neighbours and a tail-get :: Chord a -> [Chord a] -> ([Chord a],[Chord a])-get a l@[]  = ([a],l)-get a l@(b:cs)-  | (getLoc a) + 1 == getLoc b = (a:bs,cs')+module HarmTrace.HAnTree.PostProcess ( PPOption(..)
+                                     , expandChordDurations
+                                     , removePDPT, removeInsertions
+                                     , mergeDelChords ) where
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens as CT
+import HarmTrace.HAnTree.HAn
+import HarmTrace.HAnTree.Tree
+
+import Data.List(partition, find)
+import Data.Maybe (isJust, fromJust)
+-- import Debug.Trace
+
+-- Parser stuff
+import Text.ParserCombinators.UU.BasicInstances as PC
+
+
+-- Optional post-processing operations   
+data PPOption  = RemoveInsertions | RemovePDPT 
+               | MergeDelChords   | ExpandChordDurations  deriving (Eq)
+
+-- propagates the durations of the chords up into the tree
+expandChordDurations :: Tree HAn -> Tree HAn
+expandChordDurations (Node h [] a) = (Node h [] a)  where
+expandChordDurations (Node h cs a) = (Node (setDur h d) cs' a)  where
+     cs' = map expandChordDurations cs
+     d   = sum $ map (getDur . getLabel) cs'
+
+-- removes some nodes from the tree structure that are not important for
+-- similarity estimation
+removePDPT :: Tree HAn -> Tree HAn
+removePDPT = removeBy (\l -> l `elem`  [(HAnFunc PD), (HAnFunc PT)])
+
+-- Removes the HAn Nodes that were inserted by the parsing process
+removeInsertions :: Tree HAn -> Tree HAn
+removeInsertions = head . fst . remIns
+
+remIns :: Tree HAn -> ([Tree HAn], Bool)
+remIns l@(Node han [ ] _ ) = if isInserted han then ([],True) else ([l],False)
+remIns   (Node han  cn pn) = ([Node han (concat trees) pn], False) where
+  (trees,_ ) = unzip . filter (not . snd) $ map remIns cn
+  
+-- returns True if a HAn is Inserted
+isInserted :: HAn -> Bool
+isInserted (HAnChord (ChordToken _ _ _ CT.Inserted _ _)) = True
+isInserted _                                             = False
+
+--------------------------------------------------------------------------------
+-- PostProcessing a Tree HAn with the chords deleted by the parser
+--------------------------------------------------------------------------------
+
+-- top level function for merging deleted chords
+-- TODO: could be made to work on [ChordToken] instead of [ChordLabel]
+mergeDelChords :: Key -> [Error Int] -> [ChordLabel] -> Tree HAn -> Tree HAn
+mergeDelChords key pErr tok tree = 
+  head $ mergeDelChords' key (groupNeighbours (filterErrorPos pErr tok)) [tree] 
+  
+-- N.B. there is a bug in this function: if the first chords is deleted 
+-- it is not placed back because there is no chord in the tree before
+-- the deleted chord.
+
+-- merges the deleted chords back into the parsed Tree HAn
+mergeDelChords' :: Key -> [[ChordLabel]] -> [Tree HAn] -> [Tree HAn]
+mergeDelChords' _key [] tree = tree
+mergeDelChords' _key _  []    = []
+mergeDelChords'  key d (i@(Node (HAnChord c) _ _):ts)
+  | status c == CT.Inserted = i : mergeDelChords' key d ts
+  | isJust m  = i : (toDelHAn key $ fromJust m) ++ mergeDelChords' key d ts
+  | otherwise = i : mergeDelChords' key d ts
+  where m = find (\x -> (getLoc . last $ chords c) + 1 == (getLoc $ head x)) d
+mergeDelChords'  key chrds (Node han cs pn : ts) =
+  Node han (mergeDelChords' key chrds cs) pn : mergeDelChords' key chrds ts
+
+-- transforms a (deleted) chord into a Tree HAn data type
+toDelHAn :: Key -> [ChordLabel] -> [Tree HAn]
+toDelHAn key m = map f m where
+  f c@(Chord r sh _add _loc d) = (Node (HAnChord
+    (ChordToken (toScaleDegree key r) (toClassType sh) [c] CT.Deleted 1 d))
+    [] Nothing)
+
+-- returns the deleted chords, given a list of errors and the input tokes
+filterErrorPos :: [Error Int] -> [Chord a] -> [Chord a]
+filterErrorPos e c = filter (\x -> getLoc x `elem` dels) chrds ++ cDelsAtEnd
+  where
+  (delsAtEnd, dels) = partition (== (-1)) . map gPos $ filter f e
+  (chrds,cDelsAtEnd) = splitAt (length c - length delsAtEnd) c
+  gPos (PC.Inserted _ p _) = p
+  gPos (PC.Deleted  _ p _) = p
+  gPos (DeletedAtEnd _)    = (-1)
+  gPos (Replaced _ _ p _)  = p
+  f    (PC.Inserted _ _ _) = False
+  f    (PC.Deleted  _ _ _) = True
+  f    (DeletedAtEnd _)    = True
+  f    (Replaced _ _ _ _)  = False
+
+-- groups the deleted chord tokens that are neighbours, if we were not
+-- grouping chords but Integers, a result could look like:
+-- groupNeighbours [1,2,7,8,9,11,13,16,17] = [[1,2],[7,8,9],[11],[13],[16,17]]
+groupNeighbours :: [Chord a] -> [[Chord a]]
+groupNeighbours []     = []
+groupNeighbours (x:xs) = let (grp,tl) = get x xs in grp : groupNeighbours tl
+-- splits a list into a list with neighbours and a tail
+get :: Chord a -> [Chord a] -> ([Chord a],[Chord a])
+get a l@[]  = ([a],l)
+get a l@(b:cs)
+  | (getLoc a) + 1 == getLoc b = (a:bs,cs')
   | otherwise  = ([a],l) where (bs,cs') = get b cs
src/HarmTrace/HAnTree/ToHAnTree.hs view
@@ -1,48 +1,48 @@-{-# LANGUAGE TypeOperators            #-}-{-# LANGUAGE FlexibleInstances        #-}-{-# LANGUAGE FlexibleContexts         #-}-{-# LANGUAGE GADTs                    #-}--module HarmTrace.HAnTree.ToHAnTree ( GTree(..) , HAn(..) , gTreeDefault-                                   , gTreeHead , emptyHAnTree ) where--import Generics.Instant.Base-import HarmTrace.HAnTree.Tree (Tree(..))-import HarmTrace.HAnTree.HAn-import HarmTrace.HAnTree.HAnParser--class GTree a where-  gTree :: a -> [Tree HAn] --instance GTree U where-  gTree U = [Node (HAn 0 "U") [] Nothing]-  -instance (GTree a, GTree b) => GTree (a :+: b) where-  gTree (L x) = gTree x-  gTree (R x) = gTree x-  -instance (GTree a, Constructor c) => GTree (CEq c p q a) where-  gTree c@(C a) = [Node (parseHAn (conName c)) (gTree a) Nothing]-    -instance (GTree a, GTree b) => GTree (a :*: b) where-  gTree (a :*: b) = gTree a ++ gTree b--instance GTree a => GTree (Rec a) where-  gTree (Rec x) = gTree x-  -instance GTree a => GTree (Var a) where-  gTree (Var x) = gTree x---instance GTree a => GTree [a] where-  gTree x = concatMap gTree x---- Dispatcher-gTreeDefault :: (Representable a, GTree (Rep a)) => a -> [Tree HAn]-gTreeDefault = gTree . from--gTreeHead :: (GTree a) => a -> Tree HAn-gTreeHead = head . gTree--emptyHAnTree :: Tree HAn+{-# LANGUAGE TypeOperators            #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE FlexibleContexts         #-}
+{-# LANGUAGE GADTs                    #-}
+
+module HarmTrace.HAnTree.ToHAnTree ( GTree(..) , HAn(..) , gTreeDefault
+                                   , gTreeHead , emptyHAnTree ) where
+
+import Generics.Instant.Base
+import HarmTrace.HAnTree.Tree (Tree(..))
+import HarmTrace.HAnTree.HAn
+import HarmTrace.HAnTree.HAnParser
+
+class GTree a where
+  gTree :: a -> [Tree HAn] 
+
+instance GTree U where
+  gTree U = [Node (HAn 0 "U") [] Nothing]
+  
+instance (GTree a, GTree b) => GTree (a :+: b) where
+  gTree (L x) = gTree x
+  gTree (R x) = gTree x
+  
+instance (GTree a, Constructor c) => GTree (CEq c p q a) where
+  gTree c@(C a) = [Node (parseHAn (conName c)) (gTree a) Nothing]
+    
+instance (GTree a, GTree b) => GTree (a :*: b) where
+  gTree (a :*: b) = gTree a ++ gTree b
+
+instance GTree a => GTree (Rec a) where
+  gTree (Rec x) = gTree x
+  
+instance GTree a => GTree (Var a) where
+  gTree (Var x) = gTree x
+
+
+instance GTree a => GTree [a] where
+  gTree x = concatMap gTree x
+
+-- Dispatcher
+gTreeDefault :: (Representable a, GTree (Rep a)) => a -> [Tree HAn]
+gTreeDefault = gTree . from
+
+gTreeHead :: (GTree a) => a -> Tree HAn
+gTreeHead = head . gTree
+
+emptyHAnTree :: Tree HAn
 emptyHAnTree = Node (HAn 0 "empty") [] Nothing
src/HarmTrace/HAnTree/Tree.hs view
@@ -1,168 +1,168 @@--module HarmTrace.HAnTree.Tree where--import Data.Maybe-import qualified Data.Binary as B-import Control.Monad.State-import Data.List (maximumBy, genericLength)-import Control.DeepSeq------------------------------------------------------------------------------------- Tree datastructure------------------------------------------------------------------------------------- our (temporary) tree data structure, a leaf is simply represented--- as Node a [] or NodePn a [] pn-data Tree a = Node { getLabel :: !a, getChild :: ![Tree a], getPn :: !(Maybe Int) }-  deriving Eq-  --- specific show instance for pretty printing-instance (Show a) => Show (Tree a) where -  show (Node a children _) =  --filter (\x -> '\"' /= x && '\'' /= x) -                            desc where-    desc = ('[' : show a) ++ concatMap show children ++ "]"--instance (NFData a) => NFData (Tree a) where-  rnf (Node l c p) = rnf l `seq` rnf c `seq` rnf p--instance (B.Binary a) => B.Binary (Tree a) where-  put (Node l c p) = B.put l >> B.put c >> B.put p-  get = liftM3 Node B.get B.get B.get-    ------------------------------------------------------------------------------------ Creating Trees---------------------------------------------------------------------------------    -    --- returns a Tree data structure, given a string representation of a tree.-strTree :: String -> Tree String -strTree = head . strTree' where-  strTree' [] = []-  strTree' (c:cs) -    | c == '['  = Node lab (strTree' a) Nothing : strTree' b-    | c == ']'  = strTree' cs-    | otherwise = error ("cannot parse, not well formed tree description: " -                ++ [c]) where -                  (x  ,b) = splitAt (findClose cs) cs-                  (lab,a) = span (\y -> (y /= '[') && (y /= ']')) x---- given a string representation of a tree, e.g. "[b][c]]" findClose--- returns the index of the closing bracket, e.g. 6.-findClose  :: String -> Int    -findClose  s  = findClose' s 1 0-findClose' :: String -> Int -> Int -> Int -findClose' [] b ix-  | b == 0    = ix-1-  | otherwise = error -                 "not well formed tree description: cannot find closing bracket"-findClose' (c : cs) b ix-  | b == 0    = ix-1 -  | c == '['  = findClose' cs (b+1) (ix+1)-  | c == ']'  = findClose' cs (b-1) (ix+1)-  | otherwise = findClose' cs  b    (ix+1) -   ------------------------------------------------------------------------------------ Basic operations on the Tree data structure----------------------------------------------------------------------------------    --- given a list with tree getPn-getPns :: [Tree t] -> [Int]-getPns = map (fromJust . getPn)     ---- returns the list of po nrs of the children of t-getChildPns :: Tree a -> [Int]-{-# INLINE getChildPns  #-} -getChildPns (Node _lab children _pn) = map (fromJust . getPn) children---- returns the subtree of t given its post order number pn-getSubTree :: Tree t -> Int -> Tree t-getSubTree t pn = pot t!!pn---- returns True if t is a leaf and False otherwise-isLf :: (Eq t) => Tree t -> Bool-isLf t = getChild t == []--collectLeafs :: Tree t -> [Tree t]-collectLeafs t@(Node _ [] _) = [t]-collectLeafs   (Node _ cn _) = concatMap collectLeafs cn ---- returns the size of the tree-size, depth :: Tree t -> Int-size (Node _ [] _) = 1-size (Node _ children _ ) = foldr ((+) . size ) 1 children---- returns the size of a forrest of trees-sizeF, depthF :: [Tree t] -> Int-sizeF treeList = foldr ((+) . size ) 0 treeList--avgDepth :: Tree t -> Float-avgDepth t = fromIntegral (sum dep) / (genericLength dep) where -  dep = depth' 1 t--avgDepthF :: [Tree t] -> Float-avgDepthF t = let l = map avgDepth t in sum l / genericLength l ---- returns the maximum depth of a tree-depth t = maximumBy compare (depth' 1 t)---- returns the maximum depth of a forrest of trees-depthF treeList = maximumBy compare (concatMap (depth' 1) treeList) ---- depth helper-depth' :: Int -> Tree t -> [Int]-depth' x (Node _ [] _ ) = [x]-depth' x (Node _ c  _ ) = x : concatMap (depth' (x+1)) c---- recursively removes the nodes with label 'x' from a tree-remove :: (Eq t) => t -> Tree t -> Tree t-remove x = removeBy (== x)---- more general version of remove-removeBy :: (t -> Bool) -> Tree t -> Tree t-removeBy f t = head (removeBy' f t)-removeBy' :: (t -> Bool) -> Tree t -> [Tree t]-removeBy' f (Node l c pn) -  | f l       = concatMap (removeBy' f) c -  | otherwise = [(Node l (concatMap (removeBy' f) c) pn)]---- collects all the subtrees of tree in a list in post order.-pot, pot', pret, pret',potPret :: Tree t -> [Tree t]-potPret t                    =  pot' (setPre  t)-pot    t                     =  pot' (setPost t)-pot'   t@(Node _ [] _)       =  [t]-pot'   t@(Node _ children _) =  concatMap pot' children ++ [t]--- collects all the subtrees of tree in a list in pre order.-pret    t                    =  pret' (setPre t)-pret'  t@(Node _ [] _)       =  [t]-pret'  t@(Node _ children _) =  t : concatMap pret' children---- very inefficient way of converting a pre order number to a post order number--- just for testing....-preToPost :: Tree t -> Int -> Int-preToPost t pn = fromJust . getPn $ pret' (setPost t) !! pn----- Converts Node's to NodePn's and sets the post order numbers--- JPM: setPost is a typical tree labelling problem.--- Looks nicer with the state monad, I think:-setPost, setPre :: Tree t -> Tree t-setPost t = evalState (stm t) 0 where-  stm :: Tree t -> State Int (Tree t)-  stm (Node a cs _) = do cs' <- mapM stm cs-                         pn  <- get-                         modify (+1)-                         return (Node a cs' (Just pn))    ---- Sets pre order numbers -setPre t = evalState (stm t) 0 where-  stm :: Tree t -> State Int (Tree t)-  stm (Node a cs _) = do pn  <- get-                         modify (+1)-                         cs' <- mapM stm cs-                         return (Node a cs' (Just pn))                ----not very efficient, but nevertheless very effective, todo optimize elem operation-matchToTree :: Tree t -> [Int] -> [Tree t]-matchToTree t@(Node _ _ Nothing ) k = matchToTree (setPost t) k-matchToTree   (Node a cn (Just pn)) k =-  let cs = concatMap (`matchToTree` k) cn-  in if pn `elem` k then [Node a cs (Just pn)] else cs                         +
+module HarmTrace.HAnTree.Tree where
+
+import Data.Maybe
+import qualified Data.Binary as B
+import Control.Monad.State
+import Data.List (maximumBy, genericLength)
+import Control.DeepSeq
+
+--------------------------------------------------------------------------------
+-- Tree datastructure
+--------------------------------------------------------------------------------
+
+-- our (temporary) tree data structure, a leaf is simply represented
+-- as Node a [] or NodePn a [] pn
+data Tree a = Node { getLabel :: !a, getChild :: ![Tree a], getPn :: !(Maybe Int) }
+  deriving Eq
+  
+-- specific show instance for pretty printing
+instance (Show a) => Show (Tree a) where 
+  show (Node a children _) =  --filter (\x -> '\"' /= x && '\'' /= x) 
+                            desc where
+    desc = ('[' : show a) ++ concatMap show children ++ "]"
+
+instance (NFData a) => NFData (Tree a) where
+  rnf (Node l c p) = rnf l `seq` rnf c `seq` rnf p
+
+instance (B.Binary a) => B.Binary (Tree a) where
+  put (Node l c p) = B.put l >> B.put c >> B.put p
+  get = liftM3 Node B.get B.get B.get
+    
+--------------------------------------------------------------------------------
+-- Creating Trees
+--------------------------------------------------------------------------------    
+    
+-- returns a Tree data structure, given a string representation of a tree.
+strTree :: String -> Tree String 
+strTree = head . strTree' where
+  strTree' [] = []
+  strTree' (c:cs) 
+    | c == '['  = Node lab (strTree' a) Nothing : strTree' b
+    | c == ']'  = strTree' cs
+    | otherwise = error ("cannot parse, not well formed tree description: " 
+                ++ [c]) where 
+                  (x  ,b) = splitAt (findClose cs) cs
+                  (lab,a) = span (\y -> (y /= '[') && (y /= ']')) x
+
+-- given a string representation of a tree, e.g. "[b][c]]" findClose
+-- returns the index of the closing bracket, e.g. 6.
+findClose  :: String -> Int    
+findClose  s  = findClose' s 1 0
+findClose' :: String -> Int -> Int -> Int 
+findClose' [] b ix
+  | b == 0    = ix-1
+  | otherwise = error 
+                 "not well formed tree description: cannot find closing bracket"
+findClose' (c : cs) b ix
+  | b == 0    = ix-1 
+  | c == '['  = findClose' cs (b+1) (ix+1)
+  | c == ']'  = findClose' cs (b-1) (ix+1)
+  | otherwise = findClose' cs  b    (ix+1) 
+   
+--------------------------------------------------------------------------------
+-- Basic operations on the Tree data structure
+--------------------------------------------------------------------------------
+    
+-- given a list with tree getPn
+getPns :: [Tree t] -> [Int]
+getPns = map (fromJust . getPn)     
+
+-- returns the list of po nrs of the children of t
+getChildPns :: Tree a -> [Int]
+{-# INLINE getChildPns  #-} 
+getChildPns (Node _lab children _pn) = map (fromJust . getPn) children
+
+-- returns the subtree of t given its post order number pn
+getSubTree :: Tree t -> Int -> Tree t
+getSubTree t pn = pot t!!pn
+
+-- returns True if t is a leaf and False otherwise
+isLf :: (Eq t) => Tree t -> Bool
+isLf t = getChild t == []
+
+collectLeafs :: Tree t -> [Tree t]
+collectLeafs t@(Node _ [] _) = [t]
+collectLeafs   (Node _ cn _) = concatMap collectLeafs cn 
+
+-- returns the size of the tree
+size, depth :: Tree t -> Int
+size (Node _ [] _) = 1
+size (Node _ children _ ) = foldr ((+) . size ) 1 children
+
+-- returns the size of a forrest of trees
+sizeF, depthF :: [Tree t] -> Int
+sizeF treeList = foldr ((+) . size ) 0 treeList
+
+avgDepth :: Tree t -> Float
+avgDepth t = fromIntegral (sum dep) / (genericLength dep) where 
+  dep = depth' 1 t
+
+avgDepthF :: [Tree t] -> Float
+avgDepthF t = let l = map avgDepth t in sum l / genericLength l 
+
+-- returns the maximum depth of a tree
+depth t = maximumBy compare (depth' 1 t)
+
+-- returns the maximum depth of a forrest of trees
+depthF treeList = maximumBy compare (concatMap (depth' 1) treeList) 
+
+-- depth helper
+depth' :: Int -> Tree t -> [Int]
+depth' x (Node _ [] _ ) = [x]
+depth' x (Node _ c  _ ) = x : concatMap (depth' (x+1)) c
+
+-- recursively removes the nodes with label 'x' from a tree
+remove :: (Eq t) => t -> Tree t -> Tree t
+remove x = removeBy (== x)
+
+-- more general version of remove
+removeBy :: (t -> Bool) -> Tree t -> Tree t
+removeBy f t = head (removeBy' f t)
+removeBy' :: (t -> Bool) -> Tree t -> [Tree t]
+removeBy' f (Node l c pn) 
+  | f l       = concatMap (removeBy' f) c 
+  | otherwise = [(Node l (concatMap (removeBy' f) c) pn)]
+
+-- collects all the subtrees of tree in a list in post order.
+pot, pot', pret, pret',potPret :: Tree t -> [Tree t]
+potPret t                    =  pot' (setPre  t)
+pot    t                     =  pot' (setPost t)
+pot'   t@(Node _ [] _)       =  [t]
+pot'   t@(Node _ children _) =  concatMap pot' children ++ [t]
+-- collects all the subtrees of tree in a list in pre order.
+pret    t                    =  pret' (setPre t)
+pret'  t@(Node _ [] _)       =  [t]
+pret'  t@(Node _ children _) =  t : concatMap pret' children
+
+-- very inefficient way of converting a pre order number to a post order number
+-- just for testing....
+preToPost :: Tree t -> Int -> Int
+preToPost t pn = fromJust . getPn $ pret' (setPost t) !! pn
+
+
+-- Converts Node's to NodePn's and sets the post order numbers
+-- JPM: setPost is a typical tree labelling problem.
+-- Looks nicer with the state monad, I think:
+setPost, setPre :: Tree t -> Tree t
+setPost t = evalState (stm t) 0 where
+  stm :: Tree t -> State Int (Tree t)
+  stm (Node a cs _) = do cs' <- mapM stm cs
+                         pn  <- get
+                         modify (+1)
+                         return (Node a cs' (Just pn))    
+
+-- Sets pre order numbers 
+setPre t = evalState (stm t) 0 where
+  stm :: Tree t -> State Int (Tree t)
+  stm (Node a cs _) = do pn  <- get
+                         modify (+1)
+                         cs' <- mapM stm cs
+                         return (Node a cs' (Just pn))                
+
+--not very efficient, but nevertheless very effective, todo optimize elem operation
+matchToTree :: Tree t -> [Int] -> [Tree t]
+matchToTree t@(Node _ _ Nothing ) k = matchToTree (setPost t) k
+matchToTree   (Node a cn (Just pn)) k =
+  let cs = concatMap (`matchToTree` k) cn
+  in if pn `elem` k then [Node a cs (Just pn)] else cs                         
src/HarmTrace/HarmTrace.hs view
@@ -1,107 +1,103 @@-{-# LANGUAGE GADTs    #-}-{-# LANGUAGE CPP      #-}-{-# OPTIONS_GHC -Wall #-}--module HarmTrace.HarmTrace ( PPOption(..), Grammar(..), GrammarEx(..)-                           , ParseResult(..), gt2Piece-                           , string2Piece, postProc ) where--import HarmTrace.Models.Models-import HarmTrace.Models.Jazz.Main-import HarmTrace.Models.Pop.Main-import HarmTrace.HAnTree.ToHAnTree-import HarmTrace.HAnTree.Tree-import HarmTrace.HAnTree.HAn (HFunc (P))-import HarmTrace.HAnTree.PostProcess-import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens as CT-import HarmTrace.Tokenizer.Tokenizer--import Data.Ord (comparing)-import Data.List (minimumBy)--#ifdef AUDIO--- Audio/Annotation Stuff-import HarmTrace.Audio.Annotations-import HarmTrace.Audio.ChordTypes--import HarmTrace.Base.Parsing (parseDataWithErrors)-#endif---- Parser stuff-import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.BasicInstances as PC------------------------------------------------------------------------------------- Plugging everything together-----------------------------------------------------------------------------------data ParseResult a = ParseResult { parsedKey          :: Key-                                 , parsedChordLabels  :: [ChordLabel]-                                 , parsedPiece        :: [a]-                                 , pieceTreeHAn       :: Tree HAn-                                 , nrAmbTrees         :: Int-                                 , tokenizerErrors    :: [Error LineColPos ]-                                 , pieceErrors        :: [Error Int] -                                 , postProcessing     :: [PPOption]}-  --- parses s with string2Piece and merges the deleted chords with the tree--- (Representable a, GTree (Rep a))-postProc :: (GTree g) => [PPOption] -> ParseResult g -> ParseResult g-postProc opts beforePostProc = beforePostProc { pieceTreeHAn = t } -  where-  t = selectTree $ map (postProcess fs . gTreeHead) (parsedPiece beforePostProc)-  fs = map opt2Func opts-  opt2Func :: PPOption -> (Tree HAn -> Tree HAn)-  opt2Func RemoveInsertions = removeInsertions -  opt2Func RemovePDPT       = removePDPT-  opt2Func MergeDelChords   = mergeDelChords (parsedKey beforePostProc)-                                             (pieceErrors beforePostProc)-                                             (parsedChordLabels beforePostProc)-  opt2Func ExpandChordDurations = expandChordDurations--selectTree :: [Tree HAn] -> Tree HAn-selectTree [] = emptyHAnTree-selectTree ts = minimumBy (comparing getNrFuncNodes) ts--getNrFuncNodes :: Tree HAn -> Int-getNrFuncNodes (Node (HAnFunc P) nodes _) = length nodes-getNrFuncNodes _ = error "HarmTrace.hs: not a correctly formed HAn Tree" -  -postProcess :: [Tree HAn -> Tree HAn] -> Tree HAn -> Tree HAn-postProcess []     tree = tree-postProcess (f:fs) tree = f (postProcess fs tree)---- Takes a string with line-separated chords of a song and--- returns all possible parsed pieces, together with error-correction steps--- taken (on tokenizing and on musical recognition).-string2Piece :: Grammar g -> String -> ParseResult g-string2Piece g s = let-  (PieceLabel key tok, err) = parse ((,) <$> parseSongAbs <*> pEnd)-                                    (createStr (LineColPos 0 0 0) s)-  (trees, err2) = case g of -                    Jazz -> parse_h ((,) <$> pJazz key <*> pEnd)-                              (createStr 0 (toKeyRelTok key tok))  -                    Pop  -> parse_h ((,) <$> pPop  key <*> pEnd)-                              (createStr 0 (toKeyRelTok key tok))-  in ParseResult key tok trees emptyHAnTree (length trees) err err2 []---#ifdef AUDIO------------------------------------------------------------------------------------ Parsing audio file ground-truth annotations---------------------------------------------------------------------------------  --gt2Piece :: (GTree g) => Grammar g -> String -> String -> ParseResult g-gt2Piece g kstr cstr = let-  (ks , errK) = parseDataWithErrors parseKeyAnnotationData kstr-  key         = getData . head $ filter (not . isNone . keyRoot . getData) ks-  (tok, errT) = parseDataWithErrors parseAnnotationData cstr-  ppTok       = preProcess tok-  (ts, errP)  = case g of -                  Jazz -> parse_h ((,) <$> pJazz key <*> pEnd)-                            (createStr 0 (toKeyRelTok key ppTok))  -                  Pop  -> parse_h ((,) <$> pPop  key <*> pEnd)-                            (createStr 0 (toKeyRelTok key ppTok))-  in ParseResult key ppTok ts emptyHAnTree (length ts) (errK ++ errT) errP [] -#endif+{-# LANGUAGE GADTs    #-}
+{-# LANGUAGE CPP      #-}
+{-# OPTIONS_GHC -Wall #-}
+
+module HarmTrace.HarmTrace ( PPOption(..), Grammar(..), GrammarEx(..)
+                           , ParseResult(..), gt2Piece
+                           , string2Piece, postProc ) where
+
+import HarmTrace.Models.Models
+import HarmTrace.Models.Jazz.Main
+import HarmTrace.Models.Pop.Main
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.HAnTree.Tree
+import HarmTrace.HAnTree.HAn (HFunc (P))
+import HarmTrace.HAnTree.PostProcess
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens as CT
+import HarmTrace.Tokenizer.Tokenizer
+
+import Data.Ord (comparing)
+import Data.List (minimumBy)
+
+-- Audio/Annotation Stuff
+import HarmTrace.Audio.AnnotationParser
+import HarmTrace.Audio.ChordTypes
+
+import HarmTrace.Base.Parsing (parseDataWithErrors)
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.BasicInstances as PC
+
+--------------------------------------------------------------------------------
+-- Plugging everything together
+--------------------------------------------------------------------------------
+
+data ParseResult a = ParseResult { parsedKey          :: Key
+                                 , parsedChordLabels  :: [ChordLabel]
+                                 , parsedPiece        :: [a]
+                                 , pieceTreeHAn       :: Tree HAn
+                                 , nrAmbTrees         :: Int
+                                 , tokenizerErrors    :: [Error LineColPos ]
+                                 , pieceErrors        :: [Error Int] 
+                                 , postProcessing     :: [PPOption]}
+  
+-- parses s with string2Piece and merges the deleted chords with the tree
+-- (Representable a, GTree (Rep a))
+postProc :: (GTree g) => [PPOption] -> ParseResult g -> ParseResult g
+postProc opts beforePostProc = beforePostProc { pieceTreeHAn = t } 
+  where
+  t = selectTree $ map (postProcess fs . gTreeHead) (parsedPiece beforePostProc)
+  fs = map opt2Func opts
+  opt2Func :: PPOption -> (Tree HAn -> Tree HAn)
+  opt2Func RemoveInsertions = removeInsertions 
+  opt2Func RemovePDPT       = removePDPT
+  opt2Func MergeDelChords   = mergeDelChords (parsedKey beforePostProc)
+                                             (pieceErrors beforePostProc)
+                                             (parsedChordLabels beforePostProc)
+  opt2Func ExpandChordDurations = expandChordDurations
+
+selectTree :: [Tree HAn] -> Tree HAn
+selectTree [] = emptyHAnTree
+selectTree ts = minimumBy (comparing getNrFuncNodes) ts
+
+getNrFuncNodes :: Tree HAn -> Int
+getNrFuncNodes (Node (HAnFunc P) nodes _) = length nodes
+getNrFuncNodes _ = error "HarmTrace.hs: not a correctly formed HAn Tree" 
+  
+postProcess :: [Tree HAn -> Tree HAn] -> Tree HAn -> Tree HAn
+postProcess []     tree = tree
+postProcess (f:fs) tree = f (postProcess fs tree)
+
+-- Takes a string with line-separated chords of a song and
+-- returns all possible parsed pieces, together with error-correction steps
+-- taken (on tokenizing and on musical recognition).
+string2Piece :: Grammar g -> String -> ParseResult g
+string2Piece g s = let
+  (PieceLabel key tok, err) = parse ((,) <$> parseSongAbs <*> pEnd)
+                                    (createStr (LineColPos 0 0 0) s)
+  (trees, err2) = case g of 
+                    Jazz -> parse_h ((,) <$> pJazz key <*> pEnd)
+                              (createStr 0 (toKeyRelTok key tok))  
+                    Pop  -> parse_h ((,) <$> pPop  key <*> pEnd)
+                              (createStr 0 (toKeyRelTok key tok))
+  in ParseResult key tok trees emptyHAnTree (length trees) err err2 []
+
+
+--------------------------------------------------------------------------------
+-- Parsing audio file ground-truth annotations
+--------------------------------------------------------------------------------  
+
+gt2Piece :: (GTree g) => Grammar g -> String -> String -> ParseResult g
+gt2Piece g kstr cstr = let
+  (ks , errK) = parseDataWithErrors parseKeyAnnotationData kstr
+  key         = getData . head $ filter (not . isNone . keyRoot . getData) ks
+  (tok, errT) = parseDataWithErrors parseAnnotationData cstr
+  ppTok       = preProcess tok
+  (ts, errP)  = case g of 
+                  Jazz -> parse_h ((,) <$> pJazz key <*> pEnd)
+                            (createStr 0 (toKeyRelTok key ppTok))  
+                  Pop  -> parse_h ((,) <$> pPop  key <*> pEnd)
+                            (createStr 0 (toKeyRelTok key ppTok))
+  in ParseResult key ppTok ts emptyHAnTree (length ts) (errK ++ errT) errP [] 
+ src/HarmTrace/IO/BasePaths.hs view
@@ -0,0 +1,44 @@+module HarmTrace.IO.BasePaths ( BasePaths, setPaths
+                              , consBasePaths, basePathsExist
+                              , getVampDir, getFeatDir , getOutDir  
+                              , getBeatVampPath, getChromaVampPath
+                              , getKeyVampPath) where
+
+import System.FilePath ((</>))
+import HarmTrace.IO.Common (fileExists, dirExists)
+import Constants ( defaultVampDir, defaultOutDir, defaultFeatDir
+                , beatVampPath, chromaVampPath, keyVampPath)
+
+-- | A datatype for storing the base directories of the vamp plugins, feature
+-- csv files, and the chord and log Files.
+data BasePaths = BasePaths { getVampDir :: FilePath
+                           , getFeatDir :: FilePath
+                           , getOutDir  :: FilePath
+                           , getBeatVampPath   :: FilePath
+                           , getChromaVampPath :: FilePath
+                           , getKeyVampPath    :: FilePath
+                           }
+
+-- | Similar to 'consBasePaths', but instead excepts 'Maybe FilePath's instead
+-- of 'FilePath's
+setPaths :: Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> BasePaths
+setPaths mv mf mo = 
+  let vamp = maybe defaultVampDir id mv 
+      feat = maybe defaultFeatDir id mf 
+      out  = maybe defaultOutDir id mo
+  in consBasePaths vamp feat out 
+
+-- | contructs a new 'BasePaths' based on a VAMP transform directory, 
+-- a feature directory (to store the CSV files), and an output directory.
+consBasePaths :: FilePath -> FilePath -> FilePath -> BasePaths
+consBasePaths vamp feat out = BasePaths vamp feat out 
+  (vamp </> beatVampPath) (vamp </> chromaVampPath) (vamp </> keyVampPath) 
+
+-- | checks (verbosely) if the paths stored in a 'BaseDir' exist.  
+basePathsExist :: BasePaths -> IO (Bool)
+basePathsExist dirs = do v <- fileExists (getBeatVampPath dirs)
+                         c <- fileExists (getChromaVampPath dirs)  
+                         k <- fileExists (getKeyVampPath dirs)
+                         f <- dirExists  (getFeatDir dirs)
+                         o <- dirExists  (getOutDir dirs)
+                         return (v && c && k && f && o)
+ src/HarmTrace/IO/Common.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HarmTrace.IO.Common (
+
+    showFloat, showIntWithZeros,
+    getClassSizes, getTitle, getInfo, getId, 
+    readFile', hGetContents', readDataDir,
+    printLn, putErrLn, printVersion, fileExists, dirExists
+
+  ) where
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+
+-- Music stuff
+import HarmTrace.Models.Jazz.Instances ()
+import HarmTrace.Base.Parsing
+import Constants (vERSION)
+
+-- Library modules
+import System.Console.ParseArgs hiding (args) -- cabal install parseargs
+import Data.List (sort, groupBy)
+import Control.Arrow ((***))
+import System.FilePath
+import System.Directory
+import System.IO
+import Text.Printf (printf)
+
+
+--------------------------------------------------------------------------------
+-- Data set Info
+--------------------------------------------------------------------------------
+
+-- parses a database name
+pDB :: Parser String
+pDB = pString "_" *> (   pString "allanah" 
+                     <|> pString "wdick" 
+                     <|> pString "community" 
+                     <|> pString "midicons" 
+                     <|> pString "realbook" ) <* pString "." 
+
+-- parses a biab extension: "(M|S|m|s)(G|g)[0-9A-Za-z]{1}.txt"
+pBiabExt :: Parser String
+pBiabExt =   (:) <$> (pSym 'M' <|> pSym 'S' <|> pSym 'm' <|> pSym 's') <*>
+            ((:) <$> (pSym 'G' <|> pSym 'g') <*> 
+            ((:) <$>  pAscii <*> (pure []))) <* pString ".txt"
+
+-- parses a title (everything upto "_id_")
+pTitle :: Parser String
+pTitle = pManyTill pAscii (pString "_id_")
+
+-- parses the complete Biab filename
+pBiab :: Parser BiabInfo
+pBiab = BiabInfo <$> pTitle <*> pIntegerRaw <*> pDB <*> pBiabExt
+
+-- a datatype for storing the band-in-a-box filename information
+data BiabInfo = BiabInfo { getTitle :: String
+                         , getId    :: Int
+                         , _getDb   :: String
+                         , _getExt  :: String }
+
+-- gets the groundtruth information from the filename by parsing it
+getInfo :: FilePath -> BiabInfo
+getInfo = parseData pBiab
+
+-- for debugging
+-- getInfoErrors :: FilePath -> BiabInfo
+-- getInfoErrors fp = let (a, err) = parseDataWithErrors pBiab fp 
+                   -- in if not . null $ err then traceShow err a else a
+
+-- shows an integer always with 5 numbers, e.g. 45 -> 00045
+-- The function can very probably be replaced by "show", but than we have 
+-- to change the tests, and make sure it realy does not make a difference
+showIntWithZeros :: Int -> String
+showIntWithZeros i = let i' = show i 
+                     in take (5 - (length i')) (repeat '0') ++ i'
+
+-- creates a (id, title) mapping from a list of files
+createGroundTruth :: [String] -> [(String, String)]
+createGroundTruth files = [ (getTitle x, showIntWithZeros $ getId x) 
+                           | x <- map getInfo files       ]
+
+getClassSizes :: [String] -> [(String,[String])]
+getClassSizes = map ((head *** id) . unzip) . groupBy gf . createGroundTruth
+  where gf (name1, _key1) (name2, _key2) = name1 == name2
+
+-- writeGroundTruth :: FilePath -> FilePath -> IO ()
+-- writeGroundTruth infp outfp =
+  -- do  files <- readDataDir infp
+      -- writeFile outfp . Prelude.tail $ concatMap merge (createGroundTruth files) 
+         -- where merge :: (String, String) -> String
+               -- merge (x,y) = '\n' : y ++ "\t" ++ x
+
+--------------------------------------------------------------------------------
+-- Utils
+--------------------------------------------------------------------------------
+putErrLn :: String -> IO()
+putErrLn = hPutStrLn stderr
+
+printLn :: String ->IO ()
+printLn s = putStrLn s >> hFlush stdout
+
+-- Stricter readFile
+hGetContents' :: Handle -> IO [Char]
+hGetContents' hdl = do e <- hIsEOF hdl
+                       if e then return []
+                         else do c <- hGetChar hdl
+                                 cs <- hGetContents' hdl
+                                 return (c:cs)
+
+readFile' :: FilePath -> IO [Char]
+readFile' fn = do hdl <- openFile fn ReadMode
+                  xs <- hGetContents' hdl
+                  hClose hdl
+                  return xs
+                  
+readDataDir :: FilePath -> IO [FilePath]
+readDataDir fp = 
+  do fs <- getDirectoryContents fp
+     return . sort $ filter (\str -> ".txt" == takeExtension str ) fs
+
+-- | Shows a Float with 5 decimal places
+showFloat :: Float -> String
+showFloat = printf "%.6f" 
+
+printVersion :: IO ()
+printVersion = putStrLn vERSION
+
+-- | extends the 'System.Directory.doesFileExist' by printing an error message
+-- if the file does not exist.
+fileExists :: FilePath -> IO (Bool)
+fileExists fp = 
+  do e <- doesFileExist fp  
+     when (not e) $ putStrLn ("Error: file does not exists: " ++ show fp)
+     return e
+     
+-- | extends the 'System.Directory.doesDirectoryExist' by printing an error 
+-- message if the file does not exist.
+dirExists :: FilePath -> IO (Bool)
+dirExists fp = 
+  do e <- doesDirectoryExist fp  
+     when (not e) $ putStrLn ("Error: directory does not exists: " ++ show fp)
+     return e
+     
+ src/HarmTrace/IO/Downsample.hs view
@@ -0,0 +1,40 @@+module HarmTrace.IO.Downsample (downsample) where
+
+import Constants (outputSampleRate, outputBitsPerSample, outputNrOfChannels)
+
+import Sound.Sox.Convert (simple)
+import Sound.Sox.Option.Format
+
+import Data.Monoid
+import System.Exit (ExitCode (..))
+
+-- import System.Directory
+-- import System.FilePath 
+
+-- ** N.B. SoX is required **
+-- http://sox.sourceforge.net/
+-- install on Linux:   apt-get install sox libsox-fmt-all (all file formats)
+-- install on Windows: you can download the binary from the sox website. 
+--                     However, this binary does not have .mp3 support. This
+--                     website provides a binary that does:
+-- http://www.codeproject.com/Articles/33901/Compiling-SOX-with-Lame-and-Libmad-for-Windows
+
+downsample :: FilePath -> IO (FilePath)
+downsample fp = 
+  do let o   = mconcat [ numberOfChannels outputNrOfChannels
+                       , bitsPerSample    outputBitsPerSample
+                       , sampleRate       outputSampleRate   ]
+         out = fp ++ ".downsampled.wav"
+         -- out = takeDirectory fp </> "downsampled" </> takeFileName fp
+     conv <- simple none fp o out
+     if   conv /= ExitSuccess 
+          then error "HarmTrace.IO.Downsample: sox did not terminate normally"
+          else return out
+
+-- given a path to a directory with wav files (anything other than wav files
+-- is ignored will be downsamnpled.          
+-- batchDownsample :: FilePath -> IO ()
+-- batchDownsample fp = 
+  -- do fs <- getDirectoryContents fp
+     -- let fs' = filter ((==".wav") . takeExtension) (map (\x -> fp </> x) fs)
+     -- mapM_ downsample fs'
src/HarmTrace/IO/Errors.hs view
@@ -1,49 +1,49 @@-{-# OPTIONS_GHC -Wall         #-}-module HarmTrace.IO.Errors where---- Parser stuff-import Text.ParserCombinators.UU.BasicInstances  as PC (Error (..))--import Data.List (genericLength)-import System.IO (stderr, hPutStrLn)------------------------------------------------------------------------------------ Error Reporting-----------------------------------------------------------------------------------data ErrorNrs = ErrorNrs { ins :: Int, del :: Int, delEnd :: Int, rep :: Int }---- datatype for storing the number of different error types-instance Show ErrorNrs where -   show (ErrorNrs i d e r) = show i ++ " insertions, " ++ show d -        ++ " deletions, " ++ show r ++ "replacements, and " -        ++ show e ++ " unconsumed tokens"---- More concise showing errors, and in IO-showErrors :: Show a => String -> [Error a] -> IO ()-showErrors label l = case countErrors l of-  ErrorNrs i d e r -> hPutStrLn stderr (label ++ show i ++ " insertions, " -                                      ++ show d ++ " deletions, "-                                      ++ show r ++ " replacements, "-                                      ++ show e ++ " deletions at the end")--- Counts the number of insertions and deletions-countErrors :: Show a => [Error a] -> ErrorNrs-countErrors [] = ErrorNrs 0 0 0 0-countErrors ((PC.Inserted _ _ _)  :t) = inc1 (countErrors t)-countErrors ((PC.Deleted _ _ _)   :t) = inc2 (countErrors t)-countErrors ((DeletedAtEnd _)  :t)    = inc3 (countErrors t)-countErrors ((Replaced _ _ _ _):t)    = inc4 (countErrors t)--simpleErrorMeasure :: ErrorNrs -> Float-simpleErrorMeasure (ErrorNrs i d e r) = fromIntegral (i + d + e + r)--errorRatio :: Show a => [Error a] -> [b] -> Float-errorRatio errs toks = simpleErrorMeasure (countErrors errs) /-   -- probably we should not divide here by "mergeDups" ...-   -- genericLength (mergeDups (Key (Note Nothing C) MajMode) toks)-   genericLength toks--inc1, inc2, inc3, inc4 :: ErrorNrs -> ErrorNrs-inc1 e = e { ins    = ins e    + 1 }-inc2 e = e { del    = del e    + 1 }-inc3 e = e { delEnd = delEnd e + 1 }-inc4 e = e { rep    = rep e    + 1 }+{-# OPTIONS_GHC -Wall         #-}
+module HarmTrace.IO.Errors where
+
+-- Parser stuff
+import Text.ParserCombinators.UU.BasicInstances  as PC (Error (..))
+
+import Data.List (genericLength)
+import System.IO (stderr, hPutStrLn)
+--------------------------------------------------------------------------------
+-- Error Reporting
+--------------------------------------------------------------------------------
+
+data ErrorNrs = ErrorNrs { ins :: Int, del :: Int, delEnd :: Int, rep :: Int }
+
+-- datatype for storing the number of different error types
+instance Show ErrorNrs where 
+   show (ErrorNrs i d e r) = show i ++ " insertions, " ++ show d 
+        ++ " deletions, " ++ show r ++ "replacements, and " 
+        ++ show e ++ " unconsumed tokens"
+
+-- More concise showing errors, and in IO
+showErrors :: Show a => String -> [Error a] -> IO ()
+showErrors label l = case countErrors l of
+  ErrorNrs i d e r -> hPutStrLn stderr (label ++ show i ++ " insertions, " 
+                                      ++ show d ++ " deletions, "
+                                      ++ show r ++ " replacements, "
+                                      ++ show e ++ " deletions at the end")
+-- Counts the number of insertions and deletions
+countErrors :: Show a => [Error a] -> ErrorNrs
+countErrors [] = ErrorNrs 0 0 0 0
+countErrors ((PC.Inserted _ _ _)  :t) = inc1 (countErrors t)
+countErrors ((PC.Deleted _ _ _)   :t) = inc2 (countErrors t)
+countErrors ((DeletedAtEnd _)  :t)    = inc3 (countErrors t)
+countErrors ((Replaced _ _ _ _):t)    = inc4 (countErrors t)
+
+simpleErrorMeasure :: ErrorNrs -> Float
+simpleErrorMeasure (ErrorNrs i d e r) = fromIntegral (i + d + e + r)
+
+errorRatio :: Show a => [Error a] -> [b] -> Float
+errorRatio errs toks = simpleErrorMeasure (countErrors errs) /
+   -- probably we should not divide here by "mergeDups" ...
+   -- genericLength (mergeDups (Key (Note Nothing C) MajMode) toks)
+   genericLength toks
+
+inc1, inc2, inc3, inc4 :: ErrorNrs -> ErrorNrs
+inc1 e = e { ins    = ins e    + 1 }
+inc2 e = e { del    = del e    + 1 }
+inc3 e = e { delEnd = delEnd e + 1 }
+inc4 e = e { rep    = rep e    + 1 }
+ src/HarmTrace/IO/FeatExtract.hs view
@@ -0,0 +1,162 @@+{-# OPTIONS_GHC -Wall         #-}
+{-# LANGUAGE FlexibleContexts #-}
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.IO.FeatExtract
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: A module that wraps arround the sonic annotator 
+-- (<http://omras2.org/SonicAnnotator>), which is used to extract spectral
+-- and pulse features from an audio source.
+--------------------------------------------------------------------------------
+
+module HarmTrace.IO.FeatExtract (extractFeatures) where
+
+import System.IO
+import System.FilePath 
+import System.Directory (removeFile)
+import System.Process 
+import Data.List (isPrefixOf)
+
+import Constants ( keyStr, chromaStr, beatStr, deleteDownsampledWav)
+import HarmTrace.Base.Parsing
+import HarmTrace.IO.Downsample
+import HarmTrace.IO.BasePaths( BasePaths, getBeatVampPath, basePathsExist
+                             , getKeyVampPath, getChromaVampPath
+                             , getFeatDir, getOutDir)
+
+-- | Extracts the features of an arbitrary audio file, given three VAMP plugin
+-- descriptions. Before the feature extraction the audio is preprocessed 
+-- (downsampled) with the the SoX library (see 'HarmTrace.IO.Downsample')
+-- See for more information:
+--
+-- * <http://www.vamp-plugins.org>
+--
+-- * <http://omras2.org/SonicAnnotator>
+extractFeatures :: BasePaths -> FilePath 
+                -> IO (Maybe (FilePath,FilePath,FilePath))
+extractFeatures dirs f = do
+  -- check if all files and directories exists, if not fail.
+  exists <- basePathsExist dirs
+  case exists of 
+    False ->  return Nothing
+    True  ->  -- create a log file
+             do let logf = getOutDir  dirs </> takeFileName f <.> "extract.log"
+                    cfp  = getFeatDir dirs </> dropExtension f ++ chromaStr
+                    bfp  = getFeatDir dirs </> dropExtension f ++ beatStr
+                    kfp  = getFeatDir dirs </> dropExtension f ++ keyStr
+                
+                -- open a logfile
+                hdl <- openFile logf AppendMode
+                hSetBuffering hdl LineBuffering
+                
+                -- downsample the audio
+                hPutStrLn hdl (f ++ ";process audio;0;0")
+                df <- downsample f
+                hPutStrLn hdl (f ++ ";process audio;99;19")
+              
+                -- extract the features
+                sonicannotatorProcPipe (LogInfo "" "" 0 20) hdl 
+                                       (getBeatVampPath dirs)   df bfp
+                sonicannotatorProcPipe (LogInfo "" "" 0 40) hdl 
+                                       (getChromaVampPath dirs) df cfp
+                sonicannotatorProcPipe (LogInfo "" "" 0 60) hdl 
+                                       (getKeyVampPath dirs)    df kfp
+                hClose hdl
+                
+                -- remove downsampled wav file?
+                when deleteDownsampledWav $ void (removeFile df)
+                -- when (maximum (exitBeat : exitChrm : [exitKey]) > 0) 
+                return (Just (cfp,bfp,kfp))
+
+-- executes the sonicannotator and logs all execution info to a log file  
+-- TODO: perhaps output a Bool for loggin if the annotating is successful
+sonicannotatorProcPipe :: LogInfo -> Handle -> FilePath -> FilePath -> FilePath
+                       -> IO ()
+sonicannotatorProcPipe info logh transformfile inp outp= do
+  let par = "--transform" : transformfile : "--writer" : "csv" 
+          : "--csv-one-file" : [outp, inp]
+  (_,_,Just err,p) <- createProcess (proc "sonic-annotator" par) 
+                                    {std_err = CreatePipe}
+  x <- hGetContents err
+  let tlog = transformLog info x 
+  hPutStr logh $ concatMap show tlog
+  void (waitForProcess p)
+  -- return ()
+  
+--------------------------------------------------------------------------------  
+-- parsing sonic annotator logs
+--------------------------------------------------------------------------------
+  
+-- transforms a sonic annotator log file into a file that should be easy to
+-- parse, satisfying the following format: file;feature_id;percentage;total_perc
+transformLog :: LogInfo -> String -> [LogInfo] -- String
+transformLog a = combineLine a . map (parseData pLogLine) . filter f . rLines 
+  where
+  -- a filter that selects the lines that contain the information we want
+  f :: String -> Bool
+  f x = isPrefixOf "Extracting" x || isPrefixOf "    id=" x
+  -- a variant of lines that also splits on a '\r'
+  rLines :: String -> [String]
+  rLines s = let (l, s') = break (\x -> ((x == '\n') || (x == '\r'))) s
+             in  l : case s' of
+                          []      -> []
+                          (_:s'') -> rLines s''
+  
+  -- combines LogLines into LogInfo datatypes that collects the data in the 
+  -- individual lines
+  combineLine :: LogInfo -> [LogLine] -> [LogInfo]
+  combineLine i []     = [i]
+  combineLine i (l:ls) = let i' = comb i l in i' : combineLine i' ls 
+  -- comb does the actual combining
+  comb :: LogInfo -> LogLine -> LogInfo
+  comb info (LogFile n) = info {file    = n}
+  comb info (LogFeat d) = info {feature = d}
+  comb info (LogPerc p) = let t = if p `mod` 5 == 0 then 1 else 0 
+                          in  info {perc = p, total = total info + t}                 
+                          
+-- a datatype storing for collecting the information in the log file
+data LogInfo = LogInfo {file    :: String,
+                        feature :: String,
+                        perc    :: Int,
+                        total   :: Int}
+instance Show LogInfo where
+  show (LogInfo f d p t) 
+    | null f    = ""
+    | otherwise = f ++ ';' : d ++ ';' : show p ++ ';' : show t ++ "\n"
+
+-- a datatype for storing the information in one line of the log file  
+data LogLine = LogFile String
+             | LogFeat String
+             | LogPerc Int deriving Show
+    
+-- the actual parsers of the log file
+pLogLine :: P (Str Char String LineColPos) LogLine
+pLogLine =   LogFile <$> pFileName
+         <|> LogFeat <$> pFeatName
+         <|> LogPerc <$> pPerc
+
+-- the percentage of the currently extracted feature
+pPerc :: P (Str Char String LineColPos) Int
+pPerc =     pString "Extracting and writing features... " 
+        *> (pInteger <* pSym '%' <|> f <$> pString "Done")
+        <*  pList pAscii -- if there is anything left 
+  where f x = if x == "Done" then 100 else -1
+
+-- the filename
+pFileName :: P (Str Char String LineColPos) String
+pFileName =  pString "Extracting features for: " *> pQuotedString 
+
+-- the feature description
+pFeatName :: P (Str Char String LineColPos) String
+pFeatName =  pString "    id=" *> pQuotedString 
+
+-- test :: IO ()
+-- test =
+  -- do t <- readFile "D:\\chordify\\harmtrace\\test.txt"
+     -- mapM_ print (transformLog (LogInfo "" "" 0 20) t)         
src/HarmTrace/IO/Main.hs view
@@ -1,494 +1,13 @@-{-# OPTIONS_GHC -Wall            #-}-{-# LANGUAGE CPP                 #-}-{-# LANGUAGE ScopedTypeVariables #-}---- Testing-module HarmTrace.IO.Main where---- Parser stuff-import Text.ParserCombinators.UU---- Music stuff-import HarmTrace.HarmTrace-import HarmTrace.Base.MusicRep-import HarmTrace.Models.Jazz.Instances ()-import HarmTrace.HAnTree.Tree (Tree)-import HarmTrace.HAnTree.ToHAnTree-import HarmTrace.IO.Errors-import HarmTrace.Matching.Standard-import HarmTrace.Matching.GuptaNishimura-import HarmTrace.Matching.Alignment (getAlignDist, getHAnDist)--#ifdef AUDIO--- Audio stuff-import HarmTrace.Base.Parsing-import HarmTrace.Audio.Parser-import HarmTrace.Audio.Annotations-import HarmTrace.Audio.Harmonize (putSegStats)-import HarmTrace.Audio.Evaluation-import HarmTrace.Audio.ChordTypes (ChordAnnotation, AudioFeat (..), TimedData)-import Data.List (genericLength)-#endif---- Library modules-import System.Console.ParseArgs hiding (args) -- cabal install parseargs-import Data.List (sort, groupBy, intersperse)-import Control.Arrow ((***))-import System.FilePath-import System.Directory-import System.IO-import Text.Regex.TDFA hiding (match)-import Text.Printf (printf)-import System.CPUTime-import Data.Maybe (isJust, fromJust)-import Data.Binary---- Parallelism-import Control.Parallel.Strategies------------------------------------------------------------------------------------- Data set Info----------------------------------------------------------------------------------biabPat :: String-biabPat = "^(.*)_id_([0-9]{5})_(allanah|wdick|community|midicons|realbook).(M|S|m|s)(G|g)[0-9A-Za-z]{1}.txt$"     -    -getInfo :: String -> Maybe [String]     -getInfo fileName = -  do let -     (_,_,_,groups) <- fileName =~~ biabPat :: Maybe (String,String,String,[String])-     return groups--getTitle, getId, getDb :: String -> String-getTitle fn = getInfo' 0 fn     -getId fn    = getInfo' 1 fn-getDb fn    = getInfo' 2 fn-    -getInfo' :: Int -> String -> String    -getInfo' i fn = maybe "no_info" (!!i) (getInfo fn)-                    -createGroundTruth :: [String] -> [(String, String)]-createGroundTruth files = [ (getTitle x, getId x) | x <- files ]--getClassSizes :: [String] -> [(String,[String])]-getClassSizes = map ((head *** id) . unzip) . groupBy gf . createGroundTruth-  where gf (name1, _key1) (name2, _key2) = name1 == name2--writeGroundTruth :: FilePath -> FilePath -> IO ()-writeGroundTruth infp outfp =-  do  files <- readDataDir infp-      writeFile outfp . Prelude.tail $ concatMap merge (createGroundTruth files) -         where merge :: (String, String) -> String-               merge (x,y) = '\n' : y ++ "\t" ++ x------------------------------------------------------------------------------------- Symbolic Parsing IO ------------------------------------------------------------------------------------- parses a string of chords and returns a parse tree with the harmony structure-parseTree, parseTreeVerb :: (GTree g) => Grammar g -> [PPOption] -> String -                         -> IO (ParseResult g)-parseTree g opts s =-  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ string2Piece g s-     putStrLn ("parsed " ++ show (length tks) ++ " chords into " -                         ++ show n            ++ " ambiguous trees")-     if not $ null te then showErrors "tokenizer: " te -                     else putStr ""-     if not $ null pe then showErrors "parser: " pe -                     else putStr ""-     return pr--parseTreeVerb g opts s = -  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ string2Piece g s-     putStrLn ("parsed " ++ show (length tks) ++ " chords into " -                         ++ show n            ++ " ambiguous trees")-     if not $ null te then mapM_ print te-                      else putStr ""-     if not $ null pe then mapM_ print pe-                      else putStr ""-     return pr                 -      --- Batch analyzing a directory with chord sequence files with reduced output.-parseDir :: (GTree g)-         => Grammar g -> [PPOption] -> FilePath -> Maybe FilePath -> IO ()-parseDir g opts filepath bOut =     getDirectoryContents filepath -                                >>= parseDir' g opts bOut filepath . sort--parseDir' :: (GTree g)-          => Grammar g -> [PPOption] -> Maybe FilePath-          -> String -> [String] -> IO ()-parseDir' g opts bOut fp fs = -  do putStr "Filename\tNumber of trees\t"-     putStr "Insertions\tDeletions\tReplacements\tDeletions at the end\t"-     putStr "Tot_Correction\tNr_of_chords\t"-     putStrLn "Error ratio\tTime taken"-     let process :: FilePath -> FilePath -> IO ([ChordLabel],Tree HAn)-         process path x =-          do content <- readFile (path </> x)-             let (ParseResult _ tks ps ts nr e1 e2 _) -                      = postProc opts $ string2Piece g content-                 -- @Pedro: I think that the (length ts) only is here to -                 -- evaluate all trees right? Since the tree selection is now-                 -- incorporated in the postprocessing I replaced it with -                 -- length ps-                 -- t                = seq (length ts) (return ())-                 t                = seq (length ps) (return ())-                 ErrorNrs i d e r = countErrors e2-                 errRat           = errorRatio e2 tks-                 nrOfChords       = length tks -- (mergeDups toks)-             t1 <- getCPUTime-             t-             t2 <- getCPUTime-             let diff = fromIntegral (t2 - t1) / (1000000000 :: Float)-             when (not $ null e1) $ putErrLn (show x ++ ": " ++ show e1)-             printLn . concat $ intersperse "\t" [ x, show nr-                                       , show i, show d, show r, show e-                                       , show (i+d+e+r)-                                       , show nrOfChords, showFloat errRat-                                       , showFloat diff]   -             return (tks, ts)-     res <- mapM (process fp) (filter ((== ".txt") . takeExtension) fs)-     case bOut of-       Nothing -> return ()-       Just bf -> encodeFile bf (unzip res :: ([[ChordLabel]],[Tree HAn]))------------------------------------------------------------------------------------- Symbolic Matching IO-----------------------------------------------------------------------------------data MatchMode = STDiff | LCESsize | LCESsim | HAnAlign | Align-  deriving (Eq, Ord, Show)  ---- should return True if sim a b == sim b a and False otherwise  -isSymmetrical :: MatchMode -> Bool--- @pedro: I guess it is symmetrical, but I'm not 100% sure-isSymmetrical STDiff    = False -isSymmetrical LCESsize  = True-isSymmetrical LCESsim   = True-isSymmetrical HAnAlign  = True  -isSymmetrical Align     = True  -  --- matches a directory of chord description files-dirMatch :: (GTree g)-         => Grammar g -> [PPOption] -> Maybe FilePath-         -> MatchMode -> Maybe Float -> FilePath -> IO ()-dirMatch g o bIn m me fp = -  do fs <- readDataDir fp-     let process s   = let (ParseResult _ tks _ ts _nrts _ ePar _) -                              = postProc o $ string2Piece g s-                       in  (tks, ts, errorRatio ePar tks)-         filterError = if isJust me -                          then filter (\(_,_,e) -> e <= fromJust me) else id-     pss <- mapM (\f -> readFile' (fp </> f)) fs-     (tks, ps) <- case bIn of-                    Just bp -> decodeFile bp :: IO ([[ChordLabel]],[Tree HAn])-                    Nothing -> let (toks, ps', _) = unzip3 (filterError -                                                             (map process pss))-                               in return (toks, ps' `using` parList rdeepseq)-     let fsQLab = labelQuery fs-     -- print the ireval format ...-     putStr "true\n"-     if (m == LCESsize || m == LCESsim || m == HAnAlign || m == Align) -        then putStr "false\n" else putStr "true\n"-     mapM_ (putStr . (++ "\t"). getId) (fst . unzip $ filter snd fsQLab) -     putChar '\n'-     mapM_ (putStr . (++ "\t"). getId) fs -     putChar '\n'-     -- do the actual matching ... -     let match :: (a -> a -> Float) -> [a] -> [([Float],Bool)]-         match sim l = [ ([ calcSim sim x y i j -                       | (j,y)      <- zip  [0..] l], xIsQ) -- :: ([Float],Bool)-                       | (i,x,xIsQ) <- zip3 [0..] l (snd . unzip $ fsQLab)]-         -- calculate the similarity sim a b, or, if calculated, look up sim b a                         -         calcSim :: (a -> a -> Float) -> a -> a -> Int -> Int -> Float-         calcSim sim x y i j = if isSymmetrical m && j < i -                               then (fst (simMat !! j)) !! i else sim x y-         simMat, querySimMat :: [([Float],Bool)]-         simMat = (case m of -- full n x n similarity matrix-                    STDiff    -> match diffChordsLen tks-                    LCESsize  -> match getLCESsize ps-                    LCESsim   -> match getLCESsim  ps-                    HAnAlign  -> match getHAnDist  ps-                    Align     -> match (getAlignDist tempKeyC tempKeyC) tks  -                  )  where tempKeyC = (Key (Note Nothing C) MajMode)-         -- filter all non-queries, lazy evaluation should ensure the -         -- non-queries will not be evaluated-         querySimMat = (filter snd simMat) `using` parList rdeepseq-     sequence_ [ printLine x | (x,_) <- querySimMat]-     -printLine :: [Float] -> IO ()     -printLine l  = printLn (foldr (\a b -> showFloat a ++ "\t" ++ b) "" l)    -     --- labels (True/False) the songs that have multiple versions and are queries-labelQuery :: [FilePath] -> [(FilePath, Bool)]-labelQuery l = let cs = getClassSizes l in -  map (\x -> (x,(>1) . length . fromJust $ lookup (getTitle x) cs)) l ---#ifdef AUDIO------------------------------------------------------------------------------------ Audio Ground-truth annotations IO--------------------------------------------------------------------------------- --parseAnnotation, parseAnnotationVerb :: (GTree g) => Grammar g -> [PPOption] -   -> String -> String -> IO (ParseResult g)-parseAnnotation g opts k ann =-  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ gt2Piece g k ann  -     putStrLn ("key: "  ++ k)-     putStrLn ("parsed " ++ show (length tks)++ " audio chord annotations into " -                         ++ show n           ++ " ambiguous trees")-     if not $ null te then showErrors "tokenizer: " te-                     else putStr ""-     if not $ null pe then showErrors "parser: " pe-                     else putStr ""-     return pr--parseAnnotationVerb g opts k ann =-  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ gt2Piece g k ann  -     putStrLn ("key: "  ++ k)-     if not $ null te then do putStrLn "tokenizer errors:" ; mapM_ print te-                      else putStr ""-     if not $ null pe then do putStrLn "parser errors:" ; mapM_ print pe-                      else putStr ""-     putStrLn ("parsed " ++ show (length tks)++ " audio chord annotations into " -                         ++ show n           ++ " ambiguous trees")-     return pr   ---- parses a directory of annotation files and key description files--- and prints condenced parsing information to std out-parseAnnotationDir :: GTree a => Grammar a -> [PPOption] -> FilePath -> FilePath-                   -> IO ()     -parseAnnotationDir g opts kdir andir =-  do ks  <- getDirectoryContents kdir-     ans <- getDirectoryContents andir-         -- prints parse results in one line-     let prntParse :: (FilePath,FilePath) -> IO ()-         prntParse (kfp,anfp) = -           do k <- readFile kfp-              a <- readFile anfp-              printLn . concat $ intersperse "\t" (takeFileName kfp -                : (showParseResult . postProc opts $ gt2Piece g k a))-         -- filters .lab files and adds the path-         fileFilter :: FilePath -> [FilePath] -> [FilePath]-         fileFilter pf = map (combine pf) . filter ((== ".lab") . takeExtension)       --     case matchKeyAnn (fileFilter kdir  $ reverse ks) -                      (fileFilter andir $ reverse ans) of-       Just x  -> do printLn ("Filename\tkey\tnrOfTrees\tInsertions\tDeletions"-                           ++ "\tDelsAtEnd\tTotalErr\tnrOfChords\tTokenizerErr")-                     mapM_ prntParse x-       Nothing -> putStrLn ("the filenames in " ++ kdir -                         ++ " do not exactly match the ones in " ++ andir)---- Checks if the key and the annotation files all match, if this is the--- case it will return a paired list of these files-matchKeyAnn :: [FilePath] -> [FilePath] -> Maybe [(FilePath,FilePath)]     -matchKeyAnn ks ans = -  let match = and $ zipWith eqFileName ks ans-      eqFileName :: FilePath -> FilePath -> Bool-      eqFileName a b = takeFileName a == takeFileName b-  in if match then Just $ zip ks ans else Nothing---- shows some elements of a ParseResult -showParseResult :: ParseResult a -> [String]-showParseResult (ParseResult k tk _p _han n te pe _pp) =-  let pErr   = countErrors pe -      insert = ins pErr-      delete = del pErr-      endDel = delEnd pErr-      total  = insert + delete + endDel-  -- key nrOfTrees Insertions Deletions DelsAtEnd TotalErr tokenizerErr-  in show k : (map show (n : insert : delete : endDel : total -                           : length tk : length te :[]))-------------------------------------------------------------------------------------- Audio Data IO--------------------------------------------------------------------------------- ---- the strings that build up a data file   -vampStr, keyStr, chromaStr, beatStr :: String-chromaStr = "nnls-chroma_nnls-chroma_bothchroma"--- keyStr    = "qm-vamp-plugins_qm-keydetector_keystrength"-keyStr    = "nnls-chroma_nnls-chroma_chroma"-beatStr   = "qm-vamp-plugins_qm-tempotracker_beats"-vampStr   ="(^.+)_vamp_("++ chromaStr ++ '|' : keyStr ++ '|' : beatStr -                         ++ ").csv$"                             --                       --- maps readAudioFeat over a directory-readAudioFeatureDir :: FilePath -> IO [Maybe AudioFeat]-readAudioFeatureDir fp = -  do fs <- getDirectoryContents fp-     mapM (readAudioFeat fp) -       (group . sort $ filter (\x -> x =~ vampStr) fs) -       where-       group :: [FilePath] -> [(FilePath, FilePath, FilePath)]-       group (c:k:b:fs) = (c,b,k) : group fs-       group [] =[]-       group _  = error ("the number of files in the filepath " -                     ++  "cannot be divided by 3")---- given a base file path and a triple of three filenames describing --- a chroma, beat and key file, parses all data and returns an audioFeat--- if everything went well.                     -readAudioFeat :: FilePath -> (FilePath, FilePath, FilePath) -              -> IO (Maybe AudioFeat)-readAudioFeat baseURI (chroma, beat, key) = -  -- get the part of the filenames before _vamp_ and use it as ID-  let (idStr:ids) = map ((maybe "" head) . regexMatchGroups vampStr) -                    [chroma,beat,key] in-  if all (idStr ==) ids then do -- if the IDs are the same then proceed-     dChroma <- readFile (baseURI </> chroma)-     dBeat   <- readFile (baseURI </> beat)-     dKey    <- readFile (baseURI </> key)-     -- mapM_ (\x -> putStrLn ("reading: " ++ show x)) -               -- ((baseURI </> chroma):(baseURI </> beat):[baseURI </> key])-     let chrm = parseData parseChordinoData    dChroma-         beats= parseData parseBeatData        dBeat-         -- keys = parseData parseKeyStrengthData dKey-         keys = parseData parseChromaData      dKey-     -- N.B. the lines below caused a memory leak!-     -- let (chrm,  cerr) = parseDataWithErrors parseChordinoData    dChroma-         -- (beats, berr) = parseDataWithErrors parseBeatData        dBeat-         -- (keys,  kerr) = parseDataWithErrors parseKeyStrengthData dKey-     -- if not (null cerr) then showErrors "both-chroma: "    cerr else putStr ""-     -- if not (null berr) then showErrors "beat-detection: " berr else putStr ""-     -- if not (null kerr) then showErrors "key-strength: "   kerr else putStr ""-     return . Just $ AudioFeat idStr chrm beats keys-   else do putStrLn ("found non-matching set of audiofeatures with ids " -                     ++ show ids)-           return Nothing--{- | evaluluates a single labeling of a piece with a ground truth annotation -visually: -time	match	GT	MPTREE-0.0	True	NNone	NNone-0.2	True	EMaj	EMaj-... etc.-The arguments need some explanation: the first filepath should be -the filepath to one of the data files (there must be three, a chroma, a-beat and a key file, to create an AudioFeat), but without all text after-"_vamp_" e.g. for reading file1_vamp_nnls-chroma_nnls-chroma_bothchroma.csv-only file1 should be presented. The function below will now read all -three data files in by adding chromaStr, beatStr and keyStr, respectively-the second file path should just point at the ground truth annotation--}-evaluateLabeling :: (Maybe [TimedData Key] -> AudioFeat -> ChordAnnotation) -                 -> Bool -> FilePath -> FilePath -> Maybe FilePath -> IO Double-evaluateLabeling annotator prnt gtfp audiofp keyfp = do-  let (path, file) = splitFileName audiofp-      files = ( file ++ chromaStr <.> "csv"-              , file ++ beatStr   <.> "csv"-              , file ++ keyStr    <.> "csv" )-  gt        <- readAnnotation gtfp-  (Just af) <- readAudioFeat  path files--  case (keyfp,prnt) of-    (Nothing,True)  -> do printLn ("using key finding")-                          putSegStats Nothing af-                          printRelCorrectOverlap (annotator Nothing) af gt-    (Nothing,False) -> do return (relCorrectOverlap gt (annotator Nothing af))-    (Just k ,True)  -> -        do key <- readAndParseKeyAnn k-           printLn ("using groundTruth a key annotation: " ++ show key)-           putSegStats (Just key) af-           printRelCorrectOverlap (annotator (Just key)) af gt-    (Just k ,False) -> -        do key <- readAndParseKeyAnn k-           return (relCorrectOverlap gt (annotator (Just key) af))-  --- given a ground truth directory and an data directory (containing exactly--- 3 times as much files as the gt directory) all files will be labeled and--- the relative correct overlap wil be corrected an presented to the user-batchLabeling :: (Maybe [TimedData Key] -> AudioFeat -> ChordAnnotation) -              -> FilePath -> FilePath   -> Maybe FilePath -> IO ()-batchLabeling annotator gtfp audiofp keyfp = do-  gt <- getDirectoryContents gtfp-  af <- readAudioFeatureDir  audiofp-  -  -- parse the key annotations [Maybe [TimedData Key]]-  mKeys <- case keyfp of-             Nothing   -> -                do printLn "using key finding"-                   return $ repeat Nothing -             (Just fp) -> -                do printLn "using key ground-truth annotations"-                   kfs <- getDirectoryContents fp-                   -- better to move also the reading etc. to evalR-                   ks  <- mapM (\x ->readAndParseKeyAnn (fp </> x))-                           (sort $ filter ((== ".lab") . takeExtension ) kfs)  -                   return $ map Just ks--  -- this is really hacky, but there is no zipWith3M-  let files = zip (sort $ filter ((== ".lab") . takeExtension ) gt) mKeys-  -  printLn "file\trun time (seconds)\trelative correct overlap"  -  res   <- zipWithM evalR files af-  -- _ <- zipWithM printRes files res-  printLn ("average: " ++ show (sum (fmap fst res) / genericLength res))-  where-    evalR :: (FilePath, Maybe [TimedData Key]) -> Maybe AudioFeat-          -> IO (Double, Float)-    evalR _  Nothing  = error "evalR: Nothing"-    evalR (fp, maybeKey) (Just af) = do-      gt <- readAnnotation (gtfp </> fp)-      let result = relCorrectOverlap gt (annotator maybeKey af {- `using` parList rdeepseq -})-          exec   = seq result (return ())-      t1 <- getCPUTime-      exec-      t2 <- getCPUTime-      let time = fromIntegral (t2 - t1) / (1000000000000 :: Float)-      printRes fp (result, time)-      return (result, time)--    printRes :: FilePath -> (Double, Float) -> IO ()-    printRes fp (r,t) = printLn (fp ++ ":\t" ++ showFloat t ++ '\t' : show r)-                        >> hFlush stdout-#endif---- reads an annotation-readAnnotation :: FilePath -> IO ChordAnnotation-readAnnotation fp = do f <- readFile fp-                       return (parseData parseAnnotationData f)-  -readAndParseKeyAnn :: FilePath -> IO [TimedData Key]-readAndParseKeyAnn keyfp = do key <- readFile keyfp -                              return $ parseData parseKeyAnnotationData key-  ------------------------------------------------------------------------------------ Utils----------------------------------------------------------------------------------putErrLn :: String -> IO()-putErrLn = hPutStrLn stderr--printLn :: String ->IO ()-printLn s = putStrLn s >> hFlush stdout---regexMatchGroups :: String -> String -> Maybe [String]-regexMatchGroups pat str = do-  (_,_,_,groups) <- str =~~ pat :: Maybe (String,String,String,[String])-  return groups---- Stricter readFile-hGetContents' :: Handle -> IO [Char]-hGetContents' hdl = do e <- hIsEOF hdl-                       if e then return []-                         else do c <- hGetChar hdl-                                 cs <- hGetContents' hdl-                                 return (c:cs)--readFile' :: FilePath -> IO [Char]-readFile' fn = do hdl <- openFile fn ReadMode-                  xs <- hGetContents' hdl-                  hClose hdl-                  return xs-                  -readDataDir :: FilePath -> IO [FilePath]-readDataDir fp = -  do fs <- getDirectoryContents fp-     return . sort $ filter (\str -> str =~ biabPat) fs---- | Shows a Float with 5 decimal places-showFloat :: Float -> String-showFloat = printf "%.6f" +module HarmTrace.IO.Main ( 
+    module HarmTrace.IO.Parse,
+    module HarmTrace.IO.Match,
+    module HarmTrace.IO.Recognise,
+    module HarmTrace.IO.Common
+  ) where
+
+-- Simply re-export all the IO stuff
+import HarmTrace.IO.Parse
+import HarmTrace.IO.Match
+import HarmTrace.IO.Recognise
+import HarmTrace.IO.Common
+
+ src/HarmTrace/IO/Match.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HarmTrace.IO.Match ( 
+
+    MatchMode(..), dirMatch
+
+  ) where
+
+
+-- Common IO functions
+import HarmTrace.IO.Common
+
+-- Music stuff
+import HarmTrace.HarmTrace
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Jazz.Instances ()
+import HarmTrace.HAnTree.Tree (Tree)
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.IO.Errors
+import HarmTrace.Matching.Standard
+import HarmTrace.Matching.GuptaNishimura
+import HarmTrace.Matching.Alignment (getAlignDist, getHAnDist)
+
+-- Library modules
+import System.FilePath
+import Data.Maybe (isJust, fromJust)
+import Data.Binary
+
+-- Parallelism
+import Control.Parallel.Strategies
+
+
+--------------------------------------------------------------------------------
+-- Symbolic Matching IO
+--------------------------------------------------------------------------------
+
+data MatchMode = STDiff | LCESsize | LCESsim | HAnAlign | Align
+  deriving (Eq, Ord, Show)  
+
+-- should return True if sim a b == sim b a and False otherwise  
+isSymmetrical :: MatchMode -> Bool
+-- @pedro: I guess it is symmetrical, but I'm not 100% sure
+isSymmetrical STDiff    = False 
+isSymmetrical LCESsize  = True
+isSymmetrical LCESsim   = True
+isSymmetrical HAnAlign  = True  
+isSymmetrical Align     = True  
+  
+-- matches a directory of chord description files
+dirMatch :: (GTree g)
+         => Grammar g -> [PPOption] -> Maybe FilePath
+         -> MatchMode -> Maybe Float -> FilePath -> IO ()
+dirMatch g o bIn m me fp = 
+  do fs <- readDataDir fp
+     let process s   = let (ParseResult _ tks _ ts _nrts _ ePar _) 
+                              = postProc o $ string2Piece g s
+                       in  (tks, ts, errorRatio ePar tks)
+         filterError = if isJust me 
+                          then filter (\(_,_,e) -> e <= fromJust me) else id
+     pss <- mapM (\f -> readFile' (fp </> f)) fs
+     (tks, ps) <- case bIn of
+                    Just bp -> decodeFile bp :: IO ([[ChordLabel]],[Tree HAn])
+                    Nothing -> let (toks, ps', _) = unzip3 (filterError 
+                                                             (map process pss))
+                               -- the line below gives an compile error in ghci
+                               in return (toks, ps' `using` parList rdeepseq)
+                               -- in return (toks, ps')
+     let fsQLab = labelQuery fs
+     -- print the ireval format ...
+     putStr "true\n"
+     if (m == LCESsize || m == LCESsim || m == HAnAlign || m == Align) 
+        then putStr "false\n" else putStr "true\n"
+     mapM_ (putStr . (++ "\t"). showIntWithZeros . getId . getInfo) 
+           (fst . unzip $ filter snd fsQLab) 
+     putChar '\n'
+     mapM_ (putStr . (++ "\t"). showIntWithZeros . getId . getInfo) fs 
+     putChar '\n'
+     -- do the actual matching ... 
+     let match :: (a -> a -> Float) -> [a] -> [([Float],Bool)]
+         match sim l = [ ([ calcSim sim x y i j
+                       | (j,y)      <- zip  [0..] l], xIsQ) -- :: ([Float],Bool)
+                       | (i,x,xIsQ) <- zip3 [0..] l (snd . unzip $ fsQLab)]
+         -- calculate the similarity sim a b, or, if calculated, look up sim b a                         
+         calcSim :: (a -> a -> Float) -> a -> a -> Int -> Int -> Float
+         calcSim sim x y i j = if isSymmetrical m && j < i 
+                               then (fst (simMat !! j)) !! i else sim x y
+         simMat, querySimMat :: [([Float],Bool)]
+         simMat = (case m of -- full n x n similarity matrix
+                    STDiff    -> match diffChordsLen tks
+                    LCESsize  -> match getLCESsize ps
+                    LCESsim   -> match getLCESsim  ps
+                    HAnAlign  -> match getHAnDist  ps
+                    Align     -> match (getAlignDist tempKeyC tempKeyC) tks  
+                  )  where tempKeyC = (Key (Note Nothing C) MajMode)
+         -- filter all non-queries, lazy evaluation should ensure the 
+         -- non-queries will not be evaluated
+         querySimMat = (filter snd simMat) `using` parList rdeepseq
+     sequence_ [ printLine x | (x,_) <- querySimMat]
+     
+printLine :: [Float] -> IO ()     
+printLine l  = printLn (foldr (\a b -> showFloat a ++ "\t" ++ b) "" l)    
+     
+-- labels (True/False) the songs that have multiple versions and are queries
+labelQuery :: [FilePath] -> [(FilePath, Bool)]
+labelQuery l = let cs = getClassSizes l in 
+  map (\x -> (x,(>1) . length . fromJust $ lookup (getTitle $ getInfo x) cs)) l 
+ src/HarmTrace/IO/Parse.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HarmTrace.IO.Parse (
+
+    parseTree, parseTreeVerb, parseDir
+
+  ) where
+
+
+-- Common IO functions
+import HarmTrace.IO.Common
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+
+-- Music stuff
+import HarmTrace.HarmTrace
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Jazz.Instances ()
+import HarmTrace.HAnTree.Tree (Tree)
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.IO.Errors
+
+-- Library modules
+import Data.List (sort, intersperse, genericLength)
+import System.FilePath
+import System.Directory
+import System.CPUTime
+import Data.Binary
+
+
+--------------------------------------------------------------------------------
+-- Symbolic Parsing IO 
+--------------------------------------------------------------------------------
+
+-- parses a string of chords and returns a parse tree with the harmony structure
+parseTree, parseTreeVerb :: (GTree g) => Grammar g -> [PPOption] -> String 
+                         -> IO (ParseResult g)
+parseTree g opts s =
+  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ string2Piece g s
+     putStrLn ("parsed " ++ show (length tks) ++ " chords into " 
+                         ++ show n            ++ " ambiguous trees")
+     if not $ null te then showErrors "tokenizer: " te 
+                     else putStr ""
+     if not $ null pe then showErrors "parser: " pe 
+                     else putStr ""
+     return pr
+
+parseTreeVerb g opts s = 
+  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ string2Piece g s
+     putStrLn ("parsed " ++ show (length tks) ++ " chords into " 
+                         ++ show n            ++ " ambiguous trees")
+     if not $ null te then mapM_ print te
+                      else putStr ""
+     if not $ null pe then mapM_ print pe
+                      else putStr ""
+     return pr                 
+      
+-- Batch analyzing a directory with chord sequence files with reduced output.
+parseDir :: (GTree g)
+         => Grammar g -> [PPOption] -> FilePath -> Maybe FilePath -> IO ()
+parseDir g opts filepath bOut =     getDirectoryContents filepath 
+                                >>= parseDir' g opts bOut filepath . sort
+
+parseDir' :: (GTree g)
+          => Grammar g -> [PPOption] -> Maybe FilePath
+          -> String -> [String] -> IO ()
+parseDir' g opts bOut fp fs = 
+  do putStr "Filename\tNumber of trees\t"
+     putStr "Insertions\tDeletions\tReplacements\tDeletions at the end\t"
+     putStr "Tot_Correction\tNr_of_chords\t"
+     putStrLn "Error ratio"
+     --putStrLn "Error ratio\tTime taken"
+     let process :: FilePath -> FilePath -> IO ([ChordLabel],Tree HAn, Int)
+         process path x =
+          do content <- readFile (path </> x)
+             let (ParseResult _ tks ps ts nr e1 e2 _) 
+                      = postProc opts $ string2Piece g content
+                 -- @Pedro: I think that the (length ts) only is here to 
+                 -- evaluate all trees right? Since the tree selection is now
+                 -- incorporated in the postprocessing I replaced it with 
+                 -- length ps
+                 -- t                = seq (length ts) (return ())
+                 t                = seq (length ps) (return ())
+                 ErrorNrs i d e r = countErrors e2
+                 errRat           = errorRatio e2 tks
+                 nrOfChords       = length tks -- (mergeDups toks)
+             t1 <- getCPUTime
+             t
+             t2 <- getCPUTime
+             let _diff = fromIntegral (t2 - t1) / (1000000000 :: Float)
+             when (not $ null e1) $ putErrLn (show x ++ ": " ++ show e1)
+             printLn . concat $ intersperse "\t" [ x, show nr
+                                       , show i, show d, show r, show e
+                                       , show (i+d+e+r)
+                                       , show nrOfChords, showFloat errRat
+                                        -- JPM: do not print the time taken so
+                                        -- as to allow diffing output in tests
+                                       ] -- , showFloat diff]
+             return (tks, ts, i+d+e+r)
+     res <- mapM (process fp) (filter ((== ".txt") . takeExtension) fs)
+     let (cs,ts,err) = (unzip3 res :: ([[ChordLabel]],[Tree HAn], [Int]))
+     printLn ("average error: " ++ 
+               show ((fromIntegral $ sum err) / (genericLength err) :: Double))
+     case bOut of
+       Nothing -> return ()
+       Just bf -> encodeFile bf (cs, ts)
src/HarmTrace/IO/PrintTree.hs view
@@ -1,80 +1,80 @@-{-# OPTIONS_GHC -Wall #-}-module HarmTrace.IO.PrintTree ( printTreeF   , printTree-                              , printTreeHAnF, printTreeHAn) where--import System.Exit (ExitCode)-import System.Process (runProcess, waitForProcess, ProcessHandle)-import HarmTrace.HAnTree.Tree (Tree)-import HarmTrace.HAnTree.HAn (HAn)----- needs gnu wget http://www.gnu.org/software/wget/ --- or             http://gnuwin32.sourceforge.net/packages/wget.htm--printTreeHAn :: Tree HAn -> FilePath -> IO ExitCode-printTreeHAn t o = printTree (show t) (o ++ ".png")--printTreeHAnF :: [Tree HAn] -> String -> IO ExitCode-printTreeHAnF ts o = printTreeF (map show ts) o----- |gets a .png from http://ironcreek.net/phpsyntaxtree/ that prints--- the parse tree of the chord sequence that was entered as parsed by our --- HarmGram model. wget is used under the hood. If any, the first ten --- ambiguous parse trees are printed.                -printTreeF :: [String] -> -- ^ the tree description to be printed   -              String   -> -- ^ a string for generating the filenames            -              IO ExitCode      -printTreeF trees  outStr   =    printMoreTrees (take 15 trees) outStr 0 where-  printMoreTrees :: [String] -> String -> Int -> IO ExitCode-  printMoreTrees []     _   _ = error "empty list of trees, nothing to print"-  printMoreTrees [t]    out i = printTree t (nrFile out i) -  printMoreTrees (t:ts) out i = do _ <- printTree t (nrFile out i) -                                   printMoreTrees ts out  (i+1)-  nrFile :: String -> Int -> String                                      -  nrFile str i = str ++ show i ++ ".png"    -  --- |gets a .png from http://ironcreek.net/phpsyntaxtree/ that prints--- the parse tree of the chord sequence that was entered as parsed by our --- HarmGram model. wget is used under the hood. If any, the first ten --- ambiguous parse trees are printed.                           -printTree :: String ->   -- ^ the tree description to be printed-             FilePath -> -- ^ a filepath to the output file-             IO ExitCode  -printTree tree outfile  = do submit <- submitTree tree -                             _ <- waitForProcess submit -                             png <- getpng outfile -                             waitForProcess png---- wget --save-cookies cookies.txt -        --keep-session-cookies -        --post-data "data=[tree]&drawbtn=&opencount=3&closedcount=3&font=vera_sans&fontsize=8&color=on&antialias=on&autosub=on&triangles=on" -        --http://ironcreek.net/phpsyntaxtree/ -submitTree :: String -> IO ProcessHandle-submitTree tree = wget "http://ironcreek.net/phpsyntaxtree/" opt  where-  opt = [ "--save-cookies"-        , "phpsyntax_cookies.txt"-        , "--keep-session-cookies"-        , "--quiet"-        , "--delete-after"-        , "--post-data"-        , "data=" ++ tree ++ "&drawbtn=&opencount=3&closedcount=3&" ++-          "font=vera_sans&fontsize=8&color=on&antialias=on&triangles=on"-        ]-        ---        ---wget  cookies.txt --output-document %2.png http://ironcreek.net/phpsyntaxtree/dnlgraph.php -getpng :: String -> IO ProcessHandle-getpng name = wget "http://ironcreek.net/phpsyntaxtree/dnlgraph.php" opt  where-  opt = [ "--load-cookies"-        , "phpsyntax_cookies.txt"-        , "--quiet"-        , "--output-document"-        , name-        ]-        -wget :: String -> [String] -> IO ProcessHandle-wget url opt = -  do let par = url : opt-     runProcess "wget" par Nothing Nothing Nothing Nothing Nothing-+{-# OPTIONS_GHC -Wall #-}
+module HarmTrace.IO.PrintTree ( printTreeF   , printTree
+                              , printTreeHAnF, printTreeHAn) where
+
+import System.Exit (ExitCode)
+import System.Process (runProcess, waitForProcess, ProcessHandle)
+import HarmTrace.HAnTree.Tree (Tree)
+import HarmTrace.HAnTree.HAn (HAn)
+
+
+-- needs gnu wget http://www.gnu.org/software/wget/ 
+-- or             http://gnuwin32.sourceforge.net/packages/wget.htm
+
+printTreeHAn :: Tree HAn -> FilePath -> IO ExitCode
+printTreeHAn t o = printTree (show t) (o ++ ".png")
+
+printTreeHAnF :: [Tree HAn] -> String -> IO ExitCode
+printTreeHAnF ts o = printTreeF (map show ts) o
+
+
+-- |gets a .png from http://ironcreek.net/phpsyntaxtree/ that prints
+-- the parse tree of the chord sequence that was entered as parsed by our 
+-- HarmGram model. wget is used under the hood. If any, the first ten 
+-- ambiguous parse trees are printed.                
+printTreeF :: [String] -> -- ^ the tree description to be printed   
+              String   -> -- ^ a string for generating the filenames            
+              IO ExitCode      
+printTreeF trees  outStr   =    printMoreTrees (take 15 trees) outStr 0 where
+  printMoreTrees :: [String] -> String -> Int -> IO ExitCode
+  printMoreTrees []     _   _ = error "empty list of trees, nothing to print"
+  printMoreTrees [t]    out i = printTree t (nrFile out i) 
+  printMoreTrees (t:ts) out i = do _ <- printTree t (nrFile out i) 
+                                   printMoreTrees ts out  (i+1)
+  nrFile :: String -> Int -> String                                      
+  nrFile str i = str ++ show i ++ ".png"    
+  
+-- |gets a .png from http://ironcreek.net/phpsyntaxtree/ that prints
+-- the parse tree of the chord sequence that was entered as parsed by our 
+-- HarmGram model. wget is used under the hood. If any, the first ten 
+-- ambiguous parse trees are printed.                           
+printTree :: String ->   -- ^ the tree description to be printed
+             FilePath -> -- ^ a filepath to the output file
+             IO ExitCode  
+printTree tree outfile  = do submit <- submitTree tree 
+                             _ <- waitForProcess submit 
+                             png <- getpng outfile 
+                             waitForProcess png
+
+-- wget --save-cookies cookies.txt 
+        --keep-session-cookies 
+        --post-data "data=[tree]&drawbtn=&opencount=3&closedcount=3&font=vera_sans&fontsize=8&color=on&antialias=on&autosub=on&triangles=on" 
+        --http://ironcreek.net/phpsyntaxtree/ 
+submitTree :: String -> IO ProcessHandle
+submitTree tree = wget "http://ironcreek.net/phpsyntaxtree/" opt  where
+  opt = [ "--save-cookies"
+        , "phpsyntax_cookies.txt"
+        , "--keep-session-cookies"
+        , "--quiet"
+        , "--delete-after"
+        , "--post-data"
+        , "data=" ++ tree ++ "&drawbtn=&opencount=3&closedcount=3&" ++
+          "font=vera_sans&fontsize=8&color=on&antialias=on&triangles=on"
+        ]
+        
+--        
+--wget  cookies.txt --output-document %2.png http://ironcreek.net/phpsyntaxtree/dnlgraph.php 
+getpng :: String -> IO ProcessHandle
+getpng name = wget "http://ironcreek.net/phpsyntaxtree/dnlgraph.php" opt  where
+  opt = [ "--load-cookies"
+        , "phpsyntax_cookies.txt"
+        , "--quiet"
+        , "--output-document"
+        , name
+        ]
+        
+wget :: String -> [String] -> IO ProcessHandle
+wget url opt = 
+  do let par = url : opt
+     runProcess "wget" par Nothing Nothing Nothing Nothing Nothing
+
+ src/HarmTrace/IO/Recognise.hs view
@@ -0,0 +1,397 @@+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.IO.Recognise
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Handels all the IO of the 'recognise' mode of HarmTrace, which
+-- deals with audio chord transcription.
+--------------------------------------------------------------------------------
+
+module HarmTrace.IO.Recognise (
+
+    parseAnnotation, parseAnnotationVerb, parseAnnotationDir,
+    evaluateLabeling, printLabeling, batchLabeling
+
+  ) where
+
+
+-- Common IO functions
+import HarmTrace.IO.Common
+import HarmTrace.IO.BasePaths (BasePaths, getOutDir, getFeatDir)
+import Constants ( keyStr, chromaStr, beatStr )
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+
+-- Music stuff
+import HarmTrace.HarmTrace
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Jazz.Instances ()
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.IO.Errors
+import HarmTrace.Base.Parsing
+
+-- Audio stuff
+import HarmTrace.Audio.DataParser ( parseChordinoData, parseChromaData
+                                  , parseBeatBarData)
+import HarmTrace.Audio.AnnotationParser
+import HarmTrace.Audio.Annotate (putSegStats)
+import HarmTrace.Audio.Evaluation
+import HarmTrace.Audio.ChordTypes 
+import HarmTrace.IO.FeatExtract
+import Data.List (genericLength, isSuffixOf, stripPrefix)
+
+-- Library modules
+import Data.List (sort, intersperse)
+import System.FilePath
+import System.Directory
+import System.IO
+import System.CPUTime
+import Data.Maybe (isJust, fromJust, isNothing)
+-- import Control.Parallel.Strategies (parList, rdeepseq, using)
+
+--------------------------------------------------------------------------------
+-- Audio Ground-truth annotations IO
+-------------------------------------------------------------------------------- 
+
+-- @pedro: I think the three "parse" functions should go to HarmTrace.IO.Parse
+parseAnnotation, parseAnnotationVerb :: (GTree g) => Grammar g -> [PPOption] 
+   -> String -> String -> IO (ParseResult g)
+parseAnnotation g opts k ann =
+  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ gt2Piece g k ann  
+     putStrLn ("key: "  ++ k)
+     putStrLn ("parsed " ++ show (length tks)++ " audio chord annotations into " 
+                         ++ show n           ++ " ambiguous trees")
+     if not $ null te then showErrors "tokenizer: " te
+                     else putStr ""
+     if not $ null pe then showErrors "parser: " pe
+                     else putStr ""
+     return pr
+
+parseAnnotationVerb g opts k ann =
+  do let pr@(ParseResult _ tks _ _ n te pe _) = postProc opts $ gt2Piece g k ann  
+     putStrLn ("key: "  ++ k)
+     if not $ null te then do putStrLn "tokenizer errors:" ; mapM_ print te
+                      else putStr ""
+     if not $ null pe then do putStrLn "parser errors:" ; mapM_ print pe
+                      else putStr ""
+     putStrLn ("parsed " ++ show (length tks)++ " audio chord annotations into " 
+                         ++ show n           ++ " ambiguous trees")
+     return pr   
+
+-- parses a directory of annotation files and key description files
+-- and prints condenced parsing information to std out
+parseAnnotationDir :: GTree a => Grammar a -> [PPOption] -> FilePath -> FilePath
+                   -> IO ()     
+parseAnnotationDir g opts kdir andir =
+  do ks  <- getDirectoryContents kdir
+     ans <- getDirectoryContents andir
+         -- prints parse results in one line
+     let prntParse :: (FilePath,FilePath) -> IO ()
+         prntParse (kfp,anfp) = 
+           do k <- readFile kfp
+              a <- readFile anfp
+              printLn . concat $ intersperse "\t" (takeFileName kfp 
+                : (showParseResult . postProc opts $ gt2Piece g k a))
+         -- filters .lab files and adds the path
+         fileFilter :: FilePath -> [FilePath] -> [FilePath]
+         fileFilter pf = map (combine pf) . filter ((== ".lab") . takeExtension)       
+
+     case matchKeyAnn (fileFilter kdir  $ reverse ks) 
+                      (fileFilter andir $ reverse ans) of
+       Just x  -> do printVersion
+                     printLn ("Filename\tkey\tnrOfTrees\tInsertions\tDeletions"
+                           ++ "\tDelsAtEnd\tTotalErr\tnrOfChords\tTokenizerErr")
+                     mapM_ prntParse x
+       Nothing -> putStrLn ("the filenames in " ++ kdir 
+                         ++ " do not exactly match the ones in " ++ andir)
+
+-- Checks if the key and the annotation files all match, if this is the
+-- case it will return a paired list of these files
+matchKeyAnn :: [FilePath] -> [FilePath] -> Maybe [(FilePath,FilePath)]     
+matchKeyAnn ks ans = 
+  let match = and $ zipWith eqFileName ks ans
+      eqFileName :: FilePath -> FilePath -> Bool
+      eqFileName a b = takeFileName a == takeFileName b
+  in if match then Just $ zip ks ans else Nothing
+
+-- shows some elements of a ParseResult 
+showParseResult :: ParseResult a -> [String]
+showParseResult (ParseResult k tk _p _han n te pe _pp) =
+  let pErr   = countErrors pe 
+      insert = ins pErr
+      delete = del pErr
+      endDel = delEnd pErr
+      total  = insert + delete + endDel
+  -- key nrOfTrees Insertions Deletions DelsAtEnd TotalErr tokenizerErr
+  in show k : (map show (n : insert : delete : endDel : total 
+                           : length tk : length te :[]))
+
+
+--------------------------------------------------------------------------------
+-- Audio Data IO
+-------------------------------------------------------------------------------- 
+
+-- | Returns True when the argument is an audio feature file based on 
+-- the filename
+isAudioFeatureFile :: FilePath -> Bool
+isAudioFeatureFile fp
+  | isSuffixOf chromaStr fp = True
+  | isSuffixOf keyStr    fp = True
+  | isSuffixOf beatStr   fp = True
+  | otherwise               = False
+
+-- | Retuns the audio feature identifier, which equals the filename without
+-- the extension
+getAudioFeatureId :: FilePath -> Maybe String
+getAudioFeatureId fp
+  | isJust key  = key
+  | isJust chm  = chm
+  | isJust bt   = bt  
+  | otherwise   = Nothing
+  where key  = stripSuffix keyStr fp
+        chm  = stripSuffix chromaStr fp
+        bt   = stripSuffix beatStr fp
+
+        -- drops a given suffix from a string. It returns nothing if the suffix 
+        -- is not a suffix of the string
+        stripSuffix :: String -> String -> Maybe String
+        stripSuffix suf txt 
+          | isJust stp = Just . reverse $ fromJust stp
+          | otherwise  = Nothing
+          where stp = stripPrefix (reverse suf) (reverse txt) 
+
+-- maps readAudioFeat over a directory
+readAudioFeatureDir :: FilePath -> IO [AudioFeat]
+readAudioFeatureDir fp = 
+  do fs <- getDirectoryContents fp
+     mapM readAudioFeatures (group . sort $ filter isAudioFeatureFile fs) 
+       where
+       group :: [FilePath] -> [(FilePath, FilePath, FilePath)]
+       group (c:k:b:fs) = (fp </> c, fp </> b, fp </> k) : group fs
+       group [] =[]
+       group _  = error ("HarmTrace.IO.Recognise.readAudioFeatureDir: the " 
+                   ++  "number of files in the filepath cannot be divided by 3")
+
+-- Given triplet of three filenames describing 
+-- a chroma, beat and key feature file, 'readAudioFeat' parses all data and returns an 'AudioFeat'.
+readAudioFeatures :: (FilePath,FilePath,FilePath) -> IO (AudioFeat)
+readAudioFeatures (cfp,bfp,kfp) =
+  do dChroma <- readFile cfp
+     dBeat   <- readFile bfp
+     dKey    <- readFile kfp
+    
+     let chrm  = parseData parseChordinoData    dChroma
+         beats = parseData parseBeatBarData     dBeat
+         keys  = parseData parseChromaData      dKey
+     return (AudioFeat chrm beats keys)
+
+-- Given one VAMP feature CSV file, HarmTrace will look for the other two 
+-- feature files needed for chord transcription. If the first argument it True
+-- 'findAudioFeatures' outputs the filepaths of the feature files it found. 
+findAudioFeatures :: Bool -> FilePath -> IO (Maybe (FilePath,FilePath,FilePath))
+findAudioFeatures verbose fp =
+  case getAudioFeatureId fp of
+    Nothing     -> return Nothing
+    (Just afid) -> do let cfp = afid ++ chromaStr
+                          bfp = afid ++ beatStr
+                          kfp = afid ++ keyStr
+                     
+                      cfpExists <- doesFileExist cfp
+                      bfpExists <- doesFileExist bfp
+                      kfpExists <- doesFileExist kfp
+                      
+                      when (verbose && cfpExists) (putStrLn ("found: " ++ cfp))
+                      when (verbose && bfpExists) (putStrLn ("found: " ++ bfp))
+                      when (verbose && kfpExists) (putStrLn ("found: " ++ kfp))
+                      
+                      case (cfpExists && bfpExists && kfpExists) of
+                         True  -> return (Just (cfp,bfp,kfp))
+                         False -> return Nothing
+           
+-- | Evaluluates a single labeling of a piece with a ground truth annotation 
+-- visually.
+evaluateLabeling :: (Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation) 
+                 -> Bool -> FilePath -> FilePath -> Maybe FilePath -> IO Double
+evaluateLabeling annotator prnt gtfp featfp keyfp = do
+  
+  maf <- findAudioFeatures True featfp
+  af  <- readAudioFeatures (fromJust maf)
+  gt  <- readAnnotation gtfp
+  
+  case (isJust maf, keyfp, prnt) of
+    (True, Nothing,True)  -> 
+        do printLn ("using key finding")
+           putSegStats Nothing af
+           printRelCorrectOverlap (annotator Nothing) af gt
+    (True, Nothing,False) -> 
+        do return (relCorrectOverlap gt (dumpBeats $ annotator Nothing af))
+    (True, Just k ,True)  -> 
+        do key <- readAndParseKeyAnn k
+           printLn ("using groundTruth key annotation: " ++ show key)
+           putSegStats (Just key) af
+           printRelCorrectOverlap (annotator (Just key)) af gt
+    (True, Just k ,False) -> 
+        do key <- readAndParseKeyAnn k
+           return (relCorrectOverlap gt (dumpBeats $ annotator (Just key) af))
+    (False, _, _)         -> return (-1)
+  
+-- | Given a ground truth directory and an data directory (containing exactly
+-- 3 times as much files as the gt directory) all files will be labeled and
+-- the relative correct overlap wil be corrected an presented to the user
+batchLabeling :: (Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation) 
+              -> FilePath -> FilePath   -> Maybe FilePath -> IO ()
+batchLabeling annotator gtfp audiofp keyfp = do
+  gt <- getDirectoryContents gtfp
+  af <- readAudioFeatureDir  audiofp
+  
+  -- parse the key annotations [Maybe [TimedData Key]]
+  mKeys <- case keyfp of
+             Nothing   -> 
+                do printLn "using key finding"
+                   return $ repeat Nothing 
+             (Just fp) -> 
+                do printLn "using key ground-truth annotations"
+                   kfs <- getDirectoryContents fp
+                   -- better to move also the reading etc. to evalR
+                   ks  <- mapM (\x ->readAndParseKeyAnn (fp </> x))
+                           (sort $ filter ((== ".lab") . takeExtension ) kfs)  
+                   return $ map Just ks
+
+  -- this is really hacky, but there is no zipWith3M
+  let files = zip (sort $ filter ((== ".lab") . takeExtension ) gt) mKeys
+  
+  printVersion
+  printLn "file\trun time (seconds)\trelative correct overlap"  
+  res   <- zipWithM evalR files af 
+  -- let res'  = res `using` parList rdeepseq
+  printLn ("average: " ++ show (sum (fmap fst res) / genericLength res))
+  where
+    evalR :: (FilePath, Maybe [TimedData Key]) -> AudioFeat -> IO(Double, Float)
+    -- evalR _  Nothing  = error "evalR: Nothing"
+    evalR (fp, maybeKey) af = do
+      gt <- readAnnotation (gtfp </> fp)
+      let result = relCorrectOverlap gt (dumpBeats $ annotator maybeKey af)
+          exec   = seq result (return ())
+      t1 <- getCPUTime
+      exec
+      t2 <- getCPUTime
+      let runtime = fromIntegral (t2 - t1) / (1000000000000 :: Float)
+      printRes fp (result, runtime)
+      return (result, runtime)
+
+    printRes :: FilePath -> (Double, Float) -> IO ()
+    printRes fp (r,t) = printLn (fp ++ ":\t" ++ showFloat t ++ '\t' : show r)
+                        >> hFlush stdout
+
+-- takes a set of features or an audio file and writes the chords to a file
+-- the process is also logged in a logfile to keep track of the process
+-- (see HarmTrace.IO.FeatExtract)
+printLabeling :: BasePaths
+              -> (Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation) 
+              -> FilePath -> IO ()
+printLabeling dirs annotator fp = do
+  let -- we use the same logfile for adding harmtrace status
+      logf = getOutDir dirs </> takeFileName fp <.> "extract.log" 
+      out  = getOutDir dirs </> takeFileName fp <.> "chords.txt" 
+      ffp  = getFeatDir dirs </> dropExtension (takeFileName fp) ++ chromaStr
+
+  -- check (silently) if features have been extracted earlier
+  hasFt <- findAudioFeatures True ffp
+
+  case (isAudioFile fp, hasFt) of
+    -- We found an audio file and no previously extracted features: extract them
+    (True, Nothing) -> do -- extract features
+                          maf <- extractFeatures dirs fp
+                          if isNothing maf
+                             then appendFile logf (fp ++";ERROR;0;99\n")
+                             else do appendFile logf (fp ++ ";harmtrace;1;80\n")
+                                     -- print chords
+                                     readAndPrint out maf annotator
+                                     appendFile logf (fp++";harmtrace;100;99\n")
+                                     appendFile logf (fp++";DONE;100;100\n")
+                                  
+    -- We found an audio file, but also found matching features
+    (True,Just maf) -> do -- appendFile logf (fp ++ ";Initialize;1;1\n")
+                          writeFile logf (fp ++ ";harmtrace;1;80\n")
+                          readAndPrint out (Just maf) annotator 
+                          appendFile logf (fp ++ ";harmtrace;100;99\n")
+                          appendFile logf (fp ++ ";DONE;100;100\n")
+    
+    -- No audio, but one feature file: look for all feature files
+    (False,_      ) -> do maf <- findAudioFeatures True fp
+                          readAndPrint out maf annotator 
+      
+-- reads the features and prints the chords
+readAndPrint :: FilePath -> Maybe (FilePath, FilePath, FilePath) 
+             -> (Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation) 
+             -> IO ()
+readAndPrint _   Nothing   _         = return ()
+readAndPrint out (Just af) annotator = 
+  do feat <- readAudioFeatures af
+     writeAnnotation out . segPerBeat (getBeats feat) $ annotator Nothing feat
+                
+-- returns True if fp is an audio file
+isAudioFile :: FilePath -> Bool
+isAudioFile fp = elem (tail $ takeExtension fp) -- all sox file formats :-)
+  [ "8svx", "aif", "aifc", "aiff", "aiffc", "al", "amb", "amr-nb", "amr-wb" 
+  , "anb", "au", "avi", "avr", "awb", "cdda", "cdr", "cvs", "cvsd", "cvu", "dat"
+  , "dvms", "f32", "f4", "f64", "f8", "ffmpeg", "flac", "fssd", "gsm", "gsrt"
+  , "hcom", "htk", "ima", "ircam", "la", "lpc", "lpc10", "lu", "m4a", "m4b"
+  , "maud", "mp2", "mp3", "mp4", "mpg", "nist", "ogg", "prc", "raw", "s1"
+  , "s16", "s2", "s24", "s3", "s32", "s4", "s8", "sb", "sds", "sf", "sl", "smp"
+  , "snd", "sndfile", "sndr", "sndt", "sou", "sox", "sph", "sw", "txw", "u1"
+  , "u16", "u2", "u24", "u3", "u32", "u4", "u8", "ub", "ul", "uw", "vms", "voc"
+  , "vorbis", "vox", "wav", "wavpcm", "wmv", "wv", "wve", "xa"
+  ]
+
+
+-- reads an annotation
+readAnnotation :: FilePath -> IO ChordAnnotation
+readAnnotation fp = do f <- readFile fp
+                       return (parseData parseAnnotationData f)
+  
+readAndParseKeyAnn :: FilePath -> IO [TimedData Key]
+readAndParseKeyAnn keyfp = do key <- readFile keyfp 
+                              return $ parseData parseKeyAnnotationData key
+
+-- writes an annotation to a specific file
+writeAnnotation :: FilePath -> ChordBeatAnnotation -> IO ()
+writeAnnotation f ca = do hdl <- openFile f ReadWriteMode
+                          mapM_ (hPutStr hdl . show) ca
+                          hClose hdl
+
+-- segment a chord annotation per beat                       
+-- ChordAnnotation = [TimedData ChordLabel] and BeatTrackerData = [NumData] 
+segPerBeat :: BeatBarTrackData -> ChordBeatAnnotation -> ChordBeatAnnotation 
+segPerBeat  bts cds = segment (start : bts) cds where
+  start     = BeatBar (0,prevBeat . snd . beatBar $ head bts)
+  
+  -- takes a list of beats and a list of chords (where chords can span multiple 
+  -- beats) and returns a list of beat synchronised list chords (every chord
+  -- annoation has a one beat duration)
+  segment :: Show a => [BeatBar] -> [BeatTimedData a] -> [BeatTimedData a]
+  segment _   [] = []
+  segment [b] [c] 
+    | on < offset c = [setOnset c on]
+    | otherwise     = [] -- N.B. includes on == offset b!
+         where on      = fst $ beatBar b 
+  segment (a : b : bs) (c:cs)
+    | onset c <= on && offset c >= off 
+                = BeatTimedData (getData c) bt on off : segment (b:bs) (c:cs)
+    | otherwise = segment (a : b : bs) cs 
+         where (on, bt) = beatBar a
+               off     = fst $ beatBar b
+  -- should not happen
+  segment []  c = error ("we ran out of beats! Chords left: " ++ show c )
+  segment [b] c = error ("Asynchroneous beat (" ++ show b ++ 
+                         ") and chords: "       ++ show c )
+
src/HarmTrace/Matching/Alignment.hs view
@@ -1,179 +1,177 @@-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}---- $Id: Matching.hs 1260 2011-06-14 15:18:21Z bash $-module HarmTrace.Matching.Alignment ( alignChordLab, pPrintV, getAlignDist-                                    , getHAnDist, alignHAnChord-                                    -- , getDownRight, wbMatchF, align, Sim(..)-                                    -- , collectMatch-                                    ) where--import HarmTrace.Base.MusicRep-import HarmTrace.Matching.SimpleChord-import HarmTrace.Matching.HChord-import HarmTrace.Matching.Sim-import HarmTrace.HAnTree.HAn -import HarmTrace.HAnTree.Tree--import Prelude hiding (map, length, head, last, mapM_, max)--import Data.Vector hiding ((!), (++))-import qualified Data.List as L---- import Debug.Trace-{---Matching notes:-===============-** Normalisation ( sim * sim ) / (maxsim a * maxsim b) helps in practically -   all cases.-** The sampling in general has a large effect on matching speed, and a small -   effect on retrieval performance. In all observed cases using no sampling -   performs (slightly) better than not using sampling. The sample rate herein -   also has an effect: using normal integer division (`div`) deletes chords -   with a beat length of 1, which decreases retrieval performance. It is-   better to use a `div1` that also includes the chords with a duration of-   beat (see SimpleChord.myDiv)-** The mis-match penalty should be -2 > -1 < 0: -1 seems to be optimal -   (= insertion/deletion)-** Use very "conservative" similarity measures (not many things are similar) -** Using a ChordType instead of just major/minor improves results-** Using the HAnTrans information improves the similarity estimation-** using only the information of the model (HAnTrans and HAnFunc) performs-   worse than using the root and chord type-** Separating transformations (HAnTrans), i.e. Tritone substitutions, dimchord-   transformations etc., from preparations (HAnPrep), i.e. secondary dominants,-   diatonic chains etc., improves results. This is probably due to that -   previously a transformation could "override" a preparations because only-   one HAnTrans node was stored (the lowest one in the tree). -** adding similarity between various different preparations DiatV == SecDom-   improves similarity. This makes sense because both involve fifth jumps   ---}------------------------------------------------------------------------------------- Baseline chord label alignment (no model)---------------------------------------------------------------------------------    ---- returns a similarity/distance value  -getAlignDist :: Key -> Key -> [ChordLabel] -> [ChordLabel] -> Float-getAlignDist ka kb ta tb = let (_match, dist, _tab) = alignChordLab ka kb ta tb-                           in dist --                            -alignChordLab :: Key -> Key -> [ChordLabel] -> [ChordLabel] -              -> ([SimChord], Float, Vector (Vector Int))-alignChordLab ka kb ta tb = (fst $ matchToSeq match ta' tb', dis, tab) where-  (match, weight, tab) = --trace ("ta: " ++ show ta'++ "\ntb: "++ show tb') -                       align (-2) ta' tb' -  dis =   fromIntegral (weight * weight) -        / fromIntegral (maxSim ta' * maxSim tb')-  ta' = L.concatMap (toSimChords . toChordDegree ka) ta-  tb' = L.concatMap (toSimChords . toChordDegree kb) tb------------------------------------------------------------------------------------- HAn Chord alignment---------------------------------------------------------------------------------    ---- returns a similarity/distance value  -getHAnDist :: Tree HAn -> Tree HAn -> Float-getHAnDist ta tb = let (_match, dist, _tab) = alignHAnChord ta tb in dist -                            -alignHAnChord :: Tree HAn -> Tree HAn -> ([HChord], Float, Vector (Vector Int))-alignHAnChord ta tb = -  -- trace ("ta: " ++ show ta'++ "\ntb: "++ show tb' ++ "\nsim: "++ show dis)-  (fst $ matchToSeq match ta' tb', dis, tab) where-    (match, weight, tab) = align (-2) ta' tb' -    dis =   fromIntegral (weight * weight) -          / fromIntegral (maxSim ta' * maxSim tb')-    ta' = toHChords ta -    tb' = toHChords tb -  --- creates an alignment and returns the list of matches, the distance, and--- the alignment table. The first argument is the insertion/deletion--- penalty (should be a negative value).-align :: Sim a => Int -> [a] -> [a] -> ([(Int,Int)], Int, Vector (Vector Int))-align _ _  [] = ([],0,empty)-align _ [] _  = ([],0,empty)-align inDel a b = (cm, getDownRight t,t) where-  t  = wbMatchF inDel a b-  cm = toList (collectMatch t)-    -wbMatchF :: Sim a => Int -> [a] -> [a] -> Vector (Vector Int)-wbMatchF _ _ []  = empty -wbMatchF _ [] _  = empty-wbMatchF inDel a' b' = m where-  a  = fromList a' -  b  = fromList b'       -  match, fill :: Int -> Int -> Int-  {-# INLINE fill  #-}-  match i j = sim (a ! i) (b ! j)  -  -- this is the actual core recursive definintion of the algorithm-  fill 0 0 = max  (match 0 0) 0-  fill 0 j = max0 (((m ! 0   ) !(j-1)) + inDel) (match 0 j) -  fill i 0 = max0 (((m !(i-1)) ! 0   ) + inDel) (match i 0)  -  fill i j = max3 (((m !(i-1)) ! j   ) + inDel) -                  (((m !(i-1)) !(j-1)) + match i j) -                  (((m ! i)    !(j-1)) + inDel)-  m = generate (length a) (generate (length b) . fill)-------------------------------------------------------------------------------------- Getting the alignment out of the table--------------------------------------------------------------------------------- --collectMatch :: Vector (Vector Int) -> Vector (Int,Int)-collectMatch a = fromList $ collect a (length a -1, length (head a) -1) []-collect :: (Ord b, Num b) => Vector (Vector b) -> (Int, Int) -> [(Int, Int)] -        -> [(Int, Int)]-collect a c@(0,0) m = if (a!0)!0 > 0 then c : m else m-collect a c@(i,0) m = if (a!i)!0 > (a!(i-1))! 0 -                                     then c : m else collect a (i-1,0) m-collect a c@(0,j) m = if (a!0)!j > (a!0    )!(j-1) -                                     then c : m else collect a (0,j-1) m-collect a c@(i,j) m -  | (a ! i) ! j > snd o = collect a (fst o) (c : m)-  | otherwise               = collect a (fst o) m where -      o = realMax3 ((i-1,j)  , (a !(i-1)) ! j   )-                   ((i-1,j-1), (a !(i-1)) !(j-1))-                   ((i,j-1)  , (a ! i   ) !(j-1))--realMax3 :: (Ord a) => (t, a) -> (t, a) -> (t, a) -> (t, a)    -realMax3 w nw n = maxByWeight nw (maxByWeight w n) where-  maxByWeight :: Ord a => (t,a) -> (t,a) -> (t,a)                                        -  maxByWeight a@(_,wa) b@(_,wb) = if wa > wb then a else b ------------------------------------------------------------------------------------- Some LCES helper functions--------------------------------------------------------------------------------- --matchToSeq :: [(Int,Int)] -> [a] -> [a] -> ([a],[a])-matchToSeq mat aOrg bOrg = (f aMat aOrg, f bMat bOrg) where-  f m o = fst . L.unzip $ L.filter (\(_,x) -> x `L.elem` m) (L.zip o [0..]) -  (aMat, bMat) = L.unzip mat--(!) :: Vector a -> Int -> a-{-# INLINE (!) #-}-(!) = unsafeIndex--max3 :: (Ord a, Num a) => a -> a -> a -> a-{-# INLINE max3 #-}-max3 a b c = max a (max0 b c)--max0  :: (Ord a, Num a) => a -> a -> a -{-# INLINE max0 #-}-max0 a b = max a (max b 0)--- max3' w nw n = if n > nw then n else max nw w -- not correct yet--max :: (Ord a, Num a) => a -> a -> a -{-# INLINE max #-}-max x y = if x <= y then y else x--getDownRight :: Vector (Vector a) -> a-getDownRight n = last (last n) ---- pretty prints a 2 dimensional vector in a readable format-pPrintV :: Show a => Vector (Vector a) -> IO ()-pPrintV = mapM_ printLn where-  printLn :: Show a => Vector a -> IO()-  printLn v = do mapM_ (\x -> putStr (show x ++ " ")) v ; putChar '\n'-+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+module HarmTrace.Matching.Alignment ( alignChordLab, pPrintV, getAlignDist
+                                    , getHAnDist, alignHAnChord
+                                    -- , getDownRight, wbMatchF, align, Sim(..)
+                                    -- , collectMatch
+                                    ) where
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Matching.SimpleChord
+import HarmTrace.Matching.HChord
+import HarmTrace.Matching.Sim
+import HarmTrace.HAnTree.HAn 
+import HarmTrace.HAnTree.Tree
+
+import Prelude hiding (map, length, head, last, mapM_, max)
+
+import Data.Vector hiding ((!), (++))
+import qualified Data.List as L
+
+-- import Debug.Trace
+{-
+
+Matching notes:
+===============
+** Normalisation ( sim * sim ) / (maxsim a * maxsim b) helps in practically 
+   all cases.
+** The sampling in general has a large effect on matching speed, and a small 
+   effect on retrieval performance. In all observed cases using no sampling 
+   performs (slightly) better than not using sampling. The sample rate herein 
+   also has an effect: using normal integer division (`div`) deletes chords 
+   with a beat length of 1, which decreases retrieval performance. It is
+   better to use a `div1` that also includes the chords with a duration of
+   beat (see SimpleChord.myDiv)
+** The mis-match penalty should be -2 > -1 < 0: -1 seems to be optimal 
+   (= insertion/deletion)
+** Use very "conservative" similarity measures (not many things are similar) 
+** Using a ChordType instead of just major/minor improves results
+** Using the HAnTrans information improves the similarity estimation
+** using only the information of the model (HAnTrans and HAnFunc) performs
+   worse than using the root and chord type
+** Separating transformations (HAnTrans), i.e. Tritone substitutions, dimchord
+   transformations etc., from preparations (HAnPrep), i.e. secondary dominants,
+   diatonic chains etc., improves results. This is probably due to that 
+   previously a transformation could "override" a preparations because only
+   one HAnTrans node was stored (the lowest one in the tree). 
+** adding similarity between various different preparations DiatV == SecDom
+   improves similarity. This makes sense because both involve fifth jumps   
+
+-}
+
+--------------------------------------------------------------------------------
+-- Baseline chord label alignment (no model)
+--------------------------------------------------------------------------------    
+
+-- returns a similarity/distance value  
+getAlignDist :: Key -> Key -> [ChordLabel] -> [ChordLabel] -> Float
+getAlignDist ka kb ta tb = let (_match, dist, _tab) = alignChordLab ka kb ta tb
+                           in dist 
+
+                            
+alignChordLab :: Key -> Key -> [ChordLabel] -> [ChordLabel] 
+              -> ([SimChord], Float, Vector (Vector Int))
+alignChordLab ka kb ta tb = (fst $ matchToSeq match ta' tb', dis, tab) where
+  (match, weight, tab) = --trace ("ta: " ++ show ta'++ "\ntb: "++ show tb') 
+                       align (-2) ta' tb' 
+  dis =   fromIntegral (weight * weight) 
+        / fromIntegral (maxSim ta' * maxSim tb')
+  ta' = L.concatMap (toSimChords . toChordDegree ka) ta
+  tb' = L.concatMap (toSimChords . toChordDegree kb) tb
+
+--------------------------------------------------------------------------------
+-- HAn Chord alignment
+--------------------------------------------------------------------------------    
+
+-- returns a similarity/distance value  
+getHAnDist :: Tree HAn -> Tree HAn -> Float
+getHAnDist ta tb = let (_match, dist, _tab) = alignHAnChord ta tb in dist 
+                            
+alignHAnChord :: Tree HAn -> Tree HAn -> ([HChord], Float, Vector (Vector Int))
+alignHAnChord ta tb = 
+  -- trace ("ta: " ++ show ta'++ "\ntb: "++ show tb' ++ "\nsim: "++ show dis)
+  (fst $ matchToSeq match ta' tb', dis, tab) where
+    (match, weight, tab) = align (-2) ta' tb' 
+    dis =   fromIntegral (weight * weight) 
+          / fromIntegral (maxSim ta' * maxSim tb')
+    ta' = toHChords ta 
+    tb' = toHChords tb 
+  
+-- creates an alignment and returns the list of matches, the distance, and
+-- the alignment table. The first argument is the insertion/deletion
+-- penalty (should be a negative value).
+align :: Sim a => Int -> [a] -> [a] -> ([(Int,Int)], Int, Vector (Vector Int))
+align _ _  [] = ([],0,empty)
+align _ [] _  = ([],0,empty)
+align inDel a b = (cm, getDownRight t,t) where
+  t  = wbMatchF inDel a b
+  cm = toList (collectMatch t)
+    
+wbMatchF :: Sim a => Int -> [a] -> [a] -> Vector (Vector Int)
+wbMatchF _ _ []  = empty 
+wbMatchF _ [] _  = empty
+wbMatchF inDel a' b' = m where
+  a  = fromList a' 
+  b  = fromList b'       
+  match, fill :: Int -> Int -> Int
+  {-# INLINE fill  #-}
+  match i j = sim (a ! i) (b ! j)  
+  -- this is the actual core recursive definintion of the algorithm
+  fill 0 0 = max  (match 0 0) 0
+  fill 0 j = max0 (((m ! 0   ) !(j-1)) + inDel) (match 0 j) 
+  fill i 0 = max0 (((m !(i-1)) ! 0   ) + inDel) (match i 0)  
+  fill i j = max3 (((m !(i-1)) ! j   ) + inDel) 
+                  (((m !(i-1)) !(j-1)) + match i j) 
+                  (((m ! i)    !(j-1)) + inDel)
+  m = generate (length a) (generate (length b) . fill)
+
+
+--------------------------------------------------------------------------------
+-- Getting the alignment out of the table
+-------------------------------------------------------------------------------- 
+
+collectMatch :: Vector (Vector Int) -> Vector (Int,Int)
+collectMatch a = fromList $ collect a (length a -1, length (head a) -1) []
+collect :: (Ord b, Num b) => Vector (Vector b) -> (Int, Int) -> [(Int, Int)] 
+        -> [(Int, Int)]
+collect a c@(0,0) m = if (a!0)!0 > 0 then c : m else m
+collect a c@(i,0) m = if (a!i)!0 > (a!(i-1))! 0 
+                                     then c : m else collect a (i-1,0) m
+collect a c@(0,j) m = if (a!0)!j > (a!0    )!(j-1) 
+                                     then c : m else collect a (0,j-1) m
+collect a c@(i,j) m 
+  | (a ! i) ! j > snd o = collect a (fst o) (c : m)
+  | otherwise               = collect a (fst o) m where 
+      o = realMax3 ((i-1,j)  , (a !(i-1)) ! j   )
+                   ((i-1,j-1), (a !(i-1)) !(j-1))
+                   ((i,j-1)  , (a ! i   ) !(j-1))
+
+realMax3 :: (Ord a) => (t, a) -> (t, a) -> (t, a) -> (t, a)    
+realMax3 w nw n = maxByWeight nw (maxByWeight w n) where
+  maxByWeight :: Ord a => (t,a) -> (t,a) -> (t,a)                                        
+  maxByWeight a@(_,wa) b@(_,wb) = if wa > wb then a else b 
+
+--------------------------------------------------------------------------------
+-- Some LCES helper functions
+-------------------------------------------------------------------------------- 
+
+matchToSeq :: [(Int,Int)] -> [a] -> [a] -> ([a],[a])
+matchToSeq mat aOrg bOrg = (f aMat aOrg, f bMat bOrg) where
+  f m o = fst . L.unzip $ L.filter (\(_,x) -> x `L.elem` m) (L.zip o [0..]) 
+  (aMat, bMat) = L.unzip mat
+
+(!) :: Vector a -> Int -> a
+{-# INLINE (!) #-}
+(!) = unsafeIndex
+
+max3 :: (Ord a, Num a) => a -> a -> a -> a
+{-# INLINE max3 #-}
+max3 a b c = max a (max0 b c)
+
+max0  :: (Ord a, Num a) => a -> a -> a 
+{-# INLINE max0 #-}
+max0 a b = max a (max b 0)
+-- max3' w nw n = if n > nw then n else max nw w -- not correct yet
+
+max :: (Ord a, Num a) => a -> a -> a 
+{-# INLINE max #-}
+max x y = if x <= y then y else x
+
+getDownRight :: Vector (Vector a) -> a
+getDownRight n = last (last n) 
+
+-- pretty prints a 2 dimensional vector in a readable format
+pPrintV :: Show a => Vector (Vector a) -> IO ()
+pPrintV = mapM_ printLn where
+  printLn :: Show a => Vector a -> IO()
+  printLn v = do mapM_ (\x -> putStr (show x ++ " ")) v ; putChar '\n'
+
src/HarmTrace/Matching/GuptaNishimura.hs view
@@ -1,190 +1,190 @@-{-# OPTIONS_GHC -Wall #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE BangPatterns #-}-module HarmTrace.Matching.GuptaNishimura ( getLCES     , getLCESsize-                                         , getLCESdepth, getLCESsim) -where------------------------------------------------------------------------------------- Finding the Largest Common Embedable Subtrees (LCES)--- Based on: Gupta, A. and Nishimura, N. (1998) Finding largest subtrees and ---           smallest supertrees, Algorithmica, 21(2), p. 183--210--- author: Bas de Haas--------------------------------------------------------------------------------- --import Data.Ord-import Data.Maybe-import Prelude     hiding (length, last)-import Data.Vector hiding ((!), last)-import qualified Data.List as L--import HarmTrace.HAnTree.Tree-import HarmTrace.HAnTree.HAn-import HarmTrace.Matching.Sim---------------------------------------------------------------------------------- --- Top Level LCES function--------------------------------------------------------------------------------- --getLCESsim :: Tree HAn -> Tree HAn -> Float-getLCESsim ta tb = let match    = fromIntegral . snd $ getLCES ta tb-                       selfSimA = sim ta ta * cumDur ta-                       selfSimB = sim tb tb * cumDur tb-                   in (match * match) / fromIntegral (selfSimA * selfSimB)--getLCESsize :: Tree HAn -> Tree HAn -> Float-getLCESsize ta tb = let match = fromIntegral . sizeF . fst $ getLCES ta tb-                    in (match * match) / fromIntegral (size ta * size tb)---- Check ismir09 implementation-getLCESdepth :: Tree HAn -> Tree HAn -> Float-getLCESdepth ta tb = let match = avgDepthF . fst $ getLCES ta tb-                     in (match * match) / (avgDepth ta * avgDepth tb) ---- Top level function that returns the largest common embedable subtree--- of two trees-getLCES :: Tree HAn -> Tree HAn -> ([Tree HAn], Int)-getLCES ta tb = (matchToTree ta (L.map fst (L.reverse m)),w) where -  (LCES m w)  = last . last $ lces ta tb--nonMatchPenal :: Int-nonMatchPenal = 2  -  --------------------------------------------------------------------------------- --- LCES calculation---------------------------------------------------------------------------------   -  --- calculates the largest labeled common embeddable subtree-lces :: (Sim t, GetDur t) => Tree t -> Tree t -> Vector (Vector LCES)-lces ta tb = n where-  a = fromList (pot ta)-  b = fromList (pot tb)-  maxi :: Int -> [Int] -> LCES-  {-# INLINE maxi  #-}  -  maxi _ [] = emptyLCES  -  maxi i cb = (n!i) ! (L.maximumBy (comparing (\j -> getWeight $ ((n!i)!j))) cb)-  maxj :: [Int] -> Int -> LCES-  {-# INLINE maxj  #-}  -  maxj [] _ = emptyLCES  -  maxj ca j = (n ! (L.maximumBy (comparing (\i -> getWeight $ ((n!i)!j))) ca))!j-  recur 0 0 = if sim (getLabel (a ! 0)) (getLabel (b ! 0)) > 0-              then LCES [(0,0)] (durSim (getLabel (a ! 0)) (getLabel (b ! 0)))-              else emptyLCES-  recur i j = findBestMatch (sim labi labj) -                            (min (getDur labi) (getDur labj)) i j mc mi mj where  -    mi  = maxi i  (getChildPns (b ! j))-    mj  = maxj    (getChildPns (a ! i)) j-    mc  = wbMatch (getChild (a ! i)) (getChild $ b ! j) n-    !labi = getLabel (a!i) -    !labj = getLabel (b!j)-  n = generate (length a) (generate (length b) . recur)  -      --- returns the best matching candidate, given the previous candidates, the--- bipartite matching. The function depends on wheter the currend nodes --- match and whether, in that case, one of the current nodes is not allready --- matched-findBestMatch :: Int -> Int -> Int -> Int -> LCES -> LCES -> LCES -> LCES -{-# INLINE findBestMatch  #-} -findBestMatch simv dur i j a b c-  | simv <= 0 = (LCES mf (max (wf - (nonMatchPenal * dur)) 0 ))-  | otherwise = if isFree first i j       then (LCES ((i,j):mf) (wf+(dur*simv))) -                else if wf /= ws          then first           -                else if isFree second i j then (LCES ((i,j):ms) (ws+(dur*simv)))-                else if wf /= wt          then first-                else if isFree second i j then (LCES ((i,j):mt) (wt+(dur*simv)))-                else first where-    (first@(LCES mf wf) :second@(LCES ms ws) :(LCES mt wt) :[]) = mySort [a,b,c]-          ------------------------------------------------------------------------------------ Weighted Plannar Matching of a Bipartite Graph---------------------------------------------------------------------------------   -  --- returns the actual planar weighted bipartite matchings. n should contain --- the weights of the edge between a[i] and b[j]  -wbMatch   :: [Tree t] -> [Tree t] ->  Vector (Vector LCES) ->  LCES-{-# INLINE wbMatch  #-} -wbMatch _ []  _ = emptyLCES-wbMatch [] _  _ = emptyLCES-wbMatch a b n = last $ last m where -  -- returns a previously matched subtree -  subTree :: Int -> Int -> LCES -  {-# INLINE subTree  #-}  -  subTree i j = (n ! (fromJust . getPn $ a!!i)) ! (fromJust . getPn $ b!!j)-  -- this is the actual core recursive definintion of the algorithm-  match, fill :: Int -> Int -> LCES -  match i j = L.maximumBy (comparing getWeight) [maxPrv, minPrv, diagM] where-    s         = subTree i j-    !hasMatch = getWeight s > 0-    maxPrv    = if not hasMatch                    then (m ! (i-1)) ! j-                else if isFree ((m!(i-1)) ! j) i j then merge s ((m!(i-1)) ! j)-                else ((m ! (i-1)) ! j)-    minPrv    = if not hasMatch                    then (m ! i) ! (j-1)-                else if isFree ((m!i) ! (j-1)) i j then merge s ((m!i) ! (j-1)) -                else ((m ! i) ! (j-1))-    diagM     = merge s ((m ! (i-1)) ! (j-1)) -  fill 0 0 = subTree 0 0-  fill 0 j = if getWeight (subTree 0 j) > getWeight ((m ! 0) ! (j-1)) -             then subTree 0 j else (m ! 0) ! (j-1)-  fill i 0 = if getWeight (subTree i 0) > getWeight ((m ! (i-1)) ! 0) -             then subTree i 0 else ((m ! (i-1)) ! 0)-  fill i j = match i j             -  m = generate (L.length a) (generate (L.length b) . fill)   -     ------------------------------------------------------------------------------------ Some LCES helper functions--------------------------------------------------------------------------------- --data LCES =  LCES  ![(Int, Int)] !Int --getWeight :: LCES -> Int-{-# INLINE getWeight #-}-getWeight (LCES _ w) = w---- getMatch :: LCES -> [(Int, Int)]--- getMatch (LCES m _) = m--durSim :: (Sim a, GetDur a) => a -> a -> Int-durSim a b = (sim a b) * (min (getDur a) (getDur b))--emptyLCES :: LCES-{-# INLINE emptyLCES #-}-emptyLCES =  LCES [] 0  --(!) :: Vector a -> Int -> a-{-# INLINE (!) #-}-(!) = unsafeIndex--last :: Vector a -> a-{-# INLINE last #-}-last = unsafeLast--cumDur :: (GetDur a) => Tree a -> Int-cumDur a = (getDur $ getLabel a) + (L.sum $ L.map cumDur (getChild a))---- checks if the previously calculated optimal solution does not --- contain the indices i and j in a and b, resepectivly-isFree :: LCES -> Int -> Int -> Bool-{-# INLINE isFree  #-}-isFree (LCES []                 _) _ _ = True-isFree (LCES ((previ, prevj):_) _) i j = ( i > previ && j > prevj)---- mergest two lists with matches-merge :: LCES -> LCES -> LCES-{-# INLINE merge  #-}-merge (LCES a wa) (LCES b wb) = LCES (a L.++ b) (wa + wb)---- this sorting routine makes quite a large difference in runtime performance!-mySort :: [LCES] -> [LCES]-{-# INLINE mySort  #-}-mySort [a,b,c] = case (x >= y, y >= z, x >= z) of-                   (True , True , True ) -> [a,b,c]-                   (True , False, True ) -> [a,c,b]-                   (True , False, False) -> [c,a,b]-                   (False, True , True ) -> [b,a,c]-                   (False, True , False) -> [b,c,a]-                   (False, False, False) -> [c,b,a]-                   _ -> error "mySort: impossible"-  where !x = getWeight a-        !y = getWeight b-        !z = getWeight c-mySort _ = error "mySort: unexpected argument"+{-# OPTIONS_GHC -Wall #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE BangPatterns #-}
+module HarmTrace.Matching.GuptaNishimura ( getLCES     , getLCESsize
+                                         , getLCESdepth, getLCESsim) 
+where
+
+--------------------------------------------------------------------------------
+-- Finding the Largest Common Embedable Subtrees (LCES)
+-- Based on: Gupta, A. and Nishimura, N. (1998) Finding largest subtrees and 
+--           smallest supertrees, Algorithmica, 21(2), p. 183--210
+-- author: Bas de Haas
+-------------------------------------------------------------------------------- 
+
+import Data.Ord
+import Data.Maybe
+import Prelude     hiding (length, last)
+import Data.Vector hiding ((!), last)
+import qualified Data.List as L
+
+import HarmTrace.HAnTree.Tree
+import HarmTrace.HAnTree.HAn
+import HarmTrace.Matching.Sim
+
+-------------------------------------------------------------------------------- 
+-- Top Level LCES function
+-------------------------------------------------------------------------------- 
+
+getLCESsim :: Tree HAn -> Tree HAn -> Float
+getLCESsim ta tb = let match    = fromIntegral . snd $ getLCES ta tb
+                       selfSimA = sim ta ta * cumDur ta
+                       selfSimB = sim tb tb * cumDur tb
+                   in (match * match) / fromIntegral (selfSimA * selfSimB)
+
+getLCESsize :: Tree HAn -> Tree HAn -> Float
+getLCESsize ta tb = let match = fromIntegral . sizeF . fst $ getLCES ta tb
+                    in (match * match) / fromIntegral (size ta * size tb)
+
+-- Check ismir09 implementation
+getLCESdepth :: Tree HAn -> Tree HAn -> Float
+getLCESdepth ta tb = let match = avgDepthF . fst $ getLCES ta tb
+                     in (match * match) / (avgDepth ta * avgDepth tb) 
+
+-- Top level function that returns the largest common embedable subtree
+-- of two trees
+getLCES :: Tree HAn -> Tree HAn -> ([Tree HAn], Int)
+getLCES ta tb = (matchToTree ta (L.map fst (L.reverse m)),w) where 
+  (LCES m w)  = last . last $ lces ta tb
+
+nonMatchPenal :: Int
+nonMatchPenal = 2  
+  
+-------------------------------------------------------------------------------- 
+-- LCES calculation
+--------------------------------------------------------------------------------   
+  
+-- calculates the largest labeled common embeddable subtree
+lces :: (Sim t, GetDur t) => Tree t -> Tree t -> Vector (Vector LCES)
+lces ta tb = n where
+  a = fromList (pot ta)
+  b = fromList (pot tb)
+  maxi :: Int -> [Int] -> LCES
+  {-# INLINE maxi  #-}  
+  maxi _ [] = emptyLCES  
+  maxi i cb = (n!i) ! (L.maximumBy (comparing (\j -> getWeight $ ((n!i)!j))) cb)
+  maxj :: [Int] -> Int -> LCES
+  {-# INLINE maxj  #-}  
+  maxj [] _ = emptyLCES  
+  maxj ca j = (n ! (L.maximumBy (comparing (\i -> getWeight $ ((n!i)!j))) ca))!j
+  recur 0 0 = if sim (getLabel (a ! 0)) (getLabel (b ! 0)) > 0
+              then LCES [(0,0)] (durSim (getLabel (a ! 0)) (getLabel (b ! 0)))
+              else emptyLCES
+  recur i j = findBestMatch (sim labi labj) 
+                            (min (getDur labi) (getDur labj)) i j mc mi mj where  
+    mi  = maxi i  (getChildPns (b ! j))
+    mj  = maxj    (getChildPns (a ! i)) j
+    mc  = wbMatch (getChild (a ! i)) (getChild $ b ! j) n
+    !labi = getLabel (a!i) 
+    !labj = getLabel (b!j)
+  n = generate (length a) (generate (length b) . recur)  
+      
+-- returns the best matching candidate, given the previous candidates, the
+-- bipartite matching. The function depends on wheter the currend nodes 
+-- match and whether, in that case, one of the current nodes is not allready 
+-- matched
+findBestMatch :: Int -> Int -> Int -> Int -> LCES -> LCES -> LCES -> LCES 
+{-# INLINE findBestMatch  #-} 
+findBestMatch simv dur i j a b c
+  | simv <= 0 = (LCES mf (max (wf - (nonMatchPenal * dur)) 0 ))
+  | otherwise = if isFree first i j       then (LCES ((i,j):mf) (wf+(dur*simv))) 
+                else if wf /= ws          then first           
+                else if isFree second i j then (LCES ((i,j):ms) (ws+(dur*simv)))
+                else if wf /= wt          then first
+                else if isFree second i j then (LCES ((i,j):mt) (wt+(dur*simv)))
+                else first where
+    (first@(LCES mf wf) :second@(LCES ms ws) :(LCES mt wt) :[]) = mySort [a,b,c]
+          
+--------------------------------------------------------------------------------
+-- Weighted Plannar Matching of a Bipartite Graph
+--------------------------------------------------------------------------------   
+  
+-- returns the actual planar weighted bipartite matchings. n should contain 
+-- the weights of the edge between a[i] and b[j]  
+wbMatch   :: [Tree t] -> [Tree t] ->  Vector (Vector LCES) ->  LCES
+{-# INLINE wbMatch  #-} 
+wbMatch _ []  _ = emptyLCES
+wbMatch [] _  _ = emptyLCES
+wbMatch a b n = last $ last m where 
+  -- returns a previously matched subtree 
+  subTree :: Int -> Int -> LCES 
+  {-# INLINE subTree  #-}  
+  subTree i j = (n ! (fromJust . getPn $ a!!i)) ! (fromJust . getPn $ b!!j)
+  -- this is the actual core recursive definintion of the algorithm
+  match, fill :: Int -> Int -> LCES 
+  match i j = L.maximumBy (comparing getWeight) [maxPrv, minPrv, diagM] where
+    s         = subTree i j
+    !hasMatch = getWeight s > 0
+    maxPrv    = if not hasMatch                    then (m ! (i-1)) ! j
+                else if isFree ((m!(i-1)) ! j) i j then merge s ((m!(i-1)) ! j)
+                else ((m ! (i-1)) ! j)
+    minPrv    = if not hasMatch                    then (m ! i) ! (j-1)
+                else if isFree ((m!i) ! (j-1)) i j then merge s ((m!i) ! (j-1)) 
+                else ((m ! i) ! (j-1))
+    diagM     = merge s ((m ! (i-1)) ! (j-1)) 
+  fill 0 0 = subTree 0 0
+  fill 0 j = if getWeight (subTree 0 j) > getWeight ((m ! 0) ! (j-1)) 
+             then subTree 0 j else (m ! 0) ! (j-1)
+  fill i 0 = if getWeight (subTree i 0) > getWeight ((m ! (i-1)) ! 0) 
+             then subTree i 0 else ((m ! (i-1)) ! 0)
+  fill i j = match i j             
+  m = generate (L.length a) (generate (L.length b) . fill)   
+     
+--------------------------------------------------------------------------------
+-- Some LCES helper functions
+-------------------------------------------------------------------------------- 
+
+data LCES =  LCES  ![(Int, Int)] !Int 
+
+getWeight :: LCES -> Int
+{-# INLINE getWeight #-}
+getWeight (LCES _ w) = w
+
+-- getMatch :: LCES -> [(Int, Int)]
+-- getMatch (LCES m _) = m
+
+durSim :: (Sim a, GetDur a) => a -> a -> Int
+durSim a b = (sim a b) * (min (getDur a) (getDur b))
+
+emptyLCES :: LCES
+{-# INLINE emptyLCES #-}
+emptyLCES =  LCES [] 0  
+
+(!) :: Vector a -> Int -> a
+{-# INLINE (!) #-}
+(!) = unsafeIndex
+
+last :: Vector a -> a
+{-# INLINE last #-}
+last = unsafeLast
+
+cumDur :: (GetDur a) => Tree a -> Int
+cumDur a = (getDur $ getLabel a) + (L.sum $ L.map cumDur (getChild a))
+
+-- checks if the previously calculated optimal solution does not 
+-- contain the indices i and j in a and b, resepectivly
+isFree :: LCES -> Int -> Int -> Bool
+{-# INLINE isFree  #-}
+isFree (LCES []                 _) _ _ = True
+isFree (LCES ((previ, prevj):_) _) i j = ( i > previ && j > prevj)
+
+-- mergest two lists with matches
+merge :: LCES -> LCES -> LCES
+{-# INLINE merge  #-}
+merge (LCES a wa) (LCES b wb) = LCES (a L.++ b) (wa + wb)
+
+-- this sorting routine makes quite a large difference in runtime performance!
+mySort :: [LCES] -> [LCES]
+{-# INLINE mySort  #-}
+mySort [a,b,c] = case (x >= y, y >= z, x >= z) of
+                   (True , True , True ) -> [a,b,c]
+                   (True , False, True ) -> [a,c,b]
+                   (True , False, False) -> [c,a,b]
+                   (False, True , True ) -> [b,a,c]
+                   (False, True , False) -> [b,c,a]
+                   (False, False, False) -> [c,b,a]
+                   _ -> error "mySort: impossible"
+  where !x = getWeight a
+        !y = getWeight b
+        !z = getWeight c
+mySort _ = error "mySort: unexpected argument"
src/HarmTrace/Matching/HChord.hs view
@@ -1,51 +1,51 @@-module HarmTrace.Matching.HChord (HChord, Sim, toHChords) where--import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens-import HarmTrace.Matching.Sim -import HarmTrace.HAnTree.HAn-import HarmTrace.HAnTree.Tree---- represents a  very simple chord, only major and minor and a root scaledegree-data HChord = HChord { deg  :: !Int       -- I = 0, IIb = 1 ... VII = 11-                     , clss :: !ClassType -- MajClass | MinClass | DomClass | ..-                     , func :: !HFunc-                     , prep :: !Prep-                     , trns :: !Trans}--instance Sim HChord where-  {-# INLINE sim #-}-  sim (HChord r ct _fc pr tr) (HChord r2 ct2 _fc2 pr2 tr2) -    | r == r2 && ct == ct2 = 2 + sim pr pr2 + sim tr tr2-    | otherwise = -1-    -instance Show HChord where-  show (HChord r ct fc pr tr) =        show fc ++ ':' : show pr ++ ':' : show tr-                              ++ ':' : show (scaleDegrees !! r) ++ show ct--toHChords :: Tree HAn -> [HChord]-toHChords t = getHAn undefinedHChord t---- getHAn also samples/replicates the chords based on their duration in beats-getHAn :: HChord -> Tree HAn -> [HChord]-getHAn c (Node h@(HAnChord ct) [] _) -- there might be inserted chords-  | null (chords ct)   = []          -- ignore them in the matching process-  | otherwise          = let c'  = update c h -                                     -- ignore func when the chord is deleted-                             c'' = if status ct == Deleted -                                   then c' { trns = NoTrans } else c'-                         in  replicate (dur ct)    c''-                         -- in replicate ((dur ct) `div1` 2) c''-getHAn c (Node h cs _) = let c' = update c h in concatMap (getHAn c') cs--update :: HChord -> HAn -> HChord-update hc (HAn    _ _) = hc-update hc (HAnFunc  f) = hc { func = f }-update hc (HAnTrans t) = hc { trns = t }-update hc (HAnPrep  p) = hc { prep = p }-update hc (HAnChord c) = hc { deg  = diaDegToSemi $ root c-                            , clss = classType c }--undefinedHChord :: HChord-undefinedHChord =  HChord (-1 :: Int)  (MajClass :: ClassType)-                          (P :: HFunc) NoPrep NoTrans+module HarmTrace.Matching.HChord (HChord, Sim, toHChords) where
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens
+import HarmTrace.Matching.Sim 
+import HarmTrace.HAnTree.HAn
+import HarmTrace.HAnTree.Tree
+
+-- represents a  very simple chord, only major and minor and a root scaledegree
+data HChord = HChord { deg  :: !Int       -- I = 0, IIb = 1 ... VII = 11
+                     , clss :: !ClassType -- MajClass | MinClass | DomClass | ..
+                     , func :: !HFunc
+                     , prep :: !Prep
+                     , trns :: !Trans}
+
+instance Sim HChord where
+  {-# INLINE sim #-}
+  sim (HChord r ct _fc pr tr) (HChord r2 ct2 _fc2 pr2 tr2) 
+    | r == r2 && ct == ct2 = 2 + sim pr pr2 + sim tr tr2
+    | otherwise = -1
+    
+instance Show HChord where
+  show (HChord r ct fc pr tr) =        show fc ++ ':' : show pr ++ ':' : show tr
+                              ++ ':' : show (scaleDegrees !! r) ++ show ct
+
+toHChords :: Tree HAn -> [HChord]
+toHChords t = getHAn undefinedHChord t
+
+-- getHAn also samples/replicates the chords based on their duration in beats
+getHAn :: HChord -> Tree HAn -> [HChord]
+getHAn c (Node h@(HAnChord ct) [] _) -- there might be inserted chords
+  | null (chords ct)   = []          -- ignore them in the matching process
+  | otherwise          = let c'  = update c h 
+                                     -- ignore func when the chord is deleted
+                             c'' = if status ct == Deleted 
+                                   then c' { trns = NoTrans } else c'
+                         in  replicate (dur ct)    c''
+                         -- in replicate ((dur ct) `div1` 2) c''
+getHAn c (Node h cs _) = let c' = update c h in concatMap (getHAn c') cs
+
+update :: HChord -> HAn -> HChord
+update hc (HAn    _ _) = hc
+update hc (HAnFunc  f) = hc { func = f }
+update hc (HAnTrans t) = hc { trns = t }
+update hc (HAnPrep  p) = hc { prep = p }
+update hc (HAnChord c) = hc { deg  = toSemitone $ root c
+                            , clss = classType c }
+
+undefinedHChord :: HChord
+undefinedHChord =  HChord (-1 :: Int)  (MajClass :: ClassType)
+                          (P :: HFunc) NoPrep NoTrans
src/HarmTrace/Matching/Sim.hs view
@@ -1,90 +1,90 @@-{-# OPTIONS_GHC -Wall #-}-module HarmTrace.Matching.Sim where--import HarmTrace.HAnTree.HAn-import HarmTrace.HAnTree.Tree-import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens (ChordToken)---------------------------------------------------------------------------------- --- A class for representing numerical similarity between datatypes--------------------------------------------------------------------------------- --class Sim a where-  sim :: a -> a -> Int-  -instance Sim a => Sim (Tree a) where-  sim (Node l _ _) (Node l' _ _) = sim l l'--instance Sim a => Sim [a] where-  sim [ha]    [hb]    = sim ha hb -  sim (ha:ta) (hb:tb) = sim ha hb + sim ta tb-  sim _       _       = 0--instance Sim HAn where-  sim (HAn    _ a) (HAn    _ b) = if a == b then 1 else 0-  sim (HAnFunc  a) (HAnFunc  b) = sim a b-  sim (HAnPrep  a) (HAnPrep  b) = sim a b-  sim (HAnTrans a) (HAnTrans b) = sim a b-  sim (HAnChord a) (HAnChord b) = sim a b-  sim  _            _           = 0-  -instance Sim Int where-  {-# INLINE sim #-}-  sim i j = if i == j then 1 else 0--instance Sim HFunc where-  {-# INLINE sim #-}-  sim (Ton _ _m c _) (Ton _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2-  sim (Dom _ _m c _) (Dom _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2-  sim (Sub _ _m c _) (Sub _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2-  sim P             P              = 1      -  sim PD            PD             = 1      -  sim PT            PT             = 1 -  sim _             _              = 0-  -instance Sim Mode where-  {-# INLINE sim #-}-  sim MajMode MajMode = 1  -  sim MinMode MinMode = 1  -  sim _       _       = 0--instance Sim Trans where                                             -  {-# INLINE sim #-}-  sim (Trit     _v sd) (Trit     _v2 sd2) = if sd == sd2 then 1 else 0-  sim (DimTrit  _v sd) (DimTrit  _v2 sd2) = if sd == sd2 then 1 else 0-  sim (DimTrans _v sd) (DimTrans _v2 sd2) = if sd == sd2 then 1 else 0-  sim _ _ =0--instance Sim Prep where                                             -  {-# INLINE sim #-}  -  sim (DiatDom  _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 3 else 2-  sim (SecDom   _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 3 else 2-  sim (SecMin   _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 3 else 2-  -  sim (SecMin   _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 2 else 1-  sim (DiatDom  _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 2 else 1-  -  sim (SecMin   _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 2 else 1-  sim (SecDom   _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 2 else 1-  -  sim (DiatDom  _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 2 else 1-  sim (SecDom   _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 2 else 1-  -- sim  NoTrans           NoTrans = -  sim _                 _        = 0      -  -instance Sim ChordToken where-  sim c1 c2 = if c1 == c2 then 2 else 0-  --------------------------------------------------------------------------------- --- Some utility functions---------------------------------------------------------------------------------  -  --- calculates the self similarity value (used for normalisation) i.e. the--- maximum similarity score-maxSim :: Sim a => [a] -> Int-maxSim =  foldr (\a b -> sim a a + b) 0  -   -div1 :: Int -> Int -> Int -div1 n c = if n == 1 then 1 else n `div` c +{-# OPTIONS_GHC -Wall #-}
+module HarmTrace.Matching.Sim where
+
+import HarmTrace.HAnTree.HAn
+import HarmTrace.HAnTree.Tree
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens (ChordToken)
+
+-------------------------------------------------------------------------------- 
+-- A class for representing numerical similarity between datatypes
+-------------------------------------------------------------------------------- 
+
+class Sim a where
+  sim :: a -> a -> Int
+  
+instance Sim a => Sim (Tree a) where
+  sim (Node l _ _) (Node l' _ _) = sim l l'
+
+instance Sim a => Sim [a] where
+  sim [ha]    [hb]    = sim ha hb 
+  sim (ha:ta) (hb:tb) = sim ha hb + sim ta tb
+  sim _       _       = 0
+
+instance Sim HAn where
+  sim (HAn    _ a) (HAn    _ b) = if a == b then 1 else 0
+  sim (HAnFunc  a) (HAnFunc  b) = sim a b
+  sim (HAnPrep  a) (HAnPrep  b) = sim a b
+  sim (HAnTrans a) (HAnTrans b) = sim a b
+  sim (HAnChord a) (HAnChord b) = sim a b
+  sim  _            _           = 0
+  
+instance Sim Int where
+  {-# INLINE sim #-}
+  sim i j = if i == j then 1 else 0
+
+instance Sim HFunc where
+  {-# INLINE sim #-}
+  sim (Ton _ _m c _) (Ton _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2
+  sim (Dom _ _m c _) (Dom _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2
+  sim (Sub _ _m c _) (Sub _ _m2 c2 _) = if c == c2 then 1 else 0 -- 1 + sim m m2
+  sim P             P              = 1      
+  sim PD            PD             = 1      
+  sim PT            PT             = 1 
+  sim _             _              = 0
+  
+instance Sim Mode where
+  {-# INLINE sim #-}
+  sim MajMode MajMode = 1  
+  sim MinMode MinMode = 1  
+  sim _       _       = 0
+
+instance Sim Trans where                                             
+  {-# INLINE sim #-}
+  sim (Trit     _v sd) (Trit     _v2 sd2) = if sd == sd2 then 1 else 0
+  sim (DimTrit  _v sd) (DimTrit  _v2 sd2) = if sd == sd2 then 1 else 0
+  sim (DimTrans _v sd) (DimTrans _v2 sd2) = if sd == sd2 then 1 else 0
+  sim _ _ =0
+
+instance Sim Prep where                                             
+  {-# INLINE sim #-}  
+  sim (DiatDom  _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 3 else 2
+  sim (SecDom   _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 3 else 2
+  sim (SecMin   _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 3 else 2
+  
+  sim (SecMin   _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 2 else 1
+  sim (DiatDom  _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 2 else 1
+  
+  sim (SecMin   _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 2 else 1
+  sim (SecDom   _v sd) (SecMin   _v2 sd2) = if sd == sd2 then 2 else 1
+  
+  sim (DiatDom  _v sd) (SecDom   _v2 sd2) = if sd == sd2 then 2 else 1
+  sim (SecDom   _v sd) (DiatDom  _v2 sd2) = if sd == sd2 then 2 else 1
+  -- sim  NoTrans           NoTrans = 
+  sim _                 _        = 0      
+  
+instance Sim ChordToken where
+  sim c1 c2 = if c1 == c2 then 2 else 0
+  
+-------------------------------------------------------------------------------- 
+-- Some utility functions
+--------------------------------------------------------------------------------  
+  
+-- calculates the self similarity value (used for normalisation) i.e. the
+-- maximum similarity score
+maxSim :: Sim a => [a] -> Int
+maxSim =  foldr (\a b -> sim a a + b) 0  
+   
+div1 :: Int -> Int -> Int 
+div1 n c = if n == 1 then 1 else n `div` c 
  
src/HarmTrace/Matching/SimpleChord.hs view
@@ -1,23 +1,23 @@-module HarmTrace.Matching.SimpleChord (SimChord, Sim, toSimChords) where--import HarmTrace.Base.MusicRep-import HarmTrace.Matching.Sim---- represents a  very simple chord, only major and minor and a root scaledegree-data SimChord = SimChord !Int  -- I = 0, IIb = 1 ... VII = 11-                         !Mode -- maj = True, min = False-  -instance Sim SimChord where-  {-# INLINE sim #-}-  sim (SimChord r sh) (SimChord r2 sh2) -- = simInt r r2 + simInt sh sh2-    | r == r2 && sh == sh2 =  4-    | otherwise            = -1-    -instance Show SimChord where-  show (SimChord r sh) = show (scaleDegrees !! r) -                         ++ if sh == MajMode then "" else "m"-    -toSimChords :: ChordDegree -> [SimChord]-toSimChords (Chord r sh _add _loc d) = -  -- replicate (d `div1` 2) (SimChord (diaDegToSemi r) (toMode sh))-  replicate d (SimChord (diaDegToSemi r) (toMode sh))+module HarmTrace.Matching.SimpleChord (SimChord, Sim, toSimChords) where
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Matching.Sim
+
+-- represents a  very simple chord, only major and minor and a root scaledegree
+data SimChord = SimChord !Int  -- I = 0, IIb = 1 ... VII = 11
+                         !Mode -- maj = True, min = False
+  
+instance Sim SimChord where
+  {-# INLINE sim #-}
+  sim (SimChord r sh) (SimChord r2 sh2) -- = simInt r r2 + simInt sh sh2
+    | r == r2 && sh == sh2 =  4
+    | otherwise            = -1
+    
+instance Show SimChord where
+  show (SimChord r sh) = show (scaleDegrees !! r) 
+                         ++ if sh == MajMode then "" else "m"
+    
+toSimChords :: ChordDegree -> [SimChord]
+toSimChords (Chord r sh _add _loc d) = 
+  -- replicate (d `div1` 2) (SimChord (diaDegToSemi r) (toMode sh))
+  replicate d (SimChord (toSemitone r) (toMode sh))
src/HarmTrace/Matching/Standard.hs view
@@ -1,25 +1,25 @@--module HarmTrace.Matching.Standard (diffChords, diffChordsLen) where--import Data.Algorithm.Diff -- cabal install Diff--diff :: (Eq a) => [a] -> [a] -> [(DI,a)]-diff = getDiff--diffLen :: (Eq a) => [a] -> [a] -> Float-diffLen x y = fromIntegral (len (diff x y)) / fromIntegral (length x)--len :: [(DI,a)] -> Int-len []        = 0-len ((B,_):t) = len t-len ((_,_):t) = 1 + len t------------------------------------------------------------------------------------- Matching-----------------------------------------------------------------------------------diffChordsLen :: (Eq a) => [a] -> [a] -> Float-diffChordsLen = diffLen--diffChords :: (Show a, Eq a) => [a] -> [a] -> String-diffChords x y = show (diff x y)+
+module HarmTrace.Matching.Standard (diffChords, diffChordsLen) where
+
+import Data.Algorithm.Diff -- cabal install Diff
+
+diff :: (Eq a) => [a] -> [a] -> [(DI,a)]
+diff = getDiff
+
+diffLen :: (Eq a) => [a] -> [a] -> Float
+diffLen x y = fromIntegral (len (diff x y)) / fromIntegral (length x)
+
+len :: [(DI,a)] -> Int
+len []        = 0
+len ((B,_):t) = len t
+len ((_,_):t) = 1 + len t
+
+--------------------------------------------------------------------------------
+-- Matching
+--------------------------------------------------------------------------------
+
+diffChordsLen :: (Eq a) => [a] -> [a] -> Float
+diffChordsLen = diffLen
+
+diffChords :: (Show a, Eq a) => [a] -> [a] -> String
+diffChords x y = show (diff x y)
src/HarmTrace/Models/Jazz/Instances.hs view
@@ -1,250 +1,263 @@-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}-{-# LANGUAGE TemplateHaskell        #-}-{-# LANGUAGE TypeOperators          #-}-{-# LANGUAGE EmptyDataDecls         #-}-{-# LANGUAGE TypeSynonymInstances   #-}-{-# LANGUAGE ScopedTypeVariables    #-}-{-# LANGUAGE FlexibleInstances      #-}-{-# LANGUAGE FlexibleContexts       #-}-{-# LANGUAGE TypeFamilies           #-}-{-# LANGUAGE MultiParamTypeClasses  #-}-{-# LANGUAGE UndecidableInstances   #-}-{-# LANGUAGE OverlappingInstances   #-}-{-# LANGUAGE GADTs                  #-}--module HarmTrace.Models.Jazz.Instances where---- Generics stuff-import Generics.Instant.TH---- Parser stuff-import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.BasicInstances---- Music stuff-import HarmTrace.Models.Parser-import HarmTrace.Models.Jazz.Model-import HarmTrace.HAnTree.Tree-import HarmTrace.HAnTree.ToHAnTree-import HarmTrace.HAnTree.HAn-import HarmTrace.Tokenizer.Tokens as CT-import HarmTrace.Base.TypeLevel-import HarmTrace.Base.MusicRep---- Library modules-import Control.Arrow------------------------------------------------------------------------------------- The non-generic part of the parser-----------------------------------------------------------------------------------instance ParseG (Base_SD   deg clss Ze) where parseG = empty-        -instance ( ToDegree (DiatV deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom   deg) DomClass n)-         , ParseG (Base_SD (DiatV  deg) MinClass n)-         , ParseG (Base_SD (DiatVM deg) MajClass n)-         , ParseG (Base_SD         deg  MinClass n)-         , ParseG (TritMinVSub     deg  MinClass  )      -         ) => ParseG (Base_SD deg MinClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_Diat <$> parseG <*> parseG   -           <|> Cons_DiatM' <$> parseG <*> parseG   -  -instance ( ToDegree (DiatVM deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom   deg) DomClass n)-         , ParseG (Base_SD (DiatVM deg) MajClass n)-         , ParseG (Base_SD         deg  MajClass n)-         , ParseG (TritMinVSub     deg  MajClass  )-         ) => ParseG (Base_SD deg MajClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_DiatM <$> parseG <*> parseG     -           -instance ( ToDegree (VMin deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom deg) DomClass n)-         , ParseG (Base_SD (VMin deg) MinClass n)-         , ParseG (Base_SD       deg  DomClass n)-         , ParseG (TritMinVSub   deg  DomClass  )         -         ) => ParseG (Base_SD deg DomClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_Vmin <$> parseG <*> parseG              -  -instance ( ToDegree (VDom deg)-         , ParseG (Base_SD (VDom  deg) DomClass n)-         , ParseG (Base_SD        deg  DimClass n)-         , ParseG (TritMinVSub    deg  DimClass  )-         ) => ParseG (Base_SD deg DimClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG---- Ad-hoc cases for Base_Final-instance ParseG (Base_Final deg clss Ze) where parseG = empty--instance ( ParseG (FinalDimTrans deg clss)-         ) => ParseG (Base_Final deg clss (Su n)) where-  parseG =     Base_Final  <$> parseG-  -instance ( ParseG (FinalDimTrans       deg  DomClass)-         , ParseG (FinalDimTrans       deg  MinClass)-         , ParseG (Base_Final (Tritone deg) DomClass n)-         , ParseG (Base_Final (IIbDim  deg) DimClass n)-         ) => ParseG (Base_Final deg DomClass (Su n)) where-  parseG =     Base_Final     <$> parseG-           <|> Final_Tritone  <$> parseG-           <|> Final_Dim_V    <$> parseG---- Ad-hoc cases for Surface_Chord-instance ParseG (Surface_Chord deg clss Ze) where parseG = empty--instance ( ToDegree deg -         , ParseG (Surface_Chord (MinThird deg) DimClass n)-         ) => ParseG (Surface_Chord deg DimClass (Su n)) where-  parseG =     Dim_Chord_Trns <$> parseG-           <|> pChord deg DimClass-    where deg = toDegree (undefined :: deg)  -  --- all chords-instance ( ToDegree deg, ToClass clss-         ) => ParseG (Surface_Chord deg clss (Su n)) where-  parseG = pChord deg clss-    where deg = toDegree (undefined :: deg)-          clss = toClass (undefined :: clss)---pChord :: ScaleDegree -> ClassType -> PMusic (Surface_Chord deg clss (Su n))--- Do not parse Imp degrees-pChord (Note _ Imp) _clss = empty--- General case-pChord deg clss = setStatus <$> pSatisfy recognize insertion where-  recognize (ChordToken deg' clss' _cs _st _n _d) = deg == deg' && clss == clss'-  setStatus (ChordToken r t l NotParsed n d) -              = Surface_Chord (ChordToken r t l Parsed n d)-  setStatus c = Surface_Chord c-  insertion = Insertion "ChordToken" (ChordToken deg clss [] CT.Inserted 1 0) 5------------------------------------------------------------------------------------- The non-generic part of the GTree wrapper----------------------------------------------------------------------------------toGTree :: (GetDegree a, GTree a) =>-           (Int -> ScaleDegree -> Trans) -> Int -> a -> [Tree HAn]-toGTree con transp deg = [Node (HAnTrans . con 1 $ toTransSDVal transp deg) -                               (gTree deg) Nothing]---- create a branching Tree HAn-toGTreeSplit :: (GetDegree a, GetDegree b, GTree a, GTree b) =>-           (Int -> ScaleDegree -> Prep) -> b -> a -> [Tree HAn]-toGTreeSplit con vof deg  -  = Node (HAnPrep . con 1 $ toSDVal deg) (gTree vof) Nothing : gTree deg-                                 --- Ad-Hoc case for Piece -instance GTree Piece where -- we take the children to skip a "list node"-  gTree (Piece p) = [Node (HAnFunc P) (gTree p) Nothing]---- Ad-hoc cases for Base_SD-instance GTree (Base_SD deg clss Ze) where-  gTree _ = error "gTree: impossible?"-  -instance ( GTree (Base_SD (VDom   deg)  DomClass n)-         , GTree (Base_SD (DiatV  deg)  MinClass n)-         , GTree (Base_SD (DiatVM deg)  MajClass n)-         , GTree (Base_SD (VMin   deg)  MinClass n)-         , GTree (Base_SD         deg   clss     n)-         , GTree (Base_Final      deg   clss     n)-         ) => GTree (Base_SD deg clss (Su n)) where-  gTree (Base_SD d)       = gTree d-  gTree (Cons_Vdom   s d) = toGTreeSplit SecDom  s d -  gTree (Cons_Diat   s d) = toGTreeSplit DiatDom s d -  gTree (Cons_DiatM  s d) = toGTreeSplit DiatDom s d -  gTree (Cons_DiatM' s d) = toGTreeSplit DiatDom s d -  gTree (Cons_Vmin   s d) = toGTreeSplit SecMin  s d ---- Ad-hoc cases for Base_Final-instance GTree (Base_Final deg clss Ze) where-  gTree _ = error "gTree: impossible?"--instance ( GetDegree (Base_Final (Tritone deg) DomClass n)-         , GetDegree (Base_Final (IIbDim  deg) DimClass n)-         , GTree (FinalDimTrans deg clss)-         , GTree (Base_Final (Tritone deg)  DomClass n)-         , GTree (Base_Final (IIbDim  deg)  DimClass n)-         ) => GTree (Base_Final deg clss (Su n)) where-  gTree (Base_Final d)      = gTree d-  -- The tritone substitution of a relative V is as alsway one semitone above-  -- the chord it is preceding-  gTree (Final_Tritone  d)  = toGTree Trit    6  d -  gTree (Final_Dim_V    d)  = toGTree DimTrit 11 d ---- Ad-hoc cases for Surface_Chord-instance GTree (Surface_Chord deg clss Ze) where-  gTree _ = error "gTree: impossible?"-                                       -instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)-         , GTree     (Surface_Chord (MinThird deg) DimClass n)-         ) => GTree  (Surface_Chord deg clss (Su n)) where-  gTree (Surface_Chord c)  = [Node (HAnChord c) [] Nothing]-  gTree (Dim_Chord_Trns c) = toGTree DimTrans 9 c -- pretty print?-  ------------------------------------------------------------------------------------ Ad hoc getDegree instaces---------------------------------------------------------------------------------  -toTransSDVal :: (GetDegree a) => Int -> a -> ScaleDegree-toTransSDVal t d = let (a,i) = getDeg d in transposeSem a (i+t)--toSDVal :: (GetDegree a) => a -> ScaleDegree-toSDVal d = let (a,i) = getDeg d in transposeSem a i---- Given a degree getDegee ensures that all information about the internal--- structure of a scale degree,i.e. the degree and the an int value representing--- the transposition of that degree at the current level, is available.-class GetDegree a where-  getDeg :: a -> (ScaleDegree, Int) --instance GetDegree (Base_SD deg clss n) where-  getDeg (Base_SD d) = getDeg d-  getDeg (Cons_Vdom   _ d) = getDeg d -  getDeg (Cons_Diat   _ d) = getDeg d -  getDeg (Cons_DiatM  _ d) = getDeg d -  getDeg (Cons_DiatM' _ d) = getDeg d -  getDeg (Cons_Vmin   _ d) = getDeg d --instance ( GetDegree (Base_Final deg clss Ze)) where -  getDeg = error "getDegree: impossible?"-instance GetDegree (Base_Final deg clss  n) where-  getDeg (Base_Final d)  = getDeg d-  -- The tritone substitution of a relative V is as always one semitone above-  -- the chord it is preceding-  getDeg (Final_Tritone  d)  = second (+6) (getDeg d)-  getDeg (Final_Dim_V    d)  = second (+1) (getDeg d)--instance ( GetDegree (Surface_Chord deg clss Ze)) where -  getDeg = error "getDegree: impossible?"-  -instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)-         ) => GetDegree (Surface_Chord deg clss (Su n)) where-  getDeg (Surface_Chord (ChordToken d _cls _cs _stat _n _dur)) = (d,0) -  getDeg (Dim_Chord_Trns d) = second (+9) (getDeg d)------------------------------------------------------------------------------------- Instances of Representable for music datatypes-----------------------------------------------------------------------------------deriveAllL allTypes--$(fmap join $ mapM (\t -> gadtInstance ''ParseG t 'parseG 'parseGdefault)-  allTypes)--$(fmap join $ mapM (\t -> simplInstance ''GTree t 'gTree 'gTreeDefault)-  allTypes)------------------------------------------------------------------------------------- ChordToken as tokens-----------------------------------------------------------------------------------instance IsLocationUpdatedBy Int ChordToken where -  advance p c = p + chordNumReps c+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+{-# LANGUAGE TemplateHaskell        #-}
+{-# LANGUAGE TypeOperators          #-}
+{-# LANGUAGE EmptyDataDecls         #-}
+{-# LANGUAGE TypeSynonymInstances   #-}
+{-# LANGUAGE ScopedTypeVariables    #-}
+{-# LANGUAGE FlexibleInstances      #-}
+{-# LANGUAGE FlexibleContexts       #-}
+{-# LANGUAGE TypeFamilies           #-}
+{-# LANGUAGE MultiParamTypeClasses  #-}
+{-# LANGUAGE UndecidableInstances   #-}
+{-# LANGUAGE OverlappingInstances   #-}
+{-# LANGUAGE GADTs                  #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Models.Jazz.Instances
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Adhoc instances for the jazz model
+--------------------------------------------------------------------------------
+
+module HarmTrace.Models.Jazz.Instances where
+
+-- Generics stuff
+import Generics.Instant.TH
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.BasicInstances
+
+-- Music stuff
+import HarmTrace.Models.Parser
+import HarmTrace.Models.Jazz.Model
+import HarmTrace.HAnTree.Tree
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.HAnTree.HAn
+import HarmTrace.Tokenizer.Tokens as CT
+import HarmTrace.Base.TypeLevel
+import HarmTrace.Base.MusicRep
+
+-- Library modules
+import Control.Arrow
+
+--------------------------------------------------------------------------------
+-- The non-generic part of the parser
+--------------------------------------------------------------------------------
+
+instance ParseG (Base_SD   deg clss Ze) where parseG = empty
+        
+instance ( ToDegree (DiatV deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom   deg) DomClass n)
+         , ParseG (Base_SD (DiatV  deg) MinClass n)
+         , ParseG (Base_SD (DiatVM deg) MajClass n)
+         , ParseG (Base_SD         deg  MinClass n)
+         , ParseG (TritMinVSub     deg  MinClass  )      
+         ) => ParseG (Base_SD deg MinClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_Diat <$> parseG <*> parseG   
+           <|> Cons_DiatM' <$> parseG <*> parseG   
+  
+instance ( ToDegree (DiatVM deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom   deg) DomClass n)
+         , ParseG (Base_SD (DiatVM deg) MajClass n)
+         , ParseG (Base_SD         deg  MajClass n)
+         , ParseG (TritMinVSub     deg  MajClass  )
+         ) => ParseG (Base_SD deg MajClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_DiatM <$> parseG <*> parseG     
+           
+instance ( ToDegree (VMin deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom deg) DomClass n)
+         , ParseG (Base_SD (VMin deg) MinClass n)
+         , ParseG (Base_SD       deg  DomClass n)
+         , ParseG (TritMinVSub   deg  DomClass  )         
+         ) => ParseG (Base_SD deg DomClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_Vmin <$> parseG <*> parseG              
+  
+instance ( ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom  deg) DomClass n)
+         , ParseG (Base_SD        deg  DimClass n)
+         , ParseG (TritMinVSub    deg  DimClass  )
+         ) => ParseG (Base_SD deg DimClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+
+-- Ad-hoc cases for Base_Final
+instance ParseG (Base_Final deg clss Ze) where parseG = empty
+
+instance ( ParseG (FinalDimTrans deg clss)
+         ) => ParseG (Base_Final deg clss (Su n)) where
+  parseG =     Base_Final  <$> parseG
+  
+instance ( ParseG (FinalDimTrans       deg  DomClass)
+         , ParseG (FinalDimTrans       deg  MinClass)
+         , ParseG (Base_Final (Tritone deg) DomClass n)
+         , ParseG (Base_Final (IIbDim  deg) DimClass n)
+         ) => ParseG (Base_Final deg DomClass (Su n)) where
+  parseG =     Base_Final     <$> parseG
+           <|> Final_Tritone  <$> parseG
+           <|> Final_Dim_V    <$> parseG
+
+-- Ad-hoc cases for Surface_Chord
+instance ParseG (Surface_Chord deg clss Ze) where parseG = empty
+
+instance ( ToDegree deg 
+         , ParseG (Surface_Chord (MinThird deg) DimClass n)
+         ) => ParseG (Surface_Chord deg DimClass (Su n)) where
+  parseG =     Dim_Chord_Trns <$> parseG
+           <|> pChord deg DimClass
+    where deg = toDegree (undefined :: deg)  
+  
+-- all chords
+instance ( ToDegree deg, ToClass clss
+         ) => ParseG (Surface_Chord deg clss (Su n)) where
+  parseG = pChord deg clss
+    where deg = toDegree (undefined :: deg)
+          clss = toClass (undefined :: clss)
+
+
+pChord :: ScaleDegree -> ClassType -> PMusic (Surface_Chord deg clss (Su n))
+-- Do not parse Imp degrees
+pChord (Note _ Imp) _clss = empty
+-- General case
+pChord deg clss = setStatus <$> pSatisfy recognize insertion where
+  recognize (ChordToken deg' clss' _cs _st _n _d) = deg == deg' && clss == clss'
+  setStatus (ChordToken r t l NotParsed n d) 
+              = Surface_Chord (ChordToken r t l Parsed n d)
+  setStatus c = Surface_Chord c
+  insertion = Insertion "ChordToken" (ChordToken deg clss [] CT.Inserted 1 0) 5
+
+--------------------------------------------------------------------------------
+-- The non-generic part of the GTree wrapper
+--------------------------------------------------------------------------------
+toGTree :: (GetDegree a, GTree a) =>
+           (Int -> ScaleDegree -> Trans) -> Int -> a -> [Tree HAn]
+toGTree con transp deg = [Node (HAnTrans . con 1 $ toTransSDVal transp deg) 
+                               (gTree deg) Nothing]
+
+-- create a branching Tree HAn
+toGTreeSplit :: (GetDegree a, GetDegree b, GTree a, GTree b) =>
+           (Int -> ScaleDegree -> Prep) -> b -> a -> [Tree HAn]
+toGTreeSplit con vof deg  
+  = Node (HAnPrep . con 1 $ toSDVal deg) (gTree vof) Nothing : gTree deg
+                                 
+-- Ad-Hoc case for Piece 
+instance GTree Piece where -- we take the children to skip a "list node"
+  gTree (Piece p) = [Node (HAnFunc P) (gTree p) Nothing]
+
+-- Ad-hoc cases for Base_SD
+instance GTree (Base_SD deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+  
+instance ( GTree (Base_SD (VDom   deg)  DomClass n)
+         , GTree (Base_SD (DiatV  deg)  MinClass n)
+         , GTree (Base_SD (DiatVM deg)  MajClass n)
+         , GTree (Base_SD (VMin   deg)  MinClass n)
+         , GTree (Base_SD         deg   clss     n)
+         , GTree (Base_Final      deg   clss     n)
+         ) => GTree (Base_SD deg clss (Su n)) where
+  gTree (Base_SD d)       = gTree d
+  gTree (Cons_Vdom   s d) = toGTreeSplit SecDom  s d 
+  gTree (Cons_Diat   s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_DiatM  s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_DiatM' s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_Vmin   s d) = toGTreeSplit SecMin  s d 
+
+-- Ad-hoc cases for Base_Final
+instance GTree (Base_Final deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+
+instance ( GetDegree (Base_Final (Tritone deg) DomClass n)
+         , GetDegree (Base_Final (IIbDim  deg) DimClass n)
+         , GTree (FinalDimTrans deg clss)
+         , GTree (Base_Final (Tritone deg)  DomClass n)
+         , GTree (Base_Final (IIbDim  deg)  DimClass n)
+         ) => GTree (Base_Final deg clss (Su n)) where
+  gTree (Base_Final d)      = gTree d
+  -- The tritone substitution of a relative V is as alsway one semitone above
+  -- the chord it is preceding
+  gTree (Final_Tritone  d)  = toGTree Trit    6  d 
+  gTree (Final_Dim_V    d)  = toGTree DimTrit 11 d 
+
+-- Ad-hoc cases for Surface_Chord
+instance GTree (Surface_Chord deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+                                       
+instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)
+         , GTree     (Surface_Chord (MinThird deg) DimClass n)
+         ) => GTree  (Surface_Chord deg clss (Su n)) where
+  gTree (Surface_Chord c)  = [Node (HAnChord c) [] Nothing]
+  gTree (Dim_Chord_Trns c) = toGTree DimTrans 9 c -- pretty print?
+  
+--------------------------------------------------------------------------------
+-- Ad hoc getDegree instaces
+--------------------------------------------------------------------------------  
+toTransSDVal :: (GetDegree a) => Int -> a -> ScaleDegree
+toTransSDVal t d = let (a,i) = getDeg d in transposeSem a (i+t)
+
+toSDVal :: (GetDegree a) => a -> ScaleDegree
+toSDVal d = let (a,i) = getDeg d in transposeSem a i
+
+-- Given a degree getDegee ensures that all information about the internal
+-- structure of a scale degree,i.e. the degree and the an int value representing
+-- the transposition of that degree at the current level, is available.
+class GetDegree a where
+  getDeg :: a -> (ScaleDegree, Int) 
+
+instance GetDegree (Base_SD deg clss n) where
+  getDeg (Base_SD d) = getDeg d
+  getDeg (Cons_Vdom   _ d) = getDeg d 
+  getDeg (Cons_Diat   _ d) = getDeg d 
+  getDeg (Cons_DiatM  _ d) = getDeg d 
+  getDeg (Cons_DiatM' _ d) = getDeg d 
+  getDeg (Cons_Vmin   _ d) = getDeg d 
+
+instance ( GetDegree (Base_Final deg clss Ze)) where 
+  getDeg = error "getDegree: impossible?"
+instance GetDegree (Base_Final deg clss  n) where
+  getDeg (Base_Final d)  = getDeg d
+  -- The tritone substitution of a relative V is as always one semitone above
+  -- the chord it is preceding
+  getDeg (Final_Tritone  d)  = second (+6) (getDeg d)
+  getDeg (Final_Dim_V    d)  = second (+1) (getDeg d)
+
+instance ( GetDegree (Surface_Chord deg clss Ze)) where 
+  getDeg = error "getDegree: impossible?"
+  
+instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)
+         ) => GetDegree (Surface_Chord deg clss (Su n)) where
+  getDeg (Surface_Chord (ChordToken d _cls _cs _stat _n _dur)) = (d,0) 
+  getDeg (Dim_Chord_Trns d) = second (+9) (getDeg d)
+
+--------------------------------------------------------------------------------
+-- Instances of Representable for music datatypes
+--------------------------------------------------------------------------------
+
+deriveAllL allTypes
+
+$(fmap join $ mapM (\t -> gadtInstance ''ParseG t 'parseG 'parseGdefault)
+  allTypes)
+
+$(fmap join $ mapM (\t -> simplInstance ''GTree t 'gTree 'gTreeDefault)
+  allTypes)
+
+--------------------------------------------------------------------------------
+-- ChordToken as tokens
+--------------------------------------------------------------------------------
+
+instance IsLocationUpdatedBy Int ChordToken where 
+  advance p c = p + chordNumReps c
src/HarmTrace/Models/Jazz/Main.hs view
@@ -1,28 +1,41 @@--module HarmTrace.Models.Jazz.Main ( -    pJazz -  , module HarmTrace.Models.Jazz.Model-  ) where---- Parser stuff-import Text.ParserCombinators.UU---- Music stuff-import HarmTrace.Base.MusicRep-import HarmTrace.Models.Parser-import HarmTrace.Models.Jazz.Model hiding (PD,PT)--import HarmTrace.Models.Jazz.Instances ()-------------------------------------------------------------------------------------- From tokens to structured music pieces-----------------------------------------------------------------------------------pPieceMaj, pPieceMin :: PMusic [Piece]-pPieceMaj = map Piece <$> amb (parseG :: PMusic [Phrase MajMode])-pPieceMin = map Piece <$> amb (parseG :: PMusic [Phrase MinMode])--pJazz :: Key -> PMusic [Piece]-pJazz (Key _ MajMode) = pPieceMaj-pJazz (Key _ MinMode) = pPieceMin+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Models.Jazz.Main
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: The jazz parser
+--------------------------------------------------------------------------------
+
+module HarmTrace.Models.Jazz.Main ( 
+    pJazz 
+  , module HarmTrace.Models.Jazz.Model
+  ) where
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+
+-- Music stuff
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Parser
+import HarmTrace.Models.Jazz.Model hiding (PD,PT)
+
+import HarmTrace.Models.Jazz.Instances ()
+
+
+--------------------------------------------------------------------------------
+-- From tokens to structured music pieces
+--------------------------------------------------------------------------------
+
+pPieceMaj, pPieceMin :: PMusic [Piece]
+pPieceMaj = map Piece <$> amb (parseG :: PMusic [Phrase MajMode])
+pPieceMin = map Piece <$> amb (parseG :: PMusic [Phrase MinMode])
+
+pJazz :: Key -> PMusic [Piece]
+pJazz (Key _ MajMode) = pPieceMaj
+pJazz (Key _ MinMode) = pPieceMin
src/HarmTrace/Models/Jazz/Model.hs view
@@ -1,409 +1,420 @@-{-# LANGUAGE CPP                      #-}-{-# LANGUAGE TemplateHaskell          #-}-{-# LANGUAGE TypeOperators            #-}-{-# LANGUAGE EmptyDataDecls           #-}-{-# LANGUAGE TypeSynonymInstances     #-}-{-# LANGUAGE FlexibleInstances        #-}-{-# LANGUAGE ScopedTypeVariables      #-}-{-# LANGUAGE TypeFamilies             #-}-{-# LANGUAGE GADTs                    #-}--module HarmTrace.Models.Jazz.Model where--import HarmTrace.Base.TypeLevel--import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens-import Language.Haskell.TH.Syntax (Name)------------------------------------------------------------------------------------- Musical structure as a datatype-----------------------------------------------------------------------------------#ifndef NUMLEVELS-#define NUMLEVELS T5-#endif---- perhaps this module should be named differently, like Model or HarmonyModel--data MajMode-data MinMode---- High level structure-data Piece = forall mode. Piece [Phrase mode]---- The Phrase level-data Phrase mode where-  PT   :: Ton mode -> Phrase mode-  PD   :: Dom mode -> Phrase mode---- Harmonic categories--- Tonic-data Ton mode where-  -- major mode-  T_1      :: Final I MajClass         -> Ton MajMode-  T_2      :: Final I MajClass         -> Final IV MajClass -           -> Final I MajClass         -> Ton MajMode-           -  -- blues           -  -- T_4_bls  :: Final I DomClass         -> Ton mode  --  T_3_par  :: Final III MinClass       -> Ton MajMode    -  T_6_bor  :: TMinBorrow -> Ton MajMode--  -- minor mode         -  Tm_1     :: SD MinMode I MinClass    -> Ton MinMode-  Tm_2     :: Final I MinClass -> Final IV MinClass-           -> Final I MinClass         -> Ton MinMode           --  Tm_3_par :: Final IIIb MajClass      -> Ton MinMode -  Tm_6_bor :: TMajBorrow               -> Ton MinMode  -- picardy third etc.     ---- Dominant-data Dom mode where-  -- major mode-  D_1   :: SDom mode -> Dom mode -> Dom mode-  D_2   :: SD mode V DomClass        -> Dom mode-  D_3   :: SD mode V MajClass        -> Dom mode--  D_4   :: SD MajMode VII MinClass   -> Dom MajMode       -  -  -- moll-dur: minor mode borrowings in major-  -- This would be an elegant way of defining major minor borrowing,-  -- but it causes a lot of unwanted abmiguities since all the mode-  -- rules can be explained with and without borrowing-  -- D_9_bor :: Dom MinMode -> Dom MajMode-  D_8_bor :: DMinBorrow              -> Dom MajMode-  -  -- minor mode (there must be at least one rule with "MinMode" otherwise-  -- no you get a "No instance for (ParseG (Dom MinMode))" error-  Dm_4   :: SD MinMode VIIb MajClass -> Dom MinMode-  Dm_8_bor :: DMajBorrow             -> Dom MinMode---- Subdominant-data SDom mode where-  S_1_par :: SD mode II MinClass     -> SDom mode -- sub dom parallel--  -- Pretty printing? this rule compensates for the V/I Imp-  -- to be able to parse D:7 D:min G:7 C:maj (not in cmj)-  S_2_par :: SD mode II DomClass -> Final II MinClass-                                         -> SDom mode -  S_3     :: SD MajMode IV MajClass -> SDom MajMode-  S_4     :: SD MajMode III MinClass -> Final IV MajClass -                                         -> SDom MajMode-  -- S_6_par :: SD MajMode VI MinClass  -> SDom MajMode- -  -- blues-  -- S_2_bls :: SD mode IV DomClass  -> SD mode I DomClass -                                         -- -> SDom mode -  -  -- Borrowing from minor in a major mode-  -- S_7_bor :: SDom MajMode -> SDom MinMode-  S_5_bor :: SMinBorrow -> SDom MajMode -  -  -- minor mode-  Sm_3    :: SD MinMode IV  MinClass  -> SDom MinMode-  Sm_4    :: SD MinMode IIIb MajClass -> Final IV MinClass -                                          -> SDom MinMode-  -- Sm_3_par :: SD MinMode VIb MajClass -> SDom MinMode- -  Sm_5_bor :: SMajBorrow              -> SDom MinMode --  -- perhaps add a functional node for Neapolitan chords?-  Sm_6   :: SD MinMode IIb MajClass   -> SDom MinMode -- Neapolitan-  --- Borrowings from minor in a major key-data TMinBorrow = Tm_21_bor (SD MinMode I    MinClass)   -                    | Tm_23_bor (SD MinMode IIIb MajClass)   --data DMinBorrow = Dm_24_bor (SD MinMode VIIb MajClass)   -                    -- | Dm_21_bor (Final VIIb DomClass)   -                      -data SMinBorrow = Sm_20_bor (SD MinMode IV   MinClass) -                    -- | Sm_21_bor (SD MinMode VIb  MajClass)-                    | Sm_22_bor (SD MinMode IIb  MajClass)   -- Neapolitan ---- Borrowings from major in a minor key-data TMajBorrow = T_21_bor (SD MajMode I   MajClass)-                    | T_23_bor (SD MajMode III MinClass)--data DMajBorrow = D_24_bor (SD MajMode VII MinClass)   -                    -- | D_21_bor (Final VII DimClass)-                      -data SMajBorrow = S_20_bor (SD MajMode IV   MajClass) -                    -                    --- Limit secondary dominants to a few levels-type SD mode deg clss = Base_SD deg clss NUMLEVELS---- a type that can be substituted by its tritone sub and diminished 7b9-type TritMinVSub deg clss = Base_Final deg clss T2---- A Scale degree that can only translate to a surface chord--- and allows for the transformation into enharmonic equivalent --- diminshed surface chords-type FinalDimTrans deg clss = Surface_Chord deg clss T4---- A Scale degree that translates into a (non-tranformable) surface chord-type Final deg clss = Surface_Chord deg clss T1----- Datatypes for clustering harmonic degrees--- type Base_SD deg clss n = List (Base_SD' deg clss n) T4--data Base_SD deg clss n where-  Base_SD   :: TritMinVSub deg clss -- Min5    deg clss  n-            -> Base_SD deg clss (Su n)   -  -- Rule for explaining perfect secondary dominants-  Cons_Vdom :: Base_SD (VDom  deg) DomClass n -> Base_SD deg clss n-            -> Base_SD        deg  clss (Su n)-  Cons_Diat :: Base_SD (DiatV deg) MinClass n -> Base_SD deg MinClass n-            -> Base_SD        deg  MinClass (Su n)-  Cons_DiatM :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MajClass n-            -> Base_SD        deg  MajClass (Su n)-  Cons_DiatM' :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MinClass n-            -> Base_SD        deg  MinClass (Su n)-  -- Minor fifth insertion-  Cons_Vmin :: Base_SD (VMin deg) MinClass n -> Base_SD deg DomClass n-            -> Base_SD     deg  DomClass (Su n)--            -data Base_Final deg clss n where-  -- Just a "normal", final degree. The Strings are the original input.-  Base_Final     :: FinalDimTrans deg clss -> Base_Final deg clss (Su n)-  -- Tritone substitution-  Final_Tritone  :: Base_Final (Tritone deg) DomClass n-                 -> Base_Final deg DomClass (Su n)-  Final_Dim_V    :: Base_Final (IIbDim  deg) DimClass n-                 -> Base_Final deg DomClass (Su n)                 ---- Dimished tritone substitution accounting for dimished chord transistions-data Surface_Chord deg clss n where-  Surface_Chord  :: ChordToken  -                 -> Surface_Chord deg clss     (Su n)-  Dim_Chord_Trns :: Surface_Chord (MinThird deg) DimClass n-                 -> Surface_Chord deg DimClass (Su n)------------------------------------------------------------------------------------- Type Level Scale Degrees------------------------------------------------------------------------------------- typelevel chord classes -data MajClass-data MinClass-data DomClass-data DimClass---- Degrees (at the type level)-data I-data Ib-data Is-data II-data IIb-data IIs-data III-data IIIb-data IIIs-data IV-data IVb-data IVs-data V-data Vb-data Vs-data VI-data VIb-data VIs-data VII-data VIIb-data VIIs---- Used when we don't want to consider certain possibilities-data Imp---- Degrees at the value level are in Tokenizer--- Type to value conversions-class ToClass clss where-  toClass :: clss -> ClassType--instance ToClass MajClass where toClass _ = MajClass-instance ToClass MinClass where toClass _ = MinClass-instance ToClass DomClass where toClass _ = DomClass-instance ToClass DimClass where toClass _ = DimClass---- The class doesn't really matter, since the degree will be impossible to parse-instance ToClass Imp where toClass _ = DimClass--class ToDegree deg where-  toDegree :: deg -> ScaleDegree--instance ToDegree I     where toDegree _ = Note Nothing I-instance ToDegree II    where toDegree _ = Note Nothing II-instance ToDegree III   where toDegree _ = Note Nothing III-instance ToDegree IV    where toDegree _ = Note Nothing IV-instance ToDegree V     where toDegree _ = Note Nothing V-instance ToDegree VI    where toDegree _ = Note Nothing VI-instance ToDegree VII   where toDegree _ = Note Nothing VII-instance ToDegree Ib    where toDegree _ = Note (Just Fl) I-instance ToDegree IIb   where toDegree _ = Note (Just Fl) II-instance ToDegree IIIb  where toDegree _ = Note (Just Fl) III-instance ToDegree IVb   where toDegree _ = Note (Just Fl) IV-instance ToDegree Vb    where toDegree _ = Note (Just Fl) V-instance ToDegree VIb   where toDegree _ = Note (Just Fl) VI-instance ToDegree VIIb  where toDegree _ = Note (Just Fl) VII-instance ToDegree IIs   where toDegree _ = Note (Just Sh) II-instance ToDegree IIIs  where toDegree _ = Note (Just Sh) III-instance ToDegree IVs   where toDegree _ = Note (Just Sh) IV-instance ToDegree Vs    where toDegree _ = Note (Just Sh) V-instance ToDegree VIs   where toDegree _ = Note (Just Sh) VI-instance ToDegree VIIs  where toDegree _ = Note (Just Sh) VII---- Can't ever parse these-instance ToDegree Imp where toDegree _ = Note Nothing Imp-------------------------------------------------------------------------------------- Type Families for Relative Scale Degrees-------------------------------------------------------------------------------------- Diatonic fifths, and their class (comments with the CMaj scale)--- See http://en.wikipedia.org/wiki/Circle_progression-type family DiatV deg :: *-type instance DiatV I   = Imp -- V   -- G7  should be Dom-type instance DiatV V   = Imp -- II  -- Dm7 should be SDom-type instance DiatV II  = VI  -- Am7 -type instance DiatV VI  = III -- Em7-type instance DiatV III = VII -- Bhdim7 can be explained by Dim rule-type instance DiatV VII = Imp -- IV  -- FMaj7 should be SDom-type instance DiatV IV  = Imp -- I   -- CMaj7--type instance DiatV IIb  = Imp-type instance DiatV IIIb = Imp-type instance DiatV IVs  = Imp-type instance DiatV VIb  = Imp-type instance DiatV VIIb = Imp-type instance DiatV Imp  = Imp--type family DiatVM deg :: *-type instance DiatVM I   = Imp -- V   -- G7  should be Dom-type instance DiatVM V   = Imp -- Dm7 should be SDom-type instance DiatVM II  = VIb -- Ab -type instance DiatVM VI  = Imp -- Em7-type instance DiatVM III = Imp -- Bhdim7 can be explained by Dim rule-type instance DiatVM VII = Imp -- IV  -- FMaj7 should be SDom-type instance DiatVM IV  = Imp -- I   -- CMaj7--type instance DiatVM IIb  = Imp-type instance DiatVM IIIb = VIIb -type instance DiatVM IVs  = Imp-type instance DiatVM VIb  = IIIb-type instance DiatVM VIIb = Imp-type instance DiatVM Imp  = Imp------------------------------------------------------------------------------------- Type families for secondary dominants------------------------------------------------------------------------------------- Perfect fifths (class is always Dom)--- See http://en.wikipedia.org/wiki/Circle_of_fifths-type family VDom deg :: *--type instance VDom I     = Imp  -- interferes with dom -type instance VDom IIb   = VIb-type instance VDom II    = VI -type instance VDom IIIb  = VIIb -- interferes with Dm_3-type instance VDom III   = VII-type instance VDom IV    = I-type instance VDom IVs   = IIb-type instance VDom V     = II   -- interferes with Sm_1-type instance VDom VIb   = IIIb-type instance VDom VI    = III-type instance VDom VIIb  = IV-type instance VDom VII   = IVs-type instance VDom Imp   = Imp---- Perfect fifths for the minor case (this is an additional--- type family to controll the reduction of ambiguities--- specifically in the minor case)-type family VMin deg :: *-type instance VMin I     = V -type instance VMin IIb   = VIb-type instance VMin II    = VI  -- interferes with sub -type instance VMin IIIb  = VIIb-type instance VMin III   = VII-type instance VMin IV    = I-type instance VMin IVs   = IIb-type instance VMin V     = Imp -- II interferes with sub-type instance VMin VIb   = IIIb-type instance VMin VI    = III-type instance VMin VIIb  = Imp --IV -- inteferes with sub IV:min-type instance VMin VII   = IVs-type instance VMin Imp   = Imp- --- The tritone substitution--- See http://en.wikipedia.org/wiki/Tritone_substitution-type family Tritone deg :: *-type instance Tritone I     = IVs-type instance Tritone IVs   = I--type instance Tritone IIb   = V    -- gives undesired (ambiguous results) as-type instance Tritone V     = IIb  -- Dom = IIb/I = IIbdim --type instance Tritone II    = VIb -- interferes IIbDim V-type instance Tritone VIb   = II--type instance Tritone IIIb  = VI-type instance Tritone VI    = IIIb--type instance Tritone III   = VIIb -- Interferes with VIIb from minor-type instance Tritone VIIb  = III --type instance Tritone IV    = VII-type instance Tritone VII   = IV--type instance Tritone Imp   = Imp  -------------------------------------------------------------------------------------- Type families for diminished chord transformations---------------------------------------------------------------------------------- --- in combination with the secondary dominants and enharmonic equivalency--- these type families account for ascending dim chord progressions-type family IIbDim deg :: *   -type instance IIbDim I     =  IIb-type instance IIbDim IIb   =  II-type instance IIbDim II    =  IIIb-type instance IIbDim IIIb  =  III-type instance IIbDim III   =  IV-type instance IIbDim IV    =  IVs-type instance IIbDim IVs   =  V-type instance IIbDim V     =  VIb -- interferes with dim tritone V/V-type instance IIbDim VIb   =  VI -type instance IIbDim VI    =  VIIb-type instance IIbDim VIIb  =  VII-type instance IIbDim VII   =  I-type instance IIbDim Imp   =  Imp---- Dimchords can be transposed a minor third without changing their role,--- they are enharmonically equivalent.-type family MinThird deg :: *-type instance MinThird I     = IIIb -type instance MinThird IIb   = III-type instance MinThird II    = IV-type instance MinThird IIIb  = IVs-type instance MinThird III   = V-type instance MinThird IV    = VIb-type instance MinThird IVs   = VI-type instance MinThird V     = VIIb -type instance MinThird VIb   = VII-type instance MinThird VI    = I-type instance MinThird VIIb  = IIb-type instance MinThird VII   = II-type instance MinThird Imp   = Imp---- Belongs in Instances, but needs to be here due to staging restrictions-allTypes :: [Name]-allTypes = [ ''Phrase, ''Ton, ''Dom, ''SDom-           , ''TMinBorrow, ''DMinBorrow, ''SMinBorrow -           , ''TMajBorrow, ''DMajBorrow, ''SMajBorrow ]+{-# LANGUAGE CPP                      #-}
+{-# LANGUAGE TemplateHaskell          #-}
+{-# LANGUAGE TypeOperators            #-}
+{-# LANGUAGE EmptyDataDecls           #-}
+{-# LANGUAGE TypeSynonymInstances     #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE ScopedTypeVariables      #-}
+{-# LANGUAGE TypeFamilies             #-}
+{-# LANGUAGE GADTs                    #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Models.Jazz.Model
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: The jazz model
+--------------------------------------------------------------------------------
+
+module HarmTrace.Models.Jazz.Model where
+
+import HarmTrace.Base.TypeLevel
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens
+import Language.Haskell.TH.Syntax (Name)
+
+--------------------------------------------------------------------------------
+-- Musical structure as a datatype
+--------------------------------------------------------------------------------
+
+#ifndef NUMLEVELS
+#define NUMLEVELS T5
+#endif
+
+data MajMode
+data MinMode
+
+-- High level structure
+data Piece = forall mode. Piece [Phrase mode]
+
+-- The Phrase level
+data Phrase mode where
+  PT   :: Ton mode -> Phrase mode
+  PD   :: Dom mode -> Phrase mode
+
+-- Harmonic categories
+-- Tonic
+data Ton mode where
+  -- major mode
+  T_1      :: Final I MajClass         -> Ton MajMode
+  T_2      :: Final I MajClass         -> Final IV MajClass 
+           -> Final I MajClass         -> Ton MajMode
+           
+  -- blues           
+  -- T_4_bls  :: Final I DomClass         -> Ton mode  
+
+  T_3_par  :: Final III MinClass       -> Ton MajMode    
+  T_6_bor  :: TMinBorrow -> Ton MajMode
+
+  -- minor mode         
+  Tm_1     :: SD MinMode I MinClass    -> Ton MinMode
+  Tm_2     :: Final I MinClass -> Final IV MinClass
+           -> Final I MinClass         -> Ton MinMode           
+
+  Tm_3_par :: Final IIIb MajClass      -> Ton MinMode 
+  Tm_6_bor :: TMajBorrow               -> Ton MinMode  -- picardy third etc.     
+
+-- Dominant
+data Dom mode where
+  -- major mode
+  D_1   :: SDom mode -> Dom mode -> Dom mode
+  D_2   :: SD mode V DomClass        -> Dom mode
+  D_3   :: SD mode V MajClass        -> Dom mode
+
+  D_4   :: SD MajMode VII MinClass   -> Dom MajMode       
+  
+  -- moll-dur: minor mode borrowings in major
+  -- This would be an elegant way of defining major minor borrowing,
+  -- but it causes a lot of unwanted abmiguities since all the mode
+  -- rules can be explained with and without borrowing
+  -- D_9_bor :: Dom MinMode -> Dom MajMode
+  D_8_bor :: DMinBorrow              -> Dom MajMode
+  
+  -- minor mode (there must be at least one rule with "MinMode" otherwise
+  -- no you get a "No instance for (ParseG (Dom MinMode))" error
+  Dm_4   :: SD MinMode VIIb MajClass -> Dom MinMode
+  Dm_8_bor :: DMajBorrow             -> Dom MinMode
+
+-- Subdominant
+data SDom mode where
+  S_1_par :: SD mode II MinClass     -> SDom mode -- sub dom parallel
+
+  -- Pretty printing? this rule compensates for the V/I Imp
+  -- to be able to parse D:7 D:min G:7 C:maj (not in cmj)
+  S_2_par :: SD mode II DomClass -> Final II MinClass
+                                         -> SDom mode 
+  S_3     :: SD MajMode IV MajClass -> SDom MajMode
+  S_4     :: SD MajMode III MinClass -> Final IV MajClass 
+                                         -> SDom MajMode
+  -- S_6_par :: SD MajMode VI MinClass  -> SDom MajMode
+ 
+  -- blues
+  -- S_2_bls :: SD mode IV DomClass  -> SD mode I DomClass 
+                                         -- -> SDom mode 
+  
+  -- Borrowing from minor in a major mode
+  -- S_7_bor :: SDom MajMode -> SDom MinMode
+  S_5_bor :: SMinBorrow -> SDom MajMode 
+  
+  -- minor mode
+  Sm_3    :: SD MinMode IV  MinClass  -> SDom MinMode
+  Sm_4    :: SD MinMode IIIb MajClass -> Final IV MinClass 
+                                          -> SDom MinMode
+  -- Sm_3_par :: SD MinMode VIb MajClass -> SDom MinMode
+ 
+  Sm_5_bor :: SMajBorrow              -> SDom MinMode 
+
+  -- perhaps add a functional node for Neapolitan chords?
+  Sm_6   :: SD MinMode IIb MajClass   -> SDom MinMode -- Neapolitan
+  
+-- Borrowings from minor in a major key
+data TMinBorrow = Tm_21_bor (SD MinMode I    MinClass)   
+                    | Tm_23_bor (SD MinMode IIIb MajClass)   
+
+data DMinBorrow = Dm_24_bor (SD MinMode VIIb MajClass)   
+                    --  Dm_21_bor (Final VIIb DomClass)   
+                      
+data SMinBorrow = Sm_20_bor (SD MinMode IV   MinClass) 
+                    --  Sm_21_bor (SD MinMode VIb  MajClass)
+                    | Sm_22_bor (SD MinMode IIb  MajClass)   -- Neapolitan 
+
+-- Borrowings from major in a minor key
+data TMajBorrow = T_21_bor (SD MajMode I   MajClass)
+                    | T_23_bor (SD MajMode III MinClass)
+
+data DMajBorrow = D_24_bor (SD MajMode VII MinClass)   
+                    --  D_21_bor (Final VII DimClass)
+                      
+data SMajBorrow = S_20_bor (SD MajMode IV   MajClass) 
+                    
+                    
+-- Limit secondary dominants to a few levels
+type SD mode deg clss = Base_SD deg clss NUMLEVELS
+
+-- a type that can be substituted by its tritone sub and diminished 7b9
+type TritMinVSub deg clss = Base_Final deg clss T2
+
+-- A Scale degree that can only translate to a surface chord
+-- and allows for the transformation into enharmonic equivalent 
+-- diminshed surface chords
+type FinalDimTrans deg clss = Surface_Chord deg clss T4
+
+-- A Scale degree that translates into a (non-tranformable) surface chord
+type Final deg clss = Surface_Chord deg clss T1
+
+
+-- Datatypes for clustering harmonic degrees
+-- type Base_SD deg clss n = List (Base_SD' deg clss n) T4
+
+data Base_SD deg clss n where
+  Base_SD   :: TritMinVSub deg clss -- Min5    deg clss  n
+            -> Base_SD deg clss (Su n)   
+  -- Rule for explaining perfect secondary dominants
+  Cons_Vdom :: Base_SD (VDom  deg) DomClass n -> Base_SD deg clss n
+            -> Base_SD        deg  clss (Su n)
+  Cons_Diat :: Base_SD (DiatV deg) MinClass n -> Base_SD deg MinClass n
+            -> Base_SD        deg  MinClass (Su n)
+  Cons_DiatM :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MajClass n
+            -> Base_SD        deg  MajClass (Su n)
+  Cons_DiatM' :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MinClass n
+            -> Base_SD        deg  MinClass (Su n)
+  -- Minor fifth insertion
+  Cons_Vmin :: Base_SD (VMin deg) MinClass n -> Base_SD deg DomClass n
+            -> Base_SD     deg  DomClass (Su n)
+
+            
+data Base_Final deg clss n where
+  -- Just a "normal", final degree. The Strings are the original input.
+  Base_Final     :: FinalDimTrans deg clss -> Base_Final deg clss (Su n)
+  -- Tritone substitution
+  Final_Tritone  :: Base_Final (Tritone deg) DomClass n
+                 -> Base_Final deg DomClass (Su n)
+  Final_Dim_V    :: Base_Final (IIbDim  deg) DimClass n
+                 -> Base_Final deg DomClass (Su n)                 
+
+-- Dimished tritone substitution accounting for dimished chord transistions
+data Surface_Chord deg clss n where
+  Surface_Chord  :: ChordToken  
+                 -> Surface_Chord deg clss     (Su n)
+  Dim_Chord_Trns :: Surface_Chord (MinThird deg) DimClass n
+                 -> Surface_Chord deg DimClass (Su n)
+
+--------------------------------------------------------------------------------
+-- Type Level Scale Degrees
+--------------------------------------------------------------------------------
+
+-- typelevel chord classes 
+data MajClass
+data MinClass
+data DomClass
+data DimClass
+
+-- Degrees (at the type level)
+data I
+data Ib
+data Is
+data II
+data IIb
+data IIs
+data III
+data IIIb
+data IIIs
+data IV
+data IVb
+data IVs
+data V
+data Vb
+data Vs
+data VI
+data VIb
+data VIs
+data VII
+data VIIb
+data VIIs
+
+-- Used when we don't want to consider certain possibilities
+data Imp
+
+-- Degrees at the value level are in Tokenizer
+-- Type to value conversions
+class ToClass clss where
+  toClass :: clss -> ClassType
+
+instance ToClass MajClass where toClass _ = MajClass
+instance ToClass MinClass where toClass _ = MinClass
+instance ToClass DomClass where toClass _ = DomClass
+instance ToClass DimClass where toClass _ = DimClass
+
+-- The class doesn't really matter, since the degree will be impossible to parse
+instance ToClass Imp where toClass _ = DimClass
+
+class ToDegree deg where
+  toDegree :: deg -> ScaleDegree
+
+instance ToDegree I     where toDegree _ = Note Nothing I
+instance ToDegree II    where toDegree _ = Note Nothing II
+instance ToDegree III   where toDegree _ = Note Nothing III
+instance ToDegree IV    where toDegree _ = Note Nothing IV
+instance ToDegree V     where toDegree _ = Note Nothing V
+instance ToDegree VI    where toDegree _ = Note Nothing VI
+instance ToDegree VII   where toDegree _ = Note Nothing VII
+instance ToDegree Ib    where toDegree _ = Note (Just Fl) I
+instance ToDegree IIb   where toDegree _ = Note (Just Fl) II
+instance ToDegree IIIb  where toDegree _ = Note (Just Fl) III
+instance ToDegree IVb   where toDegree _ = Note (Just Fl) IV
+instance ToDegree Vb    where toDegree _ = Note (Just Fl) V
+instance ToDegree VIb   where toDegree _ = Note (Just Fl) VI
+instance ToDegree VIIb  where toDegree _ = Note (Just Fl) VII
+instance ToDegree IIs   where toDegree _ = Note (Just Sh) II
+instance ToDegree IIIs  where toDegree _ = Note (Just Sh) III
+instance ToDegree IVs   where toDegree _ = Note (Just Sh) IV
+instance ToDegree Vs    where toDegree _ = Note (Just Sh) V
+instance ToDegree VIs   where toDegree _ = Note (Just Sh) VI
+instance ToDegree VIIs  where toDegree _ = Note (Just Sh) VII
+
+-- Can't ever parse these
+instance ToDegree Imp where toDegree _ = Note Nothing Imp
+
+
+--------------------------------------------------------------------------------
+-- Type Families for Relative Scale Degrees
+--------------------------------------------------------------------------------
+
+
+-- Diatonic fifths, and their class (comments with the CMaj scale)
+-- See http://en.wikipedia.org/wiki/Circle_progression
+type family DiatV deg :: *
+type instance DiatV I   = Imp -- V   -- G7  should be Dom
+type instance DiatV V   = Imp -- II  -- Dm7 should be SDom
+type instance DiatV II  = VI  -- Am7 
+type instance DiatV VI  = III -- Em7
+type instance DiatV III = VII -- Bhdim7 can be explained by Dim rule
+type instance DiatV VII = Imp -- IV  -- FMaj7 should be SDom
+type instance DiatV IV  = Imp -- I   -- CMaj7
+
+type instance DiatV IIb  = Imp
+type instance DiatV IIIb = Imp
+type instance DiatV IVs  = Imp
+type instance DiatV VIb  = Imp
+type instance DiatV VIIb = Imp
+type instance DiatV Imp  = Imp
+
+type family DiatVM deg :: *
+type instance DiatVM I   = Imp -- V   -- G7  should be Dom
+type instance DiatVM V   = Imp -- Dm7 should be SDom
+type instance DiatVM II  = VIb -- Ab 
+type instance DiatVM VI  = Imp -- Em7
+type instance DiatVM III = Imp -- Bhdim7 can be explained by Dim rule
+type instance DiatVM VII = Imp -- IV  -- FMaj7 should be SDom
+type instance DiatVM IV  = Imp -- I   -- CMaj7
+
+type instance DiatVM IIb  = Imp
+type instance DiatVM IIIb = VIIb 
+type instance DiatVM IVs  = Imp
+type instance DiatVM VIb  = IIIb
+type instance DiatVM VIIb = Imp
+type instance DiatVM Imp  = Imp
+
+--------------------------------------------------------------------------------
+-- Type families for secondary dominants
+--------------------------------------------------------------------------------
+
+-- Perfect fifths (class is always Dom)
+-- See http://en.wikipedia.org/wiki/Circle_of_fifths
+type family VDom deg :: *
+
+type instance VDom I     = Imp  -- interferes with dom 
+type instance VDom IIb   = VIb
+type instance VDom II    = VI 
+type instance VDom IIIb  = VIIb -- interferes with Dm_3
+type instance VDom III   = VII
+type instance VDom IV    = I
+type instance VDom IVs   = IIb
+type instance VDom V     = II   -- interferes with Sm_1
+type instance VDom VIb   = IIIb
+type instance VDom VI    = III
+type instance VDom VIIb  = IV
+type instance VDom VII   = IVs
+type instance VDom Imp   = Imp
+
+-- Perfect fifths for the minor case (this is an additional
+-- type family to controll the reduction of ambiguities
+-- specifically in the minor case)
+type family VMin deg :: *
+type instance VMin I     = V 
+type instance VMin IIb   = VIb
+type instance VMin II    = VI  -- interferes with sub 
+type instance VMin IIIb  = VIIb
+type instance VMin III   = VII
+type instance VMin IV    = I
+type instance VMin IVs   = IIb
+type instance VMin V     = Imp -- II interferes with sub
+type instance VMin VIb   = IIIb
+type instance VMin VI    = III
+type instance VMin VIIb  = Imp --IV -- inteferes with sub IV:min
+type instance VMin VII   = IVs
+type instance VMin Imp   = Imp
+ 
+-- The tritone substitution
+-- See http://en.wikipedia.org/wiki/Tritone_substitution
+type family Tritone deg :: *
+type instance Tritone I     = IVs
+type instance Tritone IVs   = I
+
+type instance Tritone IIb   = V    -- gives undesired (ambiguous results) as
+type instance Tritone V     = IIb  -- Dom = IIb/I = IIbdim 
+
+type instance Tritone II    = VIb -- interferes IIbDim V
+type instance Tritone VIb   = II
+
+type instance Tritone IIIb  = VI
+type instance Tritone VI    = IIIb
+
+type instance Tritone III   = VIIb -- Interferes with VIIb from minor
+type instance Tritone VIIb  = III 
+
+type instance Tritone IV    = VII
+type instance Tritone VII   = IV
+
+type instance Tritone Imp   = Imp  
+
+
+--------------------------------------------------------------------------------
+-- Type families for diminished chord transformations
+--------------------------------------------------------------------------------
+ 
+-- in combination with the secondary dominants and enharmonic equivalency
+-- these type families account for ascending dim chord progressions
+type family IIbDim deg :: *   
+type instance IIbDim I     =  IIb
+type instance IIbDim IIb   =  II
+type instance IIbDim II    =  IIIb
+type instance IIbDim IIIb  =  III
+type instance IIbDim III   =  IV
+type instance IIbDim IV    =  IVs
+type instance IIbDim IVs   =  V
+type instance IIbDim V     =  VIb -- interferes with dim tritone V/V
+type instance IIbDim VIb   =  VI 
+type instance IIbDim VI    =  VIIb
+type instance IIbDim VIIb  =  VII
+type instance IIbDim VII   =  I
+type instance IIbDim Imp   =  Imp
+
+-- Dimchords can be transposed a minor third without changing their role,
+-- they are enharmonically equivalent.
+type family MinThird deg :: *
+type instance MinThird I     = IIIb 
+type instance MinThird IIb   = III
+type instance MinThird II    = IV
+type instance MinThird IIIb  = IVs
+type instance MinThird III   = V
+type instance MinThird IV    = VIb
+type instance MinThird IVs   = VI
+type instance MinThird V     = VIIb 
+type instance MinThird VIb   = VII
+type instance MinThird VI    = I
+type instance MinThird VIIb  = IIb
+type instance MinThird VII   = II
+type instance MinThird Imp   = Imp
+
+-- Belongs in Instances, but needs to be here due to staging restrictions
+allTypes :: [Name]
+allTypes = [ ''Phrase, ''Ton, ''Dom, ''SDom
+           , ''TMinBorrow, ''DMinBorrow, ''SMinBorrow 
+           , ''TMajBorrow, ''DMajBorrow, ''SMajBorrow ]
src/HarmTrace/Models/Models.hs view
@@ -1,17 +1,39 @@-{-# LANGUAGE GADTs                        #-}-{-# LANGUAGE KindSignatures               #-}--module HarmTrace.Models.Models where--import HarmTrace.Models.Jazz.Model as J hiding (PD, PT) -import HarmTrace.Models.Pop.Model  as P hiding (PD, PT)--import HarmTrace.HAnTree.ToHAnTree---data Grammar :: * -> * where-  Jazz :: Grammar J.Piece-  Pop  :: Grammar P.Piece--data GrammarEx where-  GrammarEx :: (GTree g) => Grammar g -> GrammarEx+{-# LANGUAGE GADTs                        #-}
+{-# LANGUAGE KindSignatures               #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Models.Models
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: All the models
+--------------------------------------------------------------------------------
+
+module HarmTrace.Models.Models where
+
+import HarmTrace.Models.Jazz.Model as J hiding (PD, PT) 
+import HarmTrace.Models.Pop.Model  as P hiding (PD, PT)
+
+import HarmTrace.HAnTree.ToHAnTree
+
+
+data Grammar :: * -> * where
+  Jazz :: Grammar J.Piece
+  Pop  :: Grammar P.Piece
+
+data GrammarEx where
+  GrammarEx :: (GTree g) => Grammar g -> GrammarEx
+
+instance Show GrammarEx where
+  show (GrammarEx Jazz) = "JazzGrammar"
+  show (GrammarEx Pop ) = "PopGrammar"
+
+instance Eq GrammarEx where
+  (GrammarEx Jazz) == (GrammarEx Jazz) = True
+  (GrammarEx Pop ) == (GrammarEx Pop ) = True
+  _                == _                = False
src/HarmTrace/Models/Parser.hs view
@@ -1,75 +1,87 @@-{-# LANGUAGE TypeOperators          #-}-{-# LANGUAGE TypeSynonymInstances   #-}-{-# LANGUAGE FlexibleInstances      #-}-{-# LANGUAGE FlexibleContexts       #-}-{-# LANGUAGE TypeFamilies           #-}-{-# LANGUAGE OverlappingInstances   #-}-{-# LANGUAGE ScopedTypeVariables    #-}---- Semi-generic parser for chords-module HarmTrace.Models.Parser where----- Parser stuff-import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.BasicInstances---- Generics stuff-import Generics.Instant.Base as G---- Music stuff-import HarmTrace.Tokenizer.Tokens-------------------------------------------------------------------------------------- The generic part of the parser-----------------------------------------------------------------------------------type PMusic a = P (Str ChordToken [ChordToken] Int) a--class Parse' f where-   parse' :: PMusic f--instance Parse' U where-  parse' = pure U--instance (ParseG a) => Parse' (Rec a) where-  parse' = Rec <$> parseG---- Not really necessary because TH is not generating any Var, but anyway-instance (ParseG a) => Parse' (Var a) where-  parse' = Var <$> parseG--instance (Constructor c, Parse' f) => Parse' (G.CEq c p p f) where-  parse' = G.C <$> parse' <?> "Constructor " ++ conName (undefined :: C c f)--instance                              Parse' (G.CEq c p q f) where -  parse' = empty--instance (Parse' f, Parse' g) => Parse' (f :+: g) where-  parse' = L <$> parse' <|> R <$> parse'--instance (Parse' f, Parse' g) => Parse' (f :*: g) where-  parse' = (:*:) <$> parse' <*> parse'---class ParseG a where-  parseG :: PMusic a--instance (ParseG a) => ParseG [a] where-  parseG = pList1 parseG-  -- We should use non-greedy parsing here, else the final Dom is never parsed-  -- as such.-  -- parseG = pList1_ng parseG--instance (ParseG a) => ParseG (Maybe a) where-  parseG = pMaybe parseG--parseGdefault :: (Representable a, Parse' (Rep a)) => PMusic a--- parseGdefault = fmap (to . head) (amb parse')--- Previously we used:-parseGdefault = fmap to parse'--- This gave rise to many ambiguities. Now we allow parse' to be ambiguous--- (note that the sum case uses <|>) but then pick only the very first tree--- from all the possible results. It remains to be seen if the first tree is--- the best...+{-# LANGUAGE TypeOperators          #-}
+{-# LANGUAGE TypeSynonymInstances   #-}
+{-# LANGUAGE FlexibleInstances      #-}
+{-# LANGUAGE FlexibleContexts       #-}
+{-# LANGUAGE TypeFamilies           #-}
+{-# LANGUAGE OverlappingInstances   #-}
+{-# LANGUAGE ScopedTypeVariables    #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  HarmTrace.Models.Parser
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Semi-generic parser for chords
+--------------------------------------------------------------------------------
+
+module HarmTrace.Models.Parser where
+
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.BasicInstances
+
+-- Generics stuff
+import Generics.Instant.Base as G
+
+-- Music stuff
+import HarmTrace.Tokenizer.Tokens
+
+
+--------------------------------------------------------------------------------
+-- The generic part of the parser
+--------------------------------------------------------------------------------
+
+type PMusic a = P (Str ChordToken [ChordToken] Int) a
+
+class Parse' f where
+   parse' :: PMusic f
+
+instance Parse' U where
+  parse' = pure U
+
+instance (ParseG a) => Parse' (Rec a) where
+  parse' = Rec <$> parseG
+
+-- Not really necessary because TH is not generating any Var, but anyway
+instance (ParseG a) => Parse' (Var a) where
+  parse' = Var <$> parseG
+
+instance (Constructor c, Parse' f) => Parse' (G.CEq c p p f) where
+  parse' = G.C <$> parse' <?> "Constructor " ++ conName (undefined :: C c f)
+
+instance                              Parse' (G.CEq c p q f) where 
+  parse' = empty
+
+instance (Parse' f, Parse' g) => Parse' (f :+: g) where
+  parse' = L <$> parse' <|> R <$> parse'
+
+instance (Parse' f, Parse' g) => Parse' (f :*: g) where
+  parse' = (:*:) <$> parse' <*> parse'
+
+
+class ParseG a where
+  parseG :: PMusic a
+
+instance (ParseG a) => ParseG [a] where
+  parseG = pList1 parseG
+  -- We should use non-greedy parsing here, else the final Dom is never parsed
+  -- as such.
+  -- parseG = pList1_ng parseG
+
+instance (ParseG a) => ParseG (Maybe a) where
+  parseG = pMaybe parseG
+
+parseGdefault :: (Representable a, Parse' (Rep a)) => PMusic a
+-- parseGdefault = fmap (to . head) (amb parse')
+-- Previously we used:
+parseGdefault = fmap to parse'
+-- This gave rise to many ambiguities. Now we allow parse' to be ambiguous
+-- (note that the sum case uses <|>) but then pick only the very first tree
+-- from all the possible results. It remains to be seen if the first tree is
+-- the best...
src/HarmTrace/Models/Pop/Instances.hs view
@@ -1,250 +1,250 @@-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}-{-# LANGUAGE TemplateHaskell        #-}-{-# LANGUAGE TypeOperators          #-}-{-# LANGUAGE EmptyDataDecls         #-}-{-# LANGUAGE TypeSynonymInstances   #-}-{-# LANGUAGE ScopedTypeVariables    #-}-{-# LANGUAGE FlexibleInstances      #-}-{-# LANGUAGE FlexibleContexts       #-}-{-# LANGUAGE TypeFamilies           #-}-{-# LANGUAGE MultiParamTypeClasses  #-}-{-# LANGUAGE UndecidableInstances   #-}-{-# LANGUAGE OverlappingInstances   #-}-{-# LANGUAGE GADTs                  #-}--module HarmTrace.Models.Pop.Instances where---- Generics stuff-import Generics.Instant.TH---- Parser stuff-import Text.ParserCombinators.UU-import Text.ParserCombinators.UU.BasicInstances---- Music stuff-import HarmTrace.Models.Parser-import HarmTrace.Models.Pop.Model-import HarmTrace.HAnTree.Tree-import HarmTrace.HAnTree.ToHAnTree-import HarmTrace.HAnTree.HAn-import HarmTrace.Tokenizer.Tokens as CT-import HarmTrace.Base.TypeLevel-import HarmTrace.Base.MusicRep---- Library modules-import Control.Arrow------------------------------------------------------------------------------------- The non-generic part of the parser-----------------------------------------------------------------------------------instance ParseG (Base_SD   deg clss Ze) where parseG = empty-        -instance ( ToDegree (DiatV deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom   deg) DomClass n)-         , ParseG (Base_SD (DiatV  deg) MinClass n)-         , ParseG (Base_SD (DiatVM deg) MajClass n)-         , ParseG (Base_SD         deg  MinClass n)-         , ParseG (TritMinVSub     deg  MinClass  )      -         ) => ParseG (Base_SD deg MinClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_Diat <$> parseG <*> parseG   -           <|> Cons_DiatM' <$> parseG <*> parseG   -  -instance ( ToDegree (DiatVM deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom   deg) DomClass n)-         , ParseG (Base_SD (DiatVM deg) MajClass n)-         , ParseG (Base_SD         deg  MajClass n)-         , ParseG (TritMinVSub     deg  MajClass  )-         ) => ParseG (Base_SD deg MajClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_DiatM <$> parseG <*> parseG     -           -instance ( ToDegree (VMin deg)-         , ToDegree (VDom deg)-         , ParseG (Base_SD (VDom deg) DomClass n)-         , ParseG (Base_SD (VMin deg) MinClass n)-         , ParseG (Base_SD       deg  DomClass n)-         , ParseG (TritMinVSub   deg  DomClass  )         -         ) => ParseG (Base_SD deg DomClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG-           <|> Cons_Vmin <$> parseG <*> parseG              -  -instance ( ToDegree (VDom deg)-         , ParseG (Base_SD (VDom  deg) DomClass n)-         , ParseG (Base_SD        deg  DimClass n)-         , ParseG (TritMinVSub    deg  DimClass  )-         ) => ParseG (Base_SD deg DimClass (Su n)) where-  parseG =     Base_SD   <$> parseG-           <|> Cons_Vdom <$> parseG <*> parseG---- Ad-hoc cases for Base_Final-instance ParseG (Base_Final deg clss Ze) where parseG = empty--instance ( ParseG (FinalDimTrans deg clss)-         ) => ParseG (Base_Final deg clss (Su n)) where-  parseG =     Base_Final  <$> parseG-  -instance ( ParseG (FinalDimTrans       deg  DomClass)-         , ParseG (FinalDimTrans       deg  MinClass)-         , ParseG (Base_Final (Tritone deg) DomClass n)-         , ParseG (Base_Final (IIbDim  deg) DimClass n)-         ) => ParseG (Base_Final deg DomClass (Su n)) where-  parseG =     Base_Final     <$> parseG-           <|> Final_Tritone  <$> parseG-           <|> Final_Dim_V    <$> parseG---- Ad-hoc cases for Surface_Chord-instance ParseG (Surface_Chord deg clss Ze) where parseG = empty---- instance ( ToDegree deg -         -- , ParseG (Surface_Chord (MinThird deg) DimClass n)-         -- ) => ParseG (Surface_Chord deg DimClass (Su n)) where-  -- parseG =     Dim_Chord_Trns <$> parseG-           -- <|> pChord deg DimClass-    -- where deg = toDegree (undefined :: deg)  -  --- all chords-instance ( ToDegree deg, ToClass clss-         ) => ParseG (Surface_Chord deg clss (Su n)) where-  parseG = pChord deg clss-    where deg = toDegree (undefined :: deg)-          clss = toClass (undefined :: clss)---pChord :: ScaleDegree -> ClassType -> PMusic (Surface_Chord deg clss (Su n))--- Do not parse Imp degrees-pChord (Note _ Imp) _clss = empty--- General case-pChord deg clss = setStatus <$> pSatisfy recognize insertion where-  recognize (ChordToken deg' clss' _cs _st _n _d) = deg == deg' && clss == clss'-  setStatus (ChordToken r t l NotParsed n d) -              = Surface_Chord (ChordToken r t l Parsed n d)-  setStatus c = Surface_Chord c-  insertion = Insertion "ChordToken" (ChordToken deg clss [] CT.Inserted 1 0) 5------------------------------------------------------------------------------------- The non-generic part of the GTree wrapper----------------------------------------------------------------------------------toGTree :: (GetDegree a, GTree a) =>-           (Int -> ScaleDegree -> Trans) -> Int -> a -> [Tree HAn]-toGTree con transp deg = [Node (HAnTrans . con 1 $ toTransSDVal transp deg) -                               (gTree deg) Nothing]---- create a branching Tree HAn-toGTreeSplit :: (GetDegree a, GetDegree b, GTree a, GTree b) =>-           (Int -> ScaleDegree -> Prep) -> b -> a -> [Tree HAn]-toGTreeSplit con vof deg  -  = Node (HAnPrep . con 1 $ toSDVal deg) (gTree vof) Nothing : gTree deg-                                 --- Ad-Hoc case for Piece -instance GTree Piece where -- we take the children to skip a "list node"-  gTree (Piece p) = [Node (HAnFunc P) (gTree p) Nothing]---- Ad-hoc cases for Base_SD-instance GTree (Base_SD deg clss Ze) where-  gTree _ = error "gTree: impossible?"-  -instance ( GTree (Base_SD (VDom   deg)  DomClass n)-         , GTree (Base_SD (DiatV  deg)  MinClass n)-         , GTree (Base_SD (DiatVM deg)  MajClass n)-         , GTree (Base_SD (VMin   deg)  MinClass n)-         , GTree (Base_SD         deg   clss     n)-         , GTree (Base_Final      deg   clss     n)-         ) => GTree (Base_SD deg clss (Su n)) where-  gTree (Base_SD d)       = gTree d-  gTree (Cons_Vdom   s d) = toGTreeSplit SecDom  s d -  gTree (Cons_Diat   s d) = toGTreeSplit DiatDom s d -  gTree (Cons_DiatM  s d) = toGTreeSplit DiatDom s d -  gTree (Cons_DiatM' s d) = toGTreeSplit DiatDom s d -  gTree (Cons_Vmin   s d) = toGTreeSplit SecMin  s d ---- Ad-hoc cases for Base_Final-instance GTree (Base_Final deg clss Ze) where-  gTree _ = error "gTree: impossible?"--instance ( GetDegree (Base_Final (Tritone deg) DomClass n)-         , GetDegree (Base_Final (IIbDim  deg) DimClass n)-         , GTree (FinalDimTrans deg clss)-         , GTree (Base_Final (Tritone deg)  DomClass n)-         , GTree (Base_Final (IIbDim  deg)  DimClass n)-         ) => GTree (Base_Final deg clss (Su n)) where-  gTree (Base_Final d)      = gTree d-  -- The tritone substitution of a relative V is as alsway one semitone above-  -- the chord it is preceding-  gTree (Final_Tritone  d)  = toGTree Trit    6  d -  gTree (Final_Dim_V    d)  = toGTree DimTrit 11 d ---- Ad-hoc cases for Surface_Chord-instance GTree (Surface_Chord deg clss Ze) where-  gTree _ = error "gTree: impossible?"-                                       -instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)-         , GTree     (Surface_Chord (MinThird deg) DimClass n)-         ) => GTree  (Surface_Chord deg clss (Su n)) where-  gTree (Surface_Chord c)  = [Node (HAnChord c) [] Nothing]-  -- gTree (Dim_Chord_Trns c) = toGTree DimTrans 9 c -- pretty print?-  ------------------------------------------------------------------------------------ Ad hoc getDegree instaces---------------------------------------------------------------------------------  -toTransSDVal :: (GetDegree a) => Int -> a -> ScaleDegree-toTransSDVal t d = let (a,i) = getDeg d in transposeSem a (i+t)--toSDVal :: (GetDegree a) => a -> ScaleDegree-toSDVal d = let (a,i) = getDeg d in transposeSem a i---- Given a degree getDegee ensures that all information about the internal--- structure of a scale degree,i.e. the degree and the an int value representing--- the transposition of that degree at the current level, is available.-class GetDegree a where-  getDeg :: a -> (ScaleDegree, Int) --instance GetDegree (Base_SD deg clss n) where-  getDeg (Base_SD d) = getDeg d-  getDeg (Cons_Vdom   _ d) = getDeg d -  getDeg (Cons_Diat   _ d) = getDeg d -  getDeg (Cons_DiatM  _ d) = getDeg d -  getDeg (Cons_DiatM' _ d) = getDeg d -  getDeg (Cons_Vmin   _ d) = getDeg d --instance ( GetDegree (Base_Final deg clss Ze)) where -  getDeg = error "getDegree: impossible?"-instance GetDegree (Base_Final deg clss  n) where-  getDeg (Base_Final d)  = getDeg d-  -- The tritone substitution of a relative V is as always one semitone above-  -- the chord it is preceding-  getDeg (Final_Tritone  d)  = second (+6) (getDeg d)-  getDeg (Final_Dim_V    d)  = second (+1) (getDeg d)--instance ( GetDegree (Surface_Chord deg clss Ze)) where -  getDeg = error "getDegree: impossible?"-  -instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)-         ) => GetDegree (Surface_Chord deg clss (Su n)) where-  getDeg (Surface_Chord (ChordToken d _cls _cs _stat _n _dur)) = (d,0) -  -- getDeg (Dim_Chord_Trns d) = second (+9) (getDeg d)------------------------------------------------------------------------------------- Instances of Representable for music datatypes-----------------------------------------------------------------------------------deriveAllL allTypes--$(fmap join $ mapM (\t -> gadtInstance ''ParseG t 'parseG 'parseGdefault)-  allTypes)--$(fmap join $ mapM (\t -> simplInstance ''GTree t 'gTree 'gTreeDefault)-  allTypes)------------------------------------------------------------------------------------- ChordToken as tokens-----------------------------------------------------------------------------------instance IsLocationUpdatedBy Int ChordToken where -  advance p c = p + chordNumReps c+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+{-# LANGUAGE TemplateHaskell        #-}
+{-# LANGUAGE TypeOperators          #-}
+{-# LANGUAGE EmptyDataDecls         #-}
+{-# LANGUAGE TypeSynonymInstances   #-}
+{-# LANGUAGE ScopedTypeVariables    #-}
+{-# LANGUAGE FlexibleInstances      #-}
+{-# LANGUAGE FlexibleContexts       #-}
+{-# LANGUAGE TypeFamilies           #-}
+{-# LANGUAGE MultiParamTypeClasses  #-}
+{-# LANGUAGE UndecidableInstances   #-}
+{-# LANGUAGE OverlappingInstances   #-}
+{-# LANGUAGE GADTs                  #-}
+
+module HarmTrace.Models.Pop.Instances where
+
+-- Generics stuff
+import Generics.Instant.TH
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+import Text.ParserCombinators.UU.BasicInstances
+
+-- Music stuff
+import HarmTrace.Models.Parser
+import HarmTrace.Models.Pop.Model
+import HarmTrace.HAnTree.Tree
+import HarmTrace.HAnTree.ToHAnTree
+import HarmTrace.HAnTree.HAn
+import HarmTrace.Tokenizer.Tokens as CT
+import HarmTrace.Base.TypeLevel
+import HarmTrace.Base.MusicRep
+
+-- Library modules
+-- import Control.Arrow
+
+--------------------------------------------------------------------------------
+-- The non-generic part of the parser
+--------------------------------------------------------------------------------
+
+instance ParseG (Base_SD   deg clss Ze) where parseG = empty
+        
+instance ( ToDegree (DiatV deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom   deg) DomClass n)
+         , ParseG (Base_SD (DiatV  deg) MinClass n)
+         , ParseG (Base_SD (DiatVM deg) MajClass n)
+         , ParseG (Base_SD         deg  MinClass n)
+         , ParseG (TritMinVSub     deg  MinClass  )      
+         ) => ParseG (Base_SD deg MinClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_Diat <$> parseG <*> parseG   
+           <|> Cons_DiatM' <$> parseG <*> parseG   
+  
+instance ( ToDegree (DiatVM deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom   deg) DomClass n)
+         , ParseG (Base_SD (DiatVM deg) MajClass n)
+         , ParseG (Base_SD         deg  MajClass n)
+         , ParseG (TritMinVSub     deg  MajClass  )
+         ) => ParseG (Base_SD deg MajClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_DiatM <$> parseG <*> parseG     
+           
+instance ( ToDegree (VMin deg)
+         , ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom deg) DomClass n)
+         , ParseG (Base_SD (VMin deg) MinClass n)
+         , ParseG (Base_SD       deg  DomClass n)
+         , ParseG (TritMinVSub   deg  DomClass  )         
+         ) => ParseG (Base_SD deg DomClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+           <|> Cons_Vmin <$> parseG <*> parseG              
+  
+instance ( ToDegree (VDom deg)
+         , ParseG (Base_SD (VDom  deg) DomClass n)
+         , ParseG (Base_SD        deg  DimClass n)
+         , ParseG (TritMinVSub    deg  DimClass  )
+         ) => ParseG (Base_SD deg DimClass (Su n)) where
+  parseG =     Base_SD   <$> parseG
+           <|> Cons_Vdom <$> parseG <*> parseG
+
+-- Ad-hoc cases for Base_Final
+instance ParseG (Base_Final deg clss Ze) where parseG = empty
+
+instance ( ParseG (FinalDimTrans deg clss)
+         ) => ParseG (Base_Final deg clss (Su n)) where
+  parseG =     Base_Final  <$> parseG
+  
+instance ( ParseG (FinalDimTrans       deg  DomClass)
+         , ParseG (FinalDimTrans       deg  MinClass)
+         , ParseG (Base_Final (Tritone deg) DomClass n)
+         , ParseG (Base_Final (IIbDim  deg) DimClass n)
+         ) => ParseG (Base_Final deg DomClass (Su n)) where
+  parseG =     Base_Final     <$> parseG
+           -- <|> Final_Tritone  <$> parseG
+           -- <|> Final_Dim_V    <$> parseG
+
+-- Ad-hoc cases for Surface_Chord
+instance ParseG (Surface_Chord deg clss Ze) where parseG = empty
+
+-- instance ( ToDegree deg 
+         -- , ParseG (Surface_Chord (MinThird deg) DimClass n)
+         -- ) => ParseG (Surface_Chord deg DimClass (Su n)) where
+  -- parseG =     Dim_Chord_Trns <$> parseG
+           -- <|> pChord deg DimClass
+    -- where deg = toDegree (undefined :: deg)  
+  
+-- all chords
+instance ( ToDegree deg, ToClass clss
+         ) => ParseG (Surface_Chord deg clss (Su n)) where
+  parseG = pChord deg clss
+    where deg = toDegree (undefined :: deg)
+          clss = toClass (undefined :: clss)
+
+
+pChord :: ScaleDegree -> ClassType -> PMusic (Surface_Chord deg clss (Su n))
+-- Do not parse Imp degrees
+pChord (Note _ Imp) _clss = empty
+-- General case
+pChord deg clss = setStatus <$> pSatisfy recognize insertion where
+  recognize (ChordToken deg' clss' _cs _st _n _d) = deg == deg' && clss == clss'
+  setStatus (ChordToken r t l NotParsed n d) 
+              = Surface_Chord (ChordToken r t l Parsed n d)
+  setStatus c = Surface_Chord c
+  insertion = Insertion "ChordToken" (ChordToken deg clss [] CT.Inserted 1 0) 5
+
+--------------------------------------------------------------------------------
+-- The non-generic part of the GTree wrapper
+--------------------------------------------------------------------------------
+toGTree :: (GetDegree a, GTree a) =>
+           (Int -> ScaleDegree -> Trans) -> Int -> a -> [Tree HAn]
+toGTree con transp deg = [Node (HAnTrans . con 1 $ toTransSDVal transp deg) 
+                               (gTree deg) Nothing]
+
+-- create a branching Tree HAn
+toGTreeSplit :: (GetDegree a, GetDegree b, GTree a, GTree b) =>
+           (Int -> ScaleDegree -> Prep) -> b -> a -> [Tree HAn]
+toGTreeSplit con vof deg  
+  = Node (HAnPrep . con 1 $ toSDVal deg) (gTree vof) Nothing : gTree deg
+                                 
+-- Ad-Hoc case for Piece 
+instance GTree Piece where -- we take the children to skip a "list node"
+  gTree (Piece p) = [Node (HAnFunc P) (gTree p) Nothing]
+
+-- Ad-hoc cases for Base_SD
+instance GTree (Base_SD deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+  
+instance ( GTree (Base_SD (VDom   deg)  DomClass n)
+         , GTree (Base_SD (DiatV  deg)  MinClass n)
+         , GTree (Base_SD (DiatVM deg)  MajClass n)
+         , GTree (Base_SD (VMin   deg)  MinClass n)
+         , GTree (Base_SD         deg   clss     n)
+         , GTree (Base_Final      deg   clss     n)
+         ) => GTree (Base_SD deg clss (Su n)) where
+  gTree (Base_SD d)       = gTree d
+  gTree (Cons_Vdom   s d) = toGTreeSplit SecDom  s d 
+  gTree (Cons_Diat   s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_DiatM  s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_DiatM' s d) = toGTreeSplit DiatDom s d 
+  gTree (Cons_Vmin   s d) = toGTreeSplit SecMin  s d 
+
+-- Ad-hoc cases for Base_Final
+instance GTree (Base_Final deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+
+instance ( GetDegree (Base_Final (Tritone deg) DomClass n)
+         , GetDegree (Base_Final (IIbDim  deg) DimClass n)
+         , GTree (FinalDimTrans deg clss)
+         , GTree (Base_Final (Tritone deg)  DomClass n)
+         , GTree (Base_Final (IIbDim  deg)  DimClass n)
+         ) => GTree (Base_Final deg clss (Su n)) where
+  gTree (Base_Final d)      = gTree d
+  -- The tritone substitution of a relative V is as alsway one semitone above
+  -- the chord it is preceding
+  -- gTree (Final_Tritone  d)  = toGTree Trit    6  d 
+  -- gTree (Final_Dim_V    d)  = toGTree DimTrit 11 d 
+
+-- Ad-hoc cases for Surface_Chord
+instance GTree (Surface_Chord deg clss Ze) where
+  gTree _ = error "gTree: impossible?"
+                                       
+instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)
+         , GTree     (Surface_Chord (MinThird deg) DimClass n)
+         ) => GTree  (Surface_Chord deg clss (Su n)) where
+  gTree (Surface_Chord c)  = [Node (HAnChord c) [] Nothing]
+  -- gTree (Dim_Chord_Trns c) = toGTree DimTrans 9 c -- pretty print?
+  
+--------------------------------------------------------------------------------
+-- Ad hoc getDegree instaces
+--------------------------------------------------------------------------------  
+toTransSDVal :: (GetDegree a) => Int -> a -> ScaleDegree
+toTransSDVal t d = let (a,i) = getDeg d in transposeSem a (i+t)
+
+toSDVal :: (GetDegree a) => a -> ScaleDegree
+toSDVal d = let (a,i) = getDeg d in transposeSem a i
+
+-- Given a degree getDegee ensures that all information about the internal
+-- structure of a scale degree,i.e. the degree and the an int value representing
+-- the transposition of that degree at the current level, is available.
+class GetDegree a where
+  getDeg :: a -> (ScaleDegree, Int) 
+
+instance GetDegree (Base_SD deg clss n) where
+  getDeg (Base_SD d) = getDeg d
+  getDeg (Cons_Vdom   _ d) = getDeg d 
+  getDeg (Cons_Diat   _ d) = getDeg d 
+  getDeg (Cons_DiatM  _ d) = getDeg d 
+  getDeg (Cons_DiatM' _ d) = getDeg d 
+  getDeg (Cons_Vmin   _ d) = getDeg d 
+
+instance ( GetDegree (Base_Final deg clss Ze)) where 
+  getDeg = error "getDegree: impossible?"
+instance GetDegree (Base_Final deg clss  n) where
+  getDeg (Base_Final d)  = getDeg d
+  -- The tritone substitution of a relative V is as always one semitone above
+  -- the chord it is preceding
+  -- getDeg (Final_Tritone  d)  = second (+6) (getDeg d)
+  -- getDeg (Final_Dim_V    d)  = second (+1) (getDeg d)
+
+instance ( GetDegree (Surface_Chord deg clss Ze)) where 
+  getDeg = error "getDegree: impossible?"
+  
+instance ( GetDegree (Surface_Chord (MinThird deg) DimClass n)
+         ) => GetDegree (Surface_Chord deg clss (Su n)) where
+  getDeg (Surface_Chord (ChordToken d _cls _cs _stat _n _dur)) = (d,0) 
+  -- getDeg (Dim_Chord_Trns d) = second (+9) (getDeg d)
+
+--------------------------------------------------------------------------------
+-- Instances of Representable for music datatypes
+--------------------------------------------------------------------------------
+
+deriveAllL allTypes
+
+$(fmap join $ mapM (\t -> gadtInstance ''ParseG t 'parseG 'parseGdefault)
+  allTypes)
+
+$(fmap join $ mapM (\t -> simplInstance ''GTree t 'gTree 'gTreeDefault)
+  allTypes)
+
+--------------------------------------------------------------------------------
+-- ChordToken as tokens
+--------------------------------------------------------------------------------
+
+instance IsLocationUpdatedBy Int ChordToken where 
+  advance p c = p + chordNumReps c
src/HarmTrace/Models/Pop/Main.hs view
@@ -1,29 +1,29 @@--module HarmTrace.Models.Pop.Main ( -    pPop-  , module HarmTrace.Models.Pop.Model-  ) where---- Parser stuff-import Text.ParserCombinators.UU---- Music stuff-import HarmTrace.Base.MusicRep-import HarmTrace.Models.Parser-import HarmTrace.Models.Pop.Model hiding (PD,PT)--import HarmTrace.Models.Pop.Instances ()-------------------------------------------------------------------------------------- From tokens to structured music pieces-----------------------------------------------------------------------------------pPieceMaj, pPieceMin :: PMusic [Piece]-pPieceMaj = map Piece <$> amb (parseG :: PMusic [Phrase MajMode])-pPieceMin = map Piece <$> amb (parseG :: PMusic [Phrase MinMode])--pPop :: Key -> PMusic [Piece]-pPop (Key _ MajMode) = pPieceMaj-pPop (Key _ MinMode) = pPieceMin-+
+module HarmTrace.Models.Pop.Main ( 
+    pPop
+  , module HarmTrace.Models.Pop.Model
+  ) where
+
+-- Parser stuff
+import Text.ParserCombinators.UU
+
+-- Music stuff
+import HarmTrace.Base.MusicRep
+import HarmTrace.Models.Parser
+import HarmTrace.Models.Pop.Model hiding (PD,PT)
+
+import HarmTrace.Models.Pop.Instances ()
+
+
+--------------------------------------------------------------------------------
+-- From tokens to structured music pieces
+--------------------------------------------------------------------------------
+
+pPieceMaj, pPieceMin :: PMusic [Piece]
+pPieceMaj = map Piece <$> amb (parseG :: PMusic [Phrase MajMode])
+pPieceMin = map Piece <$> amb (parseG :: PMusic [Phrase MinMode])
+
+pPop :: Key -> PMusic [Piece]
+pPop (Key _ MajMode) = pPieceMaj
+pPop (Key _ MinMode) = pPieceMin
+
src/HarmTrace/Models/Pop/Model.hs view
@@ -1,412 +1,395 @@-{-# LANGUAGE CPP                      #-}-{-# LANGUAGE TemplateHaskell          #-}-{-# LANGUAGE TypeOperators            #-}-{-# LANGUAGE EmptyDataDecls           #-}-{-# LANGUAGE TypeSynonymInstances     #-}-{-# LANGUAGE FlexibleInstances        #-}-{-# LANGUAGE ScopedTypeVariables      #-}-{-# LANGUAGE TypeFamilies             #-}-{-# LANGUAGE GADTs                    #-}--module HarmTrace.Models.Pop.Model where--import HarmTrace.Base.TypeLevel--import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens-import Language.Haskell.TH.Syntax (Name)------------------------------------------------------------------------------------- Musical structure as a datatype-----------------------------------------------------------------------------------#ifndef NUMLEVELS-#define NUMLEVELS T5-#endif---- perhaps this module should be named differently, like Model or HarmonyModel--data MajMode-data MinMode---- High level structure-data Piece = forall mode. Piece [Phrase mode]---- The Phrase level-data Phrase mode where-  PT   :: Ton mode -> Phrase mode-  PD   :: Dom mode -> Phrase mode---- Harmonic categories--- Tonic-data Ton mode where-  T_0   :: SDom mode -> Ton mode -> Ton mode-  -- major mode-  T_1      :: Final I MajClass         -> Ton MajMode-  -- T_2      :: Final I MajClass         -> Final IV MajClass -           -- -> Final I MajClass         -> Ton MajMode-           -  -- blues           -  T_4_bls  :: Final I DomClass         -> Ton mode  --  T_3_par  :: Final III MinClass       -> Ton MajMode    -  T_6_bor  :: TMinBorrow -> Ton MajMode--  -- minor mode         -  Tm_1     :: SD MinMode I MinClass    -> Ton MinMode-  -- Tm_2     :: Final I MinClass -> Final IV MinClass-           -- -> Final I MinClass         -> Ton MinMode           --  Tm_3_par :: Final IIIb MajClass      -> Ton MinMode -  Tm_6_bor :: TMajBorrow               -> Ton MinMode  -- picardy third etc.     ---- Dominant-data Dom mode where-  -- major mode-  D_1   :: SDom mode -> Dom mode -> Dom mode-  D_2   :: SD mode V DomClass        -> Dom mode-  D_3   :: SD mode V MajClass        -> Dom mode--  D_4   :: SD MajMode VII MinClass   -> Dom MajMode       -  -  -- moll-dur: minor mode borrowings in major-  -- This would be an elegant way of defining major minor borrowing,-  -- but it causes a lot of unwanted abmiguities since all the mode-  -- rules can be explained with and without borrowing-  -- D_9_bor :: Dom MinMode -> Dom MajMode-  D_8_bor :: DMinBorrow              -> Dom MajMode-  -  -- minor mode (there must be at least one rule with "MinMode" otherwise-  -- no you get a "No instance for (ParseG (Dom MinMode))" error-  Dm_4   :: SD MinMode VIIb MajClass -> Dom MinMode-  Dm_8_bor :: DMajBorrow             -> Dom MinMode---- Subdominant-data SDom mode where-  S_1_par :: SD mode II MinClass     -> SDom mode -- sub dom parallel--  -- Pretty printing? this rule compensates for the V/I Imp-  -- to be able to parse D:7 D:min G:7 C:maj (not in cmj)-  S_2_par :: SD mode II DomClass -> Final II MinClass-                                         -> SDom mode -  S_3     :: SD MajMode IV MajClass  -> SDom MajMode-  -- blues-  S_3_bls :: Final     IV DomClass  -> SDom mode-  S_4     :: SD MajMode III MinClass -> Final IV MajClass-                                         -> SDom MajMode-  S_7     :: SD MajMode III MinClass -> Final II MinClass-                                         -> SDom MajMode-  S_5_par :: SD MajMode VI MinClass  -> SDom MajMode--  -- Borrowing from minor in a major mode-  -- S_7_bor :: SDom MajMode -> SDom MinMode-  S_9_bor :: SMinBorrow -> SDom MajMode -  -  -- minor mode-  Sm_3    :: SD MinMode IV  MinClass  -> SDom MinMode-  Sm_4    :: SD MinMode IIIb MajClass -> Final IV MinClass -                                          -> SDom MinMode-  Sm_7    :: SD MinMode IIIb MajClass -> Final II MinClass-                                          -> SDom MinMode                                          -  Sm_5_par :: SD MinMode VIb MajClass -> SDom MinMode- -  Sm_9_bor :: SMajBorrow              -> SDom MinMode --  -- perhaps add a functional node for Neapolitan chords?-  Sm_6   :: SD MinMode IIb MajClass   -> SDom MinMode -- Neapolitan-  --- Borrowings from minor in a major key-data TMinBorrow = Tm_21_bor (SD MinMode I    MinClass)   -                    | Tm_23_bor (SD MinMode IIIb MajClass)   --data DMinBorrow = Dm_24_bor (SD MinMode VIIb MajClass)   -                    -- | Dm_21_bor (Final VIIb DomClass)   -                      -data SMinBorrow = Sm_20_bor (SD MinMode IV   MinClass) -                    -- | Sm_21_bor (SD MinMode VIb  MajClass)-                    | Sm_22_bor (SD MinMode IIb  MajClass)   -- Neapolitan ---- Borrowings from major in a minor key-data TMajBorrow = T_21_bor (SD MajMode I   MajClass)-                    | T_23_bor (SD MajMode III MinClass)--data DMajBorrow = D_24_bor (SD MajMode VII MinClass)   -                    -- | D_21_bor (Final VII DimClass)-                      -data SMajBorrow = S_20_bor (SD MajMode IV   MajClass) -                    -                    --- Limit secondary dominants to a few levels-type SD mode deg clss = Base_SD deg clss NUMLEVELS---- a type that can be substituted by its tritone sub and diminished 7b9-type TritMinVSub deg clss = Base_Final deg clss T2---- A Scale degree that can only translate to a surface chord--- and allows for the transformation into enharmonic equivalent --- diminshed surface chords-type FinalDimTrans deg clss = Surface_Chord deg clss T4---- A Scale degree that translates into a (non-tranformable) surface chord-type Final deg clss = Surface_Chord deg clss T1----- Datatypes for clustering harmonic degrees--- type Base_SD deg clss n = List (Base_SD' deg clss n) T4--data Base_SD deg clss n where-  Base_SD   :: TritMinVSub deg clss -- Min5    deg clss  n-            -> Base_SD deg clss (Su n)   -  -- Rule for explaining perfect secondary dominants-  Cons_Vdom :: Base_SD (VDom  deg) DomClass n -> Base_SD deg clss n-            -> Base_SD        deg  clss (Su n)-  Cons_Diat :: Base_SD (DiatV deg) MinClass n -> Base_SD deg MinClass n-            -> Base_SD        deg  MinClass (Su n)-  Cons_DiatM :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MajClass n-            -> Base_SD        deg  MajClass (Su n)-  Cons_DiatM' :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MinClass n-            -> Base_SD        deg  MinClass (Su n)-  -- Minor fifth insertion-  Cons_Vmin :: Base_SD (VMin deg) MinClass n -> Base_SD deg DomClass n-            -> Base_SD     deg  DomClass (Su n)--            -data Base_Final deg clss n where-  -- Just a "normal", final degree. The Strings are the original input.-  Base_Final     :: FinalDimTrans deg clss -> Base_Final deg clss (Su n)-  -- Tritone substitution-  Final_Tritone  :: Base_Final (Tritone deg) DomClass n-                 -> Base_Final deg DomClass (Su n)-  Final_Dim_V    :: Base_Final (IIbDim  deg) DimClass n-                 -> Base_Final deg DomClass (Su n)                 ---- Dimished tritone substitution accounting for dimished chord transistions-data Surface_Chord deg clss n where-  Surface_Chord  :: ChordToken  -                 -> Surface_Chord deg clss     (Su n)-  -- Dim_Chord_Trns :: Surface_Chord (MinThird deg) DimClass n-                 -- -> Surface_Chord deg DimClass (Su n)------------------------------------------------------------------------------------- Type Level Scale Degrees------------------------------------------------------------------------------------- typelevel chord classes -data MajClass-data MinClass-data DomClass-data DimClass---- Degrees (at the type level)-data I-data Ib-data Is-data II-data IIb-data IIs-data III-data IIIb-data IIIs-data IV-data IVb-data IVs-data V-data Vb-data Vs-data VI-data VIb-data VIs-data VII-data VIIb-data VIIs---- Used when we don't want to consider certain possibilities-data Imp---- Degrees at the value level are in Tokenizer--- Type to value conversions-class ToClass clss where-  toClass :: clss -> ClassType--instance ToClass MajClass where toClass _ = MajClass-instance ToClass MinClass where toClass _ = MinClass-instance ToClass DomClass where toClass _ = DomClass-instance ToClass DimClass where toClass _ = DimClass---- The class doesn't really matter, since the degree will be impossible to parse-instance ToClass Imp where toClass _ = DimClass--class ToDegree deg where-  toDegree :: deg -> ScaleDegree--instance ToDegree I     where toDegree _ = Note Nothing I-instance ToDegree II    where toDegree _ = Note Nothing II-instance ToDegree III   where toDegree _ = Note Nothing III-instance ToDegree IV    where toDegree _ = Note Nothing IV-instance ToDegree V     where toDegree _ = Note Nothing V-instance ToDegree VI    where toDegree _ = Note Nothing VI-instance ToDegree VII   where toDegree _ = Note Nothing VII-instance ToDegree Ib    where toDegree _ = Note (Just Fl) I-instance ToDegree IIb   where toDegree _ = Note (Just Fl) II-instance ToDegree IIIb  where toDegree _ = Note (Just Fl) III-instance ToDegree IVb   where toDegree _ = Note (Just Fl) IV-instance ToDegree Vb    where toDegree _ = Note (Just Fl) V-instance ToDegree VIb   where toDegree _ = Note (Just Fl) VI-instance ToDegree VIIb  where toDegree _ = Note (Just Fl) VII-instance ToDegree IIs   where toDegree _ = Note (Just Sh) II-instance ToDegree IIIs  where toDegree _ = Note (Just Sh) III-instance ToDegree IVs   where toDegree _ = Note (Just Sh) IV-instance ToDegree Vs    where toDegree _ = Note (Just Sh) V-instance ToDegree VIs   where toDegree _ = Note (Just Sh) VI-instance ToDegree VIIs  where toDegree _ = Note (Just Sh) VII---- Can't ever parse these-instance ToDegree Imp where toDegree _ = Note Nothing Imp-------------------------------------------------------------------------------------- Type Families for Relative Scale Degrees-------------------------------------------------------------------------------------- Diatonic fifths, and their class (comments with the CMaj scale)--- See http://en.wikipedia.org/wiki/Circle_progression-type family DiatV deg :: *-type instance DiatV I   = Imp -- V   -- G7  should be Dom-type instance DiatV V   = Imp -- II  -- Dm7 should be SDom-type instance DiatV II  = Imp -- VI  -- Am7 -type instance DiatV VI  = III -- Em7-type instance DiatV III = VII -- Bhdim7 can be explained by Dim rule-type instance DiatV VII = Imp -- IV  -- FMaj7 should be SDom-type instance DiatV IV  = Imp -- I   -- CMaj7--type instance DiatV IIb  = Imp-type instance DiatV IIIb = Imp-type instance DiatV IVs  = Imp-type instance DiatV VIb  = Imp-type instance DiatV VIIb = Imp-type instance DiatV Imp  = Imp--type family DiatVM deg :: *-type instance DiatVM I   = Imp -- V   -- G7  should be Dom-type instance DiatVM V   = Imp -- Dm7 should be SDom-type instance DiatVM II  = Imp -- VIb -- Ab -type instance DiatVM VI  = Imp -- Em7-type instance DiatVM III = Imp -- Bhdim7 can be explained by Dim rule-type instance DiatVM VII = Imp -- IV  -- FMaj7 should be SDom-type instance DiatVM IV  = Imp -- I   -- CMaj7--type instance DiatVM IIb  = Imp-type instance DiatVM IIIb = VIIb -type instance DiatVM IVs  = Imp-type instance DiatVM VIb  = IIIb-type instance DiatVM VIIb = Imp-type instance DiatVM Imp  = Imp------------------------------------------------------------------------------------- Type families for secondary dominants------------------------------------------------------------------------------------- Perfect fifths (class is always Dom)--- See http://en.wikipedia.org/wiki/Circle_of_fifths-type family VDom deg :: *--type instance VDom I     = Imp  -- interferes with dom -type instance VDom IIb   = VIb-type instance VDom II    = VI -type instance VDom IIIb  = VIIb -- interferes with Dm_3-type instance VDom III   = VII-type instance VDom IV    = I    -- Imp  -- interferes with S_bls VI7 -> I7-type instance VDom IVs   = IIb-type instance VDom V     = II   -- interferes with Sm_1-type instance VDom VIb   = IIIb-type instance VDom VI    = III-type instance VDom VIIb  = IV-type instance VDom VII   = IVs-type instance VDom Imp   = Imp---- Perfect fifths for the minor case (this is an additional--- type family to controll the reduction of ambiguities--- specifically in the minor case)-type family VMin deg :: *-type instance VMin I     = V -type instance VMin IIb   = VIb-type instance VMin II    = VI  -- interferes with sub -type instance VMin IIIb  = VIIb-type instance VMin III   = VII-type instance VMin IV    = I-type instance VMin IVs   = IIb-type instance VMin V     = Imp -- II interferes with sub-type instance VMin VIb   = IIIb-type instance VMin VI    = III-type instance VMin VIIb  = Imp --IV -- inteferes with sub IV:min-type instance VMin VII   = IVs-type instance VMin Imp   = Imp- --- The tritone substitution--- See http://en.wikipedia.org/wiki/Tritone_substitution-type family Tritone deg :: *-type instance Tritone I     = IVs-type instance Tritone IVs   = I--type instance Tritone IIb   = V    -- gives undesired (ambiguous results) as-type instance Tritone V     = IIb  -- Dom = IIb/I = IIbdim --type instance Tritone II    = VIb -- interferes IIbDim V-type instance Tritone VIb   = II--type instance Tritone IIIb  = VI-type instance Tritone VI    = IIIb--type instance Tritone III   = VIIb -- Interferes with VIIb from minor-type instance Tritone VIIb  = III --type instance Tritone IV    = VII-type instance Tritone VII   = IV--type instance Tritone Imp   = Imp  -------------------------------------------------------------------------------------- Type families for diminished chord transformations---------------------------------------------------------------------------------- --- in combination with the secondary dominants and enharmonic equivalency--- these type families account for ascending dim chord progressions-type family IIbDim deg :: *   -type instance IIbDim I     =  IIb-type instance IIbDim IIb   =  II-type instance IIbDim II    =  IIIb-type instance IIbDim IIIb  =  III-type instance IIbDim III   =  IV-type instance IIbDim IV    =  IVs-type instance IIbDim IVs   =  V-type instance IIbDim V     =  VIb -- interferes with dim tritone V/V-type instance IIbDim VIb   =  VI -type instance IIbDim VI    =  VIIb-type instance IIbDim VIIb  =  VII-type instance IIbDim VII   =  I-type instance IIbDim Imp   =  Imp---- Dimchords can be transposed a minor third without changing their role,--- they are enharmonically equivalent.-type family MinThird deg :: *-type instance MinThird I     = IIIb -type instance MinThird IIb   = III-type instance MinThird II    = IV-type instance MinThird IIIb  = IVs-type instance MinThird III   = V-type instance MinThird IV    = VIb-type instance MinThird IVs   = VI-type instance MinThird V     = VIIb -type instance MinThird VIb   = VII-type instance MinThird VI    = I-type instance MinThird VIIb  = IIb-type instance MinThird VII   = II-type instance MinThird Imp   = Imp---- Belongs in Instances, but needs to be here due to staging restrictions-allTypes :: [Name]-allTypes = [ ''Phrase, ''Ton, ''Dom, ''SDom-           , ''TMinBorrow, ''DMinBorrow, ''SMinBorrow -           , ''TMajBorrow, ''DMajBorrow, ''SMajBorrow ]+{-# LANGUAGE CPP                      #-}
+{-# LANGUAGE TemplateHaskell          #-}
+{-# LANGUAGE TypeOperators            #-}
+{-# LANGUAGE EmptyDataDecls           #-}
+{-# LANGUAGE TypeSynonymInstances     #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE ScopedTypeVariables      #-}
+{-# LANGUAGE TypeFamilies             #-}
+{-# LANGUAGE GADTs                    #-}
+
+module HarmTrace.Models.Pop.Model where
+
+import HarmTrace.Base.TypeLevel
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens
+import Language.Haskell.TH.Syntax (Name)
+
+--------------------------------------------------------------------------------
+-- Musical structure as a datatype
+--------------------------------------------------------------------------------
+
+#ifndef NUMLEVELS
+#define NUMLEVELS T4
+#endif
+
+-- perhaps this module should be named differently, like Model or HarmonyModel
+
+data MajMode
+data MinMode
+
+-- High level structure
+data Piece = forall mode. Piece [Phrase mode]
+
+-- The Phrase level
+data Phrase mode where
+  PT   :: Ton mode -> Phrase mode
+  PD   :: Dom mode -> Phrase mode
+
+-- Harmonic categories
+-- Tonic
+data Ton mode where
+  T_0   :: SDom mode -> Ton mode -> Ton mode
+  -- major mode
+  T_1      :: Final I MajClass         -> Ton MajMode
+           
+  -- blues           
+  T_4_bls  :: Final I DomClass         -> Ton mode  
+ 
+  T_6_bor  :: TMinBorrow -> Ton MajMode
+
+  -- minor mode         
+  Tm_1     :: SD MinMode I MinClass    -> Ton MinMode      
+
+  -- Tm_3_par :: Final IIIb MajClass      -> Ton MinMode 
+  Tm_6_bor :: TMajBorrow               -> Ton MinMode  -- picardy third etc.     
+
+-- Dominant
+data Dom mode where
+  -- major mode
+  D_1   :: SDom mode -> Dom mode -> Dom mode
+  D_2   :: SD mode V DomClass        -> Dom mode
+  D_3   :: SD mode V MajClass        -> Dom mode
+
+  D_4   :: SD MajMode VII MinClass   -> Dom MajMode       
+  
+  -- moll-dur: minor mode borrowings in major
+  -- This would be an elegant way of defining major minor borrowing,
+  -- but it causes a lot of unwanted abmiguities since all the mode
+  -- rules can be explained with and without borrowing
+  -- D_9_bor :: Dom MinMode -> Dom MajMode
+  D_8_bor :: DMinBorrow              -> Dom MajMode
+  
+  -- minor mode (there must be at least one rule with "MinMode" otherwise
+  -- no you get a "No instance for (ParseG (Dom MinMode))" error
+  Dm_4   :: SD MinMode VIIb MajClass -> Dom MinMode
+  Dm_8_bor :: DMajBorrow             -> Dom MinMode
+
+-- Subdominant
+data SDom mode where
+  S_1_par :: SD mode II MinClass     -> SDom mode -- sub dom parallel
+
+  -- Pretty printing? this rule compensates for the V/I Imp
+  -- to be able to parse D:7 D:min G:7 C:maj (not in cmj)
+  S_2_par :: SD mode II DomClass -> Final II MinClass
+                                         -> SDom mode 
+  S_3     :: SD MajMode IV MajClass  -> SDom MajMode
+  -- blues
+  S_3_bls :: Final     IV DomClass  -> SDom mode
+  S_4     :: SD MajMode III MinClass -> Final IV MajClass
+                                         -> SDom MajMode
+  S_7     :: SD MajMode III MinClass -> Final II MinClass
+                                         -> SDom MajMode
+  S_5_par :: SD MajMode VI MinClass  -> SDom MajMode
+
+  -- Borrowing from minor in a major mode
+  S_9_bor :: SMinBorrow -> SDom MajMode 
+  
+  -- minor mode
+  Sm_3    :: SD MinMode IV  MinClass  -> SDom MinMode
+  Sm_4    :: SD MinMode IIIb MajClass -> Final IV MinClass 
+                                          -> SDom MinMode
+  Sm_7    :: SD MinMode IIIb MajClass -> Final II MinClass
+                                          -> SDom MinMode                                          
+  Sm_5_par :: SD MinMode VIb MajClass -> SDom MinMode
+ 
+  Sm_9_bor :: SMajBorrow              -> SDom MinMode 
+
+  -- perhaps add a functional node for Neapolitan chords?
+  Sm_6   :: SD MinMode IIb MajClass   -> SDom MinMode -- Neapolitan
+  
+-- Borrowings from minor in a major key
+data TMinBorrow = Tm_21_bor (SD MinMode I    MinClass)   
+
+data DMinBorrow = Dm_24_bor (SD MinMode VIIb MajClass)   
+                      
+data SMinBorrow = Sm_20_bor (SD MinMode IV   MinClass) 
+                    | Sm_22_bor (SD MinMode IIb  MajClass)   -- Neapolitan 
+
+-- Borrowings from major in a minor key
+data TMajBorrow = T_21_bor (SD MajMode I   MajClass)
+
+data DMajBorrow = D_24_bor (SD MajMode VII MinClass)   
+                      
+data SMajBorrow = S_20_bor (SD MajMode IV   MajClass) 
+                    
+                    
+-- Limit secondary dominants to a few levels
+type SD mode deg clss = Base_SD deg clss NUMLEVELS
+
+-- a type that can be substituted by its tritone sub and diminished 7b9
+type TritMinVSub deg clss = Base_Final deg clss T2
+
+-- A Scale degree that can only translate to a surface chord
+-- and allows for the transformation into enharmonic equivalent 
+-- diminshed surface chords
+type FinalDimTrans deg clss = Surface_Chord deg clss T4
+
+-- A Scale degree that translates into a (non-tranformable) surface chord
+type Final deg clss = Surface_Chord deg clss T1
+
+
+-- Datatypes for clustering harmonic degrees
+-- type Base_SD deg clss n = List (Base_SD' deg clss n) T4
+
+data Base_SD deg clss n where
+  Base_SD   :: TritMinVSub deg clss -- Min5    deg clss  n
+            -> Base_SD deg clss (Su n)   
+  -- Rule for explaining perfect secondary dominants
+  Cons_Vdom :: Base_SD (VDom  deg) DomClass n -> Base_SD deg clss n
+            -> Base_SD        deg  clss (Su n)
+  Cons_Diat :: Base_SD (DiatV deg) MinClass n -> Base_SD deg MinClass n
+            -> Base_SD        deg  MinClass (Su n)
+  Cons_DiatM :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MajClass n
+            -> Base_SD        deg  MajClass (Su n)
+  Cons_DiatM' :: Base_SD (DiatVM deg) MajClass n -> Base_SD deg MinClass n
+            -> Base_SD        deg  MinClass (Su n)
+  -- Minor fifth insertion
+  Cons_Vmin :: Base_SD (VMin deg) MinClass n -> Base_SD deg DomClass n
+            -> Base_SD     deg  DomClass (Su n)
+
+            
+data Base_Final deg clss n where
+  -- Just a "normal", final degree. The Strings are the original input.
+  Base_Final     :: FinalDimTrans deg clss -> Base_Final deg clss (Su n)
+  -- Tritone substitution
+
+-- Dimished tritone substitution accounting for dimished chord transistions
+data Surface_Chord deg clss n where
+  Surface_Chord  :: ChordToken  
+                 -> Surface_Chord deg clss     (Su n)
+
+--------------------------------------------------------------------------------
+-- Type Level Scale Degrees
+--------------------------------------------------------------------------------
+
+-- typelevel chord classes 
+data MajClass
+data MinClass
+data DomClass
+data DimClass
+
+-- Degrees (at the type level)
+data I
+data Ib
+data Is
+data II
+data IIb
+data IIs
+data III
+data IIIb
+data IIIs
+data IV
+data IVb
+data IVs
+data V
+data Vb
+data Vs
+data VI
+data VIb
+data VIs
+data VII
+data VIIb
+data VIIs
+
+-- Used when we don't want to consider certain possibilities
+data Imp
+
+-- Degrees at the value level are in Tokenizer
+-- Type to value conversions
+class ToClass clss where
+  toClass :: clss -> ClassType
+
+instance ToClass MajClass where toClass _ = MajClass
+instance ToClass MinClass where toClass _ = MinClass
+instance ToClass DomClass where toClass _ = DomClass
+instance ToClass DimClass where toClass _ = DimClass
+
+-- The class doesn't really matter, since the degree will be impossible to parse
+instance ToClass Imp where toClass _ = DimClass
+
+class ToDegree deg where
+  toDegree :: deg -> ScaleDegree
+
+instance ToDegree I     where toDegree _ = Note Nothing I
+instance ToDegree II    where toDegree _ = Note Nothing II
+instance ToDegree III   where toDegree _ = Note Nothing III
+instance ToDegree IV    where toDegree _ = Note Nothing IV
+instance ToDegree V     where toDegree _ = Note Nothing V
+instance ToDegree VI    where toDegree _ = Note Nothing VI
+instance ToDegree VII   where toDegree _ = Note Nothing VII
+instance ToDegree Ib    where toDegree _ = Note (Just Fl) I
+instance ToDegree IIb   where toDegree _ = Note (Just Fl) II
+instance ToDegree IIIb  where toDegree _ = Note (Just Fl) III
+instance ToDegree IVb   where toDegree _ = Note (Just Fl) IV
+instance ToDegree Vb    where toDegree _ = Note (Just Fl) V
+instance ToDegree VIb   where toDegree _ = Note (Just Fl) VI
+instance ToDegree VIIb  where toDegree _ = Note (Just Fl) VII
+instance ToDegree IIs   where toDegree _ = Note (Just Sh) II
+instance ToDegree IIIs  where toDegree _ = Note (Just Sh) III
+instance ToDegree IVs   where toDegree _ = Note (Just Sh) IV
+instance ToDegree Vs    where toDegree _ = Note (Just Sh) V
+instance ToDegree VIs   where toDegree _ = Note (Just Sh) VI
+instance ToDegree VIIs  where toDegree _ = Note (Just Sh) VII
+
+-- Can't ever parse these
+instance ToDegree Imp where toDegree _ = Note Nothing Imp
+
+
+--------------------------------------------------------------------------------
+-- Type Families for Relative Scale Degrees
+--------------------------------------------------------------------------------
+
+
+-- Diatonic fifths, and their class (comments with the CMaj scale)
+-- See http://en.wikipedia.org/wiki/Circle_progression
+type family DiatV deg :: *
+type instance DiatV I   = Imp -- V   -- G7  should be Dom
+type instance DiatV V   = Imp -- II  -- Dm7 should be SDom
+type instance DiatV II  = Imp -- VI  -- Am7 
+type instance DiatV VI  = III -- Em7
+type instance DiatV III = VII -- Bhdim7 can be explained by Dim rule
+type instance DiatV VII = Imp -- IV  -- FMaj7 should be SDom
+type instance DiatV IV  = Imp -- I   -- CMaj7
+
+type instance DiatV IIb  = Imp
+type instance DiatV IIIb = Imp
+type instance DiatV IVs  = Imp
+type instance DiatV VIb  = Imp
+type instance DiatV VIIb = Imp
+type instance DiatV Imp  = Imp
+
+type family DiatVM deg :: *
+type instance DiatVM I   = Imp -- V   -- G7  should be Dom
+type instance DiatVM V   = Imp -- Dm7 should be SDom
+type instance DiatVM II  = Imp -- VIb -- Ab 
+type instance DiatVM VI  = Imp -- Em7
+type instance DiatVM III = Imp -- Bhdim7 can be explained by Dim rule
+type instance DiatVM VII = Imp -- IV  -- FMaj7 should be SDom
+type instance DiatVM IV  = Imp -- I   -- CMaj7
+
+type instance DiatVM IIb  = Imp
+type instance DiatVM IIIb = VIIb 
+type instance DiatVM IVs  = Imp
+type instance DiatVM VIb  = IIIb
+type instance DiatVM VIIb = Imp
+type instance DiatVM Imp  = Imp
+
+--------------------------------------------------------------------------------
+-- Type families for secondary dominants
+--------------------------------------------------------------------------------
+
+-- Perfect fifths (class is always Dom)
+-- See http://en.wikipedia.org/wiki/Circle_of_fifths
+type family VDom deg :: *
+
+type instance VDom I     = Imp  -- interferes with dom 
+type instance VDom IIb   = VIb
+type instance VDom II    = VI 
+type instance VDom IIIb  = VIIb -- interferes with Dm_3
+type instance VDom III   = VII
+type instance VDom IV    = Imp  -- interferes with S_bls VI7 -> I7
+type instance VDom IVs   = IIb
+type instance VDom V     = II   -- interferes with Sm_1
+type instance VDom VIb   = IIIb
+type instance VDom VI    = III
+type instance VDom VIIb  = IV
+type instance VDom VII   = IVs
+type instance VDom Imp   = Imp
+
+-- Perfect fifths for the minor case (this is an additional
+-- type family to controll the reduction of ambiguities
+-- specifically in the minor case)
+type family VMin deg :: *
+type instance VMin I     = V 
+type instance VMin IIb   = VIb
+type instance VMin II    = VI  -- interferes with sub 
+type instance VMin IIIb  = VIIb
+type instance VMin III   = VII
+type instance VMin IV    = I
+type instance VMin IVs   = IIb
+type instance VMin V     = Imp -- II interferes with sub
+type instance VMin VIb   = IIIb
+type instance VMin VI    = III
+type instance VMin VIIb  = Imp --IV -- inteferes with sub IV:min
+type instance VMin VII   = IVs
+type instance VMin Imp   = Imp
+ 
+-- The tritone substitution
+-- See http://en.wikipedia.org/wiki/Tritone_substitution
+type family Tritone deg :: *
+type instance Tritone I     = IVs
+type instance Tritone IVs   = I
+
+type instance Tritone IIb   = V    -- gives undesired (ambiguous results) as
+type instance Tritone V     = IIb  -- Dom = IIb/I = IIbdim 
+
+type instance Tritone II    = VIb -- interferes IIbDim V
+type instance Tritone VIb   = II
+
+type instance Tritone IIIb  = VI
+type instance Tritone VI    = IIIb
+
+type instance Tritone III   = VIIb -- Interferes with VIIb from minor
+type instance Tritone VIIb  = III 
+
+type instance Tritone IV    = VII
+type instance Tritone VII   = IV
+
+type instance Tritone Imp   = Imp  
+
+
+--------------------------------------------------------------------------------
+-- Type families for diminished chord transformations
+--------------------------------------------------------------------------------
+ 
+-- in combination with the secondary dominants and enharmonic equivalency
+-- these type families account for ascending dim chord progressions
+type family IIbDim deg :: *   
+type instance IIbDim I     =  IIb
+type instance IIbDim IIb   =  II
+type instance IIbDim II    =  IIIb
+type instance IIbDim IIIb  =  III
+type instance IIbDim III   =  IV
+type instance IIbDim IV    =  IVs
+type instance IIbDim IVs   =  V
+type instance IIbDim V     =  VIb -- interferes with dim tritone V/V
+type instance IIbDim VIb   =  VI 
+type instance IIbDim VI    =  VIIb
+type instance IIbDim VIIb  =  VII
+type instance IIbDim VII   =  I
+type instance IIbDim Imp   =  Imp
+
+-- Dimchords can be transposed a minor third without changing their role,
+-- they are enharmonically equivalent.
+type family MinThird deg :: *
+type instance MinThird I     = IIIb 
+type instance MinThird IIb   = III
+type instance MinThird II    = IV
+type instance MinThird IIIb  = IVs
+type instance MinThird III   = V
+type instance MinThird IV    = VIb
+type instance MinThird IVs   = VI
+type instance MinThird V     = VIIb 
+type instance MinThird VIb   = VII
+type instance MinThird VI    = I
+type instance MinThird VIIb  = IIb
+type instance MinThird VII   = II
+type instance MinThird Imp   = Imp
+
+-- Belongs in Instances, but needs to be here due to staging restrictions
+allTypes :: [Name]
+allTypes = [ ''Phrase, ''Ton, ''Dom, ''SDom
+           , ''TMinBorrow, ''DMinBorrow, ''SMinBorrow 
+           , ''TMajBorrow, ''DMajBorrow, ''SMajBorrow ]
src/HarmTrace/Tokenizer/Tokenizer.hs view
@@ -1,157 +1,155 @@-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}-{-# LANGUAGE DeriveDataTypeable       #-}-{-# LANGUAGE RankNTypes               #-}-{-# LANGUAGE FlexibleContexts         #-}--module HarmTrace.Tokenizer.Tokenizer ( parseChordTokens {-not used yet-}-                                     , parseSongAbs, toKeyRelTok-                                     , parseDegrees, parseDegree ) where--import HarmTrace.Base.Parsing-import HarmTrace.Base.MusicRep-import HarmTrace.Tokenizer.Tokens--import Data.Maybe------------------------------------------------------------------------------------- Tokenizing: parsing strings into tokens---------------------------------------------------------------------------------  ---- toplevel parser: parses a string of chord labels into a key relative --- representation-parseChordTokens :: ListLike s Char => s -> (PieceLabel ,[Error LineColPos])-parseChordTokens inp = parseDataWithErrors parseSongAbs inp-  --- Merges duplicate chords and transforms absolute chord labels into key--- relative tokens that can be parsed by the HarmTrace model --- (previously called mergeDups)-toKeyRelTok  :: Key -> [ChordLabel] -> [ChordToken]-toKeyRelTok k (c@(Chord r sh _add _loc d):cs) = toKeyRelTok' k -      (ChordToken (toScaleDegree k r) (toClassType sh) [c] NotParsed 1 d) cs-toKeyRelTok  _key [] = []-toKeyRelTok' :: Key ->  ChordToken -> [ChordLabel] -> [ChordToken]-toKeyRelTok' _k p [] = [p]-toKeyRelTok' k p@(ChordToken deg clss cs' _stat n d1) (c@(Chord r sh _a _l d2):cs) -  | deg == deg2 && clss == clss2 = -      toKeyRelTok' k (ChordToken deg clss (cs' ++ [c]) NotParsed (n+1) (d1+d2)) cs-  | otherwise = p : toKeyRelTok' k (ChordToken deg2 clss2 [c] NotParsed 1 d2) cs-  where clss2 = toClassType sh-        deg2  = toScaleDegree k r---- Input is a string of whitespace-separated chords, e.g.--- Bb:9(s11) E:min7 Eb:min7 Ab:7 D:min7 G:7(13) C:maj6(9)--- First token is the key of the piece-parseSongAbs :: Parser PieceLabel -- PieceRelToken -- -parseSongAbs =  PieceLabel  <$> parseKey <* pLineEnd -                            <*> (setLoc 0 <$> pListSep_ng pLineEnd parseChord )-                            <*  pList pLineEnd where-  setLoc :: Int -> [Chord a] -> [Chord a]  -  setLoc _  [] = []-  setLoc ix (Chord r c d _ l :cs) = (Chord r c d ix l) : setLoc (ix+1) cs                               -  --- For now, I assume there is always a shorthand, and sometimes extra--- degrees. I guess it might be the case that sometimes there is no shorthand,--- but then there certainly are degrees.-parseChord :: Parser ChordLabel-parseChord = f <$> parseRoot <* pSym ':'   <*> pMaybe parseShorthand-               <*> (parseDegrees `opt` []) <*  pSym ';' <*> pNaturalRaw-  where f r (Just s)    [] l = Chord r s [] 0 l-        -- if there are no degrees and no shorthand (should not occur)-        -- we make it a minor chord-        f r Nothing     [] l = Chord r Maj [] 0 l-        -- in case of there is no short hand we analyse the degree list-        f r Nothing     d  l = Chord r (analyseDegs d) d 0 l-        -- in case of a sus4/maj we also analyse the degree list-        f r (Just Sus4) d  l = Chord r (analyseDegs d) d 0 l-        f r (Just Maj)  d  l = Chord r (analyseDegs d) d 0 l-        -- if we have another short hand we ignore the degrees list-        f r (Just s)    d  l = Chord r s d 0 l---parseKey :: Parser Key        -parseKey = f <$> parseRoot <* pSym ':' <*> parseShorthand-  where f r m | m == Maj = Key r MajMode-              | m == Min = Key r MinMode-              | otherwise = error ("Tokenizer: key must be Major or Minor, "-                          ++ "found: " ++ show m)-                          ---- analyses a list of Degrees and assigns a shortHand i.e. Chord Class        -analyseDegs :: [Addition] -> Shorthand        -analyseDegs d -  | (Note (Just Fl) I3)  `elem` d = Min-  | (Note (Just Sh) I5)  `elem` d = Sev-  | (Note (Just Fl) I7)  `elem` d = Sev-  | (Note  Nothing  I7)  `elem` d = Maj7-  | (Note (Just Fl) I9)  `elem` d = Sev-  | (Note (Just Sh) I9)  `elem` d = Sev-  | (Note  Nothing  I11) `elem` d = Sev-  | (Note (Just Sh) I11) `elem` d = Sev-  | (Note (Just Fl) I13) `elem` d = Sev-  | (Note  Nothing  I13) `elem` d = Sev-  | (Note  Nothing  I3)  `elem` d = Maj-  | otherwise                     = Maj-   ---parseShorthand :: Parser Shorthand-parseShorthand =     Maj      <$ pString "maj"-                 <|> Min      <$ pString "min"-                 <|> Dim      <$ pString "dim"-                 <|> Aug      <$ pString "aug"-                 <|> Maj7     <$ pString "maj7"-                 <|> Min7     <$ pString "min7"-                 <|> Sev      <$ pString "7"-                 <|> Dim7     <$ pString "dim7"-                 <|> HDim7    <$ pString "hdim" <* opt (pSym '7') '7'-                 <|> MinMaj7  <$ pString "minmaj7"-                 <|> Maj6     <$ pString "maj6"-                 <|> Maj6     <$ pString "6"-                 <|> Min6     <$ pString "min6"-                 <|> Nin      <$ pString "9"-                 <|> Maj9     <$ pString "maj9"-                 <|> Min9     <$ pString "min9"-                 <|> Sus4     <$ pString "sus4" <?> "Shorthand"---- We don't produce intervals for a shorthand. This could easily be added,--- though.-parseDegrees :: Parser [Addition]-parseDegrees = pPacked (pSym '(') (pSym ')') -                       (catMaybes <$> (pList1Sep (pSym ',') parseDegree))-                 -parseDegree :: Parser (Maybe Addition)-parseDegree =   (Just   <$> (Note <$> pMaybe parseModifier <*> parseInterval))-            <|> Nothing <$  pSym '*' <* pMaybe parseModifier <*  parseInterval-              -parseModifier :: Parser Modifier-parseModifier =     Sh <$ pSym    's'-                <|> Sh <$ pSym    '#'-                <|> Fl <$ pSym    'b'-                <|> SS <$ pString "ss"-                <|> FF <$ pString "bb" <?> "Modifier"--parseInterval :: Parser Interval-parseInterval =  ((!!) [minBound..] ) . pred <$> pNaturalRaw--parseRoot :: Parser Root-parseRoot =     Note Nothing   A  <$ pSym 'A'-            <|> Note Nothing   B  <$ pSym 'B'-            <|> Note Nothing   C  <$ pSym 'C'-            <|> Note Nothing   D  <$ pSym 'D'-            <|> Note Nothing   E  <$ pSym 'E'-            <|> Note Nothing   F  <$ pSym 'F'-            <|> Note Nothing   G  <$ pSym 'G'-            <|> Note (Just Fl) A <$ pString "Ab"-            <|> Note (Just Fl) B <$ pString "Bb"-            <|> Note (Just Fl) C <$ pString "Cb"-            <|> Note (Just Fl) D <$ pString "Db"-            <|> Note (Just Fl) E <$ pString "Eb"-            <|> Note (Just Fl) F <$ pString "Fb"-            <|> Note (Just Fl) G <$ pString "Gb"-            <|> Note (Just Sh) A <$ pString "A#"-            <|> Note (Just Sh) B <$ pString "B#"-            <|> Note (Just Sh) C <$ pString "C#"-            <|> Note (Just Sh) D <$ pString "D#"-            <|> Note (Just Sh) E <$ pString "E#"-            <|> Note (Just Sh) F <$ pString "F#"-            <|> Note (Just Sh) G <$ pString "G#" <?> "Chord root"+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+{-# LANGUAGE DeriveDataTypeable       #-}
+{-# LANGUAGE RankNTypes               #-}
+{-# LANGUAGE FlexibleContexts         #-}
+
+module HarmTrace.Tokenizer.Tokenizer ( parseRoot,    parseShorthand
+                                     , parseSongAbs, toKeyRelTok
+                                     , parseDegrees, parseDegree ) where
+
+import HarmTrace.Base.Parsing
+import HarmTrace.Base.MusicRep
+import HarmTrace.Tokenizer.Tokens
+
+import Data.Maybe
+
+--------------------------------------------------------------------------------
+-- Tokenizing: parsing strings into tokens
+--------------------------------------------------------------------------------  
+  
+-- Merges duplicate chords and transforms absolute chord labels into key
+-- relative tokens that can be parsed by the HarmTrace model 
+-- (previously called mergeDups)
+toKeyRelTok  :: Key -> [ChordLabel] -> [ChordToken]
+toKeyRelTok k (c@(Chord r sh _add _loc d):cs) = toKeyRelTok' k 
+      (ChordToken (toScaleDegree k r) (toClassType sh) [c] NotParsed 1 d) cs
+toKeyRelTok  _key [] = []
+toKeyRelTok' :: Key ->  ChordToken -> [ChordLabel] -> [ChordToken]
+toKeyRelTok' _k p [] = [p]
+toKeyRelTok' k p@(ChordToken deg clss cs' _stat n d1) (c@(Chord r sh _a _l d2):cs) 
+  | deg == deg2 && clss == clss2 = 
+      toKeyRelTok' k (ChordToken deg clss (cs' ++ [c]) NotParsed (n+1) (d1+d2)) cs
+  | otherwise = p : toKeyRelTok' k (ChordToken deg2 clss2 [c] NotParsed 1 d2) cs
+  where clss2 = toClassType sh
+        deg2  = toScaleDegree k r
+
+-- Input is a string of whitespace-separated chords, e.g.
+-- Bb:9(s11) E:min7 Eb:min7 Ab:7 D:min7 G:7(13) C:maj6(9)
+-- First token is the key of the piece
+parseSongAbs :: Parser PieceLabel -- PieceRelToken -- 
+parseSongAbs =  PieceLabel  <$> parseKey <* pLineEnd 
+                            <*> (setLoc 0 <$> pListSep_ng pLineEnd parseChord )
+                            <*  pList pLineEnd where
+  setLoc :: Int -> [Chord a] -> [Chord a]  
+  setLoc _  [] = []
+  setLoc ix (Chord r c d _ l :cs) = (Chord r c d ix l) : setLoc (ix+1) cs                               
+  
+-- For now, I assume there is always a shorthand, and sometimes extra
+-- degrees. I guess it might be the case that sometimes there is no shorthand,
+-- but then there certainly are degrees.
+parseChord :: Parser ChordLabel
+parseChord = f <$> parseRoot <* pSym ':'   <*> pMaybe parseShorthand
+               <*> (parseDegrees `opt` []) <*  pSym ';' <*> pNaturalRaw
+  where f r (Just s)    [] l = Chord r s [] 0 l
+        -- if there are no degrees and no shorthand (should not occur)
+        -- we make it a minor chord
+        f r Nothing     [] l = Chord r Maj [] 0 l
+        -- in case of there is no short hand we analyse the degree list
+        f r Nothing     d  l = Chord r (analyseDegs d) d 0 l
+        -- in case of a sus4/maj we also analyse the degree list
+        f r (Just Sus4) d  l = Chord r (analyseDegs d) d 0 l
+        f r (Just Maj)  d  l = Chord r (analyseDegs d) d 0 l
+        -- if we have another short hand we ignore the degrees list
+        f r (Just s)    d  l = Chord r s d 0 l
+
+
+parseKey :: Parser Key        
+parseKey = f <$> parseRoot <* pSym ':' <*> parseShorthand
+  where f r m | m == Maj = Key r MajMode
+              | m == Min = Key r MinMode
+              | otherwise = error ("Tokenizer: key must be Major or Minor, "
+                          ++ "found: " ++ show m)
+                          
+
+-- analyses a list of Degrees and assigns a shortHand i.e. Chord Class        
+analyseDegs :: [Addition] -> Shorthand        
+analyseDegs d 
+  | (Note (Just Fl) I3)  `elem` d = Min
+  | (Note (Just Sh) I5)  `elem` d = Sev
+  | (Note (Just Fl) I7)  `elem` d = Sev
+  | (Note  Nothing  I7)  `elem` d = Maj7
+  | (Note (Just Fl) I9)  `elem` d = Sev
+  | (Note (Just Sh) I9)  `elem` d = Sev
+  | (Note  Nothing  I11) `elem` d = Sev
+  | (Note (Just Sh) I11) `elem` d = Sev
+  | (Note (Just Fl) I13) `elem` d = Sev
+  | (Note  Nothing  I13) `elem` d = Sev
+  | (Note  Nothing  I3)  `elem` d = Maj
+  | otherwise                     = Maj
+   
+
+
+parseShorthand :: Parser Shorthand
+parseShorthand =     Maj      <$ pString "maj"
+                 <|> Min      <$ pString "min"
+                 <|> Dim      <$ pString "dim"
+                 <|> Aug      <$ pString "aug"
+                 <|> Maj7     <$ pString "maj7"
+                 <|> Min7     <$ pString "min7"
+                 <|> Sev      <$ pString "7"
+                 <|> Dim7     <$ pString "dim7"
+                 <|> HDim7    <$ pString "hdim" <* opt (pSym '7') '7'
+                 <|> MinMaj7  <$ pString "minmaj7"
+                 <|> Maj6     <$ pString "maj6"
+                 <|> Maj6     <$ pString "6"
+                 <|> Min6     <$ pString "min6"
+                 <|> Nin      <$ pString "9"
+                 <|> Maj9     <$ pString "maj9"
+                 <|> Min9     <$ pString "min9"
+                 <|> Sus4     <$ pString "sus4" 
+                 <|> Sus2     <$ pString "sus2" 
+                 <?> "Shorthand"
+
+-- We don't produce intervals for a shorthand. This could easily be added,
+-- though.
+parseDegrees :: Parser [Addition]
+parseDegrees = pPacked (pSym '(') (pSym ')') 
+                       (catMaybes <$> (pList1Sep (pSym ',') parseDegree))
+                 
+parseDegree :: Parser (Maybe Addition)
+parseDegree =   (Just   <$> (Note <$> pMaybe parseModifier <*> parseInterval))
+            <|> Nothing <$  pSym '*' <* pMaybe parseModifier <*  parseInterval
+              
+parseModifier :: Parser Modifier
+parseModifier =     Sh <$ pSym    's'
+                <|> Sh <$ pSym    '#'
+                <|> Fl <$ pSym    'b'
+                <|> SS <$ pString "ss"
+                <|> FF <$ pString "bb" <?> "Modifier"
+
+parseInterval :: Parser Interval
+parseInterval =  ((!!) [minBound..] ) . pred <$> pNaturalRaw
+
+parseRoot :: Parser Root
+parseRoot =     Note Nothing   A  <$ pSym 'A'
+            <|> Note Nothing   B  <$ pSym 'B'
+            <|> Note Nothing   C  <$ pSym 'C'
+            <|> Note Nothing   D  <$ pSym 'D'
+            <|> Note Nothing   E  <$ pSym 'E'
+            <|> Note Nothing   F  <$ pSym 'F'
+            <|> Note Nothing   G  <$ pSym 'G'
+            <|> Note Nothing   N  <$ pSym 'N' -- for no chord            
+            <|> Note (Just Fl) A <$ pString "Ab"
+            <|> Note (Just Fl) B <$ pString "Bb"
+            <|> Note (Just Fl) C <$ pString "Cb"
+            <|> Note (Just Fl) D <$ pString "Db"
+            <|> Note (Just Fl) E <$ pString "Eb"
+            <|> Note (Just Fl) F <$ pString "Fb"
+            <|> Note (Just Fl) G <$ pString "Gb"
+            <|> Note (Just Sh) A <$ pString "A#"
+            <|> Note (Just Sh) B <$ pString "B#"
+            <|> Note (Just Sh) C <$ pString "C#"
+            <|> Note (Just Sh) D <$ pString "D#"
+            <|> Note (Just Sh) E <$ pString "E#"
+            <|> Note (Just Sh) F <$ pString "F#"
+            <|> Note (Just Sh) G <$ pString "G#" <?> "Chord root"
src/HarmTrace/Tokenizer/Tokens.hs view
@@ -1,65 +1,65 @@-{-# LANGUAGE TemplateHaskell                #-}-{-# LANGUAGE EmptyDataDecls                 #-}-{-# LANGUAGE TypeFamilies                   #-}-{-# LANGUAGE GADTs                          #-}-{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}-module HarmTrace.Tokenizer.Tokens ( ChordToken (..), PieceLabel  (..)-                                  , PieceToken (..), ParseStatus (..)-                                  ) where--import HarmTrace.Base.MusicRep-import HarmTrace.HAnTree.Binary-import Generics.Instant.TH-import Data.Binary-  ------------------------------------------------------------------------------------ Tokens for parsing chords------------------------------------------------------------------------------------- merged Chords that will be presented to the parser-data ChordToken = ChordToken { root          :: ScaleDegree-                             , classType     :: ClassType-                             , chords        :: [ChordLabel]-                             , status        :: ParseStatus-                             , chordNumReps  :: Int-                             , dur           :: Int -- duration-                             } -                             -data ParseStatus = NotParsed | Parsed | Deleted | Inserted-  deriving (Eq, Show)-                             --- a datatype to store a tokenized chords                              --- type PieceRelToken = PieceToken ChordDegree--- type PieceAbsToken = PieceToken ChordLabel-data PieceToken = PieceToken Key [ChordToken]-data PieceLabel = PieceLabel Key [ChordLabel]------------------------------------------------------------------------------------- Instances for Chord Tokens----------------------------------------------------------------------------------instance Eq ChordToken where-  (ChordToken sd clss _cs stat _n _d) == (ChordToken sd2 clss2 _cs2 stat2 _n2 _d2) -    = sd == sd2 && clss == clss2 && stat == stat2--instance Show ChordToken where-  show (ChordToken sd clss _cs Inserted _n _d) = show sd ++ show clss++"[Inserted]"-  show (ChordToken sd clss  cs Deleted  _n _d) = -    show sd ++ show clss ++ "[Deleted" ++ showChords cs ++ "]"-  show (ChordToken sd clss  cs _ _n d) = show sd ++ show clss ++ '_' : show d -                                                 ++ showChords cs    -showChords :: Show a => [Chord a] -> String  -showChords = concatMap (\x -> '[' : show x ++ "]") -------------------------------------------------------------------------------------- Binary instances-----------------------------------------------------------------------------------deriveAllL [''ChordToken, ''ParseStatus]--instance Binary ChordToken where-  put = putDefault-  get = getDefault-instance Binary ParseStatus where-  put = putDefault+{-# LANGUAGE TemplateHaskell                #-}
+{-# LANGUAGE EmptyDataDecls                 #-}
+{-# LANGUAGE TypeFamilies                   #-}
+{-# LANGUAGE GADTs                          #-}
+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+module HarmTrace.Tokenizer.Tokens ( ChordToken (..), PieceLabel  (..)
+                                  , PieceToken (..), ParseStatus (..)
+                                  ) where
+
+import HarmTrace.Base.MusicRep
+import HarmTrace.HAnTree.Binary
+import Generics.Instant.TH
+import Data.Binary
+  
+--------------------------------------------------------------------------------
+-- Tokens for parsing chords
+--------------------------------------------------------------------------------
+
+-- merged Chords that will be presented to the parser
+data ChordToken = ChordToken { root          :: ScaleDegree
+                             , classType     :: ClassType
+                             , chords        :: [ChordLabel]
+                             , status        :: ParseStatus
+                             , chordNumReps  :: Int
+                             , dur           :: Int -- duration
+                             } 
+                             
+data ParseStatus = NotParsed | Parsed | Deleted | Inserted
+  deriving (Eq, Show)
+                             
+-- a datatype to store a tokenized chords                              
+-- type PieceRelToken = PieceToken ChordDegree
+-- type PieceAbsToken = PieceToken ChordLabel
+data PieceToken = PieceToken Key [ChordToken]
+data PieceLabel = PieceLabel Key [ChordLabel]
+
+--------------------------------------------------------------------------------
+-- Instances for Chord Tokens
+--------------------------------------------------------------------------------
+instance Eq ChordToken where
+  (ChordToken sd clss _cs stat _n _d) == (ChordToken sd2 clss2 _cs2 stat2 _n2 _d2) 
+    = sd == sd2 && clss == clss2 && stat == stat2
+
+instance Show ChordToken where
+  show (ChordToken sd clss _cs Inserted _n _d) = show sd ++ show clss++"[Inserted]"
+  show (ChordToken sd clss  cs Deleted  _n _d) = 
+    show sd ++ show clss ++ "[Deleted" ++ showChords cs ++ "]"
+  show (ChordToken sd clss  cs _ _n d) = show sd ++ show clss ++ '_' : show d 
+                                                 ++ showChords cs    
+showChords :: Show a => [Chord a] -> String  
+showChords = concatMap (\x -> '[' : show x ++ "]") 
+
+
+--------------------------------------------------------------------------------
+-- Binary instances
+--------------------------------------------------------------------------------
+
+deriveAllL [''ChordToken, ''ParseStatus]
+
+instance Binary ChordToken where
+  put = putDefault
+  get = getDefault
+instance Binary ParseStatus where
+  put = putDefault
   get = getDefault
src/Main.hs view
@@ -1,319 +1,424 @@-{-# OPTIONS_GHC -Wall #-}-{-# LANGUAGE FlexibleInstances                #-}--module Main where---- Libs-import System.Console.ParseArgs hiding (args) ---- Music stuff-import HarmTrace.HarmTrace-import HarmTrace.IO.Main-import HarmTrace.IO.Errors-import HarmTrace.IO.PrintTree-import HarmTrace.HAnTree.ToHAnTree (gTreeHead)-import HarmTrace.Matching.Standard-import HarmTrace.Matching.GuptaNishimura (getLCES)-import HarmTrace.Matching.Alignment (getAlignDist, alignChordLab-                                    , pPrintV, alignHAnChord)-import HarmTrace.Audio.Harmonize    ( simpleAnnotator, headAnnotator-                                    , harmonyAnnotator)-import HarmTrace.Audio.ChordTypes (AudioFeat, ChordAnnotation, TimedData)-import HarmTrace.Base.MusicRep (Key)--import Data.List (delete)-import System.FilePath (takeFileName)------------------------------------------------------------------------------------- Command-line arguments-----------------------------------------------------------------------------------data MyArgs = SourceInputString | SourceInputFile | TargetInputFile -            | InputDir | OpMode | Print | MaxErrorRate | BinaryOut | BinaryIn-            | PrintIns | Grammar | TargetKeyInputFile-            | SourceKeyInputFile | AnnotationKeyInputDir| GroundTruthInputFile -            | GroundTruthInputDir-            deriving (Eq, Ord, Show)--myArgs :: [Arg MyArgs]-myArgs = [-          Arg { argIndex = MaxErrorRate,-                argAbbr  = Just 'e',-                argName  = Just "max-err",-                argData  = argDataOptional "float" ArgtypeFloat,-                argDesc  = "Ignore pieces with higher error rate for diff"-              },-          Arg { argIndex = SourceInputString,-                argAbbr  = Just 'c',-                argName  = Just "chords",-                argData  = argDataOptional "string" ArgtypeString,-                argDesc  = "Input Chord Sequence to parse"-              },-          Arg { argIndex = SourceInputFile,-                argAbbr  = Just '1',-                argName  = Just "sfile",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Input file (source for diff)"-              },-          Arg { argIndex = TargetInputFile,-                argAbbr  = Just '2',-                argName  = Just "tfile",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Input file (target for diff)"-              },-          Arg { argIndex = SourceKeyInputFile,-                argAbbr  = Just '3',-                argName  = Just "skey",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Source input ground-truth key annotation file"-              },-          Arg { argIndex = TargetKeyInputFile,-                argAbbr  = Just '4',-                argName  = Just "tkey",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Target input ground-truth key annotation file"-              },-          Arg { argIndex = GroundTruthInputFile,-                argAbbr  = Just '5',-                argName  = Just "gt",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Source input ground-truth chord annotation file"-              },-          Arg { argIndex = GroundTruthInputDir,-                argAbbr  = Just 'a',-                argName  = Just "gt-dir",-                argData  = argDataOptional "dir" ArgtypeString,-                argDesc  = "Ground-truth Annotation directory"-              },-          Arg { argIndex = InputDir,-                argAbbr  = Just 'd',-                argName  = Just "dir",-                argData  = argDataOptional "dir" ArgtypeString,-                argDesc  = "Input directory (process all files within)"-              },-          Arg { argIndex = AnnotationKeyInputDir,-                argAbbr  = Just 'k',-                argName  = Just "key-dir",-                argData  = argDataOptional "dir" ArgtypeString,-                argDesc  = "Ground-truth Key annotation directory"-              },              -          Arg { argIndex = OpMode,-                argAbbr  = Just 'm',-                argName  = Just "mode",-                argData  = argDataRequired "string" ArgtypeString,-                argDesc  = -                  "Mode: parse|stdiff|lces-s|lcessim|hanlign|\n" ++ -  "                                 align|harm-cr|head-cr|simple-cr"-              },-          Arg { argIndex = Grammar,-                argAbbr  = Just 'g',-                argName  = Just "grammar",-                argData  = argDataRequired "string" ArgtypeString,-                argDesc  = "Grammar to use (jazz|pop)"-              },-          Arg { argIndex = Print,-                argAbbr  = Just 'p',-                argName  = Just "print",-                argData  = Nothing,-                argDesc  = "Set this flag to print a .png of the parse"-              },-          Arg { argIndex = PrintIns,-                argAbbr  = Just 's',-                argName  = Just "print-insertions",-                argData  = Nothing,-                argDesc  = "Set this flag to show inserted nodes"-              },-          Arg { argIndex = BinaryOut,-                argAbbr  = Just 'o',-                argName  = Just "out",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Output binary file for parsing results"-              },-          Arg { argIndex = BinaryIn,-                argAbbr  = Just 'i',-                argName  = Just "in",-                argData  = argDataOptional "filepath" ArgtypeString,-                argDesc  = "Input binary file for matching"-              }-         ]------------------------------------------------------------------------------------- Main-----------------------------------------------------------------------------------data SourceData = Audio | ChordLab | Annotation-  -getDataType :: Args MyArgs -> SourceData-getDataType args-  | gotArg args SourceKeyInputFile     = Annotation -- for single files-  | gotArg args AnnotationKeyInputDir  = Annotation -- for directory reading-  | otherwise                          = ChordLab   -- add audio support---- by default all post processing operations are executed             -defaultOpts :: [PPOption]-defaultOpts  = [ RemovePDPT    , RemoveInsertions-               , MergeDelChords, ExpandChordDurations ]    -  -err1, err2, err3, err4 :: String-err1 = "Use a source file, or a directory."-err2 = "Use a source file and a target file, or a directory."-err3 = "Use a source file and optionally a target file."-err4 = "Use an audio-feature location and a ground-truth file, "++-       "or an audio-feature directory and a ground-truth directory."--main :: IO ()-main = do args <- parseArgsIO ArgsComplete myArgs-          let mode = getRequiredArg args OpMode-              grmS = getRequiredArg args Grammar-              prnt = gotArg args Print-              opts = if gotArg args PrintIns -                     then delete RemoveInsertions defaultOpts else defaultOpts-              gram = case grmS of-                       "jazz" -> GrammarEx Jazz-                       "pop"  -> GrammarEx Pop-                       s      -> usageError args ("Unknown grammar: " ++ s)-          case mode of-            "parse"     -> mainParse args opts prnt           gram-            "stdiff"    -> mainMatch args opts False STDiff   gram-            "hanlign"   -> mainMatch args opts prnt  HAnAlign gram-            "lces-s"    -> mainMatch args opts prnt  LCESsize gram-            "lcessim"   -> mainMatch args opts prnt  LCESsim  gram-            "align"     -> mainMatch args opts prnt  Align    gram-            "head-cr"   -> mainChordRec headAnnotator   args prnt-            "simple-cr" -> mainChordRec simpleAnnotator args prnt-            "harm-cr"   -> mainChordRec (harmonyAnnotator gram) args prnt-            s           -> usageError args ("Unknown mode: " ++ s)---mainParse :: Args MyArgs -> [PPOption] -> Bool -> GrammarEx -> IO ()-mainParse args o p (GrammarEx g) =-  do let cStr = getArgString args SourceInputString-         mf1  = getArgString args SourceInputFile-         ky   = getArgString args SourceKeyInputFile-         bOut = getArgString args BinaryOut-         mdir = getArgString args InputDir-         kdir = getArgString args AnnotationKeyInputDir-         sdat = getDataType args-     case (sdat, cStr, mf1,mdir, p, ky, kdir) of-       -- parse a string of chords-       (ChordLab, Just c, Nothing, Nothing , False,Nothing,Nothing)  ->-         do pr <- parseTreeVerb g o c -            mapM_ (print . gTreeHead) (parsedPiece pr)-       -- and print a parsetree     -       (ChordLab, Just c, Nothing, Nothing , True,Nothing,Nothing)   ->-         do pr <- parseTree g o c -            let ts = map gTreeHead (parsedPiece pr)-            _ <- printTreeHAn (pieceTreeHAn pr) (trimFilename ("pp" ++ c))-            printTreeHAnF ts (trimFilename c) >> return ()-       -- Parse one file, show full output-       (ChordLab, Nothing, Just f1, Nothing , False,Nothing,Nothing) -> -         do pr  <- readFile f1 >>= parseTreeVerb g o-            print (pieceTreeHAn pr)-            mapM_ (print . gTreeHead) (parsedPiece pr)-       (ChordLab, Nothing, Just f1, Nothing , True ,Nothing,Nothing) ->-       --with post processing-         do pr <- readFile f1 >>= parseTree g o-            let ts = map gTreeHead (parsedPiece pr)-            printTreeHAn (pieceTreeHAn pr) (f1 ++ ".postProc") >> return ()-            printTreeHAnF ts f1 >> return () -       -- Parse all files in one dir, show condensed output -       (ChordLab, Nothing, Nothing, Just dir, False, Nothing, Nothing) ->-         parseDir g o dir bOut-       -- ** audio ground-truth annotation part **   -       (Annotation, Nothing, Just f1,Nothing, False, Just kf, Nothing) -> -       -- parse a ground-truth annotation and its key and give verbose output      -         do key <- readFile kf-            pr  <- readFile f1 >>= parseAnnotationVerb g o key -            print (pieceTreeHAn pr)-            mapM_ (print . gTreeHead) (take 10 $ parsedPiece pr) -       (Annotation, Nothing, Just f1, Nothing , True , Just kf,Nothing) ->-       -- parse a ground-truth annotation and its key and print the parse -         do key <- readFile kf-            pr  <- readFile f1 >>= parseAnnotation g o key -            let ts = map gTreeHead (parsedPiece pr)-            printTreeHAn (pieceTreeHAn pr) (f1 ++ ".postProc") >> return ()-            printTreeHAnF ts f1 >> return ()   -       -- Parse all files in one dir, show condensed output -       (Annotation, Nothing, Nothing, Just dir,False,Nothing,Just kd) ->-         parseAnnotationDir g o kd dir-       -- Else throw error-       _ -> usageError args err1-                   -trimFilename :: String -> String-trimFilename = filter (\x -> not (elem x ":*")) . concat . words . take 20 -                   -mainMatch :: Args MyArgs -> [PPOption] -> Bool -> MatchMode -> GrammarEx -> IO ()-mainMatch args o p m (GrammarEx g) =-     do let cStr  = getArgString args SourceInputString-            mf1   = getArg args SourceInputFile-            mf2   = getArg args TargetInputFile-            mdir  = getArg args InputDir-            bIn   = getArgString args BinaryIn-            me    = getArg args MaxErrorRate-        case (cStr,mf1,mf2,mdir,p) of-          -- Parse source and target file, show full output-          (_,Just f1, Just f2, Nothing, prnt)   -> -            do c1 <- readFile' f1-               c2 <- readFile' f2-               matchFiles o m prnt c1 c2 f1 f2-          (Just c, Just f1, Nothing, Nothing, True) ->-            matchFiles o m True c f1 (trimFilename c) (trimFilename f1)-          -- match all files in one dir, show condensed output-          (_,Nothing, Nothing, Just dir, False) -> dirMatch g o bIn m me dir -          _                                     -> usageError args err2--matchFiles :: [PPOption] -> MatchMode -> Bool -> String -> String -           -> FilePath -> FilePath -> IO ()-matchFiles o m prnt f1 f2 n1 n2 = -  -- should move to HarmTrace.IO.Main-  let (ParseResult key1 toks1 _ ts1 _nr1 te1 pe1 _) -         = postProc o $ string2Piece Jazz f1-      (ParseResult key2 toks2 _ ts2 _nr2 te2 pe2 _) -         = postProc o $ string2Piece Jazz f2-  in-  do  if not $ null te1 then showErrors "tokenizer 1: " te1 else putStr ""-      if not $ null te2 then showErrors "tokenizer 2: " te2 else putStr ""-      if not $ null pe1 then showErrors "parser 1: " pe1 else putStr ""-      if not $ null pe2 then showErrors "parser 2: " pe2 else putStr ""-      case (m,prnt) of-        (STDiff,_)      -> print (diffChordsLen toks1 toks2)-        (Align  ,False) -> print (getAlignDist key1 key2 toks1 toks2)-        (Align  ,True ) -> do let (mat,v,t) = alignChordLab key1 key2 toks1 toks2 -                              pPrintV t; print mat ; print v-                              -        (HAnAlign,True ) -> do let (mat,v,t) = alignHAnChord ts1 ts2-                               pPrintV t; print mat ; print v-        -- quick and dirty LCES plotting (should move to HarmTrace.IO)-        (LCESsize,True)  -> -           do printTreeHAn ts1 (n1 ++ ".postProc") >> return ()-              printTreeHAn ts2 (n2 ++ ".postProc") >> return ()-              printTreeHAnF (fst $ getLCES ts1 ts2) -                     (  (take 10 $ takeFileName n1) ++ ".vs." -                     ++ (take 10 $ takeFileName n2) ++ ".lces") >> return ()-        _                -> error "Unimplemented." ---- given a audio feature description files and groud-truth annotation, evaluates--- a model-based chord labelling-mainChordRec :: (Maybe [TimedData Key] -> AudioFeat -> ChordAnnotation) -             -> Args MyArgs -> Bool -> IO ()-mainChordRec ann args p = -     do let af     = getArgString args SourceInputFile-            gt     = getArg args GroundTruthInputFile-            key    = getArg args SourceKeyInputFile-            afdir  = getArg args InputDir-            gtdir  = getArg args GroundTruthInputDir-            keydir = getArg args AnnotationKeyInputDir-        case (gt,af,key,gtdir,afdir,keydir,p) of-          -- evaluates a single audio feature set-          (Just g, Just a, Nothing, Nothing, Nothing, Nothing, prnt) -> -            evaluateLabeling ann prnt g a Nothing >>= print -          -- evaluates a single audio feature set and key annotation-          (Just g, Just a, k, Nothing, Nothing, Nothing, prnt) -> -            evaluateLabeling ann prnt g a k >>= print   -          -- evaluates a directory with audio features-          (Nothing, Nothing, Nothing, Just gd, Just ad, Nothing, _ ) -> -            batchLabeling ann gd ad Nothing-          -- evaluates a directory with audio features and key annotations-          (Nothing, Nothing, Nothing, Just gd, Just ad, k, _ ) -> -            batchLabeling ann gd ad k-          _                                        -> usageError args err4+{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      :  Main
+-- Copyright   :  (c) 2010-2012 Universiteit Utrecht, 2012 University of Oxford
+-- License     :  GPL3
+--
+-- Maintainer  :  bash@cs.uu.nl, jpm@cs.ox.ac.uk
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Summary: Top-level file
+--------------------------------------------------------------------------------
+
+module Main where
+
+import System.Console.CmdArgs.Explicit hiding (Arg)
+
+import Constants (vERSION)
+
+import HarmTrace.HarmTrace
+import HarmTrace.IO.Main
+import HarmTrace.IO.Errors
+import HarmTrace.IO.PrintTree
+import HarmTrace.IO.BasePaths (setPaths)
+import HarmTrace.HAnTree.ToHAnTree (gTreeHead)
+import HarmTrace.Matching.Standard
+import HarmTrace.Matching.GuptaNishimura (getLCES)
+import HarmTrace.Matching.Alignment (getAlignDist, alignChordLab
+                                    , pPrintV, alignHAnChord)
+import HarmTrace.Audio.Annotate     ( simpleAnnotator, groupAnnotator
+                                    , mptreeAnnotator)
+import HarmTrace.Audio.ChordTypes -- (AudioFeat, ChordBeatAnnotation, TimedData)
+import HarmTrace.Base.MusicRep (Key)
+
+import Data.List (delete)
+import System.FilePath (takeFileName)
+import System.Exit (exitSuccess, exitFailure)
+import Control.Monad (when)
+
+
+--------------------------------------------------------------------------------
+-- Top-level main
+--------------------------------------------------------------------------------
+harmTrace :: Mode [MyArgs]
+harmTrace = (modes "harmtrace" emptyArgs "Harmonic Analysis and Retrieval of Music"
+              [parseMode, matchMode, recogniseMode])
+            { modeGroupFlags = toGroup (inputFlags : helpVerFlags) }
+            
+inputFlags :: Flag [MyArgs]
+inputFlags = flagReq ["r", "read-flags"] (upd "ReadFlags") 
+               "file" "File to read flags from"
+
+main :: IO ()
+main = do -- Parse the arguments
+          args <- processArgs harmTrace
+          
+          -- Check if we should get flags from a file
+          -- NOTE: reading flags such as --chords="A B" will not work, because
+          -- there are spaces inside the string "A B", and `words` isn't clever
+          -- enough to ignore those.
+          newArgs <- case (getArg args "ReadFlags") of
+                       -- Nope, use the flags received
+                       Nothing -> return args
+                       -- Yes, read them from the file and overwrite the
+                       -- received flags
+                       Just f  -> readFile f >>=
+                                    return . processValue harmTrace . words
+          
+          -- Check mode and redirect control to appropriate function
+          case (getArg newArgs "Mode") of
+            Just "Parse"     -> mainParse newArgs
+            Just "Match"     -> mainMatch newArgs
+            Just "Recognise" -> mainRecognise newArgs
+            Just m           -> putStrLn ("Unknown mode: " ++ m)
+            Nothing          -> do -- Handle help and version, if present
+                                   handleHelpVer newArgs harmTrace
+                                   putStrLn "Please supply a mode"
+
+--------------------------------------------------------------------------------
+-- Parse mode
+--------------------------------------------------------------------------------
+parseMode :: Mode [MyArgs]
+parseMode = mode "parse" [Arg "Mode" "Parse"] "Parse files into harmonic analysis trees"
+              (flagArg upd0 "")
+              ([flagReq ["g", "grammar"] updG                          "string"   "Grammar to use (jazz|pop)"
+               ,flagReq ["c", "chords"]  (upd "SourceInputString")     "string"   "Input chord sequence to parse"
+               ,flagReq ["i", "file"]    (upd "SourceInputFile")       "filepath" "Input file to parse"
+               ,flagReq ["d", "dir"]     (upd "InputDir")              "filepath" "Input directory to parse all files within"
+               ,flagReq ["o", "out"]     (upd "BinaryOut")             "filepath" "Output binary file to write parse results to"
+               -- We should add a flag for describing that when using -k we are
+               -- reading files in a different input syntax...
+               ,flagReq ["k", "key"]     (upd "SourceKeyInputFile")  "filepath" "Ground-truth key annotation file"
+               ,flagReq ["x", "key-dir"] (upd "AnnotationKeyInputDir")   "filepath" "Ground-truth key annotation directory"
+               ,flagNone ["p", "print"] ((Switch "Print"):)                       "Output a .png of the parse tree"
+               ,flagNone ["s", "print-insertions"] ((Switch "PrintIns"):)         "Show inserted nodes"
+               ] ++ helpVerFlags)
+
+  where -- Flags without input
+        upd0 "--print"            args = Right $ Switch "Print"    : args
+        upd0 "-p"                 args = Right $ Switch "Print"    : args
+        upd0 "--print-insertions" args = Right $ Switch "PrintIns" : args
+        upd0 "-s"                 args = Right $ Switch "PrintIns" : args
+        upd0 s                    _    = Left  $ "Unknown argument: " ++ s
+
+mainParse :: [MyArgs] -> IO ()
+mainParse args =
+ do let cStr = getArg args "SourceInputString"
+        sif  = getArg args "SourceInputFile"
+        ky   = getArg args "SourceKeyInputFile"
+        bOut = getArg args "BinaryOut"
+        mdir = getArg args "InputDir"
+        kdir = getArg args "AnnotationKeyInputDir"
+        prnt = gotArg args "Print"
+        opts = if gotArg args "PrintIns"
+               then delete RemoveInsertions defaultOpts else defaultOpts
+
+    -- Handle help and version, if present
+    handleHelpVer args parseMode
+
+    case (getGram args) of
+      Nothing -> putStrLn "Please supply a grammar to use" >> exitFailure
+      Just (GrammarEx g) ->
+        case (cStr, sif,mdir, prnt, ky, kdir) of
+          -- parse a string of chords
+          (Just c, Nothing, Nothing, False, Nothing, Nothing)  ->
+            do pr <- parseTreeVerb g opts c
+               mapM_ (print . gTreeHead) (parsedPiece pr)
+          -- and print a parsetree     
+          (Just c, Nothing, Nothing, True, Nothing, Nothing)   ->
+            do pr <- parseTree g opts c 
+               let ts = map gTreeHead (parsedPiece pr)
+               _ <- printTreeHAn (pieceTreeHAn pr) (trimFilename ("pp" ++ c))
+               printTreeHAnF ts (trimFilename c) >> return ()
+          -- Parse one file, show full output
+          (Nothing, Just f1, Nothing, False, Nothing, Nothing) -> 
+            do pr  <- readFile f1 >>= parseTreeVerb g opts
+               print (pieceTreeHAn pr)
+               mapM_ (print . gTreeHead) (parsedPiece pr)
+          (Nothing, Just f1, Nothing , True, Nothing, Nothing) ->
+          --with post processing
+            do pr <- readFile f1 >>= parseTree g opts
+               let ts = map gTreeHead (parsedPiece pr)
+               printTreeHAn (pieceTreeHAn pr) (f1 ++ ".postProc") >> return ()
+               printTreeHAnF ts f1 >> return () 
+          -- Parse all files in one dir, show condensed output 
+          (Nothing, Nothing, Just dir, False, Nothing, Nothing) ->
+            parseDir g opts dir bOut
+          -- ** audio ground-truth annotation part **   
+          (Nothing, Just f1, Nothing, False, Just kf, Nothing) -> 
+          -- parse a ground-truth annotation and its key and give verbose output      
+            do key <- readFile kf
+               pr  <- readFile f1 >>= parseAnnotationVerb g opts key
+               print (pieceTreeHAn pr)
+               mapM_ (print . gTreeHead) (take 10 $ parsedPiece pr)
+          (Nothing, Just f1, Nothing, True , Just kf, Nothing) ->
+          -- parse a ground-truth annotation and its key and print the parse 
+            do key <- readFile kf
+               pr  <- readFile f1 >>= parseAnnotation g opts key 
+               let ts = map gTreeHead (parsedPiece pr)
+               printTreeHAn (pieceTreeHAn pr) (f1 ++ ".postProc") >> return ()
+               printTreeHAnF ts f1 >> return ()   
+          -- Parse all files in one dir, show condensed output 
+          (Nothing, Nothing, Just dir, False, Nothing, Just kd) ->
+            parseAnnotationDir g opts kd dir
+          -- Else throw error
+          _ -> usageError args err1
+
+--------------------------------------------------------------------------------
+-- Match mode
+--------------------------------------------------------------------------------
+matchMode :: Mode [MyArgs]
+matchMode = mode "match"  [Arg "Mode" "Match"] "Harmonic similarity matching"
+              (flagArg upd0 "")
+              ([flagReq ["m", "mode"]   upd1 "string" "Matching mode (stdiff|lces-s|lcessim|hanlign|align)"
+               ,flagReq ["1", "sfile"]  (upd "SourceInputFile") "filepath" "Source file to match"
+               ,flagReq ["2", "tfile"]  (upd "TargetInputFile") "filepath" "Target file to match"
+               ,flagReq ["d", "dir"]    (upd "InputDir") "filepath" "Input directory to parse all files within"
+               ,flagReq ["i", "in"]     (upd "BinaryIn") "filepath" "Input binary file for matching"
+               ,flagNone ["s", "print-insertions"] ((Switch "PrintIns"):) "Show inserted nodes"
+               ,flagReq ["g", "grammar"] updG          "string"   "Grammar to use (jazz|pop)"
+               ] ++ helpVerFlags)
+
+  where upd0 "--print-insertions" args = Right $ Switch "PrintIns" : args
+        upd0 "-s"                 args = Right $ Switch "PrintIns" : args
+        upd0 s                    _    = Left  $ "Unknown argument: " ++ s
+
+        upd1 "stdiff"  args = Right $ (MatchMode STDiff)   : args
+        upd1 "lces-s"  args = Right $ (MatchMode LCESsize) : args
+        upd1 "lcessim" args = Right $ (MatchMode LCESsim)  : args
+        upd1 "hanlign" args = Right $ (MatchMode HAnAlign) : args
+        upd1 "align"   args = Right $ (MatchMode Align)    : args
+        upd1 s         _    = Left  $ "Unknown match mode: " ++ s
+
+mainMatch :: [MyArgs] -> IO ()
+mainMatch args =
+ do let cStr  = getArg args "SourceInputString"
+        sif   = getArg args "SourceInputFile"
+        mf2   = getArg args "TargetInputFile"
+        mdir  = getArg args "InputDir"
+        bIn   = getArg args "BinaryIn"
+        me    = getArg args "MaxErrorRate"
+        mode  = getMode args
+        prnt  = gotArg args "Print"
+        opts  = if gotArg args "PrintIns"
+                then delete RemoveInsertions defaultOpts else defaultOpts
+
+    -- Handle help and version, if present
+    handleHelpVer args matchMode
+    
+    case (getGram args) of
+      Nothing -> putStrLn "Please supply a grammar to use" >> exitFailure
+      Just (GrammarEx g) ->
+        case (cStr,sif,mf2,mdir,prnt) of
+          -- Parse source and target file, show full output
+          (_, Just f1, Just f2, Nothing, _)   -> 
+            do c1 <- readFile' f1
+               c2 <- readFile' f2
+               matchFiles opts mode prnt c1 c2 f1 f2
+          (Just c, Just f1, Nothing, Nothing, True) ->
+            matchFiles opts mode True c f1 (trimFilename c) (trimFilename f1)
+          -- match all files in one dir, show condensed output
+          (_,Nothing, Nothing, Just dir, False) -> dirMatch g opts bIn mode (fmap read me) dir 
+          _                                     -> usageError args err2
+
+matchFiles :: [PPOption] -> MatchMode -> Bool -> String -> String 
+           -> FilePath -> FilePath -> IO ()
+matchFiles o m prnt f1 f2 n1 n2 = 
+  -- should move to HarmTrace.IO.Main
+  let (ParseResult key1 toks1 _ ts1 _nr1 te1 pe1 _) 
+         = postProc o $ string2Piece Jazz f1
+      (ParseResult key2 toks2 _ ts2 _nr2 te2 pe2 _) 
+         = postProc o $ string2Piece Jazz f2
+  in
+  do  if not $ null te1 then showErrors "tokenizer 1: " te1 else putStr ""
+      if not $ null te2 then showErrors "tokenizer 2: " te2 else putStr ""
+      if not $ null pe1 then showErrors "parser 1: " pe1 else putStr ""
+      if not $ null pe2 then showErrors "parser 2: " pe2 else putStr ""
+      case (m,prnt) of
+        (STDiff,_)      -> print (diffChordsLen toks1 toks2)
+        (Align  ,False) -> print (getAlignDist key1 key2 toks1 toks2)
+        (Align  ,True ) -> do let (mat,v,t) = alignChordLab key1 key2 toks1 toks2 
+                              pPrintV t; print mat ; print v
+                              
+        (HAnAlign,True ) -> do let (mat,v,t) = alignHAnChord ts1 ts2
+                               pPrintV t; print mat ; print v
+        -- quick and dirty LCES plotting (should move to HarmTrace.IO)
+        (LCESsize,True)  -> 
+           do printTreeHAn ts1 (n1 ++ ".postProc") >> return ()
+              printTreeHAn ts2 (n2 ++ ".postProc") >> return ()
+              printTreeHAnF (fst $ getLCES ts1 ts2) 
+                     (  (take 10 $ takeFileName n1) ++ ".vs." 
+                     ++ (take 10 $ takeFileName n2) ++ ".lces") >> return ()
+        _                -> error "Unimplemented."
+
+--------------------------------------------------------------------------------
+-- Recognise mode
+--------------------------------------------------------------------------------
+
+recogniseMode :: Mode [MyArgs]
+recogniseMode = mode "recognise" [Arg "Mode" "Recognise"] "Recognise chords from audio files"
+              (flagArg upd0 "")
+              ([flagReq ["m", "mode"]   upd1                           "string"   "Recognition mode (mptree|group|simple)"
+               ,flagReq ["i", "file"]   (upd "SourceInputFile")        "filepath" "Input file"
+               ,flagReq ["c", "gt"]     (upd "GroundTruthInputFile")   "filepath" "Ground truth chord annotation file (for evaluation)"
+               ,flagReq ["k", "key"]    (upd "SourceKeyInputFile")     "filepath" "Ground truth key annotation file"
+               ,flagReq ["d", "dir"]    (upd "InputDir")               "filepath" "Input directory"
+               ,flagReq ["v", "vamp-dir"] (upd "VampBaseDir")          "filepath" "Vamp-plugin transform specification base directory"
+               ,flagReq ["o", "out-dir"]  (upd "OutputDir")          "filepath" "output directory (for logs and chords)"
+               ,flagReq ["f", "csv-dir"]  (upd "CSVBaseDir")          "filepath" "directory for storing the feature csv files"
+               ,flagReq ["t", "gt-dir"] (upd "GroundTruthInputDir")    "filepath" "Ground truth chord annotation input directory (for evaluation)"
+               ,flagReq ["x", "key-dir"] (upd "AnnotationKeyInputDir") "filepath" "Ground truth chord annotation input directory (for evaluation)"
+               ,flagReq ["g", "grammar"] updG                          "string"   "Grammar to use (jazz|pop)"
+               ,flagNone ["p", "print"] ((Switch "Print"):)                       "Output a .png of the parse tree"
+               ] ++ helpVerFlags)
+
+  where upd0 "--print"            args = Right $ Switch "Print"    : args
+        upd0 "-p"                 args = Right $ Switch "Print"    : args
+        upd0 s                    _    = Left  $ "Unknown argument: " ++ s
+
+        upd1 "group"  args = Right $ (RecognitionMode (RecognitionFun groupAnnotator))  : args
+        upd1 "simple" args = Right $ (RecognitionMode (RecognitionFun simpleAnnotator)) : args
+        upd1 "mptree" args = Right $ (RecognitionMode (RecognitionFun mptreeAnnotator)) : args
+        upd1 s        _    = Left  $ "Unknown recognition mode: " ++ s
+
+mainRecognise :: [MyArgs] -> IO ()
+mainRecognise args = 
+ do let af     = getArg args "SourceInputFile"
+        gt     = getArg args "GroundTruthInputFile"
+        key    = getArg args "SourceKeyInputFile"
+        afdir  = getArg args "InputDir"
+        gtdir  = getArg args "GroundTruthInputDir"
+        keydir = getArg args "AnnotationKeyInputDir"
+        prnt   = gotArg args "Print"
+        dirs   = setPaths (getArg args "VampBaseDir")
+                          (getArg args "CSVBaseDir")
+                          (getArg args "OutputDir")
+
+    
+    -- Handle help and version, if present
+    handleHelpVer args recogniseMode
+
+    case (getGram args) of
+      Nothing -> putStrLn "Please supply a grammar to use" >> exitFailure
+      Just g ->
+        let ann :: Maybe [TimedData Key] -> AudioFeat -> ChordBeatAnnotation
+            ann = (getAnn args) g
+        in case (gt,af,key,gtdir,afdir,keydir) of
+          -- evaluates a single audio feature set
+          (Just g, Just a, Nothing, Nothing, Nothing, Nothing) -> 
+            evaluateLabeling ann prnt g a Nothing >>= print 
+          -- evaluates a single audio feature set and key annotation
+          (Just g, Just a, maybek, Nothing, Nothing, Nothing) -> 
+            evaluateLabeling ann prnt g a maybek >>= print
+          -- evaluates a single audio feature set and prints the auto-labelling
+          -- This is the only option that automatically extracts features
+          -- from an audio file (if no features, and an audio file are found)
+          -- TODO enable feature extraction for all options
+          (Nothing, Just a, Nothing, Nothing, Nothing, Nothing) -> 
+            printLabeling dirs ann a            
+          -- evaluates a directory with audio features
+          (Nothing, Nothing, Nothing, Just gd, Just ad, Nothing) -> 
+            batchLabeling ann gd ad Nothing
+          -- evaluates a directory with audio features and key annotations
+          (Nothing, Nothing, Nothing, Just gd, Just ad, maybek) -> 
+            batchLabeling ann gd ad maybek    
+          _                                        -> usageError args err4
+
+--------------------------------------------------------------------------------
+-- Arguments and utilities
+--------------------------------------------------------------------------------
+data MyArgs = Grammar GrammarEx
+            | MatchMode MatchMode
+            | RecognitionMode RecognitionFun
+            | Arg String String
+            | Switch String
+            deriving (Eq, Show)
+
+newtype RecognitionFun = RecognitionFun (GrammarEx -> Maybe [TimedData Key]
+                                           -> AudioFeat -> ChordBeatAnnotation)
+
+instance Show RecognitionFun where
+  show _ = "RecognitionFun"
+
+instance Eq RecognitionFun where
+  _ == _ = error "please do not use this"
+
+emptyArgs :: [MyArgs]
+emptyArgs = []
+
+-- Grammar parser
+updG :: String -> [MyArgs] -> Either String [MyArgs]
+updG "jazz" args = Right $ (Grammar (GrammarEx Jazz)) : args
+updG "pop"  args = Right $ (Grammar (GrammarEx Pop))  : args
+updG s      _    = Left  $ "Unknown grammar: " ++ s
+
+-- General update function
+upd :: String -> String -> [MyArgs] -> Either String [MyArgs]
+upd c s args = Right $ (Arg c s) : args
+
+getGram :: [MyArgs] -> Maybe GrammarEx
+getGram []              = Nothing
+getGram ((Grammar g):_) = Just g
+getGram (_:t)           = getGram t
+
+getMode :: [MyArgs] -> MatchMode
+getMode []                = error "impossible?"
+getMode ((MatchMode m):_) = m
+getMode (_:t)             = getMode t
+
+getAnn :: [MyArgs] -> (GrammarEx -> Maybe [TimedData Key] -> AudioFeat
+                       -> ChordBeatAnnotation)
+getAnn []                                       = error "impossible?"
+getAnn ((RecognitionMode (RecognitionFun f)):_) = f
+getAnn (_:t)                                    = getAnn t
+
+gotArg :: [MyArgs] -> String -> Bool
+gotArg []             _          = False
+gotArg ((Switch k):_) s | k == s = True
+gotArg (_:t)          s          = gotArg t s 
+
+getArg :: [MyArgs] -> String -> Maybe String
+getArg []            _             = Nothing
+getArg ((Arg k v):_) s | k == s    = Just v
+getArg (_:t)         s             = getArg t s
+
+-- Help and Version flags, used in all modes
+helpVerFlags :: [Flag [MyArgs]]
+helpVerFlags = [ flagHelpSimple ((Switch "Help"):)
+               , flagVersion ((Switch "Version"):)]
+
+-- Handler for the Help and Version flags
+handleHelpVer :: [MyArgs] -> Mode [MyArgs] -> IO ()
+handleHelpVer args mode = do
+    let ver  = gotArg args "Version"
+        help = gotArg args "Help"
+
+    -- Output version and exit
+    when ver $ putStrLn vERSION >> exitSuccess
+
+    -- Output help and exit
+    when help $ print (helpText [] HelpFormatDefault mode) >> exitSuccess
+
+--------------------------------------------------------------------------------
+-- Misc
+--------------------------------------------------------------------------------
+err1, err2, err3, err4 :: String
+err1 = "Use a source file, or a directory."
+err2 = "Use a source file and a target file, or a directory."
+err3 = "Use a source file and optionally a target file."
+err4 = "Use an audio-feature location and a ground-truth file, "++
+       "or an audio-feature directory and a ground-truth directory."
+
+usageError :: [MyArgs] -> String -> IO ()
+usageError _args err = putStrLn err
+
+trimFilename :: String -> String
+trimFilename = filter (\x -> not (elem x ":*")) . concat . words . take 20 
+
+-- by default all post processing operations are executed             
+defaultOpts :: [PPOption]
+defaultOpts  = [ RemovePDPT    , RemoveInsertions
+               , MergeDelChords, ExpandChordDurations ]