# HG changeset patch # User Fiore Martin # Date 1440602213 -3600 # Node ID 3074a84ef81e89c8268a4f82657c0b90cb10cde6 first import diff -r 000000000000 -r 3074a84ef81e .classpath --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.classpath Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff -r 000000000000 -r 3074a84ef81e .project --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.project Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,17 @@ + + + Daw + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff -r 000000000000 -r 3074a84ef81e .settings/org.eclipse.jdt.core.prefs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.settings/org.eclipse.jdt.core.prefs Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff -r 000000000000 -r 3074a84ef81e libs/beads.jar Binary file libs/beads.jar has changed diff -r 000000000000 -r 3074a84ef81e libs/jhapticgui.jar Binary file libs/jhapticgui.jar has changed diff -r 000000000000 -r 3074a84ef81e libs/jl1.0.1.jar Binary file libs/jl1.0.1.jar has changed diff -r 000000000000 -r 3074a84ef81e libs/mp3spi1.9.4.jar Binary file libs/mp3spi1.9.4.jar has changed diff -r 000000000000 -r 3074a84ef81e libs/tritonus_share.jar Binary file libs/tritonus_share.jar has changed diff -r 000000000000 -r 3074a84ef81e license.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/license.txt Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics.sln --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics.sln Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DawHaptics", "DawHaptics\DawHaptics.vcxproj", "{63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Debug|Win32.ActiveCfg = Debug|Win32 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Debug|Win32.Build.0 = Debug|Win32 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Debug|x64.ActiveCfg = Debug|x64 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Debug|x64.Build.0 = Debug|x64 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Release|Win32.ActiveCfg = Release|Win32 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Release|Win32.Build.0 = Release|Win32 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Release|x64.ActiveCfg = Release|x64 + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/Commands.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/Commands.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,51 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#include "Commands.h" + + +const char* jhapticgui::InCommands::DISPLAY_SEQUENCE = "display.seq"; +const char* jhapticgui::InCommands::SEQUENCE_VALUE_ADD = "seq.value.add"; +const char* jhapticgui::InCommands::SEQUENCE_VALUE_CHANGE = "seq.value.change"; +const char* jhapticgui::InCommands::SEQUENCE_VALUE_REMOVE = "seq.value.rem"; +const char* jhapticgui::InCommands::SEQUENCE_VALUE_FIND = "seq.value.find"; +const char* jhapticgui::InCommands::SEQUENCE_TRANSLATE = "seq.translate"; +const char* jhapticgui::InCommands::SEQUENCE_SHIFT = "seq.shift"; +const char* jhapticgui::InCommands::SEQUENCE_BEGIN = "seq.begin"; + +const char* jhapticgui::InCommands::DISPLAY_RENDER_VALUE = "display.value"; +const char* jhapticgui::InCommands::RENDER_VALUE = "render_value"; +const char* jhapticgui::InCommands::DISPLAY_PEAKS = "display.peaks"; +const char* jhapticgui::InCommands::DISPLAY_NONE = "display.none"; +const char* jhapticgui::InCommands::ROTATE_Z = "rotate.z"; +const char* jhapticgui::InCommands::ROTATE_Y = "rotate.y"; +const char* jhapticgui::InCommands::ROTATE_X = "rotate.x"; + +const char* jhapticgui::OutCommands::SCRUB_ON = "scrub.on"; +const char* jhapticgui::OutCommands::SCRUB_OFF = "scrub.off"; +const char* jhapticgui::OutCommands::SCRUB_SHIFT = "scrub.shift"; + +const char* jhapticgui::OutCommands::ADD_SEQ_VAL = "seq.val.add"; +const char* jhapticgui::OutCommands::REM_SEQ_VAL = "seq.val.rem" ; +const char* jhapticgui::OutCommands::MOVE_SEQ_VAL= "seq.val.move"; +const char* jhapticgui::OutCommands::PICK_SEQ_VAL = "seq.val.pick"; +const char* jhapticgui::OutCommands::DROP_SEQ_VAL = "seq.val.drop"; + + +const char* jhapticgui::OutCommands::LINE_TOUCHED = "line.touched"; +const char* jhapticgui::OutCommands::POINT_TOUCHED = "point.touched"; \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/Commands.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/Commands.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,56 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ + +#pragma once + +namespace jhapticgui { + struct InCommands { + static const char* DISPLAY_SEQUENCE; + static const char* SEQUENCE_BEGIN; + static const char* SEQUENCE_VALUE_ADD; + static const char* SEQUENCE_VALUE_CHANGE; + static const char* SEQUENCE_VALUE_REMOVE; + static const char* SEQUENCE_VALUE_FIND; + static const char* SEQUENCE_TRANSLATE; + static const char* SEQUENCE_SHIFT; + static const char* DISPLAY_RENDER_VALUE; + static const char* RENDER_VALUE; + static const char* ROTATE_Z; + static const char* ROTATE_Y; + static const char* ROTATE_X; + static const char* DISPLAY_NONE; + static const char* DISPLAY_PEAKS; + }; + + struct OutCommands { + static const char* SCRUB_ON ; + static const char* SCRUB_OFF ; + static const char* SCRUB_SHIFT; + + static const char* ADD_SEQ_VAL; + static const char* REM_SEQ_VAL; + static const char* PICK_SEQ_VAL; + static const char* MOVE_SEQ_VAL; + static const char* DROP_SEQ_VAL; + + static const char* LINE_TOUCHED; + static const char* POINT_TOUCHED; + }; + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/DawHaptics.vcxproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/DawHaptics.vcxproj Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {63CB2DAE-8F26-45B9-A4CD-4D7335BFC501} + v4.5 + ManagedCProj + DawHaptics + + + + Application + true + true + Unicode + v120 + + + Application + true + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + ..\..\..\bin\uk\ac\qmul\eecs\depic\daw\haptics\ + + + true + ..\..\..\bin\uk\ac\qmul\eecs\depic\daw\haptics\ + + + false + ..\..\..\bin\uk\ac\qmul\eecs\depic\daw\haptics\ + + + false + ..\..\..\bin\uk\ac\qmul\eecs\depic\daw\haptics\ + + + + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + C:\Program Files\Java\jdk1.7.0_51\include;C:\Program Files\Java\jdk1.7.0_51\include\win32;C:\H3D\HAPI\include;C:\H3D\H3DUtil\include;C:\H3D\External\include;C:\H3D\External\include\pthread;C:\H3D\HAPI\OpenHapticsRenderer\include;C:\Program Files\SensAble\3DTouch\include;%(AdditionalIncludeDirectories) + + + true + HAPI_vc10.lib;H3DUtil_vc10.lib;OpenHapticsRenderer_vc10.lib;pthreadVC2.lib;%(AdditionalDependencies) + C:\H3D\lib32;C:\H3D\External\lib32 + + + + + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + C:\Program Files\Java\jdk1.7.0_51\include;C:\Program Files\Java\jdk1.7.0_51\include\win32;C:\H3D\HAPI\include;C:\H3D\H3DUtil\include;C:\H3D\External\include;C:\H3D\External\include\pthread;C:\H3D\HAPI\OpenHapticsRenderer\include;C:\Program Files\SensAble\3DTouch\include;%(AdditionalIncludeDirectories) + + + true + HAPI_vc10.lib;H3DUtil_vc10.lib;OpenHapticsRenderer_vc10.lib;pthreadVC2.lib;%(AdditionalDependencies) + C:\H3D\lib32;C:\H3D\External\lib32 + + + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + C:\Program Files\Java\jdk1.7.0_51\include;C:\Program Files\Java\jdk1.7.0_51\include\win32;C:\H3D\HAPI\include;C:\H3D\H3DUtil\include;C:\H3D\External\include;C:\H3D\External\include\pthread;C:\H3D\HAPI\OpenHapticsRenderer\include;C:\Program Files (x86)\Novint\HDAL_SDK_2.1.3\include;C:\Program Files\SensAble\3DTouch\include;C:\Program Files\SensAble\3DTouch\utilities\include;%(AdditionalIncludeDirectories) + + + true + glut32.lib;hl.lib;hd.lib;hlu.lib;hdu.lib;%(AdditionalDependencies) + C:\H3D\lib32;C:\H3D\External\lib32;C:\Program Files\SensAble\3DTouch\lib\x64;C:\Program Files\SensAble\3DTouch\utilities\lib\x64\Release + + + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32;C:\Program Files (x86)\Novint\HDAL_SDK_2.1.3\include;C:\Program Files\SensAble\3DTouch\include;C:\Program Files\SensAble\3DTouch\utilities\include;%(AdditionalIncludeDirectories) + + + true + glut32.lib;hl.lib;hd.lib;hlu.lib;hdu.lib;%(AdditionalDependencies) + C:\Program Files\SensAble\3DTouch\lib\x64;C:\Program Files\SensAble\3DTouch\utilities\lib\x64\Release + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/DawHaptics.vcxproj.filters --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/DawHaptics.vcxproj.filters Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,72 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/DawHaptics.vcxproj.user --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/DawHaptics.vcxproj.user Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/Graph.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/Graph.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,139 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#include "StdAfx.h" +#include "Graph.h" + + +namespace jhapticgui { + Graph::Graph(HLdouble startX, HLdouble endX, HLdouble y, float width, float viewPort) : + initialYValue(y), widthMs(width), viewPortMs(viewPort) { + + start = new HPoint(0); + start->setCoord(startX, y); + + end = new HPoint(0); + end->setCoord(endX, y); + +} + +Graph::~Graph(void){ + while(!points.empty()){ + delete points.back(); + points.pop_back(); + } + + delete start; + delete end; +} + +Graph::Graph(const Graph & aGraph) { std::cout << "WARNING COPY CONSTRUCTOR CALLED " << std::endl; } + +const HPoint & Graph::addPoint(long hashCode, float x, float y){ + /* takes into account the viewport shift when adding the point */ + HPoint* p = new HPoint(hlGenShapes(1), x, y); + p->setHashCode(hashCode); + + /* ordered insert into the vector. look for the position where to insert. */ + std::vector::iterator indexItr; + for(indexItr = points.begin();indexItr != points.end(); indexItr++){ + if(compareX(*p, *(*indexItr)) < 0){ // move forward until the righ position is found + break; + } + } + + /* insert the new point */ + points.insert(indexItr,p); + + /* find the neighbours of the new point */ + HPoint* prev = start; + HPoint* next = end; + unsigned int pointsSize = points.size(); + for(unsigned int i=0; i0) + prev = points[i-1]; + if(i::iterator itr = points.begin(); itr != points.end(); itr++){ + if((*itr)->getHashCode() == hashCode){ + (*itr)->setCoord(x,y); + break; + } + } +} + +void Graph::removePoint(long hashCode){ + HPoint* pointToRemove = NULL; + /* look for the point to remove */ + for (std::vector::iterator itr = points.begin(); itr != points.end(); itr++){ + if((*itr)->getHashCode() == hashCode){ + pointToRemove = (*itr); + points.erase(itr); + break; + } + } + + if(pointToRemove == NULL){ // FIXME must give feedback + return; + } + + delete pointToRemove; +} + +int Graph::compareX(const HPoint & p1, const HPoint & p2) { + if( p1.getX() < p2.getX() ) + return -1; + else if(p1.getX() > p2.getX() ) + return 1; + else return 0; +} + +void Graph::translate( float x, float y) { + for (std::vector::iterator itr = points.begin(); itr != points.end(); itr++) { + HPoint* p = (*itr); + /* set new coordinate for this point */ + (*itr)->setCoord(static_cast(p->getX()+x),static_cast(p->getY()+y)); + } +} + +const HPoint & Graph::getStart() const { + return *start; +} + +const HPoint & Graph::getEnd() const { + return *end; +} + +void Graph::setStart(HDdouble startY){ + start->setCoord(start->getX(), startY); +} + +const std::vector & Graph::getPoints(){ + return points; +} + +} diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/Graph.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/Graph.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,79 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ + +#pragma once + +#include +#include +#include "HapticObjects.h" + +namespace jhapticgui { + +/** + * A Data structure containing all the data to represent a graph either visually or haptically. + * The graph also encompasses the concept of viewport, which is the portion of the graph which is visible + * or touchable when its dimension exceeds the scope of the screen and haptic device. + */ +class Graph{ + float initialYValue; + float widthMs; + float viewPortMs; + /* hidden copy constructor */ + Graph(const Graph & aGraph); + + HPoint *start; + HPoint *end; +public : + + std::vector points; + + Graph(HLdouble startX, HLdouble endX, HLdouble y, float width, float viewPort); + ~Graph(void); + + const HPoint & addPoint(long hashCode, float x, float y); + void changePoint(long hashCode, float x, float y); + void removePoint(long hashCode); + + const HPoint & getStart() const; + + const HPoint & getEnd() const; + + const std::vector & getPoints(); + + void setStart(double startX); + + void translate(float x, float y); + + static int compareX(const HPoint & p1, const HPoint & p2); + + inline float getInitialYValue(){ + return initialYValue; + } + + inline float getWidthMs(){ + return widthMs; + } + + inline float getViewPortMs(){ + return viewPortMs; + } + +}; + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/HapticObjects.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/HapticObjects.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,94 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#pragma once + +#include +#include +#include + +class HPoint { + HLuint hapticID; + HDdouble x, y; + GLuint displayList; + int hashCode; + +public: + + HPoint(HLuint hID) : hapticID(hID), x(0), y(0) { } + HPoint(HLuint hID, HDdouble _x, HDdouble _y) : hapticID(hID), x(_x), y(_y) { } + //Point(HLuint id, HDdouble _x, HDdouble _y) : hapticID(id), x(_x), y(_y) {} + + ~HPoint(){ + hlDeleteShapes(hapticID, 1); + } + + inline void setHashCode(int hash){ + hashCode = hash; + } + + inline const int getHashCode() const{ + return hashCode; + } + + inline void setCoord(HDdouble _x, HDdouble _y){ + x = _x; y = _y; + } + + inline HDdouble getX() const { + return x; + } + + const HLuint getHapticID() const { + return hapticID; + } + + inline HDdouble getY() const { + return y; + } + +}; + +struct HLine { + + HLine() : hapticID(hlGenShapes(1)) {} + ~HLine(){ + hlDeleteShapes(hapticID, 1); + } + + HLuint getID() const { + return hapticID; + } + + inline void setHashCode(int hash){ + hashCode = hash; + } + + inline const int getHashCode() const{ + return hashCode; + } + + HDdouble X1; + HDdouble Y1; + HDdouble X2; + HDdouble Y2; + int hashCode; +private : + HLuint hapticID; + +}; diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/HapticScene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/HapticScene.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,94 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#pragma once + + +#include "Message.h" +#include + + +namespace jhapticgui { + + class HapticScene { + + int mWidth; + int mHeight; + + protected: + void (*send) (const Message &m); + + + + double mCursorScale; + double mWorldScale; + public: + + inline int getWidth() { + return mWidth; + } + + inline int getHeight() { + return mHeight; + } + + void setWorldScale(double scale){ + mWorldScale = scale; + } + + void setCursorScale(double scale){ + mCursorScale = scale; + } + + HapticScene(void (*messageCallback) (const Message & m) ) : send(messageCallback) {} + + virtual ~HapticScene(void){} + + /* try and initialize the haptic device, return false if the initialization fails */ + virtual bool initHaptics(void) = 0; + inline virtual void setSize(int w, int h){ + mWidth = w; + mHeight = h; + } + + virtual unsigned int processMessage(const Message & m) = 0; + + virtual void beginFrame(void) = 0; + /* drawing routines (both graphically and haptically) */ + virtual void drawCursor(void) = 0; + + virtual void updateWorkspace(void) = 0; + + /* + Draws one frame both in graphics and haptics. + + messageUpdate and callbacksUpdate return are integer codes defining what needs to be updated + in the haptic and graphic scenes. The values that must be passed to this method is returned + by precessMessage. + */ + virtual void drawScene(unsigned int messageUpdate, unsigned int callbacksUpdate) = 0; + + virtual void endFrame(void) = 0; + + virtual unsigned int checkCallbacks(void) = 0; + + virtual void initGL(void) = 0; + + }; + +} diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/Message.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/Message.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,43 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#include "stdafx.h" +#include "Message.h" + +jhapticgui::Message::Message(){ + ID = 0; + std::fill(command,command+MAX_CMD_LEN,0); + std::fill(args,args+MAX_ARGS_LEN,0); +} + +jhapticgui::Message::Message(const char* c, const char* a, long id){ + ID = id; + for(int i = 0; i. +*/ +#pragma once + +namespace jhapticgui { + + struct Message { + static const int MAX_CMD_LEN = 32; + static const int MAX_ARGS_LEN = 64; + + long ID; + char command[MAX_CMD_LEN]; + char args[MAX_ARGS_LEN]; + + Message(); + + Message(const char* c, const char* a, long id); + + + + }; + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/PhantomScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/PhantomScene.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,1136 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#include "StdAfx.h" +#include "PhantomScene.h" +#include "Commands.h" + + +#if defined(WIN32) || defined(linux) +#include +#elif defined(__APPLE__) +#include +#endif + + +#if defined(WIN32) +#include +#endif + +#include + + +void jhapticgui::PhantomScene::drawPoint(const HPoint& p, float snappiness){ + hlPushAttrib(HL_HINT_BIT); + hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, 3); + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, p.getHapticID()); + hlTouchModel(HL_CONSTRAINT); + hlTouchModelf(HL_SNAP_DISTANCE, snappiness); + hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1.0); + hlMaterialf(HL_FRONT_AND_BACK, HL_DAMPING, 0); + hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0); + hlMaterialf(HL_FRONT_AND_BACK, HL_DYNAMIC_FRICTION, 0); + + //draw the point + glBegin(GL_POINTS); + glVertex3d(p.getX(), p.getY(), 0); + glEnd(); + + // End the shape. + hlEndShape(); + hlPopAttrib(); +} + +void jhapticgui::PhantomScene::drawLine(const HLine& l, float snappiness){ + hlPushAttrib(HL_HINT_BIT | HL_MATERIAL_BIT); + hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, 1); + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER,l.getID()); + hlTouchModel(HL_CONSTRAINT); + hlTouchModelf(HL_SNAP_DISTANCE, (snappiness)); + hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1); + hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0.0f /*0.5f*/); + glLineWidth(1.0); + + glBegin(GL_LINES); + + glVertex3d(l.X2, l.Y2, 0); + glVertex3d(l.X1, l.Y1, 0); + + glEnd(); + hlEndShape(); + hlPopAttrib(); +} + +void jhapticgui::PhantomScene::drawSceneGraphics() { + glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT); + glEnable(GL_COLOR_MATERIAL); + + + + /* draw the vertical line unless it's display none state */ + if (state.display == DISPLAY_VALUE_MODE){ + //draw vertical line + glColor3f(1.0, 0.0, 0.0); // red + glLineWidth(2.0); + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + + + glBegin(GL_LINES); + glVertex3d(hLine->X1, hLine->Y1, 0); + glVertex3d(hLine->X2, hLine->Y2, 0); + glEnd(); + glPopAttrib(); + } + else if (state.display == DISPLAY_GRAPH_MODE) { + //draw vertical line + glColor3f(1.0, 1.0, 0.0); // red + glLineWidth(2.0); + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + + + glBegin(GL_LINE_STRIP); + glVertex3d(graph->getStart().getX(), graph->getStart().getY(), 0); + const HPoint* lastPoint = nullptr; + for (auto& p : graph->getPoints()){ + glVertex3d(p->getX(), p->getY(), 0); + lastPoint = p; + } + + /* the Y of the end of the automation follows the last point or the begin if no points exist */ + if (lastPoint == nullptr){ + glVertex3d(graph->getEnd().getX(), graph->getStart().getY(), 0); + } + else{ + glVertex3d(graph->getEnd().getX(), lastPoint->getY(), 0); + } + + glEnd(); // GL_LINES + glPopAttrib(); + + + + glColor3f(1.0, 1.0, 1.0); // white + double dPointScale = 10 * mWorldScale; + + for (auto& p : graph->getPoints()){ + glPushMatrix(); + static bool once = false; + if (!once){ + once = true; + } + glTranslated(p->getX(), p->getY(), 0); + glScaled(dPointScale, dPointScale, dPointScale); + glutSolidSphere(0.5, 10, 10); + glPopMatrix(); + } + + } + else if (state.display == DISPLAY_EDIT_GRID_MODE){ + glColor3f(1.0, 0.0, 0.0); // red + glLineWidth(2.0); + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + + + glBegin(GL_LINES); + + //draw vertical line + glVertex3d(grid.verticalLine->X1, grid.verticalLine->Y1, 0); + glVertex3d(grid.verticalLine->X2, grid.verticalLine->Y2, 0); + + /* draw grid */ + for (auto& p : grid.lines){ + glVertex3d(p->X1, p->Y1, 0); + glVertex3d(p->X2, p->Y2, 0); + } + glEnd(); + glPopAttrib(); + + /* draw points on the vertical line of the grid */ + glColor3f(1.0, 1.0, 1.0); // white + double dPointScale = 10 * mWorldScale; + + for (auto& p : grid.points){ + glPushMatrix(); + static bool once = false; + if (!once){ + once = true; + } + glTranslated(p->getX(), p->getY(), 0); + glScaled(dPointScale, dPointScale, dPointScale); + glutSolidSphere(0.5, 10, 10); + glPopMatrix(); + } + + } +#ifdef ATTRACTION + if (state.display == ATTRACTION_DISPLAY){ + glPushMatrix(); + glTranslated(state.attractionPoint->x, state.attractionPoint->y, 0); + double dPointScale = 10 * mWorldScale; + glScaled(dPointScale, dPointScale, dPointScale); + + /* draw shpere */ + glutSolidSphere(0.5, 10, 10); + glPopMatrix(); + } +#endif + glPopAttrib(); +} + +void jhapticgui::PhantomScene::drawCage(void){ + + HLenum direction = HL_FRONT; + + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.left.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::X, grid.cage.left.offset, 0, false); + hlEndShape(); + + direction = HL_BACK; + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.right.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::X, grid.cage.right.offset, 0, false); + hlEndShape(); + + direction = HL_BACK; + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.bottom.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::Y, grid.cage.bottom.offset, 0, false); + hlEndShape(); + + direction = HL_BACK; + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.subBottom.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::Y, grid.cage.bottom.offset - 0.005, 0, false); + hlEndShape(); + + direction = HL_FRONT; + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.top.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::Y, grid.cage.top.offset, 0, false); + hlEndShape(); + + direction = HL_FRONT; + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, grid.cage.overTop.ID); + hlMaterialf(direction, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(direction); + hlMaterialf(direction, HL_POPTHROUGH, 0); + hlMaterialf(direction, HL_STATIC_FRICTION, 0); + hlMaterialf(direction, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::Y, grid.cage.top.offset - 0.005, 0, false); + hlEndShape(); +} + +void jhapticgui::PhantomScene::drawEditPlane(void){ + const double offset = -0.005; + glPushAttrib(GL_ENABLE_BIT); + glPolygonOffset(1, 1); + glEnable(GL_POLYGON_OFFSET_FILL); + + glBegin(GL_QUADS); + glVertex3f(grid.editGridLeft, grid.editGridBottom, offset); + glVertex3f(grid.editGridRight, grid.editGridBottom, offset); + glVertex3f(grid.editGridRight, grid.editGridTop, offset); + glVertex3f(grid.editGridLeft, grid.editGridTop, offset); + glEnd(); + + glDisable(GL_POLYGON_OFFSET_FILL); + glPopAttrib(); + //glEndList(); +} + +/******************************************************************************* +The main routine for rendering scene haptics. +*******************************************************************************/ +void jhapticgui::PhantomScene::drawSceneHaptics() +{ + /* stop the sticky ref if enough time has passed */ + if (state.stickyRef == StickyRef::RUNNING){ + if (clock() > state.stickyRefStart + STICKY_INTERVAL){ + state.stickyRef = StickyRef::NONE; + } + } + + hlBeginFrame(); + + // draw sticky wall + hlPushAttrib(HL_HINT_BIT); + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, stickyWall.ID); + hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1); + hlMaterialf(HL_FRONT_AND_BACK, HL_POPTHROUGH, 0); + hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, 0); + hlMaterialf(HL_FRONT_AND_BACK, HL_DYNAMIC_FRICTION, 0); + hlTouchModel(HL_CONSTRAINT); + hlTouchModelf(HL_SNAP_DISTANCE, HARD_SNAP); + hlTouchableFace(HL_FRONT_AND_BACK); + if (state.display == DISPLAY_EDIT_GRID_MODE) { + drawEditPlane(); + } + else{ + drawPlane(Axis::Z, -0.005, stickyWall.displayList); + } + hlEndShape(); + hlPopAttrib(); + + /* draw the force walls */ + hlPushAttrib(HL_HINT_BIT); + for (const PhantomScene::Wall & wall : {forceWallBack, forceWallFront}) { + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, wall.ID); + hlMaterialf(HL_BACK, HL_STIFFNESS, 1); + hlTouchModel(HL_CONTACT); + hlTouchableFace(wall.offset > 0 ? HL_BACK : HL_FRONT); + hlMaterialf(HL_FRONT, HL_POPTHROUGH, 0); + hlMaterialf(HL_FRONT, HL_STATIC_FRICTION, 0); + hlMaterialf(HL_FRONT, HL_DYNAMIC_FRICTION, 0); + drawPlane(Axis::Z, wall.offset, wall.displayList); + hlEndShape(); + } + hlPopAttrib(); + + + hlPopAttrib(); + + /* draw the horizontal line */ + if (state.display == DISPLAY_VALUE_MODE){ + drawLine(*hLine, LINE_SNAP); + } + else if (state.display == DISPLAY_GRAPH_MODE) { + //draw automation line + hlPushAttrib(HL_HINT_BIT | HL_MATERIAL_BIT); + hlHinti(HL_SHAPE_FEEDBACK_BUFFER_VERTICES, 1); + hlBeginShape(HL_SHAPE_FEEDBACK_BUFFER, hLine->getID()); + hlTouchModel(HL_CONSTRAINT); + hlTouchModelf(HL_SNAP_DISTANCE, (LINE_SNAP)); + hlMaterialf(HL_FRONT_AND_BACK, HL_STIFFNESS, 1); + hlMaterialf(HL_FRONT_AND_BACK, HL_STATIC_FRICTION, state.friction /*0.5f*/); + glLineWidth(1.0); + + glBegin(GL_LINE_STRIP); + + glVertex3d(graph->getStart().getX(), graph->getStart().getY(), 0); + const HPoint* lastPoint = nullptr; + for (auto& p : graph->getPoints()){ + glVertex3d(p->getX(), p->getY(), 0); + lastPoint = p; + } + + /* the Y of the end of the automation follows the last point or the begin if no points exist */ + if (lastPoint == nullptr){ + glVertex3d(graph->getEnd().getX(), graph->getStart().getY(), 0); + } + else{ + glVertex3d(graph->getEnd().getX(), lastPoint->getY(), 0); + } + + glEnd(); // GL_LINES_STRIP + + hlEndShape(); + hlPopAttrib(); + + + // draw points + for (auto& p : graph->getPoints()){ + drawPoint(*p, 3.0f); + } + + } + else if (state.display == DISPLAY_EDIT_GRID_MODE /*&& state.gridType != FREEFORM*/){ + + /* draw the two vertical walls */ + //drawLine(*(grid.bars.left), LINE_SNAP); + //drawLine(*(grid.bars.right), LINE_SNAP); + drawCage(); + + drawLine(*(grid.verticalLine), SOFT_SNAP); + + if (state.gridType == FREEFORM){ + for (auto& line : grid.lines){ + int index = line->getHashCode() - grid.HALF_SIZE; + drawLine(*line, 0); + } + } + else{ + for (auto& line : grid.lines){ + int index = line->getHashCode() - grid.HALF_SIZE; + if (state.gridType == GRID1 && index == 0){ + drawLine(*line, state.stickyRef == StickyRef::RUNNING ? HARD_SNAP : SOFT_SNAP); + drawPoint(*(grid.points[line->getHashCode()]), state.stickyRef == StickyRef::RUNNING ? HARD_SNAP : SOFT_SNAP); + } + else if (state.gridType == GRID3 && (index == 0 || index == 7 || index == -7)){ + drawLine(*line, state.stickyRef == StickyRef::RUNNING ? HARD_SNAP : SOFT_SNAP); + drawPoint(*(grid.points[line->getHashCode()]), state.stickyRef == StickyRef::RUNNING ? HARD_SNAP : SOFT_SNAP); + } + else{ + drawLine(*line, SOFT_SNAP); + drawPoint(*(grid.points[line->getHashCode()]), SOFT_SNAP); + } + } + } + } + hlEndFrame(); +} + + +void jhapticgui::PhantomScene::attractTo(float gain, float magnitude, bool start){ + HLdouble devicePosition[3]; + HDdouble direction[3]; + + hlGetDoublev(HL_DEVICE_POSITION, devicePosition); + + direction[0] = (state.attractionPoint)->getX() - devicePosition[0]; + direction[1] = (state.attractionPoint)->getY() - devicePosition[1]; + direction[2] = 0 - devicePosition[2]; + + HDdouble directionNorm = norm(direction); + + if (directionNorm != 0 ){ + direction[0] /= directionNorm; + direction[1] /= directionNorm; + direction[2] /= directionNorm; + } + + hlPushAttrib(HL_EFFECT_BIT); + hlEffectdv(HL_EFFECT_PROPERTY_DIRECTION, direction); + hlEffectd(HL_EFFECT_PROPERTY_GAIN, gain /* 0.5f */); + hlEffectd(HL_EFFECT_PROPERTY_MAGNITUDE, magnitude /*0.5f*/); + + if (start){ + hlStartEffect(HL_EFFECT_CONSTANT, attractionFX); + }else{ + hlUpdateEffect(attractionFX); + } + hlPopAttrib(); +} + +jhapticgui::PhantomScene::PhantomScene(void(*messageCallback) (const Message &m)) : +HapticScene(messageCallback), gCursorDisplayList(0), isLastPointTouched(false), lastTouchedPoint(-100), +isAttractedPointTouched(false) {} + + +jhapticgui::PhantomScene::~PhantomScene(void){ + delete hLine; + while (!grid.lines.empty()){ + delete grid.lines.back(); + for (int i = 0; i < grid.lines.size(); i++){ + delete grid.lines[i]; + } + } +} + + +bool jhapticgui::PhantomScene::initHaptics(void){ + HDErrorInfo error; + ghHD = hdInitDevice(HD_DEFAULT_DEVICE); + + if (HD_DEVICE_ERROR(error = hdGetError())){ + return false; + } + + ghHLRC = hlCreateContext(ghHD); + hlMakeCurrent(ghHLRC); + + // Enable optimization of the viewing parameters when rendering geometry for OpenHaptics. + hlEnable(HL_HAPTIC_CAMERA_VIEW); + + hlTouchableFace(HL_FRONT); + + stickyWall.ID = hlGenShapes(1); + stickyWall.displayList = glGenLists(1); + //glNewList(stickyWall.displayList, GL_COMPILE_AND_EXECUTE); + + forceWallFront.ID = hlGenShapes(1); + forceWallFront.displayList = glGenLists(1); + forceWallFront.offset = 0.005; + + + forceWallBack.ID = hlGenShapes(1); + forceWallBack.displayList = glGenLists(1); + forceWallBack.offset = -0.005; + +#ifdef SECOND_PLANE + id.wall2 = hlGenShapes(1); +#endif + + fx = hlGenEffects(1); + attractionFX = hlGenEffects(1); + + hLine = new HLine(); + + for (int i = 0; i < grid.lines.size(); i++){ + grid.lines[i] = new HLine(); + grid.points[i] = new HPoint(hlGenShapes(1)); + } + + grid.verticalLine = new HLine(); + + grid.cage.left.ID = hlGenShapes(1); + grid.cage.right.ID = hlGenShapes(1); + grid.cage.top.ID = hlGenShapes(1); + grid.cage.bottom.ID = hlGenShapes(1); + grid.cage.subBottom.ID = hlGenShapes(1); + grid.cage.overTop.ID = hlGenShapes(1); + + /* callbacks */ + hlAddEventCallback(HL_EVENT_TOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkContact, this); + hlAddEventCallback(HL_EVENT_UNTOUCH, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkContact, this); + hlAddEventCallback(HL_EVENT_1BUTTONDOWN, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkButtons, this); + hlAddEventCallback(HL_EVENT_2BUTTONDOWN, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkButtons, this); + hlAddEventCallback(HL_EVENT_1BUTTONUP, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkButtons, this); + hlAddEventCallback(HL_EVENT_MOTION, HL_OBJECT_ANY, HL_CLIENT_THREAD, checkMotion, this); + + + return true; +} + +void jhapticgui::PhantomScene::setSize(int w, int h){ + HapticScene::setSize(w, h); + + hduVector3Dd fromScreenSize; + fromScreen(hduVector3Dd(getWidth(), getHeight() + (2 * LINE_STRETCH), 0), fromScreenSize); +} + +void jhapticgui::PhantomScene::beginFrame(void){} + + + +void jhapticgui::PhantomScene::drawCursor(void) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + HLdouble proxyxform[16]; + + GLUquadricObj *qobj = 0; + glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT); + + glPushMatrix(); + + if (!gCursorDisplayList){ + gCursorDisplayList = glGenLists(1); + glNewList(gCursorDisplayList, GL_COMPILE); + qobj = gluNewQuadric(); + gluCylinder(qobj, 0.0, kCursorRadius, kCursorHeight, + kCursorTess, kCursorTess); + glTranslated(0.0, 0.0, kCursorHeight); + gluCylinder(qobj, kCursorRadius, 0.0, kCursorHeight / 5.0, + kCursorTess, kCursorTess); + gluDeleteQuadric(qobj); + glEndList(); + } + + // Get the proxy transform in world coordinates. + + hlGetDoublev(HL_PROXY_TRANSFORM, proxyxform); + glMultMatrixd(proxyxform); + + // Apply the local cursor scale factor. + + glScaled(mCursorScale, mCursorScale, mCursorScale); + glEnable(GL_COLOR_MATERIAL); + + glColor3f(0.0, 0.5, 1.0); + glCallList(gCursorDisplayList); + glPopMatrix(); + glPopAttrib(); + + +} + +void jhapticgui::PhantomScene::drawPlane(Axis axis, float offset, GLuint displayList, bool useDisplayList){ + + if (displayList && useDisplayList){ + glCallList(displayList); + }else{ + + if (useDisplayList){ + displayList = glGenLists(1); + glNewList(displayList, GL_COMPILE_AND_EXECUTE); + } + + glPushAttrib(GL_ENABLE_BIT); + glPolygonOffset(1, 1); + glEnable(GL_POLYGON_OFFSET_FILL); + + glBegin(GL_QUADS); + switch (axis){ + case Axis::X : + glVertex3f(offset, -5, -5); + glVertex3f(offset, 5, -5); + glVertex3f(offset, 5, 5); + glVertex3f(offset, -5, 5); + break; + case Axis::Y: + glVertex3f(-5, offset, -5); + glVertex3f(5, offset, -5); + glVertex3f(5, offset, 5); + glVertex3f(-5, offset, 5); + break; + case Axis::Z: + glVertex3f(-5, -5, offset); + glVertex3f(5, -5, offset); + glVertex3f(5, 5, offset); + glVertex3f(-5, 5, offset); + break; + }; + glEnd(); + + glDisable(GL_POLYGON_OFFSET_FILL); + + glPopAttrib(); + if (useDisplayList) { + glEndList(); + } + } +} + + + +/** + * + * The arguments to be passed are those returned respectively by processMessage and checkCallbacks + * and are used to notify the haptic engine there has been an update in the haptic scene. + * Indeed, the h3d library routines need to be called only when a change occurs. Unlike openGL rooutines, + * which need to be called on every frame + * + * @param messageUpdate the value returned by processMessage + * @param callbacksUpdate the value returned by checkCallbacks, for example when the proxy has been moved + + */ +void jhapticgui::PhantomScene::drawScene(unsigned int messageUpdate, unsigned int callbacksUpdate){ + drawSceneHaptics(); + drawSceneGraphics(); + +} + +void jhapticgui::PhantomScene::updateWorkspace(void){ + +} + + +unsigned int jhapticgui::PhantomScene::checkCallbacks(void){ + /* openhaptics call to check all the events */ + hlCheckEvents(); + return 0; +} + +#include +void HLCALLBACK jhapticgui::PhantomScene::checkMotion(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata){ + PhantomScene *pThis = static_cast(userdata); + PhantomScene::State & state = pThis->state; + + HLdouble proxyPosition[3]; + hlGetDoublev(HL_DEVICE_POSITION, proxyPosition); + + if (state.display == DISPLAY_VALUE_MODE || state.display == DISPLAY_GRAPH_MODE || state.display == DISPLAY_EDIT_GRID_MODE){ + + if ( state.touch.line || state.touch.gridLine){ + if (fabs(state.lastRecPos[0] - proxyPosition[0]) > SCRUB_THRESHOLD) { + state.lastRecPos[0] = proxyPosition[0]; + + char msgArgs[Message::MAX_ARGS_LEN]; + sprintf_s(msgArgs, "%f", pThis->getNormalizedProxyPos()[0]); + pThis->send(Message(OutCommands::SCRUB_ON, msgArgs, 0)); + } + } + } + + if (state.display == DISPLAY_EDIT_GRID_MODE && state.gridType == FREEFORM){ + /* sends touch info about the motion for free_form */ + + HLdouble proxyPosition[3]; + hlGetDoublev(HL_DEVICE_POSITION, proxyPosition); + + + if (state.touch.gridLine) { // check for untouch + const auto &element = std::find_if(pThis->grid.lines.begin(), pThis->grid.lines.end(), + [&pThis](const HLine* l){ return pThis->state.touch.gridLineID == l->getID(); } + ); + + if (element != pThis->grid.lines.end()){ + if (fabs((*element)->Y1 - proxyPosition[1]) > epsilon || proxyPosition[0] > (*element)->X2 || proxyPosition[0] < (*element)->X1){ //untouch + checkContact(HL_EVENT_UNTOUCH, pThis->lastTouchedPoint, thread, cache, userdata); + state.touch.gridLine = false; + } + } + } + + + for (const auto& l : pThis->grid.lines){ + if (fabs(l->Y1 - proxyPosition[1]) < epsilon && proxyPosition[0] > l->X1 && proxyPosition[0] < l->X2){ + /*if (pThis->lastTouchedPoint != p.hapticID){ + checkContact(HL_EVENT_UNTOUCH, pThis->lastTouchedPoint, thread, cache, userdata); + }*/ + checkContact(HL_EVENT_TOUCH, l->getID(), thread, cache, userdata); + /* keep track of the touched line */ + state.touch.gridLineID = l->getID(); + state.touch.gridLine = true; + break; + } + } + + } +} + +void HLCALLBACK jhapticgui::PhantomScene::checkButtons(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata){ + PhantomScene *pThis = static_cast(userdata); + PhantomScene::State & state = pThis->state; + + if (state.display == DISPLAY_VALUE_MODE || state.display == DISPLAY_GRAPH_MODE){ + if (event == HL_EVENT_1BUTTONDOWN){ + state.button1Pressed = true; + } + else if (event == HL_EVENT_1BUTTONUP){ + state.button1Pressed = false; + pThis->send(Message(OutCommands::SCRUB_OFF, "", 0)); + } + } + + if (state.display == DISPLAY_GRAPH_MODE){ + if (event == HL_EVENT_1BUTTONDOWN && state.touch.point != HL_OBJECT_ANY){ + state.display = DISPLAY_EDIT_GRID_MODE; + pThis->grid.on = true; + pThis->editGridPoint = state.touch.point; + pThis->createEditGrid(); + } + else if (event == HL_EVENT_2BUTTONDOWN){ + if (state.touch.point == HL_OBJECT_ANY){ // nothing touched create new point + std::array proxyPos = pThis->getNormalizedProxyPos(); + char msgArgs[Message::MAX_ARGS_LEN]; + sprintf_s(msgArgs, "%f %f", proxyPos[0], proxyPos[1]); + pThis->send(Message(OutCommands::ADD_SEQ_VAL, msgArgs, 0)); + } + else { // delete touched point + auto& element = std::find_if(pThis->graph->getPoints().begin(), + pThis->graph->getPoints().end(), [state](HPoint* const p){ return p->getHapticID() == state.touch.point; }); + + pThis->send(Message(OutCommands::REM_SEQ_VAL,"",(*element)->getHashCode())); + } + } + + } + else if (state.display == DISPLAY_EDIT_GRID_MODE){ + if (event == HL_EVENT_1BUTTONDOWN){ + state.display = DISPLAY_GRAPH_MODE; + auto& element = std::find_if(pThis->graph->getPoints().begin(), + pThis->graph->getPoints().end(), [pThis](HPoint* const p){ return p->getHapticID() == pThis->editGridPoint; }); + + pThis->editGridPoint = HL_OBJECT_ANY; + + std::array proxyPos = pThis->getNormalizedProxyPos(); + + char msgArgs[Message::MAX_ARGS_LEN]; + sprintf_s(msgArgs, "%f %f", proxyPos[0], proxyPos[1]); + + int hashCode = (*element)->getHashCode(); + pThis->send(Message(OutCommands::MOVE_SEQ_VAL, msgArgs, hashCode)); + + } + } +} + + +void HLCALLBACK jhapticgui::PhantomScene::checkContact(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata){ + PhantomScene *pThis = static_cast(userdata); + PhantomScene::State & state = pThis->state; + + if (state.display == DISPLAY_GRAPH_MODE || state.display == DISPLAY_VALUE_MODE) { + if (event == HL_EVENT_TOUCH && object == pThis->hLine->getID()) { // check line touch + state.touch.line = true; + } + else if (event == HL_EVENT_UNTOUCH && object == pThis->hLine->getID()){ // check line untouch + state.touch.line = false; + pThis->send(Message(OutCommands::SCRUB_OFF, "", 0)); + } + else if (event == HL_EVENT_TOUCH && state.display == DISPLAY_GRAPH_MODE){ // check point touch + const auto& element = std::find_if(pThis->graph->getPoints().begin(), + pThis->graph->getPoints().end(), + [object](const HPoint* const p){ return object == p->getHapticID(); }); + if (element != pThis->graph->getPoints().end()){ + state.touch.point = (*element)->getHapticID(); + pThis->send(Message(OutCommands::POINT_TOUCHED, "", (*element)->getHashCode())); + } + } + else if (event == HL_EVENT_UNTOUCH && state.display == DISPLAY_GRAPH_MODE){ // check point untouch + if (object == state.touch.point){ + state.touch.point = HL_OBJECT_ANY; + } + } + } + + if (state.display == DISPLAY_EDIT_GRID_MODE){ + if (event == HL_EVENT_TOUCH && state.stickyRef == StickyRef::NONE) { + /* look for the touched element among the lines */ + auto & element = std::find_if(pThis->grid.lines.begin(), + pThis->grid.lines.end(), [object](const HLine* l){ return l->getID() == object; }); + + if (element != pThis->grid.lines.end()){ // if found it + int hashCode = (*element)->getHashCode(); + + if (state.gridType == GRID1 && hashCode == pThis->grid.lines[15]->getHashCode()){ + state.stickyRef = StickyRef::RUNNING; + state.stickyRefStart = clock(); + } + else if (state.gridType == GRID3 && pThis->state.stickyRef == StickyRef::NONE && + (hashCode == pThis->grid.lines[15]->getHashCode() || + hashCode == pThis->grid.lines[7 + 15]->getHashCode() || + hashCode == pThis->grid.lines[-7 + 15]->getHashCode())) { + state.stickyRef = StickyRef::RUNNING; + state.stickyRefStart = clock(); + } + + pThis->send(Message(OutCommands::LINE_TOUCHED, "", hashCode)); + } + else{ // same thing with points on vertical line + /* look for the touched element among the lines */ + auto & element = std::find_if(pThis->grid.points.begin(), + pThis->grid.points.end(), [object](const HPoint* p){ return p->getHapticID() == object; }); + + if (element != pThis->grid.points.end()){ // if found it + int hashCode = (*element)->getHashCode(); + + if (state.gridType == GRID1 && hashCode == pThis->grid.points[15]->getHashCode()){ + state.stickyRef = StickyRef::RUNNING; + state.stickyRefStart = clock(); + } + else if (state.gridType == GRID3 && pThis->state.stickyRef == StickyRef::NONE && + (hashCode == pThis->grid.points[15]->getHashCode() || + hashCode == pThis->grid.points[7 + 15]->getHashCode() || + hashCode == pThis->grid.points[-7 + 15]->getHashCode())) { + state.stickyRef = StickyRef::RUNNING; + state.stickyRefStart = clock(); + } + + pThis->send(Message(OutCommands::LINE_TOUCHED, "", hashCode)); + } + } + } + + } + +#ifdef REFER + else if (event == HL_EVENT_TOUCH && !pThis->state.referenceActive) { + /* if a non-reference point is touched the snappiness is re-enabled */ + bool anotherPoint = false; + for (int i = 0; i < pThis->points.size(); i++){ + + if ((i == 15 || i == 7 + 15 || i == -7 + 15) && object == pThis->points[i].hapticID){ + break; + } + else if (object == pThis->points[i].hapticID){ // if it's another point + anotherPoint = true; + break; + } + } + + if (anotherPoint){ + pThis->state.referenceActive = true; + //FIXME to remove + const auto& element = std::find_if(pThis->points.begin(), pThis->points.end(), [object](const Point& p){ return object == p.hapticID; }); + std::cout << "true " << element->index << std::endl; + } + } +#endif +} + + +std::array jhapticgui::PhantomScene::getNormalizedProxyPos(){ + std::array toReturn; + + HLdouble proxyPosition[3]; + hlGetDoublev(HL_DEVICE_POSITION, proxyPosition); + + hduVector3Dd bottomLeftPoint, topRightPoint; + fromScreen(hduVector3Dd(0, 0, 0), bottomLeftPoint); + fromScreen(hduVector3Dd(getWidth(), getHeight(), 0), topRightPoint); + + hduVector3Dd screenCoord; + toScreen(hduVector3Dd(proxyPosition[0], proxyPosition[1], proxyPosition[2]), screenCoord); + float x = screenCoord[0] / getWidth(); + float y = screenCoord[1] / getHeight(); + + + float normalizedX = (proxyPosition[0] - bottomLeftPoint[0]) / (topRightPoint[0] - bottomLeftPoint[0]); + float normalizedY = (proxyPosition[1] - bottomLeftPoint[1]) / (topRightPoint[1] - bottomLeftPoint[1]); + + toReturn[0] = normalizedX; + toReturn[1] = normalizedY; + + return toReturn; +} + +unsigned int jhapticgui::PhantomScene::processMessage(const Message & m){ + //std::cout << "PhantomScene: message received: " << m.command << " " << m.args << " " << m.ID << std::endl; + + + if(!strcmp(m.command,InCommands::DISPLAY_RENDER_VALUE)){ + state.display = DISPLAY_VALUE_MODE; + + hduVector3Dd leftPoint, rightPoint; + fromScreen(hduVector3Dd(0, getHeight() / 2, 0), leftPoint); + fromScreen(hduVector3Dd(getWidth(), getHeight() / 2, 0), rightPoint); + + hLine->X1 = leftPoint[0]; + hLine->Y1 = leftPoint[1]; + hLine->X2 = rightPoint[0]; + hLine->Y2 = rightPoint[1]; + + } + else if (!strcmp(m.command, InCommands::DISPLAY_SEQUENCE)) { + state.display = DISPLAY_GRAPH_MODE; + + state.gridType = m.ID; + + float y = 0.0f, widthMs = 0.0f, viewPortMs = 0.0f; + /* arguments for Graph changed is the initial value of Graph */ + sscanf_s(m.args, "%f %f %f", &widthMs, &y, &viewPortMs); + + hduVector3Dd leftPoint, rightPoint; + fromScreen(hduVector3Dd(0, getHeight() * y, 0), leftPoint); + fromScreen(hduVector3Dd(getWidth(), getHeight() * y, 0), rightPoint); + + graph = new Graph(leftPoint[0], rightPoint[0], leftPoint[1], widthMs, viewPortMs); + } + else if(!strcmp(m.command,InCommands::DISPLAY_NONE)){ + state.display = NO_DISPLAY_MODE; + } + + else if (!strcmp(m.command, InCommands::SEQUENCE_VALUE_ADD)){ + float x = 0.0f, y = 0.0f; + sscanf_s(m.args, "%f %f", &x, &y); + + x = x / graph->getViewPortMs(); + + hduVector3Dd point; + fromScreen(hduVector3Dd( getWidth() * x , getHeight() *y , 0), point); + const HPoint & p = graph->addPoint(m.ID, point[0], point[1]); + } + + else if (!strcmp(m.command, InCommands::SEQUENCE_VALUE_CHANGE)){ + float x = 0.0f, y = 0.0f; + sscanf_s(m.args, "%f %f", &x, &y);// x time value, y normalized value + + x = x / graph->getViewPortMs(); + + hduVector3Dd point; + fromScreen(hduVector3Dd(getWidth() * x, getHeight() *y, 0), point); + graph->changePoint(m.ID, point[0], point[1]); + } + else if (!strcmp(m.command, InCommands::SEQUENCE_VALUE_REMOVE)){ + graph->removePoint(m.ID); + } + else if (!strcmp(m.command, InCommands::SEQUENCE_BEGIN)){ + float y; + sscanf_s(m.args, "%f", &y); + hduVector3Dd point; + fromScreen(hduVector3Dd(0, getHeight() *y, 0), point); + graph->setStart(point[1]); + } + else if (!strcmp(m.command, InCommands::RENDER_VALUE)){ + float val = 0; + sscanf_s(m.args, "%f", &val); + + state.friction = val; + } + + return 0; +} + + +void jhapticgui::PhantomScene::createEditGrid(){ + + // find the coordinates to draw the grid + const std::vector & points = graph->getPoints(); + HLuint pickedPoint = editGridPoint; + auto element = std::find_if(points.begin(), points.end(), + [pickedPoint](HPoint* p){ return p->getHapticID() == pickedPoint; }); + + if (element == points.end()){ + std::cout << " WARNING POINTS END " << std::endl; + exit(0); + } + auto index = std::distance(points.begin(), element); + + if (index == 0){ + grid.editGridLeft = graph->getStart().getX(); + } + else{ + grid.editGridLeft = graph->getPoints().at(index - 1)->getX(); + } + + if (index == points.size() - 1){ + grid.editGridRight = graph->getEnd().getX(); + } + else{ + grid.editGridRight = graph->getPoints().at(index + 1)->getX(); + } + + hduVector3Dd topPoint, bottomPoint; + fromScreen(hduVector3Dd(0, getHeight() + LINE_STRETCH, 0), topPoint); + fromScreen(hduVector3Dd(0, 0 - LINE_STRETCH, 0), bottomPoint); + + grid.editGridTop = topPoint[1]; + grid.editGridBottom = bottomPoint[1]; + + grid.cage.left.offset = grid.editGridLeft; + grid.cage.right.offset = grid.editGridRight; + grid.cage.top.offset = grid.editGridTop; + grid.cage.bottom.offset = grid.editGridBottom; + + grid.verticalLine->X1 = (*element)->getX(); + grid.verticalLine->Y1 = grid.editGridTop; + grid.verticalLine->X2 = (*element)->getX(); + grid.verticalLine->Y2 = grid.editGridBottom; + + + + + /* distribute the lines on the upper part of the line, including zero (<=) */ + for (int i = 0; i <= grid.HALF_SIZE; i++){ + hduVector3Dd glCoordinatePosition; + float halfHeight = getHeight() / 2; + fromScreen(hduVector3Dd(0, halfHeight + (i*((halfHeight + LINE_STRETCH) / grid.HALF_SIZE)), 0), glCoordinatePosition); + + grid.lines[i + grid.HALF_SIZE]->X1 = grid.editGridLeft; + grid.lines[i + grid.HALF_SIZE]->Y1 = glCoordinatePosition[1]; + grid.lines[i + grid.HALF_SIZE]->X2 = grid.editGridRight; + grid.lines[i + grid.HALF_SIZE]->Y2 = glCoordinatePosition[1]; + grid.lines[i + grid.HALF_SIZE]->setHashCode(i + grid.HALF_SIZE); // assign the index in the array + + /* points are laid on the vertical line */ + grid.points[i + grid.HALF_SIZE]->setCoord(grid.verticalLine->X1, glCoordinatePosition[1]); + grid.points[i + grid.HALF_SIZE]->setHashCode(index = i + grid.HALF_SIZE); + } + + for (int i = -1; i >= -grid.HALF_SIZE; i--){ + hduVector3Dd glCoordinatePosition; + float halfHeight = getHeight() / 2; + fromScreen(hduVector3Dd(0, halfHeight + (i*((halfHeight + LINE_STRETCH) / grid.HALF_SIZE)), 0), glCoordinatePosition); + + grid.lines[i + grid.HALF_SIZE]->X1 = grid.editGridLeft; + grid.lines[i + grid.HALF_SIZE]->Y1 = glCoordinatePosition[1]; + grid.lines[i + grid.HALF_SIZE]->X2 = grid.editGridRight; + grid.lines[i + grid.HALF_SIZE]->Y2 = glCoordinatePosition[1]; + grid.lines[i + grid.HALF_SIZE]->setHashCode(index = i + grid.HALF_SIZE); // assign the index in the array + + /* points are laid on the vertical line */ + grid.points[i + grid.HALF_SIZE]->setCoord(grid.verticalLine->X1, glCoordinatePosition[1]); + grid.points[i + grid.HALF_SIZE]->setHashCode(index = i + grid.HALF_SIZE); + } + + + editGridPointHash = (*element)->getHashCode(); +} + +void jhapticgui::PhantomScene::endFrame(void){} + + + + +void jhapticgui::PhantomScene::initGL(void){ + static const GLfloat light_model_ambient[] = { 0.3f, 0.3f, 0.3f, 1.0f }; + static const GLfloat light0_diffuse[] = { 0.9f, 0.9f, 0.9f, 0.9f }; + static const GLfloat light0_direction[] = { 0.0f, -0.4f, 1.0f, 0.0f }; + + // Enable depth buffering for hidden surface removal. + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + + // Cull back faces. + glCullFace(GL_BACK); + glEnable(GL_CULL_FACE); + + // Setup other misc features. + glEnable(GL_LIGHTING); + glEnable(GL_NORMALIZE); + glShadeModel(GL_SMOOTH); + + // Setup lighting model. + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE); + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, light_model_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse); + glLightfv(GL_LIGHT0, GL_POSITION, light0_direction); + glEnable(GL_LIGHT0); +} + +HHD jhapticgui::PhantomScene::ghHD = HD_INVALID_HANDLE; + +const unsigned int jhapticgui::PhantomScene::FRONT_BUTTON = (1 << 2); +const unsigned int jhapticgui::PhantomScene::LEFT_BUTTON = (1 << 1); +const unsigned int jhapticgui::PhantomScene::RIGHT_BUTTON = (1 << 3); +const unsigned int jhapticgui::PhantomScene::REAR_BUTTON = (1 << 0); +const unsigned int jhapticgui::PhantomScene::NO_BUTTON = 0; + +const int jhapticgui::PhantomScene::LINE_FORCE_FACTOR_STICKY = 2000; +const int jhapticgui::PhantomScene::LINE_FORCE_FACTOR_LOOSE = 100; +const int jhapticgui::PhantomScene::POINT_FORCE_FACTOR = 5; +const float jhapticgui::PhantomScene::MAX_VISCOSITY = 200;//200.0f; +const float jhapticgui::PhantomScene::VISCOSITY_RADIUS = 0.005f; +const float jhapticgui::PhantomScene::SPRING_FORCE_FACTOR = 600; + +const int jhapticgui::PhantomScene::STICKY_INTERVAL = CLOCKS_PER_SEC / 2.5; // CLOCKS_PER_SEC = 1000 + +/** + * The minimum distance the proxy has to move away from the last position + * where it sent a scrub message, in order to send another scrub message + */ +const double jhapticgui::PhantomScene::SCRUB_THRESHOLD = 0.00005; +const double jhapticgui::PhantomScene::SPRING_THRESHOLD = 0.005; +const float jhapticgui::PhantomScene::SHIFT_AMOUNT = 5.0f; // in millisec + +const int jhapticgui::PhantomScene::GRAPHIC_SCALE = 2; +const float jhapticgui::PhantomScene::SCENE_WIDTH = 0.06f;//0.1f; +const float jhapticgui::PhantomScene::SCENE_HEIGHT = 0.06f; +const float jhapticgui::PhantomScene::SCENE_DEPTH = 0.06f; + +const float jhapticgui::PhantomScene::HARD_SNAP = 5.0f;//2.7f; +const float jhapticgui::PhantomScene::SOFT_SNAP = 1.8f; // 3.0 +const float jhapticgui::PhantomScene::LINE_SNAP = 15.0f; + +const double jhapticgui::PhantomScene::kCursorRadius = 0.5; +const double jhapticgui::PhantomScene::kCursorHeight = 1.5; +const int jhapticgui::PhantomScene::kCursorTess = 15; diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/PhantomScene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/PhantomScene.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,246 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#pragma once + +#include "Message.h" +#include "HapticScene.h" +#include "HapticObjects.h" +#include "Graph.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include "utils.h" +#include +#include + + // #define SECOND_PLANE + + + +namespace jhapticgui { + + const double epsilon = 0.01; + + class PhantomScene : public HapticScene { + static HHD ghHD; + static const double kCursorRadius; + static const double kCursorHeight; + static const int kCursorTess; + static const int LINE_STRETCH = 0;// 150 ; + + + GLuint gCursorDisplayList; + HHLRC ghHLRC; + + struct Wall { + Wall() : displayList(0) {} + HLuint ID; + GLuint displayList; + double offset; + }; + + Wall stickyWall; + Wall forceWallFront; + Wall forceWallBack; + + +#ifdef SECOND_PLANE + HLuint wall2; +#endif + + enum GridType { FREEFORM = 0, GRID0, GRID1, GRID3 }; + + enum StickyRef {NONE, START, RUNNING}; + + enum class Attraction { NONE, START, RUNNING, STOP }; + + enum class Axis {X, Y, Z}; + + enum DisplayMode { NO_DISPLAY_MODE, DISPLAY_GRAPH_MODE, DISPLAY_VALUE_MODE, DISPLAY_EDIT_GRID_MODE }; + /* STOP_SCRUB is to hold the information that the scrubbing effect must be stopped until the next frame + from next frame on the mode will be set to NO_SCRUB */ + enum ScrubMode { NO_SCRUB_MODE, STOP_SCRUB_MODE, SCRUB_MODE }; + /* refers to the state of moving sequence values action */ + enum DragMode { NO_DRAG_MODE, DRAG_MODE }; + + enum ForceFieldMode { NO_FORCE_FIELD_MODE, FORCE_FIELD_MODE }; + + struct Touch { + Touch() : line(false), point(HL_OBJECT_ANY), gridLineID(HL_OBJECT_ANY), gridLine(false){} + bool line; + HLuint point; + HLuint gridLineID; + bool gridLine; + }; + + struct State{ + State() : display(NO_DISPLAY_MODE), previousDisplay(NO_DISPLAY_MODE), scrub(NO_SCRUB_MODE), + drag(NO_DRAG_MODE), forceField(NO_FORCE_FIELD_MODE), isPeaking(false), + attraction(Attraction::NONE), stickyRef(StickyRef::NONE), button1Pressed(false), friction(0.0f){} + DisplayMode display; + DisplayMode previousDisplay; + ScrubMode scrub; + DragMode drag; + ForceFieldMode forceField; + bool isPeaking; + StickyRef stickyRef; + clock_t stickyRefStart; + Attraction attraction; + HPoint* attractionPoint; + HLdouble lastRecPos[3]; + bool button1Pressed; + Touch touch; + int gridType; + float friction; + + } state; + + HLuint fx; + + HLuint attractionFX; + HLuint lastTouchedPoint; + bool isLastPointTouched; + bool isAttractedPointTouched; + HLine *hLine; + HLuint editGridPoint = HL_OBJECT_ANY; + int editGridPointHash; + Graph *graph; + + + struct Grid { + const int HALF_SIZE = 15; + bool on = false; + HLdouble editGridLeft; + HLdouble editGridRight; + HLdouble editGridTop; + HLdouble editGridBottom; + + std::array lines; + std::array points; + + HLine *verticalLine; + + struct { + Wall left; + Wall right; + Wall top; + Wall bottom; + Wall subBottom; + Wall overTop; + } cage; + } grid; + + static const unsigned int FRONT_BUTTON; + static const unsigned int LEFT_BUTTON; + static const unsigned int RIGHT_BUTTON; + static const unsigned int REAR_BUTTON; + static const unsigned int NO_BUTTON; + + static const int LINE_FORCE_FACTOR_STICKY; + static const int LINE_FORCE_FACTOR_LOOSE; + static const float MAX_VISCOSITY; + static const float VISCOSITY_RADIUS; + static const float SPRING_FORCE_FACTOR; + + static const double SCRUB_THRESHOLD; + static const double SPRING_THRESHOLD; + static const float SHIFT_AMOUNT; + + static const float HARD_SNAP; + static const float SOFT_SNAP; + static const float LINE_SNAP; + + static const HDdouble VIBRATION_AMPLITUDE; + static const HDint VIBRATION_FREQUENCY; + static const unsigned int VIBRATION_DURATION; + static const int STICKY_INTERVAL; + /** how bigger graphic coordinates are than haptic coordinates */ + static const int GRAPHIC_SCALE; + static const int POINT_FORCE_FACTOR; + + /** + * thw width of the haptic scene. This value is mapped to the haptic viewPort of the hosting application + */ + static const float SCENE_WIDTH; + static const float SCENE_HEIGHT; + static const float SCENE_DEPTH; + + /* draws the viscosity line both in graphics and haptics */ + void drawViscosityLine(unsigned int messageUpdate); + /* draws the graph both in graphics and haptics */ + void drawGraph(unsigned int messageUpdate); + + void drawSceneHaptics(); + void drawSceneGraphics(); + void drawPlane(Axis axis, float zoffset, GLuint displayList, bool useDisplayList = true); + + void attractTo(float gain, float magnitude, bool start); + + void drawPoint(const HPoint& p, float snappiness); + void drawLine(const HLine& line, float snappiness); + public: + + PhantomScene(void (*messageCallback) (const Message & m) ); + ~PhantomScene(void); + + /* try and initialize the haptic device, return false if the initialization fails */ + bool initHaptics(void); + void setSize(int width, int height); + + unsigned processMessage(const Message & m); + + void beginFrame(void); + + void endFrame(void); + /* drawing routines (both graphically and haptically) */ + + /* draws cursor and updates state.proxyPos */ + void drawCursor(void) ; + + void drawScene(unsigned int messageUpdate, unsigned int callbacksUpdate); + + static void HLCALLBACK checkContact(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata); + static void HLCALLBACK checkButtons(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata); + static void HLCALLBACK checkMotion(HLenum event, HLuint object, HLenum thread, HLcache *cache, void *userdata); + + unsigned int checkCallbacks(void); + + void updateWorkspace(void); + + virtual void initGL(void); + + void drawEditPlane(void); + + void drawCage(void); + + void createEditGrid(); + + std::array getNormalizedProxyPos(); + }; + +} + diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/stdafx.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/stdafx.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// DawPhantomHaptics.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/stdafx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/stdafx.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,18 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + +#include +#include + + + diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/targetver.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/targetver.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,470 @@ + +#include "stdafx.h" +#include "uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.h" +#include "HapticScene.h" + + + +#include +#include +#include + +#include +#ifdef FREEGLUT +#include +#endif + + +#ifdef COMPILE_FALCON +#include "FalconScene.h" +#endif + +#ifndef COMPILE_FALCON +#include "PhantomScene.h" +#endif + +using namespace jhapticgui; + + + +/* Function declarations */ +void display(void); +void reshape(int width, int height); +void idle(void); + +void send(const Message &m); // haptic scene callback function + +void checkExceptions(JNIEnv *env, char* what); +void initJniVariables(void); +void die(char* msg); +void Notify(jobject *monitor); +void MonitorEnter(jobject *monitor); +void MonitorExit(jobject *monitor); + +/* Variables */ +static JNIEnv *env; +static jobject *thisObj; +static jobject hapticListener; +static HapticScene *scene; +static Message message; + +/* JNI Variables */ + +static jfieldID disposeFieldID; +static jfieldID deviceInitFailedFieldID; +static jfieldID deviceInitDoneFieldID; +static jfieldID messageReadyFieldID; +static jfieldID commandFieldID; +static jfieldID argsFieldID; +static jfieldID IDFieldID; + +/* synchronization methods ids */ +static jmethodID notifyMethodID; +static jmethodID waitListenerMethodID; +static jmethodID messageToListenerMethodID; + + + +JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice_init + (JNIEnv *environment, jobject obj, jint w, jint h){ + + env = environment; + thisObj = &obj; + + /* JNI stuff */ + initJniVariables(); + +#ifdef COMPILE_FALCON + scene = new FalconScene(send); +#else + scene = new PhantomScene(send); +#endif + + + /* try to initialize the haptic, tell the java thread about the success/failure. The endeavour * + * takes the thisObj on the haptics java object in order to access the nodes and edges concurrently */ + MonitorEnter(thisObj); + + bool mustSayGoodbye = false; + + if(!scene->initHaptics()){ + /* set the field in the java class to comunicate the main thread (waiting * + * for the initialization to complete) that the initialization failed */ + env->SetBooleanField(*thisObj, deviceInitFailedFieldID, JNI_TRUE); + mustSayGoodbye = true; + } + + if(mustSayGoodbye) + std::cout << "Failed to initialize haptic device" << std::endl; + else + std::cout << "Haptic device successfully initialized" << std::endl; + + /* initialization is done, set the flag for the java thread*/ + env->SetBooleanField(*thisObj, deviceInitDoneFieldID, JNI_TRUE); + + /* notify the java thread */ + Notify(thisObj); + + /* release the lock */ + MonitorExit(thisObj); + + if(mustSayGoodbye){ + /* stops the haptic listener */ + send(Message("stop","",0)); + /* initialization failed: return */ + return; + } + + /* haptic device is initialized, now build the openGL window */ + + /* fake main argv and argc as this is a dll */ + char *argv[1] = {"Haptics"}; + int argc = 1; + + /* glut initialization */ + glutInit(&argc, argv); + + glutInitWindowSize( w, h ); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); + glutCreateWindow( "Haptic Device Window" ); + + /* set up GLUT callback functions */ + glutDisplayFunc ( display ); + glutReshapeFunc ( reshape ); + glutIdleFunc( idle ); + + scene->initGL(); + +#ifdef FREEGLUT + /* with this option set freeglut allows to stop the main + loop without teminating the whole application */ + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); +#endif + glutMainLoop(); + + delete scene; + return; +} + +/* GLUT display callback */ +void display(){ + + /* takes the lock on thisObj */ + MonitorEnter(thisObj); + + /* check if there is a dispose request */ + if( env->GetBooleanField(*thisObj,disposeFieldID) == JNI_TRUE ){ + /* set back to false to prevent the java thread from waiting again */ + env->SetBooleanField(*thisObj,disposeFieldID,JNI_FALSE); + /* stops the haptic listener */ + send(Message("stop","",0)); + /* notify the java thread that this thread is about to die */ + Notify(thisObj); + + /* release the lock on this object */ + MonitorExit(thisObj); +#ifdef FREEGLUT + glutLeaveMainLoop (); + return; +#else + /* if freeglut is not used, the whole application must be terminated */ + die("Haptic device disposed"); +#endif + + } + + /* check if there is a message ready for the java thread */ + bool messageReady = (env->GetBooleanField(*thisObj,messageReadyFieldID) == JNI_TRUE); + if(messageReady){ + /* get the id of the field */ + jint ID = env->GetIntField(*thisObj,IDFieldID); + jstring command = static_cast(env->GetObjectField(*thisObj, commandFieldID)); + jstring args = static_cast(env->GetObjectField(*thisObj, argsFieldID)); + + /* get lenght of the strings */ + int commandLen = env->GetStringLength(command); + int argsLen = env->GetStringLength(args); + + /* read the command fields into the command struct */ + env->GetStringUTFRegion(command,0,commandLen,message.command); + env->GetStringUTFRegion(args,0,argsLen,message.args); + message.ID = ID; + + /* set the flag back to false */ + env->SetBooleanField(*thisObj,messageReadyFieldID,JNI_FALSE); + + /* clean up references */ + env->DeleteLocalRef(command); + env->DeleteLocalRef(args); + /* notify the java thread, waiting for the command to be read */ + Notify(thisObj); + } + + /* release the lock. HapticScene methods from now on can execute commands without deadlock risk */ + MonitorExit(thisObj); + + unsigned int messageUpdate = 0; + if(messageReady) + messageUpdate = scene->processMessage(message); + + /* --- start drawing --- */ + scene->beginFrame(); + + /* draw the diagram graphicly and, if the collection has changed, haptically too */ + scene->drawCursor(); + + /* the drawing can be affected by a message from the Java Thread but also + by the user actions (move, push button etc.). That's why callbacksUpdate + value from the previous loop is passed as an arg as well */ + static unsigned int callbacksUpdate = 0; + scene->drawScene(messageUpdate,callbacksUpdate); + + scene->endFrame(); + + /* check if any callback has been issued : */ + callbacksUpdate = scene->checkCallbacks(); + + /* swap graphic buffers. */ + glutSwapBuffers(); +} + +/* GLUT reshape callback */ +void reshape (int w, int h){ + + /* only do the calculations once otherwise all the points will be + be updated to fit the screen */ + static bool doneOnce = false; + if (doneOnce){ + return; + } + doneOnce = true; + +#ifdef COMPILE_FALCON + scene->setSize(w,h); + + static const double ANGLE = 100; + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(ANGLE, // angle + (float)w / h, // aspect ratio + 0.01, // near clipping plane .01 + 500);// far clipping plane 1000 + glMatrixMode(GL_MODELVIEW); +#else + + static const double kPI = 3.1415926535897932384626433832795; + static const double kFovY = 40; + static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */); + static const double farDist = nearDist + 2.0; + double aspect; + + scene->setSize(w, h); + //cManager->setScreenHeight(h); + glViewport(0, 0, w, h); + + // Compute the viewing parameters based on a fixed fov and viewing + // a canonical box centered at the origin. + aspect = (double)w / h; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(kFovY, aspect, nearDist, farDist); + // Place the camera down the Z axis looking at the origin. + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(0, 0, nearDist + 1.0, + 0, 0, 0, + 0, 1, 0); + hduVector3Dd origin; + fromScreen(hduVector3Dd(0, 0, 0), origin); // can be replaced with code of fromscreen + + glLoadIdentity(); + gluLookAt(-origin[0], origin[1], nearDist + 1.0, + -origin[0], origin[1], 0, + 0, 1, 0); + + + // update workspace + GLdouble modelview[16]; + GLdouble projection[16]; + GLint viewport[4]; + + glGetDoublev(GL_MODELVIEW_MATRIX, modelview); + glGetDoublev(GL_PROJECTION_MATRIX, projection); + glGetIntegerv(GL_VIEWPORT, viewport); + + + hlMatrixMode(HL_TOUCHWORKSPACE); + hlLoadIdentity(); + + // Fit haptic workspace to view volume. + hluFitWorkspace(projection); + + // Compute cursor scale. + PhantomScene* phantom = static_cast(scene); + phantom->setCursorScale(hluScreenToModelScale(modelview, projection, viewport)* 20); + //scene->mCursorScale *= 20;//CURSOR_SIZE_PIXELS; + + hduVector3Dd p0, p1; + bool bNoError; + + bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0); + if (!bNoError){ + die("bNoError 1"); + } + + bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1); + if (!bNoError){ + die("bNoError"); + } + + double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0); + phantom->setWorldScale(m_windowTworldScale); + +#endif +} + +/* GLUT idle callback */ +void idle(){ + glutPostRedisplay(); +} + + +/* initialize all the variables needed for the jni access to java fields */ +void initJniVariables(void){ + /* --- CLASSES --- */ + /* this class */ + + jclass hapticDeviceClass = env->GetObjectClass(*thisObj); + if(hapticDeviceClass == NULL){ + die("Could not find the Haptics class"); + } + /* the haptic listener, member of this class */ + jclass hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/depic/jhapticgui/HapticListener;"); + if(hapticListenerClass == NULL){ + die("Could not find the haptic listener class"); + } + + /* the Object class for wait and notify methods */ + jclass objectClass = env->FindClass("Ljava/lang/Object;"); + if(objectClass == NULL ){ + die("Could not find te Object class"); + } + + /* --- FIELD ID's --- */ + + /* boolean set by this thread to notify the java thread the unsuccessful initialization of haptic device */ + deviceInitFailedFieldID = env->GetFieldID(hapticDeviceClass,"initFailed", "Z"); + if(deviceInitFailedFieldID == NULL){ + die("failed to find the initFailed field id"); + } + + /* boolean set by this thread to notify the java thread the initialization of haptic device is done */ + deviceInitDoneFieldID = env->GetFieldID(hapticDeviceClass,"initDone", "Z"); + if(deviceInitDoneFieldID == NULL){ + die("failed to find initDone field id"); + } + + /* boolean set by the java thread to notify this thread the program has been shut down */ + disposeFieldID = env->GetFieldID(hapticDeviceClass, "dispose", "Z"); + if(disposeFieldID == NULL){ + die("failed to find the disposeFieldID field id"); + } + + messageReadyFieldID = env->GetFieldID(hapticDeviceClass,"messageReady","Z"); + if(messageReadyFieldID == NULL){ + die("failed to find the messageReadyFieldID field id"); + } + + commandFieldID = env->GetFieldID(hapticDeviceClass,"command","Ljava/lang/String;"); + if(commandFieldID == NULL){ + die("failed to find the commandFieldID field id"); + } + + argsFieldID = env->GetFieldID(hapticDeviceClass,"args","Ljava/lang/String;"); + if(argsFieldID == NULL){ + die("failed to find the argsFieldID field id"); + } + + IDFieldID = env->GetFieldID(hapticDeviceClass,"ID","I"); + if(IDFieldID == NULL){ + die("failed to find the IDFieldID field id"); + } + + jfieldID hapticListenerFieldID = env->GetFieldID(hapticDeviceClass,"hapticListener","Luk/ac/qmul/eecs/depic/jhapticgui/HapticListener;"); + hapticListener = env->GetObjectField(*thisObj,hapticListenerFieldID); + if(hapticListener == NULL){ + die("failed to find hapticListener field id"); + } + + /* --- SYNCHRONIZATION METHODS ID --- */ + /* this.notify() */ + notifyMethodID = env->GetMethodID(objectClass,"notify","()V"); + if(notifyMethodID == NULL){ + die("failed to find the notify method id"); + } + + /* listener.wait() */ + waitListenerMethodID = env->GetMethodID(objectClass,"wait","()V"); + if(waitListenerMethodID == NULL){ + die("failed to find the wait method id"); + } + + messageToListenerMethodID = env->GetMethodID(hapticListenerClass,"addMessage","(Ljava/lang/String;Ljava/lang/String;I)V"); + if(messageToListenerMethodID == NULL){ + die("failed to find the addMessage method id"); + } + + /* clean up local references */ + env->DeleteLocalRef(hapticDeviceClass); + env->DeleteLocalRef(hapticListenerClass); + env->DeleteLocalRef(objectClass); +} + +void send(const Message &m){ + + jstring jstrCommand = env->NewStringUTF(m.command); + jstring jstrArgs = env->NewStringUTF(m.args); + + env->CallVoidMethod(hapticListener,messageToListenerMethodID,jstrCommand,jstrArgs,m.ID); + checkExceptions(env,"Could not call hapticListener.send()"); + + env->DeleteLocalRef(jstrCommand); + env->DeleteLocalRef(jstrArgs); +} + +void checkExceptions(JNIEnv *env, char* what){ + if(env->ExceptionOccurred()){ + std::cerr << "Exception occurred!!!" << std::endl; + std::cerr << what << std::endl; + env->ExceptionDescribe(); + exit(-1); + } +} + + +void die(char* msg){ + std::cerr << msg << std::endl; + exit(-1); +} + +void Notify(jobject *monitor){ + env->CallVoidMethod(*monitor,notifyMethodID); + checkExceptions(env, "Could not call notify()"); +} + +void MonitorEnter(jobject *monitor){ + if(env->MonitorEnter(*monitor) != JNI_OK){ + die("Could not allocate memory for monitor enter"); + } +} + +void MonitorExit(jobject *monitor){ + if(env->MonitorExit(*monitor) != JNI_OK){ + die("Could not release memory for monitor exit "); + } +} + + + diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,23 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class uk_ac_qmul_eecs_depic_haptics_HapticDevice */ + +// must change class path to match the new library +// also must change JNI class references +#ifndef _Included_uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice +#define _Included_uk_ac_qmul_eecs_depic_haptics_HapticDevice +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: uk_ac_qmul_eecs_depic_haptics_HapticDevice + * Method: init + * Signature: (II)V + */ +JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice_init + (JNIEnv *, jobject, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/utils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/utils.cpp Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,68 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ + +#include "utils.h" + +void stopExecution(char* msg){ + std::cerr << msg << std::endl; + exit(-1); +} + + +bool fromScreen(const hduVector3Dd &win, hduVector3Dd &obj) { + + hduMatrix m_worldTview; + hduMatrix m_viewTclip; + int m_viewport[4]; + glGetDoublev(GL_MODELVIEW_MATRIX, m_worldTview); + glGetDoublev(GL_PROJECTION_MATRIX, m_viewTclip); + glGetIntegerv(GL_VIEWPORT, m_viewport); + int nResult = gluUnProject( + win[0], win[1], win[2], + m_worldTview, + m_viewTclip, + m_viewport, + &obj[0], &obj[1], &obj[2]); + + return nResult == GL_TRUE; +} + +bool toScreen(const hduVector3Dd &obj, hduVector3Dd &win) { + + hduMatrix m_worldTview; + hduMatrix m_viewTclip; + int m_viewport[4]; + glGetDoublev(GL_MODELVIEW_MATRIX, m_worldTview); + glGetDoublev(GL_PROJECTION_MATRIX, m_viewTclip); + glGetIntegerv(GL_VIEWPORT, m_viewport); + int nResult = gluProject( + obj[0], obj[1], obj[2], + m_worldTview, + m_viewTclip, + m_viewport, + &win[0], &win[1], &win[2]); + + return nResult == GL_TRUE; +} + + +HDdouble norm(HDdouble v[3]){ + return sqrt( (v[0]*v[0])+(v[1]*v[1])+(v[2]*v[2])); +} + diff -r 000000000000 -r 3074a84ef81e native/DawHaptics/DawHaptics/utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/DawHaptics/DawHaptics/utils.h Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,36 @@ +/* +Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + +Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + +This program is free software: you can 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 . +*/ +#pragma once + +#include +#include "stdafx.h" + +#include + +#include +#include + + + + +void stopExecution(char* msg); +bool fromScreen(const hduVector3Dd &win, hduVector3Dd &obj); +bool toScreen(const hduVector3Dd &obj, hduVector3Dd &win); +HDdouble norm(HDdouble v[3]); + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/AudioLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/AudioLoader.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,189 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ + +package uk.ac.qmul.eecs.depic.daw; + +import java.io.BufferedInputStream; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.SwingWorker; + +import uk.ac.qmul.eecs.depic.daw.AudioLoader.ReturnObject; +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSampleWrapper; + +public class AudioLoader extends SwingWorker{ + public static final int FILE_LOAD_TOTAL_PROGRESS = 100; + /** + * The default conversion format used. Also the conversion format returned by {@code getConversionFormat()} + */ + public static final AudioFormat DEFAULT_CONVERSION_FORMAT = new AudioFormat( + 8000.0f, // sample rate + 16, // bit per sample + 1, // mono + true, // signed + false // little endian (default .wav files) + ); + + private File audioFile; + private int minChunkSize; + private int maxScaleFactor; + + public AudioLoader(File audioFile, int minChunkSize, int maxScaleFactor){ + this.audioFile = audioFile; + this.minChunkSize = minChunkSize; + this.maxScaleFactor = maxScaleFactor; + } + + /** + * Reads the audio files and build all the min and max for all the shunks of frames + */ + @Override + protected ReturnObject doInBackground() throws Exception { + /* get all the info about the file format, needed later for the estimate of the converted file length */ + AudioInputStream originalFile = AudioSystem.getAudioInputStream(audioFile); + + AudioFormat originalAudioFormat = originalFile.getFormat(); + long originalNumTotalFrames = originalFile.getFrameLength(); + float originalFrameSize = originalAudioFormat.getFrameSize(); + float originalFrameRate = originalAudioFormat.getFrameRate(); + float originalNumChannels = originalAudioFormat.getChannels(); + + if(originalNumTotalFrames == 0) + throw new UnsupportedAudioFileException("File Empty"); + /* convert the audio format to the one suitable for parsing chunks and get min and max from them (see getConversionFormat()) */ + AudioFormat conversionFormat = getConversionFormat(); + if(!AudioSystem.isConversionSupported(conversionFormat,originalAudioFormat)){ + throw new UnsupportedAudioFileException("Cannot convert file to the following format: "+conversionFormat); + } + + AudioInputStream convertedFile = AudioSystem.getAudioInputStream(conversionFormat, originalFile); + /* start parsing the file and building the chunks' minimums and maximums */ + /* all the variable from here on, unless they begin with "originalFile" refer to the converted audio stream */ + + byte[] audioBytes = new byte[minChunkSize * conversionFormat.getFrameSize()]; + /* prepare the ByteBuffer, wrapping the byte array, that will be used to read Short values */ + ByteBuffer byteBuffer = ByteBuffer.wrap(audioBytes); + /* set the endiannes according to the audio format */ + byteBuffer.order(conversionFormat.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + /* make an estimation of the frames in the converted file, based on the original file. This is necessary because * + * convertedFile is a stream pointing to original file and not a file itself. Therefore getFrameLength() returns * + * -1 as t doesn't have any knowledge of the length of the underlying file. So make an estimate of the length * + * of the file after conversion in order to allocate enough space when the ArrayList in newFlechunks is allocated* + * and for the progress of file load. */ + float convertedFrameSize = conversionFormat.getFrameSize(); + float convertedFrameRate = conversionFormat.getFrameRate(); + float convertedNumChannels = conversionFormat.getChannels(); + + long convertedNumTotalFrames = (long) ( + originalNumTotalFrames * + (convertedFrameSize/originalFrameSize) * + (convertedFrameRate/originalFrameRate) * + (convertedNumChannels/originalNumChannels)); + long convertedNumTotalBytes = convertedNumTotalFrames * conversionFormat.getFrameSize(); + + /* creates the first list of chunks with smallest size. Array size = audio frames/num frames of minimum chunk */ + WavePeaks newFileChunks = new WavePeaks(maxScaleFactor); + /* first List s for scale factor = 1, that is the finest zoom scale */ + newFileChunks.add(1, new ArrayList((int)(convertedNumTotalFrames/minChunkSize) +1)); + int numBytesRead = 0; + float totalBytesRead = 0f; + try(BufferedInputStream chunkBufferedAudio = new BufferedInputStream(convertedFile,audioBytes.length)){ + while((numBytesRead = chunkBufferedAudio.read(audioBytes)) != -1){ + if(isCancelled()) + return null; + totalBytesRead += numBytesRead; + /* normalize the progress value to total load */ + int progress = (int) ((totalBytesRead/convertedNumTotalBytes)*FILE_LOAD_TOTAL_PROGRESS); + if(progress < FILE_LOAD_TOTAL_PROGRESS) + setProgress(progress); + /* Now read the byte buffer, backed by audioByte, and find min and max. The audio format * + * has been converted to signed 16 bit frames, so it can be read in a Short value */ + Short currentMax = Short.MIN_VALUE; + Short currentMin = Short.MAX_VALUE; + + /* find maximum and minimum values in this chunk */ + byteBuffer.clear(); + byteBuffer.limit(numBytesRead); + while(byteBuffer.hasRemaining()){ + Short frame = byteBuffer.getShort(); + if(frame > currentMax) + currentMax = frame; + if(frame < currentMin) + currentMin = frame; + } + newFileChunks.get(1).add(new Chunk(currentMin, currentMax)); + } + } + + for(int scaleFactor = 2; scaleFactor <= maxScaleFactor; scaleFactor++){ + List previousList = newFileChunks.get(scaleFactor-1); + List newList = new ArrayList<>(previousList.size()/2+1); + + for(int i=0; i. +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.awt.Color; + +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; + +public interface Automation extends Sequence { + /** + * Adds a new {@code AutomationValue} object to this automation. + * + * @param position the position of the new object + * @param value the value of the new object + * + * @return the new automation just created + */ + public AutomationValue add(float position, float value); + + /** + * Removes an {@code AutomationValue} object from this automation. + * + * @param values + * @return {@code true} if this automation changed as a result of the call + */ + public boolean remove(Sequence.Value value); + + /** + * Returns the colour to draw this automation graphically + * + */ + public Color getColor(); + + /** + * Removes all the automation values. + * + */ + public void reset(); + + /** + * + * NONE_AUTOMATION's colour is white + * + */ + public final static Automation NONE_AUTOMATION = new Automation() { + + @Override + public AutomationValue add(float position, float value) { return null; } + + @Override + public boolean remove(Sequence.Value value) { + return false; + } + + @Override + public Range getRange() { + return new Range(0.0f,0.0f); + } + + @Override + public void reset() {} + + @Override + public float getBegin() { + return 0.0f; + } + + @Override + public float getEnd() { + return 0.0f; + } + + @Override + public float getLen(){ + return 0.0f; + } + + @Override + public int getValuesNum() { + return 0; + } + + @Override + public AutomationValue getValueAt( + int index) { + return null; + } + + @Override + public void addSequenceListener(SequenceListener l) {} + + @Override + public void removeSequenceListener(SequenceListener l) {} + + @Override + public Color getColor() { + return Color.WHITE; + } + }; +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/AutomationValue.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/AutomationValue.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,97 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +/** + * + * This class represent a value in an Automation. An automation is a set of line envelopes and + * a value represent one end of an automation line. + * + * Therefore an automation value normally points to where the automation changes its slope. + * + * Since an automation is a 2D graph + * + * + */ +public abstract class AutomationValue implements Comparable, Sequence.Value { + private float position; + /* ranges from 0.0 to 1.0 */ + private float value; + + public AutomationValue(float position, float value){ + this.position = position; + this.value = value; + } + + public void setTimePosition(float position){ + this.position = position; + } + + /** + * Returns the position of this automation value in milliseconds + * + * The position represents where this value is on time + * + * @return the position of this automation value in milliseconds + */ + public float getTimePosition() { + return position; + } + + public float getValue() { + return value; + } + + public void setValue(float value) { + this.value = value; + } + + + public void setLocation(float position, float value){ + setTimePosition(position); + setValue(value); + } + + /** + * Returns the enclosing Automation. + * + * @return the enclosing Automation. + */ + public abstract Automation getAutomation(); + + @Override + public int compareTo(AutomationValue v) { + if(v == null) + return 1; + if(position < v.getTimePosition()) + return -1; + else if(position > v.getTimePosition()) + return 1; + else + return 0; + } + + @Override + public String toString(){ + return "Automation Value. position:"+position+" value:"+value; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Chunk.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Chunk.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,64 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; + + +public class Chunk extends Range { + public static final Chunk SILENCE = new Chunk((short)0,(short)0); + private float normalizedStart; + private float normalizedEnd; + + public Chunk(Chunk chunk1, Chunk chunk2) { + super(chunk1, chunk2); + + normalizedStart = Math.min(chunk1.getNormStart(), chunk2.getNormStart()); + normalizedEnd = Math.max(chunk1.getNormEnd(), chunk2.getNormEnd()); + } + + public Chunk(Short s1, Short s2) { + normalizedStart = (s1-Short.MIN_VALUE)/(float)domain(); + normalizedEnd = (s2-Short.MIN_VALUE)/(float)domain(); + + MathUtils.Scale scale = new MathUtils.Scale(0.0f, 1.0f, -1.0f, 1.0f); + start = scale.linear(normalizedStart); + end = scale.linear(normalizedEnd); + } + + public static int domain() { + return Short.MAX_VALUE - Short.MIN_VALUE; + } + + public float getNormStart(){ + return normalizedStart; + } + + public float getNormEnd(){ + return normalizedEnd; + } + + @Override + public String toString(){ + return "Chunk - range:["+getStart()+","+getEnd()+"] normalized range ["+getNormStart()+","+getNormEnd()+"]" ; + } +} + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Clip.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Clip.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,202 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.Range; + + +public class Clip extends Range implements Comparable { + private Sample sample; + private int sampleStart; + private float startMs; + private float endMs; + private float millisecPerChunk; + private float sampleStartMs; + + public Clip(int start, int end, Sample sample, int sampleStart, float millisecPerChunk) { + super(start, end); + this.sample = sample; + this.sampleStart = sampleStart; + + this.millisecPerChunk = millisecPerChunk; + updateTimeMs(); + sampleStartMs = sampleStart * millisecPerChunk; + } + + /** + * + * Copy constructor + * + * @param c another instance of {@code Clip} + */ + public Clip(Clip c){ + this(c.start,c.end,c.sample,c.sampleStart,c.millisecPerChunk); + } + + private void updateTimeMs(){ + startMs = start * millisecPerChunk; + endMs = end * (millisecPerChunk) + millisecPerChunk; + } + + /** + * Shifts the clips of the amount given as argument + * @param amount + */ + public void shift(int amount){ + start += amount; + end = end += amount; + + updateTimeMs(); + } + + public void setStartAt(int position){ + int diff = position - start; + start = position; + end = end + diff; + + updateTimeMs(); + } + + public Sample getSample() { + return sample; + } + + /** + * Returns the chunks index where this selection starts relative to the start of the sample. + * + * This is different from {@code getStart()}, as {@code getStart()} returns the + * absolute position (a.k.a. chunk index) of the selection start + * + * @return + */ + public int getSampleStart(){ + return sampleStart; + } + + public float getSampleStartMs(){ + return sampleStartMs; + } + + public float getStartTimeMs(){ + return startMs; + } + + public float getEndTimeMs(){ + return endMs; + } + + public float getMillisecPerChunk(){ + return millisecPerChunk; + } + + /** + * Checks whether {@code p} is contained in the selection, + * that is greater than {@code getStart()} and lower than {@code getEnd()}. + * + * + * @param p the integer value to check for + * @return {@code true} if {@code pos} is contained in the selection, {@code false} otherwise + */ + public boolean contains(int pos){ + return ((pos>=getStart()) && (pos<=getEnd())); + } + + public boolean containsTime(float time){ + return time >= startMs && time <= endMs; + } + + /** + * Splits the selection around the given position. + * + * If {@code pos} is less than the clip start or greater than the clip end, then the array will have size 1 and will + * contain this Clip. Conversely if {@code pos} is contained in this clip an array with two new instances of {@code Clip} is returned. The clip at index 0, will range + * from {@code geStart()} to {@code pos-1}, whereas the clip at index 1 will range from {@code pos} to {@code getEnd()} + * + * + * @param pos the split position + * @return an array of one or two clips, resulted from a split at position {@code pos} + */ + public Clip [] split(int pos){ + /* if pos can go from start+1 to end * + * if pos == start+1, the split will be [start,start] and [start+1,end] * + * if pos == end, the split will be [start,end-1] and [end,end] */ + if(pos <= getStart() || pos > getEnd()){ + return new Clip [] {this}; + }else{ + return new Clip [] { + new Clip(getStart(),pos-1,sample,sampleStart,millisecPerChunk), + new Clip(pos,getEnd(),sample,sampleStart+pos-getStart(),millisecPerChunk) + }; + } + } + + @Override + public int compareTo(Clip s) { + return getStart().compareTo(s.getStart()); + } + + @Override + public String toString(){ + return super.toString()+ ", selection in Ms:["+startMs+","+endMs+"], sample:"+ sample+ ", sample start:" +sampleStart; + } + + public static Clip join(Clip clip1, Clip clip2){ + /* nulls not joinable */ + if(clip1 == null || clip2 == null){ + return null; + } + + /* clips with different sample or different resolution not joinable */ + if(!clip1.sample.equals(clip2.sample) || clip1.millisecPerChunk != clip2.millisecPerChunk ){ + return null; + } + + /* try clip2 after clip1 */ + if(clip1.end+1 == clip2.start){ + int clip1Len = clip1.end - clip1.start; + + if(clip1.sampleStart + clip1Len + 1 == clip2.sampleStart){ + return new Clip( + clip1.start, + clip2.end, + clip1.sample, + clip1.sampleStart, + clip1.millisecPerChunk + ); + } + /* try clip1 after clip2 */ + }else if(clip2.end+1 == clip1.start){ + int clip2Len = clip2.end - clip2.start; + + if(clip2.sampleStart + clip2Len + 1 == clip2.sampleStart){ + return new Clip( + clip2.start, + clip1.end, + clip2.sample, + clip2.sampleStart, + clip2.millisecPerChunk + ); + } + } + + /* not joinable */ + return null; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/ClipList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/ClipList.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,425 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ClipList extends ArrayList { + private static final long serialVersionUID = 1L; + private int scaleFactor; + private List starts; + private Map peaksMap; + private List changeListeners; + private ClipListEditor clipListEditor; + + public ClipList(int scaleFactor, Map peaksMap,SoundWave wave) { + this.scaleFactor = scaleFactor; + this.peaksMap = peaksMap; + changeListeners = new ArrayList<>(1); + starts = new ArrayList<>(); + clipListEditor = new ClipListEditor(); + } + + /** + * Adds a new {@code SampleSelection}. The order of selections is kept (ordered insert). + * + * @param s the new selection to add + */ + + @Override + public boolean add(Clip s){ + int index = Collections.binarySearch(starts, s.getStart()); + + if(index < 0) + index = ~index; + + starts.add(index, s.getStart()); + super.add(index,s); + return true; + } + + /** + * Unsupported method + * + * @throws UnsupportedOperationException + * + */ + @Override + public void add(int index, Clip element){ + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection collection){ + for(Clip c : collection){ + add(c); + } + return (!collection.isEmpty()); + } + + @Override + public boolean addAll(int index, Collection c){ + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c){ + boolean changed = false; + for( Object o : c){ + boolean removed = remove(o); + changed = changed || removed; + } + return changed; + } + + @Override + public boolean retainAll(Collection collection){ + List newList = new ArrayList<>(size()); + for(Clip c : this){ + if(collection.contains(c)){ + newList.add(c); + } + } + + if(newList.size() == size()) + return false; + + clear(); + addAll(newList); + return true; + } + + @Override + public boolean remove(Object c){ + boolean removed = super.remove(c); + if(removed){ + starts.remove(((Clip)c).getStart()); // FIXME boh + } + return removed; + } + + + public void clear(){ + super.clear(); + starts.clear(); + } + + public void addSoundWaveListener(SoundWaveListener l ){ + changeListeners.add(l); + } + + public boolean removeSoundWaveListener(SoundWaveListener l ){ + return changeListeners.remove(l); + } + + protected void fireSoundWaveListeners(SoundWaveEvent evt){ + for(SoundWaveListener l : changeListeners){ + l.update(evt); + } + } + + /** + * Returns the length of the SoundWave + * + * @return + */ + public int getLength() { + if(isEmpty()) + return 0; + else { + int max = get(0).getEnd(); + for(int i=1; i< size(); i++){ + if(max < get(i).getEnd()){ + max = get(i).getEnd(); + } + } + + return max+1; + } + } + + public float getLengthMs(){ + if(isEmpty()) + return 0.0f; + else{ + float max = get(0).getEndTimeMs(); + for(int i=1; i< size(); i++){ + if(max < get(i).getEndTimeMs()){ + max = get(i).getEndTimeMs(); + } + } + + return max; + } + } + + public List getClipsAt(int pos){ // FIXME maybe find a better algorithm + if(isEmpty()) + return Collections.emptyList(); + + List clipsAt = new ArrayList<>(size()); + for(Clip s : this){ + /* take advantage of the fact that ClipList is ordered by start */ + if(s.getStart() > pos){ + break; + }else if(s.contains(pos)){ + clipsAt.add(s); + } + } + +// if(selections.get(chachedIndex).contains((int)p)){ +// return selections.get(chachedIndex); +// }else{ +// for(int i=0;i getClipsAtTime(float time){ + if(isEmpty()){ + return Collections.emptyList(); + } + + List clipsAt = new ArrayList<>(size()); + for(Clip s : this){ + /* clips are ordered by start (and hence startTimeMs) so next clips will all have * + * startTimeMs greater than time. Further check is therefore useless */ + if(s.getStartTimeMs() > time){ + break; + }else if(s.containsTime(time)){ + clipsAt.add(s); + } + } + return clipsAt; + } + + /** + * changes the scale factor of this clip list. All the contained clips will be rescaled according to the new scale factor. + * + * @param newScaleFactor + */ + public void setScaleFactor(int newScaleFactor){ + for(int i=0; i splitList(int pos, List list){ + List newList = new LinkedList<>(); + + for(int i=0; i copiedClips; + Selection copySelection; + boolean copied; + + ClipListEditor(){ + copiedClips = new LinkedList<>(); + copySelection = Selection.ZERO_SELECTION; + } + + @Override + public boolean cut(SoundWave wave, Selection selection) { + if(selection == null || selection.getStart() == selection.getEnd()){ + return false; + } + + /* cut also copies in memory */ + copySelection = selection; + + /* splits this clipList on start and end of selection and + * saves in copiedClips the clips within the selection */ + + /* get the split list, splitting by selection start */ + List splitList = ClipList.splitList(selection.getStart(),ClipList.this); + /* substitute this list with the split list */ + ClipList.this.clear(); + ClipList.this.addAll(splitList); + + /* get the split list, splitting by selection end */ + splitList = ClipList.splitList(selection.getEnd(),ClipList.this); + /* substitute this list with the split list */ + ClipList.this.clear(); + ClipList.this.addAll(splitList); + + copiedClips.clear(); + for(Clip c : ClipList.this){ + /* when a clip is split at position, position will go with the right split * + * this is why getStart() must be grater and equal and getEnd() lower but not equal */ + if(c.getStart() >= selection.getStart() && c.getEnd() < selection.getEnd()){ + copiedClips.add(c); + } + } + copied = removeAll(copiedClips); + + if(copied){ + fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.CUT,copiedClips)); + } + return copied; + } + + @Override + public boolean copy(SoundWave wave, Selection selection) { + if(selection == null || selection.getStart() == selection.getEnd()){ + return false; + } + /* keep track of the selection used to copy */ + copySelection = selection; + + /* splits on the selection start */ + List splitList = splitList(selection.getStart(),ClipList.this); + Iterator iterator = splitList.iterator(); + + /* removes all the clips at the left of selection start */ + while(iterator.hasNext()){ + Clip c = iterator.next(); + if(c.getStart() < selection.getStart()){ // not equal because when splitting at position + iterator.remove(); // position will go with the right split + } + } + + /* splits what's left at the selection end */ + splitList = splitList(selection.getEnd(),splitList); + iterator = splitList.iterator(); + + /* removes all the clips at the right of selectino end */ + while(iterator.hasNext()){ + Clip c = iterator.next(); + if(c.getEnd() >= selection.getEnd()){ + iterator.remove(); + } + } + + copiedClips = splitList; + + if(splitList.isEmpty()){ + return false; + }else{ + fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.COPY,copiedClips)); + return true; + } + } + + @Override + public boolean paste(SoundWave wave, int position) { + if(copiedClips.isEmpty()){ + return false; + } + + /* shift the clips to the new position */ + for(Clip c : copiedClips){ + /* shift the clip to the new position. Keeps into account the position relative * + * to the original copy selection (c.getStart() - copySelection.getStart()). * + * This is to have the clips original to be pasted as they were in the original * + * selection, avoid them to all be to be placed with start at position */ + Clip newClip = new Clip(c); + newClip.setStartAt(position + c.getStart() - copySelection.getStart()); + + /* add the new clips after shifting them to the new position */ + add(newClip); + } + + fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.PASTE,copiedClips)); + return true; + } + + @Override + public boolean insert(SoundWave wave, int position){ + if(copiedClips.isEmpty()){ + return false; + } + + /* split the list at position */ + List splitList = ClipList.splitList(position, ClipList.this); + + /* shift right all the clips at the right of position */ + for(Clip c : splitList){ + if(c.getStart() >= position){ // >= because when split at position, position will go to the right split + c.shift((int)copySelection.lenght()); + } + } + + /* insert the copied clips */ + for(Clip c : copiedClips){ + /* shift the clip to the new position. Keeps into account the position relative * + * to the original copy selection (c.getStart() - copySelection.getStart()). * + * This is to have the clips original to be pasted as they were in the original * + * selection, avoid them to all be to be placed with start at position */ + Clip newClip = new Clip(c); + newClip.setStartAt(position + c.getStart() - copySelection.getStart()); + + /* add the new clips after shifting them to the new position */ + splitList.add(newClip); + } + + /* update this clip list with the new list */ + ClipList.this.clear(); + ClipList.this.addAll(splitList); + + fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.INSERT,copiedClips)); + return true; + } + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Daw.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Daw.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,64 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import javax.swing.SwingUtilities; + +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSoundEngineFactory; +import uk.ac.qmul.eecs.depic.daw.gui.MainFrame; + +public class Daw implements Runnable{ + private static SoundEngineFactory soundEngineFactory; + + /** + * Main function. Calls {@code run()} in the Event dispatching thread. + * + * @param args command line argument vector + */ + public static void main(String[] args) { + System.out.println("Digital Audio Workstation prototype running on Java "+ + System.getProperty("java.version")+" "+System.getProperty("os.arch")); + Daw daw = new Daw(); + try { + SwingUtilities.invokeAndWait(daw); + } + catch (Exception e) { + e.printStackTrace(); + } + + } + + /** + * Creates a new {@code MainFrame} and makes it visible in the Event Dispatching Thread. + */ + public void run(){ + MainFrame frame = new MainFrame(); + /* become visible */ + frame.pack(); + frame.setVisible(true); + } + + public static SoundEngineFactory getSoundEngineFactory(){ + if(soundEngineFactory == null){ + soundEngineFactory = new BeadsSoundEngineFactory(); + } + return soundEngineFactory; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/DbWave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/DbWave.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,307 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.util.ArrayList; +import java.util.List; + +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; + +public class DbWave implements Wave { + private Wave soundWave ; + private Sequence peakLevelSequence; + + public DbWave(Wave soundWave){ + this.soundWave = soundWave; + } + + @Override + public int getChunkNum() { + return soundWave.getChunkNum(); + } + + @Override + public Chunk getChunkAt(int i) { + return new DBChunk(soundWave.getChunkAt(i)); + } + + @Override + public void setScaleFactor(int scaleFactor) { + soundWave.setScaleFactor(scaleFactor); + } + + @Override + public int getScaleFactor() { + return soundWave.getScaleFactor(); + } + + @Override + public int getMaxScaleFactor() { + return soundWave.getMaxScaleFactor(); + } + + @Override + public float getWaveTime() { + return soundWave.getWaveTime(); + } + + @Override + public float getMillisecPerChunk() { + return soundWave.getMillisecPerChunk(); + } + + /** + * Returns a sequence representing the peak meter line, if any + * + * @return the peak level sequence or {@code null} if this wave doesn't have a sequence + */ + @Override + public Sequence getSequence(){ + return peakLevelSequence; + } + + public void createNewSequence(){ + peakLevelSequence = new PeakMeterSequence(this); + } + + public void deleteSequence(){ + peakLevelSequence = null; + } + + @Override + public boolean hasSequence(){ + return (peakLevelSequence != null); + } + + + private static class PeakMeterSequence implements Sequence { + private static final float TAN_THETA = 1/5000.0f; + float previousPeak; + float previousPeakTime; + Range range; + List values; + private float len; + + PeakMeterSequence(Wave w){ + previousPeak = 0.0f; + previousPeakTime = 0.0f; + values = new ArrayList<>(w.getChunkNum()/5); + range = new Range(0.0f,1.0f); + len = w.getChunkNum()*w.getMillisecPerChunk(); + + for(int i=0; i 0, otherwise would not be in this block */ + float timeLeftToZero = decayTime - time; + /* returns the y value of the peak line at timePoistion */ + peakLineDecayPoint = timeLeftToZero * TAN_THETA; + } + + if(c.getNormEnd() > peakLineDecayPoint) { + final int index = values.size(); + final float value = c.getNormEnd(); + + /* this chunk was higher than the peak line at time * + * position therefore it becomes the new peak value */ + previousPeak = value; + previousPeakTime = time; + + values.add(new Sequence.Value() { + @Override + public int index() { + return index; + } + + @Override + public float getValue() { + return value; + } + + @Override + public float getTimePosition() { + return time; + } + + @Override + public Sequence getSequence() { + return PeakMeterSequence.this; + } + + @Override + public String toString(){ + return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition(); + } + }); + }else if( MathUtils.equal(time, decayTime, w.getMillisecPerChunk())){ + previousPeak = 0.0f; + previousPeakTime = time; + final int index = values.size(); + values.add(new Sequence.Value() { + @Override + public int index() { + return index; + } + + @Override + public float getValue() { + return 0.0f; + } + + @Override + public float getTimePosition() { + return time; + } + + @Override + public Sequence getSequence() { + return PeakMeterSequence.this; + } + + @Override + public String toString(){ + return "Peak meter sequence value. Value:"+getValue()+" time pos:"+getTimePosition(); + } + }); + } + } + } + + /** + * Calculates the point on the time line (x-axis) where the peak curve will be completely decayed + * to zero. + * + * @param time the time of the decay point + * @param previousPeak the previous peak value. that is where the line has started decaying + * @param previousPeakTime the time of the previous peak value + * + * @return + */ + float calculateDecayLineAtTime(float time,float previousPeak, float previousPeakTime){ + /* decayTime is the time where the previous peak was plus the time * + * that the peak line will take to decay to zero */ + float decayTime = previousPeakTime +(previousPeak / TAN_THETA); + + /* the peak line has already decayed to zero in the past */ + if(time > decayTime){ + return 0.0f; + } + + /* always > 0, otherwise would have returned already */ + float timeLeftToZero = decayTime - time; + /* returns the y value of the peak line at timePoistion */ + return timeLeftToZero * TAN_THETA; + } + + @Override + public float getBegin() { + return 0.0f; + } + + @Override + public float getEnd() { + return values.isEmpty() ? getBegin() : values.get(values.size()-1).getValue(); + } + + @Override + public int getValuesNum() { + return values.size(); + } + + @Override + public Value getValueAt(int index) { + return values.get(index); + } + + @Override + public Range getRange() { + return range; + } + + @Override + public void addSequenceListener(SequenceListener l) { + // this sequence is immutable, therefore listeners would never be notfied + } + + @Override + public void removeSequenceListener(SequenceListener l) { + // this sequence is immutable, therefore listeners would never be notfied + } + + @Override + public float getLen() { + return len; + } + + } + +} + +class DBChunk extends Chunk { + float normStart; + float normEnd; + + DBChunk(Chunk c) { + super((short)0,(short)0); + start = MathUtils.toDb(Math.abs(c.getStart())); + if(Float.isInfinite(start)){ + start = 0.0f; + } + end = MathUtils.toDb(Math.abs(c.getEnd())); + if(Float.isInfinite(end)){ + end = 0.0f; + } + + normStart = 0.0f; + + float max = Math.max(Math.abs(c.getStart()),Math.abs(c.getEnd())); + float db = MathUtils.toDb(max); + if(Float.isInfinite(db)){ + normEnd = 0; + }else if(db < -60){ + normEnd = 0; + }else{ + normEnd = new MathUtils.Scale(-60.0f, 0.0f, 0.0f, 1.0f).linear(db); + } + } + + @Override + public float getNormStart() { + return normStart; + } + + @Override + public float getNormEnd() { + return normEnd; + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Direction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Direction.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,27 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +public enum Direction { + UP, + DOWN, + LEFT, + RIGHT, + NONE +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Parameter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Parameter.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,108 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.Range; + +public interface Parameter { + public interface Type { + String getLabel(); + + String getUnitofMeasurment(); + } + + public Type GAIN_TYPE = new Type(){ + @Override + public String getLabel() { + return "Gain "; + } + + @Override + public String getUnitofMeasurment(){ + return "db"; + } + }; + + public Type PAN_TYPE = new Type(){ + @Override + public String getLabel() { + return "Pan "; + } + + @Override + public String getUnitofMeasurment(){ + return ""; + } + }; + + public float getValue(); + + public void setValue(float value); + + public float getInitialValue(); + + public Range getRange(); + + public Type getType(); + + public void automate(Automation a); + + public static final Parameter NONE_PARAMETER = new Parameter() { + private Type NONE_TYPE = new Type(){ + @Override + public String getLabel() { + return "None"; + } + + @Override + public String getUnitofMeasurment(){ + return ""; + } + }; + + Range r = new Range(0.0f,0.0f); + + @Override + public float getValue() { + return 0; + } + + @Override + public void setValue(float value) { } + + @Override + public Range getRange() { + return r; + } + + @Override + public Type getType() { + return NONE_TYPE; + } + + @Override + public void automate(Automation a) {} + + @Override + public float getInitialValue() { + return 0; + } + + }; +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/ParametersControl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/ParametersControl.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,37 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + + +public interface ParametersControl { + + Parameter getGainParameter(); + + Parameter getPanParameter(); + + public Automation getCurrentAutomation(); + + public Parameter getCurrentAutomationType(); + + public void setCurrentAutomation(Parameter.Type type); + + public Automation getAutomation(Parameter.Type type); + + public Parameter getParameter(Parameter.Type type); +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Sample.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Sample.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,49 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + + +public interface Sample { + + public int getBytesPerSample(); + + public void getFrame(int arg0, float[] arg1); + + public void getFrameCubic(double arg0, float[] arg1); + + public void getFrameLinear(double arg0, float[] arg1); + + public void getFrameNoInterp(double arg0, float[] arg1); + + public void getFrames(int arg0, float[][] arg1); + + public float getLength(); + + public int getNumChannels(); + + public long getNumFrames(); + + public float getSampleRate(); + + public double msToSamples(double arg0); + + public double samplesToMs(double arg0); + + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Selection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Selection.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,165 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.Range; + + +public final class Selection extends Range{ + public static Selection ZERO_SELECTION = new Selection(0,1); + private int scaleFactor; + private boolean open; + + /** + * Constructs a new instance of {@code LoopPoints} from the bound values passed as argument. + * + * The lower value is assigned to the start of the loop, whereas the higher value is assigned to the end of the loop. + * If the two values are equals the loop is assumed to have no end but only a starting point. + * + * @param start the start of the range, it must be greater or equal than 0 + * @param end the end of the range, it must be either greater or equal than 0 or equal to {@code NO_POSITION} + * @param scaleFactor + * + * @throws IndexOutOfBoundsException whether {@code start} is a negative + * value or {@code end} is a negative value different from {@code NO_POSITION} + * @throws IllegalArgumentException if {@code end} is greater than {@code start} + */ + public Selection(int start, int end, int scaleFactor) { + super(start,end); + if(start < 0 && !equals(ZERO_SELECTION)){ + throw new IndexOutOfBoundsException("bounds value must be a positive integer . start:"+start); + } + if(end < 0){ + throw new IndexOutOfBoundsException("bounds value must be a positive integer end:"+end); + } + if(start > end ){ + throw new IllegalArgumentException("start cannot be greater than end. start:"+start+" end:"+end); + } + + open = false; + this.scaleFactor = scaleFactor; // FIXME controls on scaleFactor + } + + public Selection(int start, int scaleFactor){ + super(start,start); + if(start < 0){ + throw new IndexOutOfBoundsException("bounds value must be a positive integer . start:"+start); + } + + this.scaleFactor = scaleFactor; + open = true; + } + + /** + * Returns the end value of the loop + * + * @return the end point or -1 if the loop has only the start and no end to indicate the final frame. + * This value is consistent with the {@code Clip interface} + * + * @see javax.sound.sampled.Clip#setLoopPoints(int, int) + */ + @Override + public final Integer getEnd(){ + if(open) + return -1; + else + return super.getEnd(); + } + + /** + * Returns the scale factor of this selection + * + * @return the scale factor of this selection + */ + public int getScaleFactor(){ + return scaleFactor; + } + + /** + * Checks whether the selection defines an open or closed interval. + * + * The selection is open is the end is equal to {@code NO_POSITION}. + * + * @return {@code true} is the selection is open, {@code false} otherwise + */ + public boolean isOpen(){ + return open; + } + + + + /** + * Returns {@code true} if {@code p} is contained in the selection ( p is greater than {@code getStart()} + * and smaller than {@code getEnd()} ). + * + * If the selection is open, {@code false} is returned. + * + * @param p the integer vale to check for + * + * @return @return {@code true} if {@code p} is contained in the selection, {@code false} otherwise + */ + public boolean contains(int p){ + if(isOpen()){ + return false; + }else{ + return ((p>=getStart()) && (p<=getEnd())); + } + + } + + @Override + public String toString(){ + if(open) + return getClass().getSimpleName()+" ["+getStart()+",inf), scale factor:"+scaleFactor; + else + return getClass().getSimpleName()+" ["+getStart()+","+getEnd()+"], scale factor:"+scaleFactor; + } + + /** + * Convert a selection to another selection with a new scale factor. + * + * The {@code start} and {@code end} of the selection are changed accordingly. + * + * @param s the selection to convert + * @param newFactor the factor of the converted selection + * @return {@code s} if {@code newFactor} is already the scale factor of {@code s}, a new selection + * + */ + public static Selection convertFactor(Selection s, int newFactor){ + if(s.getScaleFactor() == newFactor) + return s; + int newStart = (int)(s.getStart() * Math.pow(2,s.getScaleFactor()-newFactor)); + if(!s.isOpen()){ + int newEnd = (int)(s.getEnd() * Math.pow(2,s.getScaleFactor()-newFactor)); + return new Selection(newStart,newEnd,newFactor); + }else{ + return new Selection(newStart,newFactor); + } + } + + public static int convertFactor(int position, int fromFactor, int toFactor){ + if(fromFactor == toFactor){ + return position; + } + + return (int)(position * Math.pow(2,fromFactor-toFactor)); + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Sonification.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Sonification.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,35 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +public interface Sonification { + + public SequenceMapping getSequenceMapping(SoundType soundType); + + public void withinSelection(float g, float p); + + public void outsideSelection(); + + public void play(SoundType type); + + public void play(SoundType type, float gain, float pan); + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Sound.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Sound.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,93 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public class Sound { + private Enum type; + private float pan; + private float pitch; + private float gain; + private Object soundSource; + private PropertyChangeSupport changeSupport; + + public Sound(Enum type) { + this.type= type; + gain = 1.0f; + pan = 0.0f; + pitch = 440.0f; + changeSupport = new PropertyChangeSupport(this); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + changeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + changeSupport.removePropertyChangeListener(listener); + } + + public Enum getType(){ + return type; + } + + public float getPitch() { + return pitch; + } + + public Sound setPitch(float pitch) { + float oldPitch = this.pitch; + this.pitch = pitch; + changeSupport.firePropertyChange("pitch", oldPitch, pitch); + return this; + } + + public float getGain() { + return gain; + } + + public Sound setGain(float gain) { + float oldGain = this.gain; + this.gain = gain; + changeSupport.firePropertyChange("gain", oldGain, gain); + return this; + } + + public Sound setPan(float pan){ + float oldPan = this.pan; + this.pan = pan; + changeSupport.firePropertyChange("pan", oldPan, pan); + return this; + } + + public float getPan(){ + return pan; + } + + public Object getSource(){ + return soundSource; + } + + @Override + public String toString(){ + return "Sound type:"+getType(); + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundEngineFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundEngineFactory.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,28 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + + +public interface SoundEngineFactory { + public SoundWave createSoundWave(); + + public Parameter createParameter(Parameter.Type type, SoundWave wave); + + public Sonification getSharedSonification(); +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundType.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,31 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +public enum SoundType { + HAPTIC_PORT_TOUCH, + ERROR, + SHIFT_START, + SHIFT_END, + OK, + AUTOMATION, + LOOP, + PEAK_LEVEL, + ZOOM_LEVEL; +}; diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundWave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundWave.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,158 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.List; + +import javax.swing.JComponent; + +public interface SoundWave extends Wave { + + public static final String FILE_ERROR_PROPERTY = "FILE_ERROR_PROPERTY"; + public static final int FILE_LOAD_TOTAL_PROGRESS = 100; + public static final int MIN_SUPPORTED_CHUNK_SIZE = 1; + public static final int STOP_SCANNING = -100; + + + + /** + * Adds a {@code SoundWaveListener} to this {@code SoundWave}. + * + * The listener will be notified when a {@SoundWaveEvent} happens. + * Adding twice the same object (as per the {@code equals()}) as well as + * adding {@code null} will have no effect. + * + * @param l the listener to add to this {@code SoundWave} + * + */ + public void addSoundWaveListener(SoundWaveListener l); + + /** + * Removes {@code l} from the listeners if it has been previously added. + * + * @param l the listener to remove + */ + public void removeSoundWaveListener(SoundWaveListener l); + + + /** + * + * + * @param stream a stream of audio data. The stream is wrapped with a {@code BufferedInputStream} before + * being used. + * @param propertyChangelistener + */ + public void loadAudioData(File audioFile,PropertyChangeListener propertyChangelistener); + + public void stopLoading(boolean mayInterruptIfRunning); + + /** + * Disposes the current audio data. + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code CLOSE} event. A call to this method has no effect if no audio data is loaded. + * + */ + public void close(); + + public DbWave getDbWave(); + + public void generatePeakMeter(boolean generate); + + public TransportControl getTransportControl(); + + public int getCurrentChunkPosition(); + + public void scan(int position); + + public JComponent [] getPreferencesPanels(); + + /** + * Sets a new selection for this sound wave. + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code SELECTION_CHANGED} event. + * + * @param selection the new selection. Using {@code null} or {@code Selection.VOID_SELECTION} will remove the selection + */ + public void setSelection(Selection selection); + + /** + * Sets a new position for this sound wave. + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code POSITION_CHANGED} event. The event argument is an open {@code Selection} + * whose start is the new position and scale factor is 1. + * + * @param position the new position + */ + public void setPosition(int position); + + + /** + * + * @param chuckPosition + * @param scaleFactor + * @return or {@code -1} if chunkPosition is greater than the chunk number to less than 0 + */ + public List getFramesPositionAt(int chuckPosition); + + + public SoundWaveEditor getEditor(); + + /** + * The current Automation + * + * @return the current {@code Automation} object or {@code null} if no sample is loaded and + */ + + + public ParametersControl getParametersControl(); + + + public interface TransportControl { + + /** + * Plays the sound sample. + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code START} event. + * + * + */ + public void play(); + + public void setLoop(boolean on); + + public boolean isLoopOn(); + + public void stop(); + + public void pause(); + + public void rew(); + + public void fwd(); + + public float getPlayPosition(); + } + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundWaveEditor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundWaveEditor.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,29 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +public interface SoundWaveEditor { + public boolean cut(SoundWave wave, Selection s); + + public boolean copy(SoundWave wave, Selection s); + + public boolean paste(SoundWave wave, int position); + + public boolean insert(SoundWave wave, int position); +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundWaveEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundWaveEvent.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,144 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.util.EventObject; + +public class SoundWaveEvent extends EventObject { + private static final long serialVersionUID = 1L; + private Object args; + private String type; + + /** + * Creates a new Sound event with no argument + * @param soundWave + * @param type + */ + public SoundWaveEvent(SoundWave soundWave, String type){ + super(soundWave); + this.type = type; + } + + public SoundWaveEvent(SoundWave soundWave, String type, Object args){ + super(soundWave); + this.type = type; + setArgs(args); + } + + /** + * Returns a reference to the sound wave this event originated from + * + * @return the sound wave this event originated from + */ + @Override + public SoundWave getSource(){ + return (SoundWave)super.getSource(); + } + + public String getType(){ + return type; + } + + /** + * Returns the arguments of this event. + * + * Different types of event require different type of arguments. See the static event + * types to know which argument they come with. + * + * @return the argument object or {@code null} if this event was constructed with + * no arguments. + */ + public Object getArgs() { + return args; + } + + public void setArgs(Object args) { + this.args = args; + } + + /** + * Event generated when an audio file is open and a new sound wave is created. + */ + public static final String OPEN = "OPEN"; + /** + * /** + * Event generated when the sound wave is closed and all the related resources + * (sound file descriptors etc.) disposed. + */ + public static final String CLOSE = "CLOSE"; + /** + * Event generated when the sound wave selection changes. + * + * The event argument is a {@code Selection} object with the new selection. + */ + public static final String SELECTION_CHANGED = "SELECTION_CHANGED"; + /** + * Event generated when the sound wave's cursor position changes as a + * consequence of scrubbing through the sound wave. + * + * The argument is an open {@code Selection}, normalized to {@code SoundWave}'s current scale factor, + * whose start value is the new position. + * + * @see Selection#convertFactor(Selection, int) + */ + public static final String POSITION_CHANGED = "POSITION_CHANGED"; + /** + * Event generated when the sound wave playback is started. + */ + public static final String START = "START"; + /** + * Event generated when the sound wave playback is stopped. + */ + public static final String STOP = "STOP"; + /** + * Event generated when the sound wave playback is paused. + */ + public static final String PAUSE = "PAUSE"; + /** + * Event generated when an automation is changed. + * + * The argument of this event is the new {@code Automation} object which has been set as current + * for the sound wave. If the automation was of String {@code NONE}, then the argument will be {@code null} + */ + public static final String AUTOMATION_CHANGED = "AUTOMATION_CHANGED"; + + public static final String CUT = "CUT"; + + public static final String COPY = "COPY"; + + public static final String PASTE = "PASTE"; + + public static final String INSERT = "INSERT"; + + public static final String PEAK_METER = "VIEW_CHANGED"; + + /** + * argument is the scanning poistion + */ + public static final String SCAN = "SCAN"; + + /** + * Event generated when the current zoom factor of the {@code SoundWave} is changed. + * + * + */ + public static final String SCALE_FACTOR_CHANGED = "SCALE_FACTOR_CHANGED"; + + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/SoundWaveListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/SoundWaveListener.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,25 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +public interface SoundWaveListener { + public void update(SoundWaveEvent evt); +} + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/Wave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/Wave.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,49 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +public interface Wave { + public int getChunkNum(); + + public Chunk getChunkAt(int i); + + /** + * Sets the new scale factor. + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code SCALE_FACTOR_CHANGED} event. + * + * @param scaleFactor the new scale factor + */ + public void setScaleFactor(int scaleFactor); + + public int getScaleFactor(); + + public int getMaxScaleFactor(); + + public float getWaveTime(); + + public float getMillisecPerChunk(); + + public Sequence getSequence(); + + public boolean hasSequence(); +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/WavePeaks.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/WavePeaks.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,59 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw; + +import java.util.ArrayList; +import java.util.List; + + +public class WavePeaks extends ArrayList> { + private static final long serialVersionUID = 1L; + private boolean constructed; + + public WavePeaks(int maxScaleFactor) { + super(maxScaleFactor+1); + /* wave peaks indexes the list of chunks by scale factor since there is + * no 0 scaleFactor the first position of the list is never used but it must + * be filled up in order to avoid index out of bounds exception */ + for(int i=0; i element){ + if(constructed && scaleFactor == 0) + throw new IllegalArgumentException("scaleFactor cannot be 0"); + super.set(scaleFactor, element); + } + + @Override + public boolean add(List element){ + if(constructed) + throw new UnsupportedOperationException("Use add(scaleFactor), List"); + else + return super.add(element); + } + + + public List withScaleFactor(int scaleFactor){ + return super.get(scaleFactor); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsAutomation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsAutomation.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,329 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.beadsproject.beads.core.UGen; +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.AutomationValue; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; + +public class BeadsAutomation extends UGen implements Automation { + private static final float MIN_THRESHOLD = 0.05f; + //private Parameter parameter; + private BeadsSoundWave wave; + private List values; + private List listeners; + private Range range; + private Color color; + + /* begin and end values are the automation values at the ends * + * of the clip */ + private AutomationValue beginValue; + private AutomationValue endValue; + /* holds a copy of the values and it's accessed only by * + * calculateBuffer and recompute which are synchronized methods */ + private List valuesForSound; + private float value; + + + public BeadsAutomation(BeadsSoundWave wave,Range range,Color color, float initialValue){ + super(wave.ac,1); + this.wave = wave; + this.range = range; + this.color = color; + + listeners = new ArrayList<>(); + values = new ArrayList<>(); + valuesForSound = new ArrayList<>(values); + + beginValue = new BeadsAutomationValue(0.0f,initialValue){ + @Override + public float getValue() { + if(values.size() > 0){ + return values.get(0).getValue(); + }else{ + return BeadsAutomation.this.getValue(); + } + } + }; + + endValue = new BeadsAutomationValue(wave.samplePlayer.getLenght(),initialValue){ + /* end value always returns the last value float value */ + @Override + public float getValue() { + if(values.size() > 0){ + return values.get(values.size()-1).getValue(); + }else{ + return beginValue.getValue(); + } + } + }; + } + + @Override + public Color getColor() { + return color; + } + + @Override + public void setValue(float value){ + this.value = value; + fireAutomationListeners(SequenceEvent.What.BEGIN_CHANGED, beginValue); + + } + + @Override + public float getValue(){ + return value; + } + + @Override + public AutomationValue add(float position, float value) { + AutomationValue newAutomationValue = new BeadsAutomationValue(position,value); + values.add(newAutomationValue); + /* sort the values collection by their position */ + Collections.sort(values); + for(int i=0; i segBegin.getTimePosition() && samplePos < segEnd.getTimePosition()){ + break; + }else{ + /* prepare segBegin and segEnd for the next cycle */ + segBegin = valuesForSound.get(j); + segEnd = (j != valuesForSound.size()-1) ? valuesForSound.get(j+1) : endValue ; + } + } + + /* now segBegin and segEnd are the end of the segment where the position lays,* + * so now calculate interpolation between them to get the envelope value */ + float segLen = segEnd.getTimePosition() - segBegin.getTimePosition(); + if(segLen == 0){ + bufOut[0][i] = segEnd.getTimePosition(); + return; + } + /* let D be the distance between the sample position and the beginning of the * + * segment then ratio is the ratio between D and the segment length */ + float ratio = (samplePos - segBegin.getTimePosition()) / segLen; + + /* this comes from the following formula: + * newBufferValue = segBegin.getValue + (ratio * (segEnd.getValue() - segBegin.getValue()) */ + bufOut[0][i] = (1f - ratio) * segBegin.getValue() + ratio * segEnd.getValue(); + /* clipping */ + if(bufOut[0][i] < range.getStart() + MIN_THRESHOLD ) // FIXME + bufOut[0][i] = range.getStart(); + if(bufOut[0][i] > range.getEnd()) + bufOut[0][i] = range.getEnd(); + + } + } + + @Override + public Range getRange() { + return range; + } + + @Override + public int getValuesNum() { + return values.size(); + } + + @Override + public AutomationValue getValueAt( + int index) { + return values.get(index); + } + + @Override + public String toString(){ + return "Automation: range:"+range; + } + + @Override + public void addSequenceListener(SequenceListener l) { + listeners.add(l); + } + + @Override + public void removeSequenceListener(SequenceListener l) { + listeners.remove(l); + + } + + protected void fireAutomationListeners(SequenceEvent.What what,AutomationValue val){ + SequenceEvent evt = new SequenceEvent(this,val,what); + for(SequenceListener l : listeners){ + l.sequenceUpdated(evt); + } + } + + private class BeadsAutomationValue extends AutomationValue { + private int index; + private void setIndex(int i) { + index = i; + } + + public BeadsAutomationValue(float position, float value) { + super(position, value); + } + + @Override + public Automation getAutomation() { + return BeadsAutomation.this; + } + + @Override + public void setLocation(float position, float value){ + int thisIndex = values.indexOf(this); + + /* check boundaries */ + if(position < 0) + position = 0; + if(value > range.getEnd()) + value = range.getEnd(); + else if(value < range.getStart()) + value = range.getStart(); + + /* check position boundaries. an automation value cannot be moved left past * + * the previous automation value and right past the next automation value */ + if(thisIndex > 0 && position < values.get(thisIndex-1).getTimePosition()){ + position = values.get(thisIndex-1).getTimePosition(); // check left + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); // FIXME maybe check in automation graph + } + + if(thisIndex < values.size()-1 && values.get(thisIndex+1).getTimePosition() < position){ + position = values.get(thisIndex+1).getTimePosition(); // check right + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); // FIXME maybe check in automation graph + } + + super.setLocation(position, value); + /*no sort necessary as by setting location the order of the values + is not changed as a value cannot be moved past its neighbours*/ + recompute(); + fireAutomationListeners(SequenceEvent.What.VALUE_CHANGED,this); + } + + @Override + public void setTimePosition(float position){ + super.setTimePosition(position); + recompute(); + fireAutomationListeners(SequenceEvent.What.VALUE_CHANGED, this); + } + + @Override + public void setValue(float value){ + if(value > range.getEnd()) + value = range.getEnd(); + else if(value < range.getStart()) + value = range.getStart(); + + super.setValue(value); + if(index() == 0){ + BeadsAutomation.this.setValue(value); + } + recompute(); + fireAutomationListeners(SequenceEvent.What.VALUE_CHANGED, this); + } + + @Override + public Sequence getSequence() { + return BeadsAutomation.this; + } + + @Override + public int index() { + return index; + } + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsParametersControl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsParametersControl.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,139 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.daw.Parameter.Type; +import uk.ac.qmul.eecs.depic.daw.ParametersControl; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; + +class BeadsParametersControl implements ParametersControl { + Parameter[] parameters; + GainParameter playersGain; + PanParameter playersPan; + + Automation currentAutomation; + Parameter currentAutomationParameter; + Map automations; + private BeadsSoundWave soundWave; + + BeadsParametersControl(BeadsSoundWave soundWave){ + this.soundWave = soundWave; + automations = new HashMap<>(); + automations.put(Parameter.NONE_PARAMETER.getType(), Automation.NONE_AUTOMATION); + currentAutomation = Automation.NONE_AUTOMATION; + currentAutomationParameter = Parameter.NONE_PARAMETER; + + parameters = new Parameter[2]; + /* gain for soundWave */ + parameters[0] = new GainParameter(soundWave.ac, 2); + /* pan for soundWave */ + parameters[1] = new PanParameter(soundWave.ac); + } + + + @Override + public Automation getCurrentAutomation() { + return currentAutomation; + } + + @Override + public Parameter getCurrentAutomationType() { + return currentAutomationParameter; + } + + + @Override + public void setCurrentAutomation(Parameter.Type type) { + currentAutomation = getAutomation(type); + + currentAutomationParameter = Parameter.NONE_PARAMETER; + for(Parameter p : parameters){ + if(p.getType().equals(type)){ + currentAutomationParameter = p; + } + } + soundWave.fireSoundWaveListeners(SoundWaveEvent.AUTOMATION_CHANGED, currentAutomation); + } + + @Override + public Automation getAutomation(Type type) { + if(type == null){ + type = Parameter.NONE_PARAMETER.getType(); + }; + + Automation automation = automations.get(type); + + if(automation != null){ + return automation; + } + + if(Parameter.GAIN_TYPE.equals(type)) { + automation = new BeadsAutomation(soundWave, + parameters[0].getRange(), + Color.ORANGE, + parameters[0].getInitialValue()); + + automations.put(type, automation); + parameters[0].automate(automation); + return automation; + }else if (Parameter.PAN_TYPE.equals(type)) { + automation = new BeadsAutomation(soundWave, + parameters[1].getRange(), + Color.CYAN, + parameters[1].getInitialValue()); + + automations.put(type, automation); + parameters[1].automate(automation); + return automation; + }else { + throw new IllegalArgumentException("Parameter not recognized: "+type); + } + + } + + @Override + public Parameter getParameter(Parameter.Type type) { + for(Parameter p : parameters){ + if(p.getType().equals(type)){ + return p; + } + } + + return Parameter.NONE_PARAMETER; + } + + + @Override + public Parameter getGainParameter() { + return parameters[0]; + } + + + @Override + public Parameter getPanParameter() { + return parameters[1]; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSampleWrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSampleWrapper.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,118 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import uk.ac.qmul.eecs.depic.daw.Sample; + +public class BeadsSampleWrapper implements Sample { + private net.beadsproject.beads.data.Sample delegate; + + net.beadsproject.beads.data.Sample getDelegate(){ + return delegate; + } + + public BeadsSampleWrapper(net.beadsproject.beads.data.Sample delegate) { + this.delegate = delegate; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof BeadsSampleWrapper){ + return equals(((BeadsSampleWrapper)obj).delegate); + }else{ + return delegate.equals(obj); + } + } + + + @Override + public int getBytesPerSample() { + return delegate.getBytesPerSample(); + } + + + @Override + public void getFrame(int arg0, float[] arg1) { + delegate.getFrame(arg0, arg1); + } + + @Override + public void getFrameCubic(double arg0, float[] arg1) { + delegate.getFrameCubic(arg0, arg1); + } + + @Override + public void getFrameLinear(double arg0, float[] arg1) { + delegate.getFrameLinear(arg0, arg1); + } + + @Override + public void getFrameNoInterp(double arg0, float[] arg1) { + delegate.getFrameNoInterp(arg0, arg1); + } + + @Override + public void getFrames(int arg0, float[][] arg1) { + delegate.getFrames(arg0, arg1); + } + + @Override + public float getLength() { + return delegate.getLength(); + } + + @Override + public int getNumChannels() { + return delegate.getNumChannels(); + } + + @Override + public long getNumFrames() { + return delegate.getNumFrames(); + } + + @Override + public float getSampleRate() { + return delegate.getSampleRate(); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public double msToSamples(double arg0) { + return delegate.msToSamples(arg0); + } + + public void run() { + delegate.run(); + } + + @Override + public double samplesToMs(double arg0) { + return delegate.samplesToMs(arg0); + } + + @Override + public String toString() { + return delegate.toString(); + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSoundEngineFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSoundEngineFactory.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,74 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.daw.Parameter.Type; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundEngineFactory; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSonification; + +public class BeadsSoundEngineFactory implements SoundEngineFactory { + private static final int MAX_TRACK_SCALE_FACTOR = 10; + private static final int MIN_CHUNCKSIZE = 8; + private int minChunk; + private int maxScaleFactor; + private int initialScaleFactor; + private Sonification sonification; + + public BeadsSoundEngineFactory(){ + minChunk = MIN_CHUNCKSIZE; + maxScaleFactor = MAX_TRACK_SCALE_FACTOR; + initialScaleFactor = MAX_TRACK_SCALE_FACTOR/2 + 1; + sonification = new BeadsSonification(); + } + + @Override + public SoundWave createSoundWave() { + return new BeadsSoundWave( + minChunk, + maxScaleFactor, + initialScaleFactor); + } + + public BeadsSoundEngineFactory setSoundWaveParameters(int minChunk,int maxScaleFactor, int initialScaleFactor){ + this.minChunk = minChunk; + this.maxScaleFactor = maxScaleFactor; + this.initialScaleFactor = initialScaleFactor; + return this; + } + + @Override + public Parameter createParameter(Type type, SoundWave wave) { + if(Parameter.GAIN_TYPE.equals(type)) { + return new GainParameter(((BeadsSoundWave)wave).ac,2); + }else if(Parameter.PAN_TYPE.equals(type)) { + return new PanParameter(((BeadsSoundWave)wave).ac); + }else{ + return null; + } + } + + @Override + public Sonification getSharedSonification() { + return sonification; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSoundWave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsSoundWave.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,519 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import javax.swing.JComponent; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import uk.ac.qmul.eecs.depic.daw.AudioLoader; +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Chunk; +import uk.ac.qmul.eecs.depic.daw.Clip; +import uk.ac.qmul.eecs.depic.daw.ClipList; +import uk.ac.qmul.eecs.depic.daw.DbWave; +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.daw.ParametersControl; +import uk.ac.qmul.eecs.depic.daw.Sample; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEditor; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; +import uk.ac.qmul.eecs.depic.daw.WavePeaks; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +class BeadsSoundWave implements SoundWave { + private Set listeners; + private int minChunkSize; + private int maxScaleFactor; + private AudioLoader worker; + private Selection selection; + private int cursorPosition; + private int currentScaleFactor; + private int referenceScaleFactor; + private final TransportControl transportControl; + private final BeadsParametersControl parametersControl; + private DbWave dbWave; + + /* fields used by package classes */ + final ClipList clips; + final AudioContext ac; + final Map peaksMap; + final List granulars; + final SamplePlayer samplePlayer; + + + /** + * + * @param minChunkSize the minimum chunk size in frames + * @param maxScaleFactor the maximum scale factor + */ + public BeadsSoundWave(int minChunkSize, int maxScaleFactor, int currentScaleFactor){ + this.minChunkSize = minChunkSize; + this.maxScaleFactor = maxScaleFactor; + this.currentScaleFactor = currentScaleFactor; + referenceScaleFactor = currentScaleFactor; + listeners = new HashSet<>(); + peaksMap = new HashMap<>(); + granulars = new ArrayList<>(); + transportControl = new BeadsTransportControl(this); + clips = new ClipList(currentScaleFactor,peaksMap,this); + /* Forwards events triggered by the ClipList */ + clips.addSoundWaveListener(new SoundWaveListener(){ + @Override + public void update(SoundWaveEvent evt) { + if(!evt.getType().equals(SoundWaveEvent.OPEN)){ + for(SoundWaveListener l : listeners) + l.update(evt); + } + } + }); + selection = Selection.ZERO_SELECTION; + dbWave = new DbWave(this); + + /* create and start the audio engine. Need to do it from the beginning * + * in order to enable wave scrubbing when playback is stopped */ + ac = new AudioContext(); + ac.start(); + /* player of all the samples loaded in this sound wave */ + samplePlayer = new SamplePlayer(ac,clips); + samplePlayer.pause(true); + + parametersControl = new BeadsParametersControl(this); + UGen gain = (UGen)parametersControl.getParameter(Parameter.GAIN_TYPE); + UGen pan = (UGen)parametersControl.getParameter(Parameter.PAN_TYPE); + + pan.addInput(samplePlayer); + gain.addInput(pan); + + //BeadsAutomation panAutom = (BeadsAutomation)parametersControl.getAutomation(Type.PAN); + //panAutom.addInput(pan); + ac.out.addInput(gain); + + + /* parameters check */ + if(minChunkSize < MIN_SUPPORTED_CHUNK_SIZE) + throw new IllegalArgumentException("minChunkSize can't be smaller than MIN_SUPPORTED_CHUNK_SIZE ("+MIN_SUPPORTED_CHUNK_SIZE+')'); + + if(maxScaleFactor < 1) + throw new IllegalArgumentException( + "scaleFactor (" +maxScaleFactor+ ") must be greater or equal to 1"); + if(!isPowerOf2(minChunkSize)){ + throw new IllegalArgumentException("min chuck value must be an integer power of 2"); + } + } + + @Override + public void addSoundWaveListener(SoundWaveListener l){ + if(l != null) + listeners.add(l); + } + + @Override + public void removeSoundWaveListener(SoundWaveListener l){ + listeners.remove(l); + } + + /** + * Triggered the registered listeners after an event happens. + * + * @param args + */ + protected void fireSoundWaveListeners(String type, Object args){ + SoundWaveEvent evt = null; + if(args == null){ + evt = new SoundWaveEvent(this,type); + }else{ + evt = new SoundWaveEvent(this,type,args); + } + + for(SoundWaveListener l : listeners) + l.update(evt); + } + + @Override + public void loadAudioData(File audioFile, PropertyChangeListener propertyChangelistener ){ + /* set the parameters to the worker, job's going to be done in the bg */ + worker = new BeadsAudioLoader(audioFile,minChunkSize,maxScaleFactor); + worker.addPropertyChangeListener(propertyChangelistener); + worker.execute(); + } + + @Override + public void stopLoading(boolean mayInterruptIfRunning){ + if(worker != null) + worker.cancel(mayInterruptIfRunning); + } + + @Override + public void close(){ + if(!samplePlayer.isPaused()){ + samplePlayer.pause(true); + } + + granulars.clear(); + peaksMap.clear(); + clips.clear(); + setPosition(0); + fireSoundWaveListeners(SoundWaveEvent.CLOSE,null); + } + + @Override + public int getCurrentChunkPosition(){ + return cursorPosition; + } + + @Override + public List getFramesPositionAt(int chunkPosition){ // FIXME *** + + List clipsAtPos = clips.getClipsAt(chunkPosition); + if(clipsAtPos.isEmpty()) + return new ArrayList<>(0); + + /* create the array list to return */ + List framesPositions = new ArrayList<>(clipsAtPos.size()); + + for(Clip c : clipsAtPos){ + if(c.contains(chunkPosition)){ + /* get the frames in the converted format corresponding to * + * the beginning of the chunk at chunkPosition */ + int convertedFrames = chunkPosition * (minChunkSize << (currentScaleFactor-1)); + framesPositions.add((int)( + convertedFrames + * ((float)c.getSample().getSampleRate()/BeadsAudioLoader.DEFAULT_CONVERSION_FORMAT.getFrameRate())) + ); + return framesPositions; + } + } + + return framesPositions; + } + + @Override + public void setSelection(Selection s){ + selection = s; + if(s == null){ + selection = Selection.ZERO_SELECTION; + /* reset the sample player loop points */ + samplePlayer.setLoopPointsFraction(0.0f,1.0f); + }else{ + /* open selection will produces no selection * + * just move the cursor to the selection start */ + if(s.isOpen()){ + selection = Selection.ZERO_SELECTION; + setPosition(s.getStart()); + } + + if(selection.equals(Selection.ZERO_SELECTION)){ + samplePlayer.setLoopPointsFraction(0,1); + }else{ + float sampleSizeInChunks = clips.getLength(); + samplePlayer.setLoopPointsFraction( + s.getStart()/sampleSizeInChunks, + s.isOpen() ? 1.0f : s.getEnd()/sampleSizeInChunks); + } + } + fireSoundWaveListeners(SoundWaveEvent.SELECTION_CHANGED,selection); + } + + @Override + public float getMillisecPerChunk(){ // FIXME *** + return samplePlayer.getLenght()/clips.getLength(); + } + + @Override + public void scan(int position){ + /* don't go too left, past the beginning of the track */ + if(position < 0 && position != STOP_SCANNING){ + return; + } + + /* if sample is loaded update it */ + if(samplePlayer.getLenght() != 0){ + if(position == STOP_SCANNING){ // stop scanning + /* turn off all granular players */ + for(GranularPlayer granular : granulars){ + granular.pause(true); + } + + }else{ + /* active clips are assumed to always hold a different sample from one another */ + List activeClips = clips.getClipsAt(position); + + for(GranularPlayer granular : granulars){ + Clip withSameSample = null; + for(Clip c : activeClips){ + if(c.getSample().equals(granular.getSample())){ + withSameSample = c; + break; + } + } + + if(withSameSample == null){ + /* if not in current position, then silence it. if it was * + * active before, it gets turned of; otherwise nothing happens */ + granular.pause(true); + }else{ + granular.setPosition( + /* start of sample in this clip (in ms) */ + withSameSample.getSampleStartMs() + + /* plus the milliseconds before position */ + withSameSample.getMillisecPerChunk() * (position - withSameSample.getStart()) + ); + if(granular.isPaused()) + granular.start(); + } + } + } + fireSoundWaveListeners(SoundWaveEvent.SCAN, position); + } + + /* update the position*/ + if(position != STOP_SCANNING){ + setPosition(position); + } + } + + @Override + public void setPosition(int position){ + /* if sample is loaded, update it */ + if(samplePlayer.getLenght() != 0){ + float timePosition = position * getMillisecPerChunk(); + /* if the user clicks on a point past the loop, just ignore the loop * + * otherwise the cursor would immediately jump at the beginning of the loop */ + if(timePosition > samplePlayer.getLoop().getEnd()){ + samplePlayer.setLoopType(SamplePlayer.LoopType.NO_LOOP); + }else{ + samplePlayer.setLoopType(SamplePlayer.LoopType.LOOP); + } + samplePlayer.setPosition(timePosition); + } + + cursorPosition = position; + fireSoundWaveListeners(SoundWaveEvent.POSITION_CHANGED,position); + } + + @Override + public void setScaleFactor(int scaleFactor){ + /* just makes sure scale factor is not out of bounds */ + if(scaleFactor > maxScaleFactor || scaleFactor < 1 ) + throw new IllegalArgumentException("Scale factor ("+scaleFactor+") cannot be greater than max scale factor or less than 1"); + + + /* the absolute position of the cursor changes according to the scale factor */ + cursorPosition = Selection.convertFactor(cursorPosition, currentScaleFactor, scaleFactor); + currentScaleFactor = scaleFactor; // FIXME remove currentScaleFactor and use selections.getScaleFactor + clips.setScaleFactor(scaleFactor); + + /* adjust grain attributes and playback rate according to scale factory */ + float msPerChunk = getMillisecPerChunk(); + for(GranularPlayer gp : granulars){ + gp.setGrainAttributes(msPerChunk*4, msPerChunk*4); + gp.setPitch((float)currentScaleFactor/referenceScaleFactor); + } + + fireSoundWaveListeners(SoundWaveEvent.SCALE_FACTOR_CHANGED,scaleFactor); + } + + @Override + public int getScaleFactor(){ + return currentScaleFactor; + } + + @Override + public int getMaxScaleFactor(){ + return maxScaleFactor; + } + + @Override + public int getChunkNum(){ + return clips.getLength(); + } + + @Override + public Chunk getChunkAt(int pos){ + List clipsAtPos = clips.getClipsAt(pos); + if(clipsAtPos.isEmpty()) + return Chunk.SILENCE; + + Chunk chunk = null; + for(Clip c : clipsAtPos){ + WavePeaks g = peaksMap.get(c.getSample()); + + if(chunk == null){ + chunk = g.withScaleFactor(currentScaleFactor).get(c.getSampleStart()+(pos-c.getStart())); + }else{ + /* the chunk at the sample start + the position of the cursor relative to the start of the clip * + * The Chunk constructor with two chunks c1 and c2 as arguments merges the two chunks into a new one with * + * getStart = max(c1.getStarte(),c2.getStart()) and getEnd = min(c1.getEnd(),c2.getEnd()) */ + chunk = new Chunk(chunk,g.withScaleFactor(currentScaleFactor).get(c.getSampleStart()+(pos-c.getStart()))); + } + } + + return chunk; + } + + @Override + public float getWaveTime(){ + return samplePlayer.getLenght(); + } + + + public TransportControl getTransportControl(){ + return transportControl; + } + + @Override + public SoundWaveEditor getEditor(){ + return clips.getClipEditor(); + } + + @Override + public ParametersControl getParametersControl(){ + return parametersControl; + } + + @Override + public DbWave getDbWave() { + return dbWave; + } + + + @Override + public void generatePeakMeter(boolean generate){ + if(generate){ + dbWave.createNewSequence(); + }else{ + dbWave.deleteSequence(); + } + /* getSequence returns null if the sequence has been deleted */ + fireSoundWaveListeners(SoundWaveEvent.PEAK_METER,dbWave.getSequence()); + } + + @Override + public Sequence getSequence(){ + return getParametersControl().getCurrentAutomation(); + } + + /** + * Returns {@code true} if this sound wave currently has an automation set, via its {@code ParametersControl} + * + * @return {@code true} if it has an automation set, {@code false} otherwise + * + */ + @Override + public boolean hasSequence(){ + return !getParametersControl().getCurrentAutomation().equals(Automation.NONE_AUTOMATION); + } + + @Override + public JComponent[] getPreferencesPanels() { + return null; + } + + /* a power of two has only one bit set to '1' */ + private static boolean isPowerOf2(int value){ + if(value <= 0) + return false; + boolean bitFound = false; + + while(value != 0){ + if((value & 1) == 1){ + if(bitFound) // another bit already found previously: value is not power of 2 + return false; + else // first bit found + bitFound = true; + } + value >>= 1; + } + /* only one bit found: value is power of two */ + return true; + } + + private class BeadsAudioLoader extends AudioLoader{ + public BeadsAudioLoader(File audioFile, int minChunkSize, + int maxScaleFactor) { + super(audioFile, minChunkSize, maxScaleFactor); + } + + @Override + public void done(){ + ReturnObject returnObject = null; + if(isCancelled()) + return; + + try { + returnObject = get(); + } catch (InterruptedException e) { + return; // user interrupted the load + } catch (ExecutionException e) { + firePropertyChange(FILE_ERROR_PROPERTY,"",e.getMessage()); + if(e.getCause() instanceof RuntimeException) + e.printStackTrace(); + return; + } + + /* sets the new data underling the model */ + peaksMap.put(returnObject.sample,returnObject.peaks); + List currentPeaks = returnObject.peaks.withScaleFactor(currentScaleFactor); + clips.add(new Clip( + cursorPosition, + cursorPosition+currentPeaks.size()-1, // ends points to the last chunk + returnObject.sample, // the sample + 0, // the start position n the sample + returnObject.sample.getLength()/currentPeaks.size())); // the chunks of this sample at saleFactor scale factor + fireSoundWaveListeners(SoundWaveEvent.OPEN,null); + /* sample player for listening to the sample */ + samplePlayer.pause(true); + + GranularPlayer granular = new GranularPlayer(ac,returnObject.sample); + granulars.add(granular); + /* adds the ugen to the master gain */ + ((UGen)parametersControl.getParameter(Parameter.GAIN_TYPE)).addInput(granular); + + setProgress(FILE_LOAD_TOTAL_PROGRESS); + /* notify listeners */ + setSelection(new Selection(0,1)); + fireSoundWaveListeners(SoundWaveEvent.OPEN,null); + } + } + + +} + +/* + * + * sample rate: number of samples per second + * sample size in bit: + * frame: all samples for each channel at a given time + * frame rate: for each frame contains more sample, frame rate is slower than sample rate + * frame size: header + (num samples per frame * num channels) + * + */ \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/BeadsTransportControl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/BeadsTransportControl.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,99 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; + +class BeadsTransportControl implements SoundWave.TransportControl { + BeadsSoundWave wave; + private boolean loopOn; + + public BeadsTransportControl(BeadsSoundWave wave) { + this.wave= wave; + } + + @Override + public void play() { + if(wave.samplePlayer.getLenght() != 0){ + /* start the audio engine */ + wave.samplePlayer.start(); + wave.fireSoundWaveListeners(SoundWaveEvent.START, wave.samplePlayer.getPosition()); + } + } + + @Override + public void setLoop(boolean on){ + loopOn = on; + wave.samplePlayer.setLoopEnabled(loopOn); + + } + + @Override + public boolean isLoopOn(){ + return loopOn; + } + + + @Override + public void pause() { + if(wave.samplePlayer.getLenght() != 0){ + wave.samplePlayer.pause(true); + wave.fireSoundWaveListeners(SoundWaveEvent.PAUSE, wave.samplePlayer.getPosition()); + + wave.setPosition( + (int) ((wave.samplePlayer.getPosition()/wave.clips.getLengthMs()) * wave.clips.getLength()) + ); + } + } + + /** + * Stops playing the sound sample + * + * Registered {@code SoundWaveListener} objects are notified with a + * {@code STOP} event. + * + */ + @Override + public void stop() { + if(wave.samplePlayer.getLenght() != 0){ + wave.samplePlayer.pause(true); + /* place the cursor back to the beginning of the loop */ + wave.samplePlayer.setPosition(wave.samplePlayer.getLoop().getStart()); + wave.fireSoundWaveListeners(SoundWaveEvent.STOP, wave.samplePlayer.getPosition()); + } + } + + + @Override + public void rew() { + wave.setPosition(0); + } + + @Override + public void fwd() { + wave.setPosition(wave.getChunkNum()-1); + } + + @Override + public float getPlayPosition(){ + return (float)wave.samplePlayer.getPosition(); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/GainParameter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/GainParameter.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,124 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.Static; +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; + +class GainParameter extends Gain implements Parameter { + public final static Range RANGE = new Range(-60.0f,6.0f); + + + GainParameter(AudioContext ac, int inouts, float gain) { + super(ac, inouts); + super.setGain(new Static(ac, gain)); + + } + + GainParameter(AudioContext ac, int inouts) { + super(ac, inouts); + super.setGain(new Static(ac, MathUtils.toAmp(getInitialValue()))); + } + + @Override + public float getInitialValue(){ + return 0.0f; + } + + @Override + public Range getRange() { + return RANGE; + } + + /** + * @param value amp value in dB + */ + @Override + public synchronized void setValue(float value){ + super.getGainUGen().setValue(MathUtils.toAmp(value)); + } + + @Override + public synchronized float getValue(){ + return getGain(); + } + + @Override + public synchronized void calculateBuffer(){ + super.calculateBuffer(); + } + + @Override + public Parameter.Type getType() { + return Parameter.GAIN_TYPE; + } + + @Override + public void automate(Automation a) { + setGain(new DbToAmpConverter(context ,(BeadsAutomation)a)); + } + + private static class DbToAmpConverter extends UGen { + + UGen delegate; + + DbToAmpConverter(AudioContext ac, UGen delegate){ + super(ac, delegate.getOuts()); + this.delegate = delegate; + } + + @Override + public void calculateBuffer() { + delegate.calculateBuffer(); + } + + @Override + public float getValue() { + return MathUtils.toAmp(delegate.getValue()); + } + + @Override + public float getValue(int i, int j){ + return MathUtils.toAmp(delegate.getValue(i, j)); + } + + @Override + public double getValueDouble(){ + return MathUtils.toAmp(delegate.getValue()); + } + + @Override + public double getValueDouble(int i, int j){ + return MathUtils.toAmp(delegate.getValue(i, j)); + } + + @Override + public void setValue(float value){ + delegate.setValue(MathUtils.toDb(value)); + } + + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/GranularPlayer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/GranularPlayer.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,138 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.core.UGenChain; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.data.buffers.OneWindow; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.GranularSamplePlayer; +import net.beadsproject.beads.ugens.Static; +import uk.ac.qmul.eecs.depic.daw.Sample; + +/** + * + * Plays a sample on a granular synthesizer + * + * It has two entries in its class Preferences: + * + * + */ +class GranularPlayer extends UGenChain { + /* position controls the position in the sample of the granular player */ + private UGen position; + /* playerGain controls the gain of the granular player before the general * + * players gain. it is used to turn the granular synthesis sound on and off upon scrubbing */ + private Gain playerGain; + /* granular player from beads */ + private GranularSamplePlayer player; + + private Sample sample; + + public GranularPlayer(AudioContext ac, BeadsSampleWrapper sample) { + super(ac,0,1); + /* granular player for scrubbing */ + this.sample = sample; + player = new GranularSamplePlayer(ac,sample.getDelegate()); + player.setLoopType(GranularSamplePlayer.LoopType.NO_LOOP_FORWARDS); + + + + position = new Static(ac,-1.0f); + player.setPosition(position); + + super.pause(true); + player.pause(true); + + addToChainOutput(player); + } + + /** Turns on the granular player by (smoothly) setting granularPlayerAttack to 0 + */ + @Override + public void start(){ + pause(false); + } + + public void setFadeRatio(float ratio){ + Buffer b = new OneWindow().generateBuffer(OneWindow.DEFAULT_BUFFER_SIZE); + int fade = (int) (OneWindow.DEFAULT_BUFFER_SIZE/2.0f * ratio); + double halfPI = Math.PI/2; + for(int i=0; i< fade; i++){ + b.buf[i] = (float)Math.sin((double)i/fade*halfPI); + b.buf[OneWindow.DEFAULT_BUFFER_SIZE-fade+i] = (float)Math.sin((double)halfPI - (i/fade * halfPI)); + } + + for(int i =0; i<300;i++ ) + System.out.println(b.buf[i]); + + player.setWindow(b); + } + + + @Override + public void pause(boolean paused){ + if(paused && isPaused()) // already paused, do nothing + return; + player.pause(paused); + super.pause(paused); + } + + @Override + public void kill(){ + playerGain.kill(); + super.kill(); + } + + public Sample getSample(){ + return sample; + } + + /** + * Sets the new position of the granular player + * + * @param newPosition position in milliseconds + */ + public void setPosition(float newPosition){ + position.setValue(newPosition); + } + + /** + * Sets the current position of the granular player + * + * @return the current position in milliseconds + */ + public float getPosition(){ + return position.getValue(); + } + + public void setGrainAttributes(float size, float interval){ + // FIXME remove methos if not used +// player.getGrainSizeUGen().setValue(size); +// player.getGrainIntervalUGen().setValue(interval); + + } + + public void setPitch(float pitch){ + player.setPitch(new Static(context,pitch)); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/PanParameter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/PanParameter.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,79 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.ugens.Panner; +import net.beadsproject.beads.ugens.Static; +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.patterns.Range; + +public class PanParameter extends Panner implements Parameter { + public final static Range RANGE = new Range<>(-1.0f,1.0f); + + public PanParameter(AudioContext ac) { + super(ac,new Static(ac, 0.0f)); + } + + public PanParameter(AudioContext ac, float ipos) { + super(ac, ipos); + } + + @Override + public Range getRange() { + return RANGE; + } + + @Override + public Parameter.Type getType() { + return Parameter.PAN_TYPE; + } + + /** + * Sets the pan position + * + * synchronized with {@code calculateBuffer} + */ + @Override + public synchronized void setValue(float v){ + super.getPosUGen().setValue(v); + } + + @Override + public synchronized float getValue(){ + return super.getPos(); + } + + @Override + public float getInitialValue(){ + return 0.0f; + } + + @Override + public synchronized void calculateBuffer(){ + super.calculateBuffer(); + } + + @Override + public void automate(Automation a) { + this.setPos((BeadsAutomation)a); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/SamplePlayer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/SamplePlayer.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,400 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +/* + * This file is part of Beads. See http://www.beadsproject.net for all information. + */ + +import java.util.Collections; +import java.util.List; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.ugens.Static; +import uk.ac.qmul.eecs.depic.daw.Clip; +import uk.ac.qmul.eecs.depic.daw.ClipList; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; +import uk.ac.qmul.eecs.depic.patterns.Range; + +/** + * + */ +public class SamplePlayer extends UGen implements SoundWaveListener { + + public static final int NUM_CHANNELS = 2; + public static final float ADAPTIVE_INTERP_LOW_THRESH = 0.5f; + public static final float ADAPTIVE_INTERP_HIGH_THRESH = 2.5f; + + + /** + * Used to determine which kind of loop the sample player will use. + */ + public static enum LoopType { + /** Play forwards without looping. */ + NO_LOOP, + /** Play forwards with loop. */ + LOOP, + }; + + + /** The position in milliseconds. */ + protected double position; + + /** The rate envelope. */ + protected UGen rateEnvelope; + + /** The millisecond position increment per sample. Calculated from the ratio of the {@link AudioContext}'s sample rate and the Sample's sample rate. */ + protected double positionIncrement; + + + /** The loop type. */ + protected LoopType loopType; + + /** Flag to determine whether playback starts at the beginning of the sample or at the beginning of the loop. */ + protected boolean startLoop; + + + /** The rate. Calculated and used internally from the rate envelope. */ + protected float rate; + + /** The loop start. Calculated and used internally from the loop start envelope. */ + protected float loopStart; + + /** The loop end. Calculated and used internally from the loop end envelope. */ + protected float loopEnd; + + /** Bead responding to sample at end (only applies when not in loop mode). */ + private Bead endListener; + + /* lenght of the sample player */ + private float length; + + private ClipList clips; + + private List clipsAtPosition; + private boolean loopEnabled; + + + /** + * Instantiates a new SamplePlayer with given number of outputs. + * + * @param context the AudioContext. + * @param outs the number of outputs. + */ + public SamplePlayer(AudioContext context, ClipList clips) { + super(context, NUM_CHANNELS); + this.clips = clips; + clipsAtPosition = Collections.emptyList(); + clips.addSoundWaveListener(this); + rateEnvelope = new Static(context, 1.0f); + loopType = LoopType.NO_LOOP; + loopStart = 0.0f; + loopEnd = clips.getLength(); + length = 0.0f; + positionIncrement = context.samplesToMs(1); + } + + + /** + * Sets the playback position to the end of the Sample. + */ + public void setToEnd() { + position = length; + } + + /** + * Determines whether the playback position is within the loop points. + * + * @return true if the playback position is within the loop points. + */ + public boolean inLoop() { + return position < Math.max(loopStart, loopEnd) && position > Math.min(loopStart, loopEnd); + } + + /** + * Sets the playback position to the loop start point. + */ + public void setToLoopStart() { + position = Math.min(loopStart, loopEnd); + } + + /** + * Starts the sample at the given position. + * + * @param msPosition the position in milliseconds. + */ + public void start(float msPosition) { + position = msPosition; + start(); + } + + /** + * Resets the position to the start of the Sample. + */ + public void reset() { + position = 0f; + } + + /** + * Gets the playback position. + * + * @return the position in milliseconds. + */ + public double getPosition() { + return position; + } + + /** + * Sets the playback position. This will not work if the position envelope is not null. + * + * @param position the new position in milliseconds. + */ + public void setPosition(double position) { + this.position = position; + } + + + /** + * Gets the rate UGen. + * + * @return the rate UGen. + */ + public UGen getRateUGen() { + return rateEnvelope; + } + + + /** + * Sets the rate to a UGen. + * + * @param rateUGen the new rate UGen. + */ + public void setRate(UGen rateUGen) { + this.rateEnvelope = rateUGen; + } + + /** + * Gets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can + * be used interchangeably). + * + * @return the rate envelope. + */ + public UGen getPitchUGen() { + return rateEnvelope; + } + + + /** + * Sets the rate UGen (this method is provided so that SamplePlayer and GranularSamplePlayer can + * be used interchangeably). + * + * @param rateUGen the new rate UGen. + */ + public void setPitch(UGen rateUGen) { + this.rateEnvelope = rateUGen; + } + + + + /** + * Sets both loop points to static values as fractions of the Sample length, + * overriding any UGens that were controlling the loop points. + * + * @param start the start value, as fraction of the Sample length. + * @param end the end value, as fraction of the Sample length. + */ + public void setLoopPointsFraction(float start, float end) { + float l = getLenght(); + loopStart = start * l; + loopEnd = end * l; + } + + public Range getLoop(){ + return new Range(loopStart,loopEnd); + } + + /** + * Gets the loop type. + * + * @return the loop type. + */ + public LoopType getLoopType() { + return loopType; + } + + /** + * Sets the loop type. + * + * @param loopType the new loop type. + */ + public void setLoopType(LoopType loopType) { + if(loopEnabled){ + this.loopType = loopType; + } + } + + + public void setLoopEnabled(boolean enabled){ + loopEnabled = enabled; + this.loopType = loopEnabled ? LoopType.LOOP : LoopType.NO_LOOP; + } + + /** + * Gets the sample rate. + * + * @return the sample rate, in samples per second. + */ + public float getSampleRate() { + return rate; + } + + + @Override + public void calculateBuffer(){ + //major speed up possible here if these envelopes are all either null or paused + //(can we pause Envelope when it is not doing anything?). + //if this holds true we can tell buffer to just grab the whole frame at the given rate + //and then update the position all at once. + rateEnvelope.update(); + + for (int i = 0; i < bufferSize; i++) { + /* calculate the samples */ + + /* initialize to silence */ + for (int j = 0; j < NUM_CHANNELS; j++) { + bufOut[j][i] = 0.0f; + } + + if(!clipsAtPosition.isEmpty()){ /* if no current sample, then output silence */ + for(Clip s : clipsAtPosition){ + float [] frame = new float[s.getSample().getNumChannels()]; + /* Always use adaptive Interpolation */ + if(rate > ADAPTIVE_INTERP_HIGH_THRESH) { + s.getSample().getFrameNoInterp(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); + } else if(rate > ADAPTIVE_INTERP_LOW_THRESH) { + s.getSample().getFrameLinear(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); + } else { + s.getSample().getFrameCubic(s.getSampleStartMs()+(position-s.getStartTimeMs()), frame); + } + + /* if the sample is mono, fill the two channels with the same frame */ + for (int j = 0; j < NUM_CHANNELS; j++) { + bufOut[j][i] += frame[j % frame.length]; + /* clip the sound */ + if(bufOut[j][i] > 1.0f ) + bufOut[j][i] = 1.0f; + else if(bufOut[j][i] < -1.0f){ + bufOut[j][i] = -1.0f; + } + } + } + } + + //update the position, loop state, direction + calculateNextPosition(i); + } + } + + public float getLenght(){ + return Math.max(length,clips.getLengthMs()); + } + + public void setLength(float length){ + this.length = length; + } + + /** + * Used at each sample in the perform routine to determine the next playback position. + * + * @param i the index within the buffer loop. + */ + protected void calculateNextPosition(int i) { + rate = rateEnvelope.getValue(0, i); + switch(loopType) { + case NO_LOOP: + position += positionIncrement * rate; + if(position > clips.getLengthMs() || position < 0.0f) + atEnd(); + break; + case LOOP: + position += positionIncrement * rate; + if(rate > 0 && position > Math.max(loopStart, loopEnd)) { + position = Math.min(loopStart, loopEnd); + } else if(rate < 0 && position < Math.min(loopStart, loopEnd)) { + position = Math.max(loopStart, loopEnd); + } + break; + } + clipsAtPosition = clips.getClipsAtTime((float)position); + } + + /** + * Called when at the end of the Sample, assuming the loop mode is non-looping, or beginning, if the SamplePlayer is playing backwards.. + */ + private void atEnd() { + if(endListener != null) { + endListener.message(this); + } + reTrigger(); + } + + /** + * Sets a {@link Bead} that will be triggered when this SamplePlayer gets to the end. This occurs when the SamplePlayer's + * position reaches then end when playing forwards in a non-looping mode, or reaches the the beginning when playing backwards in a + * non-looping mode. It is never triggered in a looping mode. As an alternative, you can use the method {@link Bead.#setKillListener(Bead)} + * as long as {@link #setKillOnEnd(boolean)} is set to true. In other words, you set this SamplePlayer to kill itself when it + * reaches the end of the sample, and then use the functionality of {@link Bead}, which allows you to create a trigger + * whenever a Bead is killed. Set to null to remove the current listener. + * + * @param endListener the {@link Bead} that responds to this SamplePlayer reaching its end. + */ + public void setEndListener(Bead endListener) { + this.endListener = endListener; + } + + /** + * Gets the current endListener. + * @see {#setEndListener(Bead)}. + * @return the current endListener. + */ + public Bead getEndListener() { + return endListener; + } + + /** + * Re trigger the SamplePlayer from the beginning. + */ + public void reTrigger() { + reset(); + this.pause(false); + } + + /** + * Updates the lenght when the sound wave changes + */ + @Override + public void update(SoundWaveEvent evt) { + length = clips.getLengthMs(); + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/ZoomUGen.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/ZoomUGen.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,62 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGenChain; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.ugens.CombFilter; +import net.beadsproject.beads.ugens.Envelope; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.WavePlayer; + +public class ZoomUGen extends UGenChain { + + public ZoomUGen(AudioContext context, int outputs) { + super(context, 0, outputs); + + + WavePlayer sin = new WavePlayer(context,440,Buffer.SINE); + Envelope env = new Envelope(context); + + CombFilter comb = new CombFilter(context,200); + + env.addSegment(0.8f, 50f,5).addSegment(0.0f, 100f,3.0f); + + Gain gain = new Gain(context,1); + + comb.addInput(sin); + gain.setGain(env); + gain.addInput(comb); + + addToChainOutput(gain); + + } + + public static void main(String argv[]){ + AudioContext ac = new AudioContext(); + + ac.out.addInput(new ZoomUGen(ac,1)); + + ac.start(); + + } + + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/AutomationSound.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/AutomationSound.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,76 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.core.UGenChain; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.ugens.BiquadFilter; +import net.beadsproject.beads.ugens.Function; +import net.beadsproject.beads.ugens.Noise; +import net.beadsproject.beads.ugens.TapIn; +import net.beadsproject.beads.ugens.TapOut; +import net.beadsproject.beads.ugens.WavePlayer; + +public class AutomationSound extends UGenChain { + private WavePlayer sinOsc; + private UGen noise; + private BiquadFilter highPass; + private Function add; + private TapIn delayIn; + private TapOut delayOut; + + public AutomationSound(AudioContext ac) { + super(ac, 0, 2); + + /* create ugens */ + sinOsc = new WavePlayer(ac,0.0f,Buffer.SINE); + noise = new Noise(ac); + highPass = new BiquadFilter(ac,1); + delayIn = new TapIn(ac,10f); + delayOut = new TapOut(ac,delayIn,0.01f); + + + /* configure ugens */ + sinOsc.setFrequency(noise); + sinOsc.setPhase(-0.35f); + + highPass.setType(BiquadFilter.BESSEL_HP); + highPass.setFrequency(2000); + highPass.setQ(3.25f); + + /* connect ugens */ + delayIn.addInput(sinOsc); + add = new Function(delayOut,sinOsc){ + @Override + public float calculate() { + return x[0] + x[1]; + } + + }; + delayIn.addInput(add); + highPass.addInput(add); + + /* output of this ugen */ + addToChainOutput(highPass); + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsAutomationMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsAutomationMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,88 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.data.Sample; +import net.beadsproject.beads.data.SampleManager; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.SamplePlayer; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.PitchedUGen; +import uk.ac.qmul.eecs.depic.patterns.Range; + +public class BeadsAutomationMapping extends BeadsSequenceMapping { + private final Range PITCH_SPAN = new Range(0.2f,1.2f); + private Sample sample; + + BeadsAutomationMapping(){ + sample = SampleManager.sample(getClass().getResourceAsStream("metallic.wav")); + } + + BeadsAutomationMapping(AudioContext ac){ + super(ac); + sample = SampleManager.sample(getClass().getResourceAsStream("metallic.wav")); + } + + @Override + protected PitchedUGen createRenderCurveUGen() { + return new AutomationPitchUgen(ac); + } + + @Override + protected Range getRenderCurvePitchSpan() { + return PITCH_SPAN; + } + + private class AutomationPitchUgen extends PitchedUGen { + SamplePlayer player; + Gain gain; + + public AutomationPitchUgen(AudioContext ac) { + super(ac); + player = new SamplePlayer(ac,sample); + gain = new Gain(ac,1,0.15f); + player.setLoopType(SamplePlayer.LoopType.LOOP_FORWARDS); + gain.addInput(player); + addToChainOutput(gain); + } + + @Override + public void setPitch(float pitch) { + player.getPitchUGen().setValue(pitch); + } + + @Override + public void setPitch(UGen pitch) { + player.setPitch(pitch); + } + + @Override + public void setExtremityThresholds(float threshold) { + + } + + @Override + public void setLen(float sustain, float decay) { + + } + } +} + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsPeakLevelMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsPeakLevelMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,160 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import java.util.prefs.Preferences; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.ugens.Envelope; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.WavePlayer; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSonification; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.PitchedUGen; +import uk.ac.qmul.eecs.depic.patterns.Range; + +public class BeadsPeakLevelMapping extends BeadsSequenceMapping { + private final static Range PITCH_SPAN = new Range(100.0f,440.0f); + + static { + /* loads the buffer for the PeakLevelPitchedUgen and stores it into staticBuffs */ + int bufferLen = 4096; + Buffer pulse = new Buffer(bufferLen); + int pulseWidth = bufferLen/10; + + for(int i=0; i getRenderCurvePitchSpan() { + return PITCH_SPAN; + } + + class PeakLevePitchedUGen extends PitchedUGen { + private Gain renderCurveAtGain; + private WavePlayer player; + private WavePlayer borderThresholdPlayer; + private Gain borderThresholdWet; + private float maxFreq; + private float minFreq; + private float borderPitchThreshold; + + public PeakLevePitchedUGen(AudioContext ac, Range freqSpan, Buffer buffer) { + super(ac); + if(maxFreq < minFreq){ + throw new IllegalArgumentException("minFreq must be lower than maxFreq. minFreq: "+minFreq+" maxFreq:"+maxFreq ); + } + + this.minFreq = freqSpan.getStart(); + this.maxFreq = freqSpan.getEnd(); + renderCurveAtGain = new Gain(ac,1, (Float)Preferences.userNodeForPackage(BeadsSonification.class).getFloat("render_curve.gain",1.0f)); + player = new WavePlayer(ac,minFreq,buffer); + + /* border threshold is rendered with a pulse wave that increases in amplitude as + * the frequency approaches the zero frequency. the pulse is added to the sine wave */ + borderThresholdWet = new Gain(ac,1,0.0f); + borderThresholdPlayer = new WavePlayer(ac,minFreq,Buffer.staticBufs.get("Pulse")); + borderThresholdWet.addInput(borderThresholdPlayer); + + renderCurveAtGain.addInput(player); + renderCurveAtGain.addInput(borderThresholdWet); + + addToChainOutput(renderCurveAtGain); + } + + @Override + public void setPitch(float pitch){ + /* set the frequency for both the actual curve sound and the threshold sound */ + player.setFrequency(pitch); + borderThresholdPlayer.setFrequency(pitch); + + if(borderPitchThreshold < 0.0f ) { // threshold disabled + borderThresholdWet.setGain(0.0f); + }else if(maxFreq - pitch <= borderPitchThreshold){ + float freqSpan = pitch - (maxFreq - borderPitchThreshold); + borderThresholdWet.setGain( freqSpan/borderPitchThreshold ); // ranges from 0 to 0.5 see setBorderThreshold + }else if(pitch - minFreq <= borderPitchThreshold){ + borderThresholdWet.setGain( (borderPitchThreshold-Math.abs(minFreq-pitch)) /borderPitchThreshold ); // ranges from 0 to 0.5 + }else { // distance higher than the threshold + borderThresholdWet.setGain(0.0f); + } + } + + @Override + public void setPitch(UGen pitch) { + player.setFrequency(pitch); + //borderThresholdPlayer.setFrequency(pitch); + } + + public float getFrequency(float freq){ + return player.getFrequency(); + } + + @Override + public void start(){ + player.start(); + System.out.println("Neads Seq mapping start"); + } + + @Override + public void kill(){ + renderCurveAtGain.kill(); + System.out.println("Neads Seq mapping kill"); + } + + @Override + public void setLen(float sustain, float decay){ + Envelope env = new Envelope(context,sustain); + env.addSegment(0.0f,decay, this); + renderCurveAtGain.setGain(env); + } + + @Override + public void setExtremityThresholds(float borderThreshold){ + borderPitchThreshold = (maxFreq - minFreq)/2 * borderThreshold; + } + + @Override + public void messageReceived(Bead b){ + kill(); + } + + } +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsSequenceMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsSequenceMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,232 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import java.util.prefs.Preferences; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.data.Sample; +import net.beadsproject.beads.data.SampleManager; +import net.beadsproject.beads.ugens.Envelope; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.SamplePlayer; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.PitchedUGen; +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.Sequence.Value; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +/** + * + * An abstract implementation of sequence mapping. + * It takes care of the interface implementation using the beads library. + * Subclasses just need to implement the abstract protected methods to + * render the sequence with their own sound. + * + */ +public abstract class BeadsSequenceMapping implements SequenceMapping { + private static final float RENDER_VALUE_MAX_PITCH = 10.f; + private static final float RENDER_VALUE_MIN_PITCH = 0.4f; + + protected static final float RATE_STEP = 0.1f; + + protected AudioContext ac; + /* the master gain */ + protected Gain masterGain; + /* sound used in renderValue() */ + private Sample pop; + + private PitchedUGen renderCurveAtUGen; + private Gain renderCurveGain; + + private Sample curveZeroSample; + + /** + * + * @param ac an audio context. The audio context must be started outside this class + */ + public BeadsSequenceMapping(AudioContext ac){ + this.ac = ac; + masterGain = new Gain(ac, 1, 1.0f); + ac.out.addInput(masterGain); + /* retrieve the sample to play */ + pop = SampleManager.sample(getClass().getResourceAsStream("pop.mp3")); + curveZeroSample = SampleManager.sample(getClass().getResourceAsStream("zero.wav")); + } + + public BeadsSequenceMapping(){ + this(new AudioContext()); + } + + @Override + public void renderValue(Value val) { + float v = val.getValue(); + Sequence sequence = val.getSequence(); + Range range = sequence.getRange(); + + /* Map the value to the pitch: + * value / range = pitch - RENDER_VALUE_MIN_PITCH / RENDER_VALUE_MAX_PITCH-RENDER_VALUE_MIN_PITCH */ + MathUtils.Scale scale = new MathUtils.Scale( + range, + new Range(RENDER_VALUE_MIN_PITCH,RENDER_VALUE_MAX_PITCH) + ); + + float pitch = scale.linear(v); + + SamplePlayer valuePlayer = new SamplePlayer(ac,pop); + valuePlayer.setKillOnEnd(true); + valuePlayer.getRateUGen().setValue(pitch); + + masterGain.addInput(valuePlayer); + } + + protected abstract PitchedUGen createRenderCurveUGen(); + + protected abstract Range getRenderCurvePitchSpan(); + + protected UGen getZeroPlayer(){ + SamplePlayer zeroPlayer = new SamplePlayer(ac,curveZeroSample); + zeroPlayer.setKillOnEnd(true); + return zeroPlayer; + } + + @Override + public void renderCurve(Sequence s, float startTime) { + if(startTime < 0.0f){ + if(renderCurveGain != null){ + renderCurveGain.kill(); + renderCurveGain = null; + } + return; + } + final PitchedUGen sequencePlayer = createRenderCurveUGen(); + final SamplePlayer valuePlayer = new SamplePlayer(ac,pop); + final Gain sequencePlayerGain = new Gain(ac,2,0.5f); + /* several values need to be played, the player will be killed at the end of the sequence */ + valuePlayer.setKillOnEnd(false); + valuePlayer.setPosition(pop.getLength()); + + MathUtils.Scale scale = new MathUtils.Scale(s.getRange(),getRenderCurvePitchSpan()); + + /* map the begin of sequence to the pitch of the PitchedUGen */ + float pitchVal = scale.linear(s.getBegin()); + final Envelope pitch = new Envelope(ac,pitchVal); + + /* add the segments to the envelopes according to the sequence values * + * so for every i-th value , a new segment is going to the i-th value, * + * in t(i-th value)-t(i minus one-th), where t is the time in ms of each value */ + float lastValueTime = 0.0f; + for(int i=0; i(RENDER_VALUE_MIN_PITCH,RENDER_VALUE_MAX_PITCH)) + .linear(s.getValueAt(i).getValue()); + + pitch.addSegment(pitchVal, s.getValueAt(i).getTimePosition() - lastValueTime, new Bead(){ + /* this method is executed when the segment has been played all */ + @Override + protected void messageReceived(Bead message){ + /* pitch is linear to val.getValue. MIN_PITCH is added so if v is 0 the pitch is not 0 */ + valuePlayer.getRateUGen().setValue(sequenceValuePitch); + valuePlayer.reTrigger(); + } + }); + lastValueTime = s.getValueAt(i).getTimePosition(); + } + + /* add the last segment going from the last sequence value to the end of the sequence * + * Also adds a Bead that's triggered when the envelope reaches the end. The Beads * + * kills the wave player and the frequency envelope. * + * See Envelope.addSegment(float endValue, float duration, Bead trigger) */ + pitchVal = scale.linear(s.getEnd()); + pitch.addSegment(pitchVal, s.getLen() - lastValueTime, new Bead(){ + @Override + protected void messageReceived(Bead message){ + pitch.kill(); + sequencePlayerGain.kill(); + valuePlayer.kill(); + } + }); + + sequencePlayerGain.addInput(valuePlayer); + /* set the pitch envelope as the frequency for the wavePlayer */ + sequencePlayer.setPitch(pitch); + sequencePlayerGain.addInput(sequencePlayer); + /* add to the master gain for the sound to be played */ + renderCurveGain = sequencePlayerGain; + masterGain.addInput(sequencePlayerGain); + } + + @Override + public void renderCurveAt(Sequence sequence, float time, float duration){ + if(duration < 0.0f ){ + if(renderCurveAtUGen != null) + renderCurveAtUGen.kill(); + renderCurveAtUGen = null; + return; + } + + /* look for the two sequence values, whose times contain time in between */ + float valueAtTime = new MathUtils.Interpolate(sequence).linear(time); + float pitch = new MathUtils.Scale(sequence.getRange(),getRenderCurvePitchSpan()).linear(valueAtTime); + + Preferences prefs = Preferences.userNodeForPackage(BeadsSequenceMapping.class); + float borderThreshold = prefs.getFloat("borders.threshold", 0.01f); + + /* infinite duration means play until renderCurveAt is called again with duration = -1 */ + if(Float.isInfinite(duration)){ + if(renderCurveAtUGen == null){ + renderCurveAtUGen = createRenderCurveUGen(); + renderCurveAtUGen.setPitch(pitch); + renderCurveAtUGen.setExtremityThresholds(borderThreshold); + masterGain.addInput(renderCurveAtUGen); + renderCurveAtUGen.start(); + }else{ + renderCurveAtUGen.setPitch(pitch); + } + }else if(duration > 0.0f){ + if(renderCurveAtUGen == null){ + renderCurveAtUGen = createRenderCurveUGen(); + renderCurveAtUGen.setPitch(pitch); + renderCurveAtUGen.setExtremityThresholds(borderThreshold); + renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f); + ac.out.addInput(renderCurveAtUGen); + renderCurveAtUGen.start(); + }else{ + renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f); + } + } + + /* if the curve is at zero play the zero signal */ + float zeroPoint = sequence.getRange().getStart() + sequence.getRange().lenght()/2.0f; + + if(MathUtils.equal(valueAtTime, zeroPoint, 0.01)){ + masterGain.addInput(getZeroPlayer()); + } + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsSonification.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/BeadsSonification.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,180 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import java.util.prefs.Preferences; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.data.Sample; +import net.beadsproject.beads.data.SampleManager; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.Noise; +import net.beadsproject.beads.ugens.OnePoleFilter; +import net.beadsproject.beads.ugens.Panner; +import net.beadsproject.beads.ugens.SamplePlayer; +import net.beadsproject.beads.ugens.Static; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.Sound; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +public class BeadsSonification implements Sonification { + + private AudioContext ac; + private SequenceMapping automationMapping; + private SequenceMapping peakLevelMapping; + private Sample error; + private Sample ok; + private Sample shiftBorders; + private Sample loop; + private UGen selectionSound; + + + public BeadsSonification() { + ac = new AudioContext(); + Gain gain = new Gain(ac,1,1.0f); + ac.out.addInput(gain); + + automationMapping = new BeadsAutomationMapping(ac); + peakLevelMapping = new BeadsPeakLevelMapping(ac); + + error = SampleManager.sample(getClass().getResourceAsStream("error.mp3") ); + ok = SampleManager.sample(getClass().getResourceAsStream("ok.mp3") ); + shiftBorders = SampleManager.sample(getClass().getResourceAsStream("selection_borders.wav") ); + loop = SampleManager.sample(getClass().getResourceAsStream("loop.mp3") ); + ac.start(); + } + + @Override + public SequenceMapping getSequenceMapping(SoundType soundType) { + if(soundType == SoundType.AUTOMATION){ + return automationMapping; + }else if(soundType == SoundType.PEAK_LEVEL){ + return peakLevelMapping; + }else{ + throw new IllegalArgumentException("Sound Type not recognized: "+soundType); + } + } + + private void play(Sound sound, float g, float p){ + UGen output = null; + + SoundType s = (SoundType) sound.getType(); + switch(s){ + + case HAPTIC_PORT_TOUCH : { + output = new Buzz(ac); + break; + } + + case ERROR : { + output = playSample(error); + break; + } + + case OK : { + output = playSample(ok); + break; + } + + case LOOP : { + output = playSample(loop); + break; + } + + case SHIFT_START : + case SHIFT_END : { + SamplePlayer player = new SamplePlayer(ac,shiftBorders); + player.setKillOnEnd(true); + + if(sound.getType() == SoundType.SHIFT_START){ + player.setPitch(new Static(ac,1.5f)); + }else{ + player.setLoopType(SamplePlayer.LoopType.NO_LOOP_FORWARDS); + } + output = player; + break; + } + + default : return; + } + + Panner pan = new Panner(ac, p); + Gain gain = new Gain(ac, 1, g); + + gain.addInput(output); + pan.addInput(gain); + ac.out.addInput(pan); + + output.start(); + } + + @Override + public void play(SoundType soundType){ + play(new Sound(soundType), 1.0f, 0.0f); + } + + @Override + public void play(SoundType soundType, float gain, float pan){ + play(new Sound(soundType), gain, pan); + } + + @Override + public void withinSelection(float g, float p){ + if(selectionSound != null) + return; + + Noise noise = new Noise(ac); + OnePoleFilter filter = new OnePoleFilter( + ac, + Preferences.userNodeForPackage(BeadsSonification.class).getInt("selection.filter", 350) + ); + + filter.addInput(noise); + + Panner pan = new Panner(ac, p); + Gain gain = new Gain(ac, 1, g); + + gain.addInput(filter); + pan.addInput(gain); + ac.out.addInput(pan); + + /* set the source to stop the sound later */ + selectionSound = pan; + pan.start(); + } + + @Override + public void outsideSelection(){ + if(selectionSound != null){ + selectionSound.kill(); + selectionSound = null; + } + + } + + private UGen playSample(Sample s){ + SamplePlayer player = new SamplePlayer(ac,s); + player.setKillOnEnd(true); + return player; + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/Buzz.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/Buzz.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,92 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGenChain; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.events.KillTrigger; +import net.beadsproject.beads.ugens.Envelope; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.WavePlayer; + +public class Buzz extends UGenChain { + private Gain gain; + private Envelope env; + private WavePlayer wp; + private float len; + + public static void main (String argv[]) { + AudioContext x = new AudioContext(); + + Buzz b = new Buzz(x); + x.out.addInput(b); + x.start(); + } + + /** + * Crate a new Buzz 200 millisec long + * + * @param ac the audio context of the Buzz + */ + public Buzz(AudioContext ac) { + super(ac,0,1); + + len = 200.0f; + gain = new Gain(ac,1,0.5f); + env = new Envelope(ac,1.0f); + env.addSegment(1.0f, len , new KillTrigger(gain)); + + /* make a kind of impulse wave */ + Buffer b = new Buffer(4096); + for(int i=0; i. +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.core.UGenChain; + +/** + * A UGen with pitch + * + * + */ +public abstract class PitchedUGen extends UGenChain { + + public PitchedUGen(AudioContext ac) { + super(ac, 0, 2); + } + + public abstract void setPitch(float pitch); + + public abstract void setPitch(UGen pitch); + + /** + * Sets a threshold for the sonification at the extremities of the range + * This can be useful to flag that extremities are about to be reached + * + * @param threshold the threshold but be + */ + public abstract void setExtremityThresholds(float threshold); + + public abstract void setLen(float sustain, float decay); + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/SonificationPrefsPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/beads/sonification/SonificationPrefsPanel.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,186 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.beads.sonification; + + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.prefs.Preferences; + +import javax.swing.JLabel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; + +import uk.ac.qmul.eecs.depic.daw.gui.PreferencesPanel; + +import javax.swing.JCheckBox; + +public class SonificationPrefsPanel extends PreferencesPanel { + private static String [] keys = new String [] { + "selection.filter", + "borders.threshold", + "render_val.ref", + "render_curve.gain" + }; + + private static final long serialVersionUID = 1L; + private JSpinner selectionFilterSpinner; + private JSpinner borderThresholdSpinner; + private JCheckBox valRefCheckBox; + private JLabel lblRenderCurveGain; + private JSpinner curveGainSpinner; + + + private static SonificationPrefsPanel singleton; + public static SonificationPrefsPanel getInstance(){ + if(singleton == null) + singleton = new SonificationPrefsPanel(); + return singleton; + } + + /** + * Create the panel. + */ + private SonificationPrefsPanel() { + + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{30, 70, 78, 53, 0}; + gridBagLayout.rowHeights = new int[]{30, 18, 0, 0, 0, 0, 0}; + gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + setLayout(gridBagLayout); + + JLabel selectionFilterLabel = new JLabel("Selection filter :"); + GridBagConstraints gbc_selectionFilterLabel = new GridBagConstraints(); + gbc_selectionFilterLabel.anchor = GridBagConstraints.WEST; + gbc_selectionFilterLabel.insets = new Insets(0, 0, 5, 5); + gbc_selectionFilterLabel.gridx = 1; + gbc_selectionFilterLabel.gridy = 1; + add(selectionFilterLabel, gbc_selectionFilterLabel); + + /* prefs are used to initialize the spinners and the grain ugens */ + Preferences prefs = Preferences.userNodeForPackage(BeadsSonification.class); + + selectionFilterSpinner = new JSpinner(); + selectionFilterSpinner.setModel(new SpinnerNumberModel( + new Integer(prefs.getInt(getPrefsList()[0], new Integer(500))), + new Integer(0), // min value + new Integer(1000), // max value + new Integer(10))); // increment + GridBagConstraints gbc_selectionFilterSpinner = new GridBagConstraints(); + gbc_selectionFilterSpinner.insets = new Insets(0, 0, 5, 0); + gbc_selectionFilterSpinner.anchor = GridBagConstraints.NORTH; + gbc_selectionFilterSpinner.fill = GridBagConstraints.HORIZONTAL; + gbc_selectionFilterSpinner.gridx = 3; + gbc_selectionFilterSpinner.gridy = 1; + add(selectionFilterSpinner, gbc_selectionFilterSpinner); + + JLabel borderThreshLabel = new JLabel("Border Threshold :"); // FIXME boudles + GridBagConstraints gbc_borderThreshLabel = new GridBagConstraints(); + gbc_borderThreshLabel.anchor = GridBagConstraints.WEST; + gbc_borderThreshLabel.insets = new Insets(0, 0, 5, 5); + gbc_borderThreshLabel.gridx = 1; + gbc_borderThreshLabel.gridy = 2; + add(borderThreshLabel, gbc_borderThreshLabel); + + borderThresholdSpinner = new JSpinner(); + borderThresholdSpinner.setModel(new SpinnerNumberModel( + new Float(prefs.getFloat(getPrefsList()[1], new Float(-0.1f))), + new Float(-0.1f), // min value + new Float(1.0f), // max value + new Float(0.05f))); // increment + GridBagConstraints gbc_borderThreshSpinner = new GridBagConstraints(); + gbc_borderThreshSpinner.insets = new Insets(0, 0, 5, 0); + gbc_borderThreshSpinner.fill = GridBagConstraints.HORIZONTAL; + gbc_borderThreshSpinner.anchor = GridBagConstraints.NORTH; + gbc_borderThreshSpinner.gridx = 3; + gbc_borderThreshSpinner.gridy = 2; + add(borderThresholdSpinner, gbc_borderThreshSpinner); + + JLabel sequenceValRefLabel = new JLabel("Sequence val ref:"); + GridBagConstraints gbc_sequenceValRefLabel = new GridBagConstraints(); + gbc_sequenceValRefLabel.anchor = GridBagConstraints.WEST; + gbc_sequenceValRefLabel.insets = new Insets(0, 0, 5, 5); + gbc_sequenceValRefLabel.gridx = 1; + gbc_sequenceValRefLabel.gridy = 3; + add(sequenceValRefLabel, gbc_sequenceValRefLabel); + + valRefCheckBox = new JCheckBox(""); + valRefCheckBox.setSelected(prefs.getBoolean(getPrefsList()[3], false)); + sequenceValRefLabel.setLabelFor(valRefCheckBox); + GridBagConstraints gbc_valRefCheckBox = new GridBagConstraints(); + gbc_valRefCheckBox.anchor = GridBagConstraints.WEST; + gbc_valRefCheckBox.insets = new Insets(0, 0, 5, 0); + gbc_valRefCheckBox.gridx = 3; + gbc_valRefCheckBox.gridy = 3; + add(valRefCheckBox, gbc_valRefCheckBox); + + lblRenderCurveGain = new JLabel("Render Curve Gain:"); + GridBagConstraints gbc_lblRenderCurveGain = new GridBagConstraints(); + gbc_lblRenderCurveGain.insets = new Insets(0, 0, 5, 5); + gbc_lblRenderCurveGain.gridx = 1; + gbc_lblRenderCurveGain.gridy = 4; + add(lblRenderCurveGain, gbc_lblRenderCurveGain); + + curveGainSpinner = new JSpinner(); + curveGainSpinner.setModel(new SpinnerNumberModel( + new Float(prefs.getFloat(getPrefsList()[3], new Float(1.0f))), + new Float(0), // min value + new Float(1), // max value + new Float(0.1))); // increment + GridBagConstraints gbc_spinner = new GridBagConstraints(); + gbc_spinner.insets = new Insets(0, 0, 5, 0); + gbc_spinner.fill = GridBagConstraints.HORIZONTAL; + gbc_spinner.gridx = 3; + gbc_spinner.gridy = 4; + add(curveGainSpinner, gbc_spinner); + + /* sets all the strings for the screen reader */ + configureAccessibleContext(); + } + + + private void configureAccessibleContext(){ + borderThresholdSpinner.getEditor().getAccessibleContext().setAccessibleName("Border Threshold"); // FIXME bundle + selectionFilterSpinner.getEditor().getAccessibleContext().setAccessibleName("Selectionf Filter"); + } + + @Override + public void savePrefs() { + Preferences prefs = Preferences.userNodeForPackage(BeadsSonification.class); + + prefs.putInt(getPrefsList()[0], (Integer)selectionFilterSpinner.getValue()); + prefs.putFloat(getPrefsList()[1], (Float)borderThresholdSpinner.getValue()); + prefs.putBoolean(getPrefsList()[2], (Boolean)valRefCheckBox.isSelected()); + prefs.putFloat(getPrefsList()[3], (Float)curveGainSpinner.getValue()); + + } + + @Override + public String getTitle(){ + return "Sonification Synthesis"; + } + + @Override + public String[] getPrefsList() { + return keys; + } +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/error.mp3 Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/error.mp3 has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/loop.mp3 Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/loop.mp3 has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/metallic.wav Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/metallic.wav has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/ok.mp3 Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/ok.mp3 has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/pop.mp3 Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/pop.mp3 has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/selection_borders.wav Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/selection_borders.wav has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/beads/sonification/zero.wav Binary file src/uk/ac/qmul/eecs/depic/daw/beads/sonification/zero.wav has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/ArrangeWindow.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/ArrangeWindow.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,320 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.KeyStroke; +import javax.swing.border.Border; + +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; + +/** + * + * The panel containing the audio tracks. + * + * Tracks are arranged in a scrollable BoxLayout. + * + */ +public class ArrangeWindow extends JPanel implements PropertyChangeListener, SoundWaveListener { + private static final long serialVersionUID = 1L; + public static final Color BACKGROUND_COLOR = new Color(226,226,226); + public static final int SPACE_BETWEEN_TRACKS = 5; + public static final Border BORDER_UNSELECTED = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(1, 0, 1, 0), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY)); + public static final Border BORDER_SELECTED = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(1, 0, 1, 0, new Color(214, 193, 99)), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY)); + + + /* tracks panel contains the rule and audio tracks */ + private JScrollPane tracksPanelScroll; + /* view of the trackPanelScroll and panel where all the audio tracks are places */ + private JPanel tracksPanel; + /* left panel with AudioTrackParameters */ + private JPanel parametersPanel; + private Rule rule; + private int currentTrackIndex; + private List tracks; + private List audioTrackParameters; + private List spacesBetweenTracks; + private List spacesBetweenTrackParameters; + private MouseInteraction mouseInteraction; + + + public ArrangeWindow(){ + tracks = new ArrayList<>(); + audioTrackParameters = new ArrayList<>(); + spacesBetweenTracks = new ArrayList<>(); + spacesBetweenTrackParameters = new ArrayList<>(); + mouseInteraction = new MouseInteraction(); + + setLayout(new BorderLayout()); + setBackground(BACKGROUND_COLOR); + + /* left panel: header (of the same height as rule) and parameters */ + parametersPanel = new JPanel(); + parametersPanel.setLayout(new BoxLayout(parametersPanel,BoxLayout.Y_AXIS)); + parametersPanel.add(Box.createRigidArea(new Dimension(247,Rule.HEIGHT+3))); + parametersPanel.setBackground(BACKGROUND_COLOR); + + add(parametersPanel,BorderLayout.WEST); + + /* right panel: scrollable panel with rule and tracks as header*/ + tracksPanel = new JPanel(); + tracksPanel.setLayout(new BoxLayout(tracksPanel,BoxLayout.Y_AXIS)); + tracksPanel.setBackground(BACKGROUND_COLOR); + rule = new Rule(); + rule.setBackground(BACKGROUND_COLOR); + + + tracksPanelScroll = new JScrollPane(tracksPanel, + JScrollPane.VERTICAL_SCROLLBAR_NEVER, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + + /* disable the scrolling via the left-right keys to let the cursor scrub handler + * take over when the track is focused */ + tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none"); + tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none"); + /* setPreferredSize important, otherwise the view port gets resized to the track size * + * as soon as the window is resized and therefore the scroll bar disappear */ + tracksPanelScroll.setPreferredSize(new Dimension(500,500)); + /* rule is set as the header of the scroll pane with audio tracks */ + tracksPanelScroll.setColumnHeaderView(rule); + + add(tracksPanelScroll, BorderLayout.CENTER); + + currentTrackIndex = -1; + } + + /** + * Adds a track to this component. The added track is automatically set as the current track. + * + * Adding a track that's already in the arrange window will have no effect. + * + * @param track the new track to add. It must be a track that is not currently + * contained in this arrange window + */ + public void addTrack(AudioTrack track){ + if(tracks.contains(track)) + return; + + AudioTrackParameters trackParameters = new AudioTrackParameters(track,1); + + /* install the mouse listener */ + track.addMouseListener(mouseInteraction); + trackParameters.addMouseListener(mouseInteraction); // FIXME verify + + + /* add track and parameters in the respective lists */ + tracks.add(track); + audioTrackParameters.add(trackParameters); + Component box1 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS); + Component box2 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS); + spacesBetweenTracks.add(box1); + spacesBetweenTrackParameters.add(box2); + + /* add track and parameters in the respective panels */ + parametersPanel.add(box1); + parametersPanel.add(trackParameters); + tracksPanel.add(box2); + tracksPanel.add(track); + /* set the new track as the current */ + setCurrentTrack(getTracksCount()-1); + + /* set the label. Users will count starting from 1 and not from 0 */ + trackParameters.setLabel("new Track "+(getCurrentTrackIndex()+1)); + repaint(); + } + + public AudioTrack getTrackAt(int index){ + return tracks.get(index); + } + + public AudioTrackParameters getTrackParametersAt(int index){ + return audioTrackParameters.get(index); + } + + /** + * + * @return the current track or {@code null} if there are no tracks + * in this arrange window + */ + public AudioTrack getCurrentTrack(){ + if(currentTrackIndex == -1) + return null; + return tracks.get(currentTrackIndex); + } + + /** + * Sets the track at the specified index as current. + * + * @param index the index of the new current track + * @throws ArrayIndexOutOfBoundsException if {@code index} is lower than 0 or greater or equal to {@code getTracksCount()} + * + * + */ + public void setCurrentTrack(int index){ + /* removes this from the listeners of the previous current track */ + if(currentTrackIndex != -1){ + AudioTrack previousTrack = getTrackAt(currentTrackIndex); + previousTrack.removePropertyChangeListener(this); + previousTrack.getSoundWave().removeSoundWaveListener(this); + previousTrack.setBorder(BORDER_UNSELECTED); + audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_UNSELECTED); + } + currentTrackIndex = index; + + AudioTrack currentTrack = getTrackAt(currentTrackIndex); + /* install listeners in the new audio track */ + currentTrack.addPropertyChangeListener(this); + currentTrack.getSoundWave().addSoundWaveListener(this); + /* set the borders */ + currentTrack.setBorder(BORDER_SELECTED); + audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_SELECTED); + + rule.setAudioTrack(getTrackAt(currentTrackIndex)); + } + + /** + * + * @return the index of the currently selected track or {@code -1} if no track + * is open in the arrange window + */ + public int getCurrentTrackIndex(){ + return currentTrackIndex; + } + + /** + * Returns the number of tracks in this arrange window. + * + * @return the number of tracks in this arrange window + */ + public int getTracksCount(){ + return tracks.size(); + } + + public Rule getRule(){ + return rule; + } + + /** + * + * On {@code SELECTION_CHANGED} event from the current audio track, updates the other + * tracks accordingly. + * + * This enforces a unique selection and cursor position over the whole arrange window + * + * @param evt the sound wave event + * + */ + @Override + public void update(SoundWaveEvent evt) { + + if(SoundWaveEvent.SCAN.equals(evt.getType())){ + for(int i=0; i< getTracksCount(); i++){ + SoundWave wave = getTrackAt(i).getSoundWave(); + /* only change selection if it's not the one that triggered the event */ + if(!wave.equals(evt.getSource())){ + wave.scan((Integer)evt.getArgs()); + } + } + } + + if(SoundWaveEvent.POSITION_CHANGED.equals(evt.getType()) ){ + for(AudioTrack track : tracks){ + if(!track.getSoundWave().equals(evt.getSource())){ + track.getSoundWave().setPosition((Integer)evt.getArgs()); + } + } + + /* this is called from the current audio track sound wave. The * + * selection is changed in the other audio tracks accordingly */ + //SoundWave currentWave = evt.getSource(); + +// for(int i=0; i< getTracksCount(); i++){ +// SoundWave wave = getTrackAt(i).getSoundWave(); +// /* only change selection if it's not the one that triggered the event */ +// if(!wave.equals(currentWave)){ +// wave.setSelection((Selection)evt.getArgs()); +// } +// } + } + } + + private class MouseInteraction extends MouseAdapter { + @Override + public void mousePressed(MouseEvent e){ + Component c = e.getComponent(); + int index = tracks.lastIndexOf(c); + + if(index == getCurrentTrackIndex()){ + return; + }else if(index != -1){ + setCurrentTrack(index); + return; + } + + index = audioTrackParameters.lastIndexOf(c); + if(index == getCurrentTrackIndex()){ + return; + }else if(index != -1){ + setCurrentTrack(index); + return; + } + + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + switch(evt.getPropertyName()){ + case "mouseDragSelection" : { + Selection selection = (Selection)evt.getNewValue(); + for(AudioTrack track : tracks){ + if(!track.equals(evt.getSource())){ + track.trackInteraction.setMouseSelection( + selection.getStart(), selection.getEnd()); + } + } + } break; + } + } + + + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrack.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrack.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,520 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; + +import javax.swing.JPanel; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Chunk; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; +import uk.ac.qmul.eecs.depic.daw.Wave; +import uk.ac.qmul.eecs.depic.daw.haptics.HapticViewPort; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +/** + * + * + * + * + * This class has the following bound properties : + *
    + *
  • scaleFactor: the scale factor of each pixel of the sound wave representation. The bigger the + * scale factor, the lower the resolution. a scale factor ranges from 1, highest resolution supported by + * the {@code SoundWave} instance, to the max scale factor supported by the backing {@code SoundWave} model - {@code int}
  • + *
  • cursorPos: the position of the cursor - {@code int}
  • + *
  • mouseDragSelection: the range of the selection on this track as the user drags the mouse - {@code SelectionRange}
  • + *
  • the preferred size - {@code Dimension} + *
+ * + * + * + */ +public class AudioTrack extends JPanel implements SoundWaveListener { + private static final long serialVersionUID = 1L; + + public static final int MAX_TRACK_HEIGHT = 130; + public static final int MAX_TRACK_WIDTH = Integer.MAX_VALUE; + + public static final Color WAVE_COLOR = new Color(7,47,140); + public static final Color CURSOR_COLOR = new Color(44,47,56); + public static final Color SELECTION_COLOR = new Color(218,218,240); + public static final Color OVERLAY_BG_COLOR = new Color(222,222,235); + public static final Color VIEW_PORT_COLOR = Color.GREEN; + + /* these fields are used by friend classes * + * they're final to protect them from being changed */ + final AudioTrackInput trackInteraction; + final AudioTrackSonification trackSonification; + + private SequenceGraph automationGraph; + private SequenceGraph peakMeterGraph; + private final CursorUpdaterOnPlay clipInteraction; + protected SoundWave soundWave; + + private HapticViewPort hapticViewPort; + + private Selection currentSelection; + private int scaleFactor; // bound property + private int cursorPos; // bound property + private float secondsPerPixel; + private boolean showHapticViewPort; + boolean showAutomationSound; + boolean showPeakLevelSound; + private boolean showDbWave; + + public AudioTrack(SoundWave soundWave){ + if(soundWave == null) + throw new IllegalArgumentException("soundWave cannot be null"); + + this.soundWave = soundWave; + soundWave.addSoundWaveListener(this); + + + setBackground(Color.WHITE); + + scaleFactor = 1; + currentSelection = new Selection(0,scaleFactor); + + /* set up sequence graphs */ + ChangeListener sequenceGraphListener = new ChangeListener(){ + @Override + public void stateChanged(ChangeEvent e) { + repaint(); + } + }; + + automationGraph = new SequenceGraph(Color.WHITE, getSize()); + automationGraph.addChangeListener(sequenceGraphListener); + peakMeterGraph = new SequenceGraph(Color.GRAY, getSize()); + peakMeterGraph.addChangeListener(sequenceGraphListener); + + clipInteraction = new CursorUpdaterOnPlay(); + trackInteraction = new AudioTrackInput(this); + trackSonification = new AudioTrackSonification(this); + hapticViewPort = new HapticViewPort(1); + + setMaximumSize(new Dimension(MAX_TRACK_WIDTH,MAX_TRACK_HEIGHT)); + //setMinimumSize(new Dimension(100,TRACK_HEIGHT)); + setPreferredSize(new Dimension(0,MAX_TRACK_HEIGHT)); + + this.setFocusable(true); + + addPropertyChangeListener(trackSonification); + addMouseListener(trackInteraction); + addMouseMotionListener(trackInteraction); + addKeyListener(trackInteraction); + } + + @Override + public void update(SoundWaveEvent evt) { // FIXME fare file closed + String evtType = evt.getType(); + if(SoundWaveEvent.OPEN.equals(evtType)) { + SoundWave wave = evt.getSource(); + hapticViewPort.setTrackSize(wave.getChunkNum()); // FIXME check + _setScaleFactor(wave.getScaleFactor()); + setCursorPos(0); + } else if(SoundWaveEvent.SCALE_FACTOR_CHANGED.equals(evtType)){ + _setScaleFactor((Integer)evt.getArgs()); + }else if (SoundWaveEvent.SELECTION_CHANGED.equals(evtType)){ + Selection oldSelection = currentSelection; + currentSelection = (Selection)evt.getArgs(); + /* update the mouse interaction object accordingly */ + trackInteraction.setMouseSelection(currentSelection.getStart(), currentSelection.getEnd()); + firePropertyChange("mouseDragSelection",oldSelection,currentSelection); + repaint(); + + }else if(SoundWaveEvent.POSITION_CHANGED.equals(evtType)){ + /* update the position according to the Selection object returned by evt.getArgs() * + * the selection is open. The Selection's scale factor is also taken into account * + * when calculating the new position. In particular the ratio between the * + * selction's scale factor and this object current scale factor */ + setCursorPos((Integer)evt.getArgs()); + + + }else if(SoundWaveEvent.START.equals(evtType) || + SoundWaveEvent.STOP.equals(evtType) || + SoundWaveEvent.PAUSE.equals(evtType)){ + clipInteraction.update(evt); + }else if (SoundWaveEvent.AUTOMATION_CHANGED.equals(evtType)){ + Automation automation = (Automation)evt.getArgs(); + + /* if it's an automation different from NONE, paint the overlay: make the bg darker */ + if(Automation.NONE_AUTOMATION.equals(automation)){ + setBackground(Color.WHITE); + automationGraph.removeChangeListener(trackSonification); + }else{ + setBackground(OVERLAY_BG_COLOR); + } + + /* resize automation graph, if currently showing any */ + automationGraph.setMillisecPerPixel(evt.getSource().getMillisecPerChunk()); + automationGraph.setColor(automation.getColor()); + automationGraph.setSize(getSize()); + automationGraph.setSequence(automation); + automationGraph.addChangeListener(trackSonification); + + }else if(SoundWaveEvent.CLOSE.equals(evtType) || + SoundWaveEvent.CUT.equals(evtType) || + SoundWaveEvent.PASTE.equals(evtType) || + SoundWaveEvent.INSERT.equals(evtType)){ + hapticViewPort.setTrackSize(evt.getSource().getChunkNum()); + repaint(); + }else if(SoundWaveEvent.PEAK_METER.equals(evtType)){ + + peakMeterGraph.setMillisecPerPixel(evt.getSource().getMillisecPerChunk()); + peakMeterGraph.setSize(getSize()); + /* listens to the changes, so it will repaint after setSequence */ + peakMeterGraph.setSequence((Sequence)evt.getArgs()); + + } + } + + @Override + public void paintComponent(Graphics g){ + super.paintComponent(g); + Graphics2D g2 = (Graphics2D)g; + Color oldColor = g2.getColor(); + + /* get height and width. needed for painting */ + int height = getHeight(); + int width = getWidth(); + + /* paint the selection grey background*/ + Selection mouseSelection = trackInteraction.getMouseSelection(); + if(!mouseSelection.isOpen()){ + g2.setColor(SELECTION_COLOR); + g2.fillRect(mouseSelection.getStart(), 0, Math.abs(mouseSelection.getStart()-mouseSelection.getEnd()), height); + } + + /* paint the central line */ + g2.setColor(CURSOR_COLOR); + g2.draw(new Line2D.Float(0f, height/2,width,height/2)); + + /* paint the sound wave, if any */ + if(soundWave != null ){ + Wave wave = showDbWave ? soundWave.getDbWave() : soundWave; + g2.setColor(WAVE_COLOR); + int horizontalPixel = 0; + for(int i=0; i getSoundWave().getMaxScaleFactor() ){ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + return; + } + + int oldScaleFactor = scaleFactor; + scaleFactor = factor; + + secondsPerPixel = soundWave.getMillisecPerChunk()/1000; + + /* update haptic view port */ + hapticViewPort.setScaleFactor(scaleFactor); + + /* resize the selection after zooming in/out */ + Selection oldSelection = currentSelection; + int start = (int) (oldSelection.getStart() * Math.pow(2,oldScaleFactor - factor)); + if(oldSelection.isOpen()) + currentSelection = new Selection(start,factor); + else{ + int end = (int) (oldSelection.getEnd() * Math.pow(2,oldScaleFactor - factor)); + currentSelection = new Selection(start,end,factor); + } + + /* if the selection is open, it will be painted as a one-pixel selection */ + trackInteraction.setMouseSelection(currentSelection.getStart(), currentSelection.getEnd()); + + /* make listeners aware the mouse selection has changed */ + firePropertyChange("mouseDragSelection",oldSelection,currentSelection); + + setPreferredSize(new Dimension(soundWave.getChunkNum()+30,MAX_TRACK_HEIGHT)); + revalidate(); + + setCursorPos(soundWave.getCurrentChunkPosition()); + /* update the sequences */ + automationGraph.setSize(getSize()); + automationGraph.setMillisecPerPixel(AudioTrack.this.soundWave.getMillisecPerChunk()); + automationGraph.updateSequence(); + + peakMeterGraph.setSize(getSize()); + peakMeterGraph.setMillisecPerPixel(AudioTrack.this.soundWave.getMillisecPerChunk()); + peakMeterGraph.updateSequence(); + + repaint(); + firePropertyChange("scaleFactor", oldScaleFactor, scaleFactor); + } + + public Selection getSelection(){ + return currentSelection; + } + + /** + * Doesn't change the cursor position of the underlying soundwave + * @param position + */ + public void setCursorPos(int position) { + int oldCursorPos = cursorPos; + cursorPos = position; + + repaint(); + scrollRectToVisible(new Rectangle(new Point(position,0))); + firePropertyChange("cursorPos", oldCursorPos, position); + } + + public int getCursorPos(){ + return cursorPos; + } + + public float getSecondsPerPixel(){ + return secondsPerPixel; + } + + public SequenceGraph getAutomationGraph(){ + return automationGraph; + } + + public SequenceGraph getPeakLevelGraph(){ + return peakMeterGraph; + } + + /** + * Makes it available to {@code Rule} class to notify listeners after changing + * the mouseDragSelection. + * + * @param property the programmatic name of the property that was changed + * @param oldValue the old value of the property + * @param newValue the new value of the property + */ + @Override + protected void firePropertyChange(String property, Object oldValue, Object newValue){ + super.firePropertyChange(property, oldValue, newValue); + } + + public HapticViewPort getHapticViewPort(){ + return hapticViewPort; + } + + public void showHapticViewPort(boolean show){ + showHapticViewPort = show; + repaint(); + } + + public void showAutomationSound(boolean show){ + showAutomationSound = show; + + SequenceMapping sonificationSeqMapping = Daw.getSoundEngineFactory().getSharedSonification() + .getSequenceMapping(SoundType.AUTOMATION); + if(show){ + sonificationSeqMapping.renderCurveAt( + soundWave.getSequence(), + soundWave.getMillisecPerChunk() * cursorPos, + SequenceMapping.DURATION_INF); + }else{ + sonificationSeqMapping.renderCurveAt(null, 0.0f, SequenceMapping.DURATION_STOP); + } + + } + + public void showPeakLevelSound(boolean show){ + showPeakLevelSound = show; + + SequenceMapping sonificationSeqMapping = Daw.getSoundEngineFactory().getSharedSonification() + .getSequenceMapping(SoundType.PEAK_LEVEL); + if(show){ + sonificationSeqMapping.renderCurveAt( + soundWave.getDbWave().getSequence(), + soundWave.getMillisecPerChunk() * cursorPos, + SequenceMapping.DURATION_INF); + }else{ + sonificationSeqMapping.renderCurveAt(null, 0.0f, SequenceMapping.DURATION_STOP); + } + } + + public void showDbWave(boolean show){ + showDbWave = show; + repaint(); + } + + + public int normToWidthconvert(float f){ + return hapticViewPort.getPosition(f); + } + /** + * + * @param s a value sample value ranging from Short.MIN_VALUE to Short.MAX_VALUE + * @param height the height of the graphic component where the track is to be drawn + * @return the Y value in the graphic component resulted from mapping the sample to the graphic component height + */ + public int normToHeightConvert(float f){ + /* in a JPanel pixel y coordinate increases from top to bottom, whereas the * + * in the signed short PCM format y value increased from bottom to top * + * so the sign of the value is reversed to match it with the JPanel space */ + int height = getHeight(); + return height - (int)( f *height); + } + + /** + * Returns a point in the {@code Automation} coordinates - x = length in millis, + * y = range of the automation - corresponding to the point on {@code track} + * which has coordinates {@code (mouseX, mouseY) } + * + * + * @param track + * @param mouseX the {@code x} coordinate of the point on {@code track} + * @param mouseY the {@code y} coordinate of the point on {@code track} + * @return the point in the {@code Automation} coordinates corresponding to (mouseX,mouseY) + * or {@code null} if mouseX is bigger than the automation length (in chunks of the track's {@code SoundWave}) + * or mouseY is bigger than the track's height, or if either is lower than zero. + * + */ + public static Point2D.Float getAutomationCoord(AudioTrack track, int mouseX, int mouseY ){ + SoundWave wave = track.getSoundWave(); + Automation autom = wave.getParametersControl().getCurrentAutomation(); + + /* get height and width of the track panel */ + float height = track.getSize().height; + + float width = track.getSoundWave().getChunkNum(); + + if(mouseX >= width || mouseY > height || mouseX < 0 || mouseY < 0 ){ + return null; + } + + /* calculate the automation x (a.k.a. position over time) from * + * the mouse click according following relation: * + * mouse x / width of the panel = automation x position / length of sample */ + float automationX = (float)(autom.getLen()*(mouseX/width)); + /* calculate the automation y (a.k.a. value int he parameter range) from * + * the mouse click according following relation: * + * mouse y / height of the panel = automation y position / value range * + * since in swing y has 0 at the top the y value is reversed height-mouseY */ + float automationY = (float)(autom.getRange().lenght() *((height-mouseY)/height)); + return new Point2D.Float(automationX,automationY+autom.getRange().getStart()); + } + + + + private class CursorUpdaterOnPlay implements ActionListener { + private static final int REFRESH_DELAY = 50; + Timer timer = new Timer(REFRESH_DELAY,this);; + + public void update(SoundWaveEvent evt) { + if(SoundWaveEvent.START.equals(evt.getType())){ + timer.start(); + }else if(SoundWaveEvent.STOP.equals(evt.getType()) || + SoundWaveEvent.PAUSE.equals(evt.getType())){ + timer.stop(); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + int cursorPos = Math.round(soundWave.getTransportControl().getPlayPosition()/soundWave.getMillisecPerChunk()); + setCursorPos(cursorPos); + } + } // class ClipInteraction + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackInput.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackInput.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,465 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.Dimension; +import java.awt.Shape; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.event.MouseInputAdapter; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.AutomationValue; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Direction; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; + +/* + * This class handles the interaction with the mouse. When the mouse is dragged around + * the corresponding selection appears on this audio track. When the user releases the + * mouse button (and therefore stops dragging) the selection is assigned to + * the underlying sound wave by a call to setSelection(). + */ + final class AudioTrackInput extends MouseInputAdapter implements KeyListener { + private static final int KEY_SELECTION_INCREMENT = 1; + private static final int KEY_PRESS_NO_SELECTION = -1; + private int mousePress; // where the user presses the mouse + private int keyPress; + /* used to check whether the user is dragging a new selection */ + private boolean isDragging; + private Selection mouseSelection; + private AudioTrack track; + private SequencePoint dragAutomationPoint; + + AudioTrackInput(AudioTrack track){ + this.track = track; + setAudioTrackActions(track); + mouseSelection = Selection.ZERO_SELECTION; + keyPress = KEY_PRESS_NO_SELECTION; + } + + private void setAudioTrackActions(AudioTrack track){ + for(Action action : AutomationGraphActions.getInstance()){ + Object unique = new Object(); + + track.getInputMap(JComponent.WHEN_FOCUSED).put((KeyStroke)action.getValue(Action.ACCELERATOR_KEY), unique); + track.getActionMap().put(unique,action); + } + } + + /** + * Sets the mouse selection to a new {@code Selection} ranging from {@code start} to {@code end}. + * + * @param start + * @param end + */ + public void setMouseSelection(int start, int end){ + Selection oldSelection = mouseSelection; + if(end == -1) // open Selection + mouseSelection = new Selection(start,track.getScaleFactor()); + else + mouseSelection = new Selection(start,end,track.getScaleFactor()); + + track.firePropertyChange("mouseDragSelection", + oldSelection, + mouseSelection); + track.repaint(); + } + + /** + * Sets the mouse selection and sets the beginning of the selection action to + * either {@code start} or {@start end} (according to the value of {@code mouseAtStart}). + * + * The beginning of the selection action is where a user presses the mouse click + * in order to start a dragging action which creates the selections. The click + * point affects how the selection is: if the user drags towards + * left, the selection starts from the current dragging point and ends at the click point; + * conversely if the user drags towards right the selection starts at the click point and + * end at the current dragging point. The current dragging point changes as the user drags the mouse + * around + * + * @param start the beginning of the selection + * @param end start the end of the selection + * @param mouseAtStart {@code true} if the beginning of the selection action was the + * selection start, {@code false} if it's the selection end + * + */ + private void setMouseSelection(int start, int end, boolean mouseAtStart){ // FIXME remove + if(end == -1){ // open selection + mousePress = start; + }else{ + mousePress = mouseAtStart ? start : end; + } + setMouseSelection(start,end); + } + + /** + * Returns the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection. + * + * @return the current mouse selection or {@code Selection.VOID_SELECTION} if there is no selection. + */ + public Selection getMouseSelection(){ + return mouseSelection; + } + + @Override + public void mousePressed(MouseEvent evt){ + if(SwingUtilities.isLeftMouseButton(evt)){ + /* if automation is on */ + if(track.getSoundWave().hasSequence()){ + for(SequencePoint p : track.getAutomationGraph().getSequencePoints()){ + if(p.contains(evt.getX(),evt.getY())){ + dragAutomationPoint = p; + return; + } + } + } + + /* keep track of where the mouse is pressed. If the user drags a selection this is needed * + * to make the right selection according to the direction of the dragging. see mouseDragged() */ + mousePress = evt.getX(); + } + } + + @Override + public void mouseDragged(MouseEvent evt){ + if(SwingUtilities.isLeftMouseButton(evt)){ + + /* bound the dragging to the space within the sound wave in the track */ + Dimension trackSize = track.getSize(); + if(evt.getX()<0 || evt.getX() >= track.getSoundWave().getChunkNum() || evt.getY()<0 || evt.getY() > trackSize.height ) + return; + + /* if we are dragging an automation point, change its coordinate and refresh the automation */ + if(track.getSoundWave().hasSequence() && dragAutomationPoint != null){ + Point2D.Float pointCoord = AudioTrack.getAutomationCoord(track,evt.getX(),evt.getY()); + AutomationValue automVal = (AutomationValue)dragAutomationPoint.getSequenceValue(); + automVal.setLocation(pointCoord.x,pointCoord.y); + + return; + } + + /* update the mouse selection as the user drags the mouse. The selection differs * + * according to the direction of the dragging. If the user moves right from where * + * they pressed, then the start of the selection is the press point, and the end the * + * current dragging point (evt.getX()). Conversely, if the user moves to the left, * + * the start of the selection is the dragging point and the end is the press point. * + * The press point is the value assigned to mousePressX in mousePressed */ + + isDragging = true; + if(evt.getX() > mousePress){ + setMouseSelection(mousePress,evt.getX()); + }else{ + setMouseSelection(evt.getX(),mousePress); + } + } + } + + @Override + public void mouseReleased(MouseEvent evt){ + if(SwingUtilities.isLeftMouseButton(evt)){ + dragAutomationPoint = null; + if(isDragging){ + /* if the user was dragging update the selection in the sound wave */ + track.getSoundWave().setSelection(getMouseSelection()); + isDragging = false; + } + } + } + + @Override + public void mouseClicked(MouseEvent evt){ + /* left click : set the cursor position * + * right click : open pop up menu */ + if(SwingUtilities.isLeftMouseButton(evt)){ + track.getSoundWave().setPosition(evt.getX()); + }else if(SwingUtilities.isRightMouseButton(evt)){ + /* show automation popup only if an automation is is displayed */ + if(track.getSoundWave().hasSequence()){ + for(Shape point : track.getAutomationGraph().getSequencePoints()){ + if(point.contains(evt.getX(), evt.getY())){ + showAutomationPopup(point,evt.getX(),evt.getY()); + return; + } + } + + for(Shape line : track.getAutomationGraph().getSequenceLines()){ + if(line.contains(evt.getX(), evt.getY())){ + showAutomationPopup(line,evt.getX(),evt.getY()); + return; + } + } + } + + /* gets here only if no automation has been found under the mouse pointer */ + if(track.getSoundWave().getDbWave().hasSequence()){ + showPeakLevelPopup(evt.getX(),evt.getY()); + } + } + } + + + void showAutomationPopup(final Shape selectedShape, final int x, final int y){ + final SoundWave wave = track.getSoundWave(); + if(x >= wave.getChunkNum()){ + /* don't show if the user clicks past the sound wave */ + return; + } + + JPopupMenu pop = new JPopupMenu(); + if(selectedShape instanceof SequencePoint){ + pop.add(new AbstractAction("Remove automation point"){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + SequencePoint point = (SequencePoint)selectedShape; + Automation automation = wave.getParametersControl().getCurrentAutomation(); + automation.remove((AutomationValue)point.getSequenceValue()); + } + }); + }else{ // Line2D + pop.add(new AbstractAction("Add automation point"){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + Automation automation = wave.getParametersControl().getCurrentAutomation(); + Point2D.Float p = AudioTrack.getAutomationCoord(track,x,y); + automation.add(p.x, p.y); + } + }); + } + + pop.add(new AbstractAction("Reset Automation"){ + private static final long serialVersionUID = 1L; + + @Override + public void actionPerformed(ActionEvent evt) { + AutomationGraphActions.getInstance().resetAutomationAction.handleActionPerformed(track); + } + }); + + Action listenToAutomation = new AbstractAction("Listen to Automation"){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + AutomationGraphActions.getInstance().listenWholeAutomationAction.handleActionPerformed(track); + } + }; + listenToAutomation.putValue(Action.ACCELERATOR_KEY, + AutomationGraphActions.getInstance().listenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY)); + pop.add(listenToAutomation); + + Action stopListenToAutomation = new AbstractAction("Stop Listen To Automation"){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + AutomationGraphActions.getInstance().stopListenWholeAutomationAction.handleActionPerformed(track); + } + }; + stopListenToAutomation.putValue(Action.ACCELERATOR_KEY, + AutomationGraphActions.getInstance().stopListenWholeAutomationAction.getValue(Action.ACCELERATOR_KEY)); + pop.add(stopListenToAutomation); + + Action switchListenAutomation = new AbstractAction("Switch Listen Automation "+ + (AutomationGraphActions.getInstance().switchListenAutomationAction.isSwitchOn() ? "off" : "on")){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + AutomationGraphActions.getInstance().switchListenAutomationAction.handleActionPerformed(track); + } + }; + switchListenAutomation.putValue(Action.ACCELERATOR_KEY, + AutomationGraphActions.getInstance().switchListenAutomationAction.getValue(Action.ACCELERATOR_KEY)); + pop.add(switchListenAutomation); + pop.show(track, x, y); + } + + void showPeakLevelPopup(int x , int y){ + final SoundWave wave = track.getSoundWave(); + if(x >= wave.getChunkNum()){ + /* don't show if the user clicks past the sound wave */ + return; + } + + JPopupMenu pop = new JPopupMenu(); + + pop.add(new AbstractAction("Switch listen peak level " + + (AutomationGraphActions.getInstance().switchListenPeakLevelAction.isSwitchOn() ? "off" : "on")){ + private static final long serialVersionUID = 1L; + + @Override + public void actionPerformed(ActionEvent evt) { + AutomationGraphActions.getInstance().switchListenPeakLevelAction.handleActionPerformed(track); + } + }); + + pop.add(new AbstractAction("Listen to Peak Level"){ + private static final long serialVersionUID = 1L; + + @Override + public void actionPerformed(ActionEvent evt) { + AutomationGraphActions.getInstance().listenWholePeakLevelAction.handleActionPerformed(track); + } + }); + + pop.show(track,x,y); + } + + @Override + public void keyPressed(KeyEvent evt) { + /* adjust the selection */ + if(evt.isShiftDown() && evt.isControlDown() && + !mouseSelection.equals(Selection.ZERO_SELECTION )){ + Direction d = Direction.NONE; + if(evt.getKeyCode() == KeyEvent.VK_LEFT){ + d = Direction.LEFT; + }else if(evt.getKeyCode() == KeyEvent.VK_RIGHT){ + d = Direction.RIGHT; + } else { + return; + } + + SoundWave wave = track.getSoundWave(); + int cursorPos = wave.getCurrentChunkPosition(); + if(cursorPos == mouseSelection.getStart()){ + if(d == Direction.LEFT){ // left + wave.setSelection(new Selection( + mouseSelection.getStart()-KEY_SELECTION_INCREMENT, + mouseSelection.getEnd(), + wave.getScaleFactor())); + wave.scan(cursorPos-KEY_SELECTION_INCREMENT); + }else{ // right + if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){ + /* cannot push it too right so as to trespass the end of the selection */ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + }else{ + wave.setSelection(new Selection( + mouseSelection.getStart()+KEY_SELECTION_INCREMENT, + mouseSelection.getEnd(), + wave.getScaleFactor())); + wave.scan(cursorPos+KEY_SELECTION_INCREMENT); + } + } + }else if (cursorPos == mouseSelection.getEnd()){ + if(d == Direction.LEFT){ // left + if(mouseSelection.getEnd() - mouseSelection.getStart() <= KEY_SELECTION_INCREMENT){ + /* cannot push it too keft so as to trespass the start of the selection */ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + }else{ + wave.setSelection(new Selection( + mouseSelection.getStart(), + mouseSelection.getEnd()-KEY_SELECTION_INCREMENT, + wave.getScaleFactor())); + wave.scan(cursorPos-KEY_SELECTION_INCREMENT); + } + }else{ // right + wave.setSelection(new Selection( + mouseSelection.getStart(), + mouseSelection.getEnd()+KEY_SELECTION_INCREMENT, + wave.getScaleFactor())); + wave.scan(cursorPos+KEY_SELECTION_INCREMENT); + } + } + + /* make selection with right arrow key */ + } else if(evt.getKeyCode() == KeyEvent.VK_RIGHT && evt.isShiftDown() ){ + if(keyPress == KEY_PRESS_NO_SELECTION){ // new selection + keyPress = track.getSoundWave().getCurrentChunkPosition(); + setMouseSelection(keyPress, keyPress+KEY_SELECTION_INCREMENT); + /* scrub along with the selection right end */ + track.getSoundWave().scan(getMouseSelection().getEnd()); + /* start the selection sound */ + }else{ // user is adjusting the selection + /* if mouseSelection.getStart == keypress, it means the selection is at the * + * right side of where the user started to select. Conversely id mouse selection * + * .getEnd == keypress, the selection is at the left of the point where the user * + * started to select. In the former case pressing the right key will expand * + * the selection to the right, whereas in latter case pressing the right key will * + * shrink the selection to the right. */ + + if(mouseSelection.getStart() == keyPress){ + setMouseSelection(keyPress,mouseSelection.getEnd()+KEY_SELECTION_INCREMENT); + track.getSoundWave().scan(getMouseSelection().getEnd()); + }else{ // getEnd == keyPress + setMouseSelection(mouseSelection.getStart()+KEY_SELECTION_INCREMENT,keyPress); + track.getSoundWave().scan(getMouseSelection().getStart()); + } + } + + /* make selection with left arrow key */ + }else if(evt.getKeyCode() == KeyEvent.VK_LEFT && evt.isShiftDown()){ + if(keyPress == KEY_PRESS_NO_SELECTION){ + keyPress = track.getSoundWave().getCurrentChunkPosition(); + + /* keep the selection after the beginning - 0 - of the audio track */ + int start = keyPress-KEY_SELECTION_INCREMENT > 0 ? keyPress-KEY_SELECTION_INCREMENT : 0; + setMouseSelection(start,keyPress); + /* scrub along with the selection left end */ + track.getSoundWave().scan(getMouseSelection().getStart()); + }else{ // user is adjusting the selection + /* See selectio adjustment for the right key. * + * The idea is the same, just with inverted left and right */ + if(mouseSelection.getEnd() == keyPress){ + setMouseSelection(mouseSelection.getStart()-KEY_SELECTION_INCREMENT,keyPress); + track.getSoundWave().scan(getMouseSelection().getStart()); + }else{ // getStart == keyPress + setMouseSelection(keyPress,mouseSelection.getEnd()-KEY_SELECTION_INCREMENT); + track.getSoundWave().scan(getMouseSelection().getEnd()); + } + } + + /* jump to beginning of selection, if any */ + }else if(evt.getKeyCode() == KeyEvent.VK_F2 || evt.getKeyCode() == KeyEvent.VK_F3){ + if(mouseSelection.equals(Selection.ZERO_SELECTION)){ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + }else if (evt.getKeyCode() == KeyEvent.VK_F2){ + track.getSoundWave().setPosition(mouseSelection.getStart()); + }else{ + track.getSoundWave().setPosition(mouseSelection.getEnd()); + } + } + } + + @Override + public void keyReleased(KeyEvent evt) { + /* create selection in the model when user releases shift key */ + if(evt.getKeyCode() == KeyEvent.VK_SHIFT && keyPress != KEY_PRESS_NO_SELECTION){ + keyPress = KEY_PRESS_NO_SELECTION; + track.getSoundWave().setSelection(getMouseSelection()); + } + } + + @Override + public void keyTyped(KeyEvent evt) { } +} // class AudioTrackMouseInteraction + + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackParameters.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,281 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JToggleButton; +import javax.swing.ListCellRenderer; +import javax.swing.border.MatteBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Parameter; +import uk.ac.qmul.eecs.depic.daw.Parameter.Type; +import uk.ac.qmul.eecs.depic.daw.ParametersControl; +import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; + +public class AudioTrackParameters extends JPanel { + private static final long serialVersionUID = 1L; + private final static int SLIDER_SCALE = 100; + + private AudioTrack track; + public final JLabel nameLabel; + public final JLabel gainLabel; + public final JToggleButton muteButton; + public final JToggleButton soloButton; + public final JButton fxButton; + public final JComboBox automationComboBox; + public final JSlider gainSlider; + public final JLabel panLabel; + public final JSlider panSlider; + + /** + * Create the panel. + */ + public AudioTrackParameters(AudioTrack track, int index) { + this.track = track; + + this.setPreferredSize(new Dimension(247, 130)); //FIXME const values + this.setMaximumSize(new Dimension(247, 130)); + + + nameLabel = new JLabel("New Track "+index); + nameLabel.setBounds(6, 8, 233, 17); + nameLabel.setBorder(new MatteBorder(0, 0, 1, 0, (Color) new Color(0, 0, 0))); + + soloButton = new JToggleButton("S"); + soloButton.setBounds(205, 84, 36, 20); + soloButton.getAccessibleContext().setAccessibleName("Solo"); + soloButton.setMargin(new Insets(0, 0, 0, 0)); + soloButton.setMinimumSize(new Dimension(20, 20)); + soloButton.setMaximumSize(new Dimension(20, 20)); + soloButton.setPreferredSize(new Dimension(20, 20)); + + muteButton = new JToggleButton("M"); + muteButton.setBounds(166, 84, 38, 20); + muteButton.getAccessibleContext().setAccessibleName("Mute"); + muteButton.setMargin(new Insets(0, 0, 0, 0)); + muteButton.setFont(new Font("Tahoma", Font.PLAIN, 11)); + muteButton.setPreferredSize(new Dimension(20, 20)); + muteButton.setMinimumSize(new Dimension(20, 20)); + muteButton.setMaximumSize(new Dimension(20, 20)); + + fxButton = new JButton("Fx"); + fxButton.setBounds(12, 30, 40, 20); + fxButton.setPreferredSize(new Dimension(20, 20)); + fxButton.setMinimumSize(new Dimension(20, 20)); + fxButton.setMaximumSize(new Dimension(20, 20)); + fxButton.setMargin(new Insets(0, 0, 0, 0)); + fxButton.setFont(new Font("Tahoma", Font.PLAIN, 11)); + + automationComboBox = new JComboBox<>(); + automationComboBox.setBounds(166, 29, 73, 18); + + JLabel lblAutomation = new JLabel("Automation:"); + lblAutomation.setBounds(95, 30, 65, 16); + lblAutomation.setLabelFor(automationComboBox); + + gainLabel = new JLabel(""); + gainLabel.setBounds(6, 106, 87, 16); + setLayout(null); + add(nameLabel); + add(fxButton); + add(gainLabel); + add(lblAutomation); + add(automationComboBox); + add(muteButton); + add(soloButton); + + gainSlider = new JSlider(); + gainSlider.setBounds(103, 103, 136, 21); + add(gainSlider); + + panLabel = new JLabel("Pan: 0.0 "); + panLabel.setBounds(6, 84, 61, 16); + add(panLabel); + + panSlider = new JSlider(); + panSlider.setBounds(68, 84, 78, 21); + add(panSlider); + + initComponentsValues(); + addListeners(); + } + + private void addListeners(){ + gainSlider.addChangeListener(new GainListener()); + panSlider.addChangeListener(new PanListener()); + automationComboBox.addItemListener(new AutomationComboBoxListener()); + + } + + private void initComponentsValues(){ + ParametersControl parametersControl = track.getSoundWave().getParametersControl(); + + /* gain slider */ + Parameter volume = parametersControl.getGainParameter(); + gainSlider.setMinimum((int)(volume.getRange().getStart()*SLIDER_SCALE)); + gainSlider.setMaximum((int)(volume.getRange().getEnd()*SLIDER_SCALE)); + gainSlider.setValue((int)volume.getValue()*SLIDER_SCALE); + + gainLabel.setText(volume.getType().getLabel()+(volume.getInitialValue()+" "+volume.getType().getUnitofMeasurment())); + gainLabel.setLabelFor(gainSlider); + + Parameter pan = parametersControl.getPanParameter(); + panSlider.setMinimum((int)(pan.getRange().getStart()*SLIDER_SCALE)); + panSlider.setMaximum((int)(pan.getRange().getEnd()*SLIDER_SCALE)); + panSlider.setValue((int)pan.getValue()*SLIDER_SCALE); + + + float panValue = panSlider.getValue(); + panLabel.setText(pan.getType().getLabel()+(panValue/SLIDER_SCALE)); + panLabel.setLabelFor(panSlider); + /* automation combo box */ + + class ParameterTypeRenderer extends JLabel implements ListCellRenderer { + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent( + JList list, Type value, int index, + boolean isSelected, boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + setText(" "+value.getLabel()); + + return this; + } + + } + + automationComboBox.setModel(new DefaultComboBoxModel( + new Parameter.Type[] {Parameter.NONE_PARAMETER.getType(), Parameter.GAIN_TYPE, Parameter.PAN_TYPE})); + + automationComboBox.setRenderer(new ParameterTypeRenderer()); + } + + public void setLabel(String label){ + nameLabel.setText(label); + + track.getAccessibleContext().setAccessibleName(label); + } + + /** + * Returns the text of this component label + * + * @see #setLabel(String) + * @return the text of this component label + */ + @Override + public String toString(){ + return nameLabel.getText(); + } + + private class GainListener implements ChangeListener { + @Override + public void stateChanged(ChangeEvent evt) { + JSlider gain = (JSlider)evt.getSource(); + + float newValue = (float)gain.getValue()/SLIDER_SCALE; + Parameter gainParam = track.getSoundWave().getParametersControl().getGainParameter(); + gainLabel.setText(gainParam.getType().getLabel()+(newValue)+" "+gainParam.getType().getUnitofMeasurment()); + gainParam.setValue(newValue); + } + } + + private class PanListener implements ChangeListener { + @Override + public void stateChanged(ChangeEvent evt) { + JSlider pan = (JSlider)evt.getSource(); + + float newValue = (float)pan.getValue()/SLIDER_SCALE; + Parameter panParam = track.getSoundWave().getParametersControl().getPanParameter(); + panParam.setValue(newValue); + panLabel.setText(panParam.getType().getLabel()+(newValue)); + } + } + + private class AutomationComboBoxListener implements ItemListener { + @Override + public void itemStateChanged(ItemEvent evt) { + if(evt.getStateChange() == ItemEvent.SELECTED){ + Parameter.Type type = (Parameter.Type)evt.getItem(); + track.getSoundWave().getParametersControl().setCurrentAutomation(type); + + if(Parameter.GAIN_TYPE.equals(type)){ + /* get the now current autiomation */ + Automation a = track.getSoundWave().getParametersControl().getCurrentAutomation(); + a.addSequenceListener(new SequenceListener(){ + + @Override + public void sequenceUpdated(T t) { + if(t.getSource().getValuesNum() != 0){ + gainSlider.setEnabled(false); + }else { + gainSlider.setEnabled(true); + float newValue = t.getSource().getBegin() * SLIDER_SCALE; + gainSlider.setValue((int)newValue); + } + } + + }); + }else if(Parameter.PAN_TYPE.equals(type)) { + /* get the now current autiomation */ + Automation a = track.getSoundWave().getParametersControl().getCurrentAutomation(); + a.addSequenceListener(new SequenceListener(){ + + @Override + public void sequenceUpdated(T t) { + if(t.getSource().getValuesNum() != 0){ + panSlider.setEnabled(false); + }else { + panSlider.setEnabled(true); + float newValue = (float)t.getSource().getBegin() * SLIDER_SCALE; + panSlider.setValue((int) newValue); + } + } + + }); + } + } + } + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackSonification.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/AudioTrackSonification.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,114 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; + +class AudioTrackSonification implements PropertyChangeListener, ChangeListener { + private Sonification sonification = Daw.getSoundEngineFactory().getSharedSonification(); + private AudioTrack track; + + AudioTrackSonification(AudioTrack track){ + this.track = track; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if("cursorPos".equals(evt.getPropertyName())){ + AudioTrack track = (AudioTrack)evt.getSource(); + SoundWave soundWave = track.getSoundWave(); + int newPos = (Integer)evt.getNewValue(); + + if(soundWave.hasSequence()){ + /* check if the cursor stumbled upon or left an automation point */ + for(SequencePoint p : track.getAutomationGraph().getSequencePoints()){ + if(p.isXCentredAt(newPos)){ + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(p.getSequenceValue()); //FIXME remove comments + } + } + + if(track.showAutomationSound){ + sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurveAt( + soundWave.getSequence(), + soundWave.getMillisecPerChunk() * newPos, + Float.POSITIVE_INFINITY); + } + } + + if(soundWave.getDbWave().hasSequence()){ + /* check if the cursor stumbled upon or left an automation point */ +// for(SequencePoint p : track.getPeakLevelGraph().getSequencePoints()){ +// if(p.isXCentredAt(newPos)){ +// sonification.getSequenceMapping(GuiSoundType.PEAK_LEVEL).renderValue(p.getSequenceValue()); +// } +// } + + if(track.showPeakLevelSound){ + sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurveAt( + soundWave.getDbWave().getSequence(), + soundWave.getMillisecPerChunk() * newPos, + Float.POSITIVE_INFINITY); + } + } + + if(track.trackInteraction.getMouseSelection().isOpen()){ + return; + } + + /* play selection sound if within selection, stop it if without */ + if(track.trackInteraction.getMouseSelection().contains(newPos)){ + sonification.withinSelection(1.0f, 0.0f); + }else { + sonification.outsideSelection(); + } + + if(track.trackInteraction.getMouseSelection().getStart() == newPos){ + sonification.play(SoundType.SHIFT_START); + }else if(track.trackInteraction.getMouseSelection().getEnd() == newPos){ + sonification.play(SoundType.SHIFT_END); + } + }else if("mouseDragSelection".equals(evt.getPropertyName())){ + if( ((Selection)evt.getNewValue()).isOpen() ){ + sonification.outsideSelection(); + } + } + } + + @Override + public void stateChanged(ChangeEvent evt) { + if(track.showAutomationSound){ + SoundWave soundWave = track.getSoundWave(); + sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurveAt( + soundWave.getSequence(), + soundWave.getMillisecPerChunk() * soundWave.getCurrentChunkPosition(), + Float.POSITIVE_INFINITY); + } + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/AutomationGraphActions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/AutomationGraphActions.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,478 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.geom.Point2D; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.prefs.Preferences; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.AutomationValue; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSonification; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +/** + * + * Actions for the manipulation of the automation graph. + * + * + */ +class AutomationGraphActions implements Iterable { + private static AutomationGraphActions singleton; + private SequencePoint heldPoint; + + public final AddAutomationValueAction addAutomationValueAction; + public final RemoveAutomationValueAction removeAutomationValueAction; + public final MoveAutomationValueAction moveAutomationValueUpAction; + public final MoveAutomationValueAction moveAutomationValueLeftAction; + public final MoveAutomationValueAction moveAutomationValueDownAction; + public final MoveAutomationValueAction moveAutomationValueRightAction; + public final ResetAutomationValuesAction resetAutomationAction; + public final ListenWholeAutomationAction listenWholeAutomationAction; + public final StopListenWholeAutomationAction stopListenWholeAutomationAction; + public final SwitchListenAutomationAction switchListenAutomationAction; + public final SwitchListenPeakLevelAction switchListenPeakLevelAction; + public final ListenWholePeakLevelAction listenWholePeakLevelAction; + + private Sonification sonification; + + static AutomationGraphActions getInstance(){ + if(singleton == null) + singleton = new AutomationGraphActions(); + return singleton; + } + + private AutomationGraphActions (){ + addAutomationValueAction = new AddAutomationValueAction(); + removeAutomationValueAction = new RemoveAutomationValueAction(); + moveAutomationValueUpAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl UP"),0.0f,0.05f); + moveAutomationValueDownAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl DOWN"),0.0f,-0.05f); + moveAutomationValueLeftAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl LEFT"),-50.0f,0.0f); + moveAutomationValueRightAction = new MoveAutomationValueAction(KeyStroke.getKeyStroke("ctrl RIGHT"),50.0f,0.0f); + resetAutomationAction = new ResetAutomationValuesAction(); + listenWholeAutomationAction = new ListenWholeAutomationAction(); + stopListenWholeAutomationAction = new StopListenWholeAutomationAction(); + switchListenAutomationAction = new SwitchListenAutomationAction(); + switchListenPeakLevelAction = new SwitchListenPeakLevelAction(); + listenWholePeakLevelAction = new ListenWholePeakLevelAction(); + + sonification = Daw.getSoundEngineFactory().getSharedSonification(); + } + + /** + * Create a new Actions than plays the automation value sound when it is at zero point, + * that is in the middle of the automation range + * + * @param val + * @return + */ + private static Action createZeroValAction(final Sequence.Value val){ + return new AbstractAction() { + private static final long serialVersionUID = 1L; + private boolean doneOnce = false; + @Override + public void actionPerformed(ActionEvent evt){ + /* only play feedback once. This action remains until a new action + * for the same keystroke is installed. A new action is installed + * when the user presses ctrl + up/down/left/right arrow (see MoveAutomation + * Feedback as to be given only once by each zeroAction, otherwise + * the sonification will be played each time the user releases a ctrl + + * arrow action, regardless of whether they actually moved an automation or nor */ + if(doneOnce){ + return; + }else{ + doneOnce = true; + } + Daw.getSoundEngineFactory().getSharedSonification().getSequenceMapping(SoundType.AUTOMATION).renderValue( + new Sequence.Value() { + @Override + public int index() { + return val.index(); + } + + @Override + public float getValue() { + Range range = val.getSequence().getRange(); + return range.getStart() + range.lenght()/2; + } + + @Override + public float getTimePosition() { + return val.getTimePosition(); + } + + @Override + public Sequence getSequence() { + return val.getSequence(); + } + }); + } + }; + } + + @Override + public Iterator iterator(){ + return new Iterator(){ + private int index = 0; + private Action [] actions = new Action [] { + addAutomationValueAction, + removeAutomationValueAction, + moveAutomationValueUpAction, + moveAutomationValueLeftAction, + moveAutomationValueDownAction, + moveAutomationValueRightAction, + resetAutomationAction, + listenWholeAutomationAction, + stopListenWholeAutomationAction, + switchListenAutomationAction, + switchListenPeakLevelAction, + listenWholePeakLevelAction + }; + + @Override + public boolean hasNext() { + return index < actions.length; + } + + @Override + public Action next() { + if(index == actions.length) + throw new NoSuchElementException(); + return actions[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove not supported"); + } + + }; + } + + static abstract class AutomationGraphAction extends AbstractAction { + private static final long serialVersionUID = 1L; + + AutomationGraphAction(KeyStroke keyStroke){ + putValue(Action.ACCELERATOR_KEY,keyStroke); + } + + @Override + public void actionPerformed(ActionEvent evt){ + handleActionPerformed((AudioTrack)evt.getSource()); + } + + public abstract void handleActionPerformed(AudioTrack track); + + } + + class AddAutomationValueAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + public AddAutomationValueAction() { + super(KeyStroke.getKeyStroke("ctrl INSERT")); + } + + @Override + public void handleActionPerformed(AudioTrack track) { + if(track == null || !track.getSoundWave().hasSequence()) { + return; + } + + SoundWave wave = track.getSoundWave(); + int cursorPos = wave.getCurrentChunkPosition(); + + Point2D.Float p = AudioTrack.getAutomationCoord(track, cursorPos, track.getHeight()/2); + + /* if user is trying to create an automation outside the bounds of the automation, notify the error */ + if(p == null){ + sonification.play(SoundType.ERROR); + return; + } + + /* if a new point is created when moving another point the held point will still be the old one * + * by setting it to null the hel point will become the new one as soon as the user will try to move it */ + heldPoint = null; + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue( + wave.getParametersControl().getCurrentAutomation().add(p.x, p.y)); + } + } // class AddAutomationAction + + class MoveAutomationValueAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + private float deltaX; + private float deltaY; + + public MoveAutomationValueAction(KeyStroke keyStroke, float deltaX, float deltaY){ + super(keyStroke); + this.deltaX = deltaX; + this.deltaY = deltaY; + heldPoint = null; + } + + @Override + public void handleActionPerformed(final AudioTrack track) { + if(track == null || !track.getSoundWave().hasSequence()){ + heldPoint = null; + return; + } + + int cursorPos = track.getSoundWave().getCurrentChunkPosition(); + + /* if ctrl is held (heldPoint != null) it means the user is still moving around a * + * point that they grabbed previously. So the check that the point be under the * + * cursor must not be done, and the point can be moved and played anyway */ + if(heldPoint != null){ + changeAutomation((AutomationValue)heldPoint.getSequenceValue(),track); + return; + } + + /* find the automaton value under the cursor position if any */ + for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){ + if( itr.isXCentredAt(cursorPos) ){ + /* p is now the heldPoint, until the user releaser the ctrl key this point will be referenced * + * in next move action even if it's not under the the cursor is not under the cursor anymore */ + heldPoint = itr; + /* set up an action that will stop scrubbing free the held point when the user releases the ctrl key */ + track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "heldPoint"); + track.getActionMap().put("heldPoint", new AbstractAction(){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent evt) { + heldPoint = null; + /* stop scrubbing */ + track.getSoundWave().scan(SoundWave.STOP_SCANNING); + /* de-register itself from the track registered actions */ + track.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0 , true), "none"); + track.getActionMap().remove("heldPoint"); + } + }); + + changeAutomation((AutomationValue)itr.getSequenceValue(),track); + break; + } + } + } + + protected void changeAutomation(AutomationValue val, AudioTrack track){ + val.setLocation(val.getTimePosition()+deltaX, val.getValue()+deltaY); + + /* install the action that plays the zero value when the key is released */ + KeyStroke thisActionKeyStroke = (KeyStroke)getValue(Action.ACCELERATOR_KEY); + KeyStroke zeroValKeyStroke = KeyStroke.getKeyStroke(thisActionKeyStroke.getKeyCode(),thisActionKeyStroke.getModifiers(),true); + + if(Preferences.userNodeForPackage(BeadsSonification.class).getBoolean("render_val.ref", false)){ + track.getInputMap(JComponent.WHEN_FOCUSED).put(zeroValKeyStroke, "zeroVal"); + track.getActionMap().put("zeroVal", createZeroValAction(val)); + } + + /* besides moving the automation, scrub the cursor along */ + int scanTo = (int)(val.getTimePosition() / track.getSoundWave().getMillisecPerChunk()); + track.getSoundWave().scan(scanTo); + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(heldPoint.getSequenceValue()); + } + } // class MoveAutomationValueAction + + class RemoveAutomationValueAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + public RemoveAutomationValueAction() { + super(KeyStroke.getKeyStroke("ctrl DELETE")); + } + + @Override + public void handleActionPerformed(AudioTrack track) { + if(track == null || !track.getSoundWave().hasSequence()){ + return; + } + + int cursorPos = track.getSoundWave().getCurrentChunkPosition(); + + /* find the automaton value */ + SequencePoint p = null; + for(SequencePoint itr : track.getAutomationGraph().getSequencePoints()){ + float dist = Math.abs(cursorPos - (int)itr.getCenterX() ); + + if( dist < SequencePoint.SIZE ){ + if(p == null || dist < Math.abs((int)p.getCenterX() - (int)itr.getCenterX())){ + p = itr; + /* don't break as it keeps looking for other points, which might be closer to the cursor */ + } + } + } + + /* if found, then remove it and play it */ + if(p != null){ + ((Automation)p.getSequenceValue().getSequence()).remove((AutomationValue)p.getSequenceValue()); + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(p.getSequenceValue()); + } + } + + } // class RemoveAutomationAction + + class ResetAutomationValuesAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + public ResetAutomationValuesAction() { + super(KeyStroke.getKeyStroke("ctrl R ")); + } + + @Override + public void handleActionPerformed(AudioTrack track){ + if(track == null || !track.getSoundWave().hasSequence()){ + return; + } + + int confirmation = JOptionPane.showConfirmDialog(track, + "Are you sure you want to reset the automation ?", // FIXME bundle + "Confirmation Dialog", + JOptionPane.YES_NO_OPTION + ); + + if(confirmation == JOptionPane.YES_OPTION){ + track.getSoundWave().getParametersControl().getCurrentAutomation().reset(); + } + } + } // class ClearAutomationValuesAction + + class ListenWholeAutomationAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + public ListenWholeAutomationAction() { + super(KeyStroke.getKeyStroke("ctrl shift L")); + } + + @Override + public void handleActionPerformed(AudioTrack track){ + if(track == null || (!track.getSoundWave().hasSequence()) ){ + sonification.play(SoundType.ERROR); + return; + } + + track.getSoundWave().getTransportControl().rew(); + sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve( + track.getSoundWave().getParametersControl().getCurrentAutomation(),0.0f); + track.getSoundWave().getTransportControl().play(); + } + } // class SonifyAutomationAction + + class StopListenWholeAutomationAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + public StopListenWholeAutomationAction(){ + super(KeyStroke.getKeyStroke("ctrl K")); + } + + @Override + public void handleActionPerformed(AudioTrack track){ + sonification.getSequenceMapping(SoundType.AUTOMATION).renderCurve( + null,-100.0f); + sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve( + null,-100.0f); + track.soundWave.getTransportControl().stop(); + } + } + + class SwitchListenAutomationAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + private boolean isOn; + + public SwitchListenAutomationAction(){ + super(KeyStroke.getKeyStroke("ctrl L")); + isOn = false; + } + + @Override + public void handleActionPerformed(AudioTrack track){ + if(track.getSoundWave().hasSequence()){ + isOn = !isOn; + + track.showAutomationSound(isOn); + sonification.play(SoundType.OK); + }else{ + sonification.play(SoundType.ERROR); + } + } + + public boolean isSwitchOn(){ + return isOn; + } + } // class SwitchListenAutomation + + class SwitchListenPeakLevelAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + private boolean isOn; + + SwitchListenPeakLevelAction(){ + super(KeyStroke.getKeyStroke("ctrl P")); + isOn = false; + } + + @Override + public void handleActionPerformed(AudioTrack track){ + if( track == null || (!track.getSoundWave().getDbWave().hasSequence())){ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + return; + } + isOn = !isOn; + + track.showPeakLevelSound(isOn); + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.OK); + } + + public boolean isSwitchOn(){ + return isOn; + } + } // SwitchListenPeakLevelAction + + class ListenWholePeakLevelAction extends AutomationGraphAction { + private static final long serialVersionUID = 1L; + + ListenWholePeakLevelAction(){ + super(KeyStroke.getKeyStroke("ctrl shift P")); + } + + @Override + public void handleActionPerformed(AudioTrack track){ + if(track == null || (!track.getSoundWave().getDbWave().hasSequence()) ){ + sonification.play(SoundType.ERROR); + return; + } + + track.getSoundWave().getTransportControl().rew(); + sonification.getSequenceMapping(SoundType.PEAK_LEVEL).renderCurve( + track.getSoundWave().getDbWave().getSequence(),0.0f); + track.getSoundWave().getTransportControl().play(); + } + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/MainFrame.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/MainFrame.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,276 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.Action; +import javax.swing.Box; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; +import javax.swing.UnsupportedLookAndFeelException; + +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.gui.actions.Actions; + + +public class MainFrame extends JFrame { + private static final long serialVersionUID = 1L; + private Actions actions; + private ArrangeWindow arrangeWindow; + private JToolBar transportBar; + + public MainFrame(){ + super("Cross-Modal DAW Prototype"); + /* makes the overwritten getContentPane() correct */ + setContentPane(new JPanel()); + + /* try and set system look and feel */ + + try { + for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (Exception e) { + // If Nimbus is not available, you can set the GUI to another look and feel. + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + SwingUtilities.updateComponentTreeUI(this); + } catch (ClassNotFoundException|InstantiationException|IllegalAccessException|UnsupportedLookAndFeelException e1) { + // do nothing and stick with java look&feel + } + } + + setDefaultCloseOperation(EXIT_ON_CLOSE); // FIXME make own close operation + + JPanel contentPane = (JPanel)getContentPane(); + contentPane.setLayout(new BorderLayout()); + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + + int screenWidth = (int)screenSize.getWidth(); + int screenHeight = (int)screenSize.getHeight(); + + setLocation(screenWidth / 16, screenHeight / 16); + contentPane.setPreferredSize(new Dimension( + screenWidth * 5 / 8, screenHeight * 5 / 8)); + + arrangeWindow = new ArrangeWindow(); + transportBar = new JToolBar(){ + private static final long serialVersionUID = 1L; + + @Override + protected JButton createActionComponent(Action a){ + /* add the accessible name to the button using the action name */ + JButton b = super.createActionComponent(a); + + + b.getAccessibleContext().setAccessibleName((String)a.getValue(Action.NAME)); + return b; + } + }; + contentPane.add(transportBar,BorderLayout.NORTH); + /* arrange window scroll contains the tracks and the Track parameters. The * + * scroll bar appears when the number of tracks exceeds the screen size */ + JScrollPane arrangeWindowScroll = new JScrollPane(arrangeWindow); + arrangeWindowScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + arrangeWindowScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + contentPane.add(arrangeWindowScroll,BorderLayout.CENTER); + + + /* remove actions for scrolling left and right: the content of this scroll pane only * + * grows in height when track are added. Moreover this actions conflict with the * + * scrubbing through the audio track with right and left arrow keys */ + InputMap im = arrangeWindowScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0),"none"); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0),"none"); + + + getArrangeWindow().addTrack(new AudioTrack(Daw.getSoundEngineFactory().createSoundWave())); + + actions = new Actions(this); + initMenu(actions); + initTransportBar(actions); + initScrubActions(); + } + + protected void initMenu(Actions actions){ + JMenuBar menuBar = new JMenuBar(); + this.setJMenuBar(menuBar); + + /* -- File Menu -- */ + JMenu fileMenu = new JMenu(actions.getVoidAction("file")); + menuBar.add(fileMenu); + + fileMenu.add( new JMenuItem(actions.open())); + fileMenu.add(new JMenuItem(actions.close())); + fileMenu.addSeparator(); + + fileMenu.add(new JMenuItem(actions.properties())); + + fileMenu.addSeparator(); + fileMenu.add(new JMenuItem(actions.exit())); + + + /* -- Edit Menu -- */ + JMenu editMenu = new JMenu(actions.getVoidAction("edit")); + menuBar.add(editMenu); + + editMenu.add(new JMenuItem(actions.cut())); + editMenu.add(new JMenuItem(actions.copy())); + editMenu.add(new JMenuItem(actions.paste())); + editMenu.add(new JMenuItem(actions.insert())); + + /* -- View Menu -- */ + JMenu viewMenu = new JMenu(actions.getVoidAction("view")); + menuBar.add(viewMenu); + + viewMenu.add(new JMenuItem(actions.zoomIn())); + viewMenu.add(new JMenuItem(actions.zoomOut())); + viewMenu.add(new JMenuItem(actions.showDb())); + viewMenu.add(new JMenuItem(actions.generatePeaks())); + viewMenu.add(new JMenuItem(actions.findNextPoint())); + viewMenu.add(new JMenuItem(actions.findPreviousPoint())); + + /* -- Track Menu --*/ + JMenu trackMenu = new JMenu(actions.getVoidAction("track")); + menuBar.add(trackMenu); + + trackMenu.add(new JMenuItem(actions.addTrack())); + trackMenu.add(new JMenuItem(actions.removeSelection())); + trackMenu.add(new JMenuItem(actions.switchAutomation())); + + /* -- Haptics Menu -- */ + JMenu hapticsMenu = new JMenu(actions.getVoidAction("haptics")); + menuBar.add(hapticsMenu); + + hapticsMenu.add(new JMenuItem(actions.startPhantom())); + hapticsMenu.add(new JMenuItem(actions.showHaptics())); + hapticsMenu.add(new JMenuItem(actions.toggleScrub())); + } + + protected void initTransportBar(Actions actions){ + /* removes space as a trigger for JButtons when they are focused * + * this way space can be used to play/stop the current track */ + + /* action for play/stop the current track */ + JComponent contentPane = (JComponent)getContentPane(); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_DOWN_MASK), "play"); + contentPane.getActionMap().put("play", actions.play()); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, InputEvent.CTRL_DOWN_MASK), "rew"); + contentPane.getActionMap().put("rew", actions.rew()); + + + /* remove left and right arrow key from transport bar in order not to conflict with the cursor scrubbing */ + transportBar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none"); + transportBar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none"); + transportBar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "none"); + transportBar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "none"); + + /* set up transport bar buttons */ + JToggleButton playButton = new JToggleButton(actions.play()); + playButton.setHideActionText(true); + playButton.getAccessibleContext().setAccessibleName((String)playButton.getAction().getValue(Action.NAME)); + /* add(Action a) creates a button with the specified action. it is overwritten * + * in order to add a name to the accessible context - see constructor */ + transportBar.add(playButton); + transportBar.add(actions.stop()); + transportBar.add(actions.rew()); + transportBar.add(actions.forward()); + + transportBar.add(Box.createRigidArea(new Dimension(20,0))); + JToggleButton loopButton = new JToggleButton(actions.loop()); + loopButton.setHideActionText(true); + loopButton.getAccessibleContext().setAccessibleName((String)loopButton.getAction().getValue(Action.NAME)); + + transportBar.add(loopButton); + } + + protected void initScrubActions(){ + JComponent contentPane = (JComponent)getContentPane(); + + /* install the keystroke for left and right keys which will be mapperd respectively to scrub left and right */ + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0), "scrub forw"); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0), "scrub backw"); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F4,0), "scrub play"); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F4,0,true), "scrub release"); + + + /* install the keystrokes for stopping the scrub all the modifiers are installed as well to prevent * + * a sequence of actions such as : [press right, press ctrl, release right, release ctrl] to leave the * + * scrubbing on. This would happen because press right will have no modifiers, whereas release right * + * would have ctrl modifier and it would not match a simple KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0,true) */ + for(int modifier : new int []{ + 0, // no modifiers + InputEvent.SHIFT_DOWN_MASK, + InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK, + InputEvent.META_DOWN_MASK, + InputEvent.ALT_DOWN_MASK, + InputEvent.ALT_GRAPH_DOWN_MASK, + InputEvent.CTRL_DOWN_MASK}){ + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,modifier,true), "scrub release"); + contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,modifier,true), "scrub release"); + } + + /* install the actions: scrub left - scrub right - stop scrub release - stop scrub right */ + contentPane.getActionMap().put("scrub backw",actions.scrubAction(Actions.ScrubType.SCRUB_BACKWARDS)); + contentPane.getActionMap().put("scrub forw",actions.scrubAction(Actions.ScrubType.SCRUB_FORWARDS)); + contentPane.getActionMap().put("scrub release" ,actions.scrubAction(Actions.ScrubType.STOP_SCRUB)); + contentPane.getActionMap().put("scrub play" ,actions.scrubAction(Actions.ScrubType.PLAY_SCRUB)); + + } + + @Override + public JPanel getContentPane(){ + return (JPanel)super.getContentPane(); + } + + public ArrangeWindow getArrangeWindow(){ + return arrangeWindow; + } + + public JToolBar getTransportBar(){ + return transportBar; + } + + public Actions getActions(){ + return actions; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/PreferencesDialog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/PreferencesDialog.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,96 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; + + +public class PreferencesDialog extends JDialog { + private static final long serialVersionUID = 1L; + private final JTabbedPane content = new JTabbedPane(); + private ArrayList panels; + + private ActionListener actionListener = new ActionListener(){ + @Override + public void actionPerformed(ActionEvent evt) { + if("OK".equals(evt.getActionCommand())){ + savePrefs(); + dispose(); + }else if("Cancel".equals(evt.getActionCommand())){ + dispose(); + } + } + }; + + /** + * Create the dialog. + */ + public PreferencesDialog(Frame owner) { + super(owner,"Preferences",true); + panels = new ArrayList<>(); + + setBounds(100, 100, 387, 269); + getContentPane().setLayout(new BorderLayout()); + //content.setBorder(new EmptyBorder(5, 5, 5, 5)); + getContentPane().add(content, BorderLayout.CENTER); + + { + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); + buttonPane.setBorder(BorderFactory.createMatteBorder(1,0,0,0,Color.GRAY)); + getContentPane().add(buttonPane, BorderLayout.SOUTH); + { + JButton okButton = new JButton("OK"); + okButton.setActionCommand("OK"); + okButton.addActionListener(actionListener); + buttonPane.add(okButton); + getRootPane().setDefaultButton(okButton); + } + { + JButton cancelButton = new JButton("Cancel"); + cancelButton.setActionCommand("Cancel"); + cancelButton.addActionListener(actionListener); + buttonPane.add(cancelButton); + } + } + } + + private void savePrefs(){ + for(PreferencesPanel p : panels) + p.savePrefs(); + } + + public void addPanel(PreferencesPanel p){ + panels.add(p); + content.add(p,p.getTitle()); + } +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/PreferencesPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/PreferencesPanel.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,32 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import javax.swing.JPanel; + +public abstract class PreferencesPanel extends JPanel { + private static final long serialVersionUID = 1L; + + public abstract void savePrefs(); + + public abstract String getTitle(); + + public abstract String [] getPrefsList(); + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/Rule.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/Rule.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,313 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.event.MouseEvent; +import java.awt.geom.Line2D; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.DecimalFormat; + +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleState; +import javax.accessibility.AccessibleStateSet; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.event.MouseInputAdapter; + +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; + + +public class Rule extends JComponent implements PropertyChangeListener , Accessible { + private static final long serialVersionUID = 1L; + public static final int DEFAULT_TICK_INTERVAL = 50; + public static final int TICK_HEIGHT = 10; + public static final int HEIGHT = 40; + public static final int CURSOR_HEIGHT = 10; + public static final int CURSOR_WIDTH = 20; + public static final Color LOOP_MARKERS_COLOR = new Color(10,100,30); + public static final Color CURSOR_COLOR = AudioTrack.WAVE_COLOR; + private static final DecimalFormat TICK_FORMAT = new DecimalFormat("###.#"); + + private AudioTrack track; + private int scaleFactor; + private Polygon cursor; + private Polygon loopStartMark; + private Polygon loopEndMark; + + + public Rule(){ + Dimension size = new Dimension(AudioTrack.MAX_TRACK_WIDTH,HEIGHT); + setMaximumSize(size); + + setPreferredSize(new Dimension(0,HEIGHT)); + setBorder(BorderFactory.createMatteBorder(0,0,1,0,Color.black)); + + /* a triangle initially centred on 0*/ + cursor = new Polygon( + new int[]{0,-CURSOR_WIDTH/2,CURSOR_WIDTH/2}, + new int[] {HEIGHT,HEIGHT-CURSOR_HEIGHT,HEIGHT-CURSOR_HEIGHT}, + 3); + loopStartMark = new Polygon(); + loopEndMark = new Polygon(); + + MouseInteraction mouseInteraction = new MouseInteraction(); + addMouseListener(mouseInteraction); + addMouseMotionListener(mouseInteraction); + } + + @Override + public void paintComponent(Graphics g){ + Graphics2D g2 = (Graphics2D)g; + Color oldColor = g2.getColor(); + + g2.setColor(getBackground()); + g2.fillRect(0, 0, getWidth(), getHeight()); + g2.setColor(getForeground()); + + Line2D line = new Line2D.Float(); + int height = getHeight(); + /* draw rule's ticks */ + + if(track.getSecondsPerPixel() == 0){ + for(int i=DEFAULT_TICK_INTERVAL; i< getSize().width; i += DEFAULT_TICK_INTERVAL ){ + line.setLine(i, height, i, height-TICK_HEIGHT); + g2.draw(line); + } + }else{ + int tickInterval = (int) ( (1/track.getSecondsPerPixel() / 2 )); + for(int i=tickInterval, j=1; i< getSize().width; i += tickInterval, j++ ){ + if(j % 2 == 1){ + line.setLine(i, height, i, height-TICK_HEIGHT/3); + g2.draw(line); + }else{ + line.setLine(i, height, i, height-TICK_HEIGHT); + g2.draw(line); + String tick = TICK_FORMAT.format(j/2);//track.getSecondsPerPixel()*i); + int stringLen = (int)g2.getFontMetrics().getStringBounds(tick, g2).getWidth(); + g2.drawString(tick, i-(stringLen/2), height-TICK_HEIGHT-3); + } + } + } + + + /* draw the cursor in WAVE_COLOR */ + g2.setColor(CURSOR_COLOR); + g2.fill(cursor); + + /* draw the loop markers, if any */ + g2.setColor(LOOP_MARKERS_COLOR); + g2.fill(loopStartMark); + g2.fill(loopEndMark); + if(loopEndMark.npoints != 0){ + g2.drawLine(loopStartMark.xpoints[0], + HEIGHT - (loopStartMark.getBounds().height/2), + loopEndMark.xpoints[0]-1,// with -1 it's drawn it better + HEIGHT - (loopEndMark.getBounds().height/2) + ); + } + + g2.setColor(oldColor); + } + + public void setAudioTrack(AudioTrack t){ + /* removes itself as a listener to the previous track, if any */ + if(track != null){ + track.removePropertyChangeListener(this); + } + /* adds itself as a listener to the new track */ + if(t != null){ + t.addPropertyChangeListener(this); + scaleFactor = t.getScaleFactor(); + }else { + scaleFactor = 1; + } + track = t; + } + + public int getCursorPos() { + Rectangle bounds = cursor.getBounds(); + return bounds.x + bounds.width/2; + } + + public void setCursorPos(int position) { + Rectangle bounds = cursor.getBounds(); + int currentCenterX = bounds.x + bounds.width/2; + cursor.translate(position-currentCenterX, 0); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + switch(evt.getPropertyName()){ + case "cursorPos" : { + setCursorPos((Integer)evt.getNewValue()); + repaint(); + break; + } + case "scaleFactor" : { + scaleFactor = (Integer)evt.getNewValue(); + repaint(); + break; + } + case "mouseDragSelection" : { + Selection selection = (Selection)evt.getNewValue(); + if(selection.isOpen() ){ // no loop + loopStartMark.reset(); + loopEndMark.reset(); + }else{ + loopStartMark.reset(); + /* first point is the actual marker */ + loopStartMark.addPoint(selection.getStart(), HEIGHT-(CURSOR_HEIGHT)); + loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2), HEIGHT-(CURSOR_HEIGHT*2)); + loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2),HEIGHT); + + loopEndMark.reset(); + /* first point is the actual marker */ + loopEndMark.addPoint(selection.getEnd(),HEIGHT-(CURSOR_HEIGHT)); + loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT); + loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT-(CURSOR_HEIGHT*2)); + } + repaint(); + break; + } + case "preferredSize" : { + /* resize the width acording to the new value of the listened component */ + Dimension d = (Dimension)evt.getNewValue(); + setPreferredSize(new Dimension(d.width,HEIGHT)); + break; + } + } + } + + @Override + public AccessibleContext getAccessibleContext(){ + if(accessibleContext == null){ + accessibleContext = new AccessibleRule(); + } + return accessibleContext; + } + + private class MouseInteraction extends MouseInputAdapter { + private Polygon clickedMark; // either loopStartMark or loopEndMark + int clickOffset; + + @Override + public void mousePressed(MouseEvent evt){ + if(loopStartMark.contains(evt.getX(),evt.getY())){ // click on the loop start mark + clickedMark = loopStartMark; + clickOffset = evt.getX() - loopStartMark.xpoints[0]; + }else if(loopEndMark.contains(evt.getX(),evt.getY())){ // click on the loop end mark + clickedMark = loopEndMark; + clickOffset = evt.getX() - loopEndMark.xpoints[0]; + }else if(cursor.contains(evt.getX(),evt.getY())){ + clickedMark = cursor; + clickOffset = evt.getX() - cursor.xpoints[0]; + } + } + + @Override + public void mouseDragged(MouseEvent evt){ + /* check again that clickedMark is != null and not reset * + * things might change in the middle of the mouse interaction * + * if the property change listener is called from another source */ + if(clickedMark == null) + return; + + if(clickedMark != cursor && clickedMark.npoints > 0 && track != null){ // loop marks + int mouseSelectionX = 0; + int mouseSelectionY = 0; + if(clickedMark == loopStartMark){ // dragging the start mark + if(evt.getX() > loopEndMark.xpoints[0] - 1 || (evt.getX()-clickOffset < 0) ) + return; // can't drag start mark too right, past the end mark or too left past the screen + mouseSelectionX = evt.getX()-clickOffset; + mouseSelectionY = loopEndMark.xpoints[0]; + }else{ // dragging the end mark + if(evt.getX() < loopStartMark.xpoints[0] + 1) + return; // can't drag end mark too left, past the end mark + mouseSelectionX = loopStartMark.xpoints[0]; + mouseSelectionY = evt.getX()-clickOffset; + } + scrollRectToVisible(new Rectangle(evt.getX(), evt.getY(), 1, 1)); + track.trackInteraction.setMouseSelection(mouseSelectionX, mouseSelectionY); + } else if (clickedMark == cursor){ // cursor mark + track.getSoundWave().scan(evt.getX()); + } + } + + @Override + public void mouseReleased(MouseEvent evt){ + if(clickedMark != null){ + if(clickedMark == cursor){ + track.getSoundWave().scan(SoundWave.STOP_SCANNING); // stop scrubbing + }else + track.getSoundWave().setSelection(track.trackInteraction.getMouseSelection()); + } + clickedMark = null; + clickOffset = 0; + } + + @Override + public void mouseMoved(MouseEvent evt){ + if(cursor.contains(evt.getPoint())){ + setCursor(new Cursor(Cursor.E_RESIZE_CURSOR)); + }else if(loopStartMark.contains(evt.getPoint())){ + setCursor(new Cursor(Cursor.E_RESIZE_CURSOR)); + }else if(loopEndMark.contains(evt.getPoint())){ + setCursor(new Cursor(Cursor.W_RESIZE_CURSOR)); + }else{ + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + } + } + + protected class AccessibleRule extends AccessibleJComponent { + private static final long serialVersionUID = 1L; + + protected AccessibleRule(){ + this.setAccessibleName("Time Bar"); + this.setAccessibleDescription( "Measures he time of tracks"); + } + + @Override + public AccessibleRole getAccessibleRole(){ + return AccessibleRole.RULER; + } + + @Override + public AccessibleStateSet getAccessibleStateSet(){ + AccessibleStateSet states = super.getAccessibleStateSet(); + + states.add(AccessibleState.HORIZONTAL); + states.add(AccessibleState.VISIBLE); + + return states; + } + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/SequenceGraph.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/SequenceGraph.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,262 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +public class SequenceGraph implements SequenceMapping, SequenceListener { + private static final float SEQUENCE_LINE_WIDTH = 4.0f; + private static final int SEQUENCE_POINTS_CAPACITY = 30; + + private Sequence sequence; + private Color color; + private List sequencePoints; + private List sequenceLines; + private Dimension size; + private Collection listeners; + private float millisecPerPixel; + + public SequenceGraph(Color color, Dimension size) { + sequencePoints = new ArrayList<>(SEQUENCE_POINTS_CAPACITY); + sequenceLines = new ArrayList<>(SEQUENCE_POINTS_CAPACITY-1); + listeners = new ArrayList<>(); + this.color = color; + this.size = size; + } + + public Dimension getSize(){ + return size; + } + + public void setSize(Dimension size){ + this.size = size; + } + + public void setColor(Color c){ + this.color = c; + } + + public float getMillisecondsPerPixel(){ + return millisecPerPixel; + } + + public void setMillisecPerPixel(float millisecPerPixel){ + this.millisecPerPixel = millisecPerPixel; + } + + public Color getColor(){ + return color; + } + + public List getSequencePoints(){ + return sequencePoints; + } + + public List getSequenceLines(){ + return sequenceLines; + } + + public void draw(Graphics g ){ + if(sequenceLines.isEmpty()){ + return; + } + + Graphics2D g2 = (Graphics2D)g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Color oldColor = g2.getColor(); + + g2.setColor(color); + + /* draw sequence points and lines */ + for(Shape point : sequencePoints){ + g2.fill(point); + } + + for(Shape line : sequenceLines){ + g2.fill(line); + } + /* restores previous state */ + g2.setColor(oldColor); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + + public void addChangeListener(ChangeListener l){ + listeners.add(l); + } + + public void removeChangeListener(ChangeListener l){ + listeners.remove(l); + } + + protected void fireChangeListeners(){ + ChangeEvent evt = new ChangeEvent(this); + for(ChangeListener l : listeners){ + l.stateChanged(evt); + } + } + + @Override + public void sequenceUpdated(T evt) { + updateSequence(); + } + + /** + * + * Sets a new sequence to be listened to. If the argument is {@code null} + * it stops listening to the previously set sequence, if any,m and clears all + * sequence points and lines. it calls {@code updateSequence}. + * + * @param s the new sequence. + */ + public void setSequence(Sequence s){ + /* remove this from the old sequence if any and add this to the new sequence */ + if(sequence != null){ + sequence.removeSequenceListener(this); + } + + if(s != null){ + s.addSequenceListener(this); + } + + sequence = s; + updateSequence(); + } + + public Sequence getSequence(){ + return sequence; + } + + /** + * Recompute the sequence points and lines. It should be called whenever a change is done to this + * object. For example after {@code setSize} and {@code setMilliseconsPerPixel}. + * + */ + public void updateSequence(){ + /* clear previous values. will rebuild them if type is not NONE */ + sequencePoints.clear(); + sequenceLines.clear(); + + if(sequence == null){ + /* no sequence represented any more. Fire listeners with * + * sequencePoints and sequenceLines cleared */ + fireChangeListeners(); + return; + } + + /* height and width of this panel in order to draw proportionately */ + int height = getSize().height; + int width = getSize().width; + + /* get the sequence range */ + MathUtils.Scale scale = new MathUtils.Scale(sequence.getRange(),new Range(0.0f,(float)height)); + BasicStroke stroke = new BasicStroke(SEQUENCE_LINE_WIDTH); + + /* if there are no sequence values, draw a straight line from the beginning to the end of the track */ + if(sequence.getValuesNum() == 0){ + sequenceLines.add(stroke.createStrokedShape( + new Line2D.Float( + 0f, + height - scale.linear(sequence.getBegin()),/* height is reversed as in swing the top is 0 */ + width, + height - scale.linear(sequence.getEnd()))/* height is reversed as in swing the top is 0 */ + )); + }else{ + Sequence.Value previous = null; + /*previousX and previousY are the centre of sequence values calculated in the previous cycle */ + int previousX = 0; + int previousY = 0; + + for(int i=0; i. +*/ +package uk.ac.qmul.eecs.depic.daw.gui; + +import java.awt.geom.Ellipse2D; + +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +public class SequencePoint extends Ellipse2D.Float { + private static final long serialVersionUID = 1L; + public final static int SIZE = 9; + private Sequence.Value seqValue; + + + + public SequencePoint(Sequence.Value sequenceValue, float x, float y) { + super(x, y, SIZE, SIZE); + this.seqValue = sequenceValue; + } + + public Sequence.Value getSequenceValue(){ + return seqValue; + } + + @Override + public String toString(){ + return "Sequence point ["+super.x+","+super.y+"]"; + + } + + public boolean isXCentredAt(int p){ + return MathUtils.equal(p, x+SIZE/2, 0.2f); + } + + public boolean isYCentredAt(int p){ + return MathUtils.equal(p, y+SIZE/2, 0.2f); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/Actions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/actions/Actions.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,951 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui.actions; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.InputStream; +import java.util.ResourceBundle; +import java.util.concurrent.ExecutionException; +import java.util.prefs.Preferences; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; +import javax.swing.Action; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.ProgressMonitor; +import javax.swing.SwingWorker; +import javax.swing.filechooser.FileNameExtensionFilter; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Direction; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.BeadsSequenceMapping; +import uk.ac.qmul.eecs.depic.daw.beads.sonification.SonificationPrefsPanel; +import uk.ac.qmul.eecs.depic.daw.gui.ArrangeWindow; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrack; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrackParameters; +import uk.ac.qmul.eecs.depic.daw.gui.MainFrame; +import uk.ac.qmul.eecs.depic.daw.gui.PreferencesDialog; +import uk.ac.qmul.eecs.depic.daw.haptics.DawHapticListener; +import uk.ac.qmul.eecs.depic.daw.haptics.HapticTrigger; +import uk.ac.qmul.eecs.depic.daw.haptics.HapticTrigger.Command; +import uk.ac.qmul.eecs.depic.daw.haptics.HaptificationPrefsPanel; +import uk.ac.qmul.eecs.depic.daw.referencesound.GridSound; +import uk.ac.qmul.eecs.depic.jhapticgui.HapticDevice; +import uk.ac.qmul.eecs.depic.jhapticgui.HapticDeviceFactory; +import uk.ac.qmul.eecs.depic.jhapticgui.HapticDeviceWinFactory; +import uk.ac.qmul.eecs.depic.jhapticgui.HapticListener; +import uk.ac.qmul.eecs.depic.jhapticgui.Haptics; +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + + +public class Actions { + public enum ScrubType { + STOP_SCRUB, + SCRUB_FORWARDS, + SCRUB_BACKWARDS, + PLAY_SCRUB + }; + + + private MainFrame frame; + private String currentDirectory; + private Haptics haptics; + private Sonification sonification; + + private EditActions editActions; + private TransportControlActions transportCtrlActions; + + private Action openAction; + private Action closeAction; + private Action exitAction; + private Action preferencesAction; + private Action zoomInAction; + private Action zoomOutAction; + private Action addTrackAction; + private Action removeSelection; + private Action switchAutomation; + + private Action startPhantomAction; + private ShowHapticsAction showHapticsAction; + private PauseHapticsAction pauseHapticsAction; + private Action rotateHapticsYAction; + private Action rotateHapticsZAction; + private Action scrubForwardsAction; + private Action scrubBackwardsAction; + private Action stopScrubAction; + private Action playScrubAction; + private Action generatePeaksAction; + private Action showDbAction; + private Action findNextPointAction; + private Action findPreviousPointAction; + private Action toggleScrubAction; + private int currentFindPosition; + + public Actions(MainFrame f){ + frame = f; + sonification = Daw.getSoundEngineFactory().getSharedSonification(); + + editActions = new EditActions(f); + transportCtrlActions = new TransportControlActions(f); + + openAction = new OpenAction(); + closeAction = new CloseAction(); + preferencesAction = new PreferencesAction(); + exitAction = new ExitAction(); + zoomInAction = new ZoomAction("view.zoom_in",-1); + zoomOutAction = new ZoomAction("view.zoom_out",1); + addTrackAction = new AddTrackAction(); + removeSelection = new RemoveSelectionAction(); + switchAutomation = new SwitchAutomationAction(); + + startPhantomAction = new StartHapticDeviceAction(); + + showHapticsAction = new ShowHapticsAction(); + pauseHapticsAction = new PauseHapticsAction(); + rotateHapticsYAction = new RotateHapticsAction("y"); + rotateHapticsZAction = new RotateHapticsAction("z"); + + scrubForwardsAction = new ScrubAction("track.scrub_forwards",ScrubAction.DEFAULT_DELTA); + scrubBackwardsAction = new ScrubAction("track.scrub_backwards",-ScrubAction.DEFAULT_DELTA); + stopScrubAction = new ScrubAction("track.stop_scrub",null); + playScrubAction = new ScrubAction("track.stop_scrub",0); + + toggleScrubAction = new ToggleScrubAction(); + + generatePeaksAction = new GeneratePeaksAction(); + showDbAction = new ShowDbViewAction(); + + findNextPointAction = new FindNextPointAction(Direction.RIGHT); + findPreviousPointAction = new FindNextPointAction(Direction.LEFT); + currentFindPosition = -1; + } + + public Action getVoidAction(String resource){ + return new MenuAction(resource){ + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent evt) { } + }; + } + + public Action open(){ + return openAction; + } + + public Action close(){ + return closeAction; + } + + public Action properties(){ + return preferencesAction; + } + + public Action exit(){ + return exitAction; + } + + public Action zoomIn(){ + return zoomInAction; + } + + public Action zoomOut(){ + return zoomOutAction; + } + + public Action showDb(){ + return showDbAction; + } + + public Action findNextPoint(){ + return findNextPointAction; + } + + public Action findPreviousPoint(){ + return findPreviousPointAction; + } + + public Action generatePeaks(){ + return generatePeaksAction; + } + + public Action cut(){ + return editActions.cutAction; + } + + public Action paste(){ + return editActions.pasteAction; + } + + public Action copy(){ + return editActions.copyAction; + } + + public Action insert(){ + return editActions.insertAction; + } + + public Action play(){ + return transportCtrlActions.playAction; + } + + /** + * + * The loop can be set on and of setting the {@code Action.SELECTED_KEY}, property + * + * @return + */ + public Action loop(){ + return transportCtrlActions.loopAction; + } + + public Action stop(){ + return transportCtrlActions.stopAction; + } + + public Action rew(){ + return transportCtrlActions.rewAction; + } + + public Action forward(){ + return transportCtrlActions.fwdAction; + } + + + public Action addTrack(){ + return addTrackAction; + } + + public Action removeSelection(){ + return removeSelection; + } + + public Action switchAutomation(){ + return switchAutomation; + } + + public Action startPhantom(){ + return startPhantomAction; + } + + public Action showHaptics(){ + return showHapticsAction; + } + + public Action pauseHaptics(){ + return pauseHapticsAction; + } + + public Action rotateHaptics(boolean y){ + if(y) + return rotateHapticsYAction; + else + return rotateHapticsZAction; + } + + /** + * Returns an action for scrubbing through the current sound wave + * + * @param determines the action effect: forwards scrub forwards if {@code true}, backwards id {@code false} and stop scrubbing + * if {@code null} + * + * @return the {@code Action} instance for scrubbing through the current sound wave. Multiple calls + * to this methods with the same value of {@code forwards} would return the same object. + */ + public Action scrubAction(ScrubType type){ + switch(type) { + case STOP_SCRUB : + return stopScrubAction; + case SCRUB_FORWARDS : + return scrubForwardsAction; + case SCRUB_BACKWARDS : + return scrubBackwardsAction; + case PLAY_SCRUB : + return playScrubAction; + default : + throw new IllegalArgumentException("Wrong scrub action type " + type ); + } + } + + + public Action toggleScrub(){ + return toggleScrubAction; + } + + /* --- ACTION CLASSES --- */ + private class OpenAction extends MenuAction implements PropertyChangeListener { + private static final long serialVersionUID = 1L; + + private ProgressMonitor progressMonitor; + + OpenAction() { + super("file.open"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null) + return; + + /* open a file chooser */ + JFileChooser fileChooser = new JFileChooser(currentDirectory); + AudioFileFormat.Type[] fileTypes = AudioSystem.getAudioFileTypes(); + String[] extensions = new String[fileTypes.length]; + for(int i=0; i< fileTypes.length; i++){ + extensions[i] = fileTypes[i].getExtension(); + } + + /* filter the selection by audio file extensions only */ + fileChooser.setFileFilter(new FileNameExtensionFilter("Audio Files",extensions)); + + int retVal = fileChooser.showOpenDialog(frame); + if(retVal != JFileChooser.APPROVE_OPTION) + return; + File selectedFile = fileChooser.getSelectedFile(); + /* saves the current directoy to re-open the file chooser next time */ + currentDirectory = selectedFile.getParent(); + progressMonitor = new ProgressMonitor(frame,"Loading File", "", 0, SoundWave.FILE_LOAD_TOTAL_PROGRESS); + + track.getSoundWave().loadAudioData(selectedFile, this); + } + + /** + * Monitors the progress of file loading + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null) + return; + + if("progress".equals(evt.getPropertyName())){ + if(progressMonitor.isCanceled()){ + track.getSoundWave().stopLoading(true); + } + else if((Integer)evt.getNewValue() == SoundWave.FILE_LOAD_TOTAL_PROGRESS){ // complete + closeAction.setEnabled(true); // now can be closed + progressMonitor.close(); + } + else + progressMonitor.setProgress((Integer)evt.getNewValue()); + }else if(SoundWave.FILE_ERROR_PROPERTY.equals(evt.getPropertyName())){ + //dialog.dispose(); + JOptionPane.showMessageDialog(frame, "Could not open the file: "+evt.getNewValue()); + } + } + } // class OpenAction + + private class CloseAction extends MenuAction { + private static final long serialVersionUID = 1L; + + CloseAction(){ + super("file.close"); + setEnabled(false); + } + + @Override + public void actionPerformed(ActionEvent evt){ + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track != null) + track.getSoundWave().close(); + } + } // class CloseAction + + private class PreferencesAction extends MenuAction { + private static final long serialVersionUID = 1L; + + PreferencesAction(){ + super("file.properties"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + PreferencesDialog dialog = new PreferencesDialog(frame); + +// dialog.addPanel(new GranularPrefsPanel(frame.getArrangeWindow())); FIXME line to remove + dialog.addPanel(SonificationPrefsPanel.getInstance()); + dialog.addPanel(HaptificationPrefsPanel.getInstance()); + + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + dialog.setVisible(true); + } + } // class PropertiesAction + + private class ExitAction extends MenuAction { + private static final long serialVersionUID = 1L; + + ExitAction() { + super("file.exit"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + if(haptics != null) + haptics.dispose(); + System.exit(0); + } + } // class ExitAction + + private class ZoomAction extends MenuAction{ + private static final long serialVersionUID = 1L; + private int zoomFactor; + + ZoomAction(String resource, int zoomFactor){ + super(resource); + this.zoomFactor = zoomFactor; + } + + @Override + public void actionPerformed(ActionEvent evt) { + ArrangeWindow arrangeWindow = frame.getArrangeWindow(); + for(int i=0; i track.getSoundWave().getMaxScaleFactor()){ + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.ERROR); + return; + } + track.setScaleFactor(track.getScaleFactor()+zoomFactor); + } + + + } + } // class ZoomAction + + + + private class AddTrackAction extends MenuAction { + private static final long serialVersionUID = 1L; + + AddTrackAction(){ + super("track.add_track"); + } + + @Override + public void actionPerformed(ActionEvent e) { + AudioTrack newTrack = new AudioTrack(Daw.getSoundEngineFactory().createSoundWave()); + frame.getArrangeWindow().addTrack(newTrack); + newTrack.scrollRectToVisible(newTrack.getBounds()); + } + }// class AddTrackAction + + private class SwitchAutomationAction extends MenuAction { + private static final long serialVersionUID = 1L; + + SwitchAutomationAction() { + super("track.switch_automation"); + } + + @Override + public void actionPerformed(ActionEvent e){ + int trackIndex = frame.getArrangeWindow().getCurrentTrackIndex(); + if(trackIndex == -1) + return; + + resetFindIndex(); + AudioTrackParameters parameters = frame.getArrangeWindow().getTrackParametersAt(trackIndex); + int itemCount = parameters.automationComboBox.getItemCount(); + int selectedIndex = parameters.automationComboBox.getSelectedIndex(); + parameters.automationComboBox.setSelectedIndex((selectedIndex+1)%itemCount); + } + + } // class SwitchAutomationAction + + private class StartHapticDeviceAction extends MenuAction { + private static final long serialVersionUID = 1L; + private HapticDeviceFactory hapticBuilder; + private boolean hapticStarted; + + StartHapticDeviceAction(){ + super("haptics.falcon_start"); + hapticBuilder = new HapticDeviceWinFactory(); + hapticStarted = false; + } + + @Override + public void actionPerformed(ActionEvent e ){ + if(! hapticStarted) { + new SwingWorker(){ + + @Override + protected Haptics doInBackground() throws Exception { + /* init the haptic device in a background thread */ + HapticListener listener = new DawHapticListener(frame); + InputStream PantomLib = DawHapticListener.class.getResourceAsStream("DawHaptics.dll"); + //Haptics haptics = hapticBuilder.getHapticDevice(PantomLib, listener, frame.getSize()); + Haptics haptics = hapticBuilder.getHapticDevice(PantomLib, listener, new Dimension(1470, 918)); + return haptics; + } + + @Override + protected void done(){ + /* init finished. If successful add the haptic device to the SoundWave listeners */ + try { + haptics = get(); + /* set the parent component of message dialogs */ + + showHapticsAction.setEnabled(true); + toggleScrubAction.setEnabled(true); + putValue(MenuAction.NAME, + ResourceBundle.getBundle(MenuAction.class.getName()).getString("haptics.falcon_stop.text")); + hapticStarted = true; + Thread.sleep(2000); + frame.requestFocus(); + } catch (InterruptedException | ExecutionException e) { + JOptionPane.showMessageDialog(frame, "failed to initialize haptic device ("+e.getMessage()+")"); + e.printStackTrace(); + } + } + }.execute(); + }else{ // stop haptic device + haptics.dispose(); + haptics = null; + putValue(MenuAction.NAME,ResourceBundle.getBundle(MenuAction.class.getName()).getString("haptics.falcon_start.text")); + hapticStarted = false; + } + } + } // class StartPhantomAction + + private class ShowHapticsAction extends MenuAction { + private static final long serialVersionUID = 1L; + private UniqueHapticTrigger uniqueHapticTrigger; + + + public ShowHapticsAction() { + super("haptics.show"); + + uniqueHapticTrigger = new UniqueHapticTrigger(); + + + /* enabled when haptics is created */ + setEnabled(false); + } + + @Override + public void actionPerformed(ActionEvent evt) { + /* sets a new track to listen to for the haptics */ + + ArrangeWindow arrangeWindow = frame.getArrangeWindow(); + AudioTrack track = arrangeWindow.getCurrentTrack(); + + if(track == null){ + haptics.sendMessage(HapticTrigger.Command.DISPLAY_NONE, "", 0); + Daw.getSoundEngineFactory().getSharedSonification().play(SoundType.OK); + return; + } + + /* set the track the haptic commands will apply to */ + ((DawHapticListener)haptics.getListener()).setTrack(track); + if(haptics instanceof HapticDevice){ + + float hapticViewPortMs = track.getSoundWave().getMillisecPerChunk() * + track.getHapticViewPort().getWidth(); + if(track.getSoundWave().getDbWave().hasSequence()){ + /* peaks view doesn't allow normal scrubbing */ + uniqueHapticTrigger.set(new HapticTrigger((HapticDevice)haptics, HapticTrigger.DisplayType.DISPLAY_PEAKS)); + uniqueHapticTrigger.addToListener(track.getSoundWave()); + + haptics.sendMessage(Command.DISPLAY_PEAKS, "", 0); + + /* the argument is a sequence of peak levels */ + Sequence sequence = track.getSoundWave().getDbWave().getSequence(); + + for(int i=0; i< sequence.getValuesNum(); i++){ + Sequence.Value seqVal = sequence.getValueAt(i); + Preferences prefs = Preferences.userNodeForPackage(BeadsSequenceMapping.class); // FIXME remove beads dependency + float borderThreshold = prefs.getFloat("borders.threshold", 0.01f); + if( sequence.getRange().getEnd() - seqVal.getValue() <= borderThreshold){ +// if(seqVal.getValue() >= sequence.getRange().getEnd() - borderThreshold){ + haptics.sendMessage(Command.SEQUENCE_VALUE_ADD, + Float.toString(seqVal.getTimePosition()/hapticViewPortMs), + seqVal.hashCode()); + } + } + }else if(track.getSoundWave().hasSequence()){ // Automation + /* automation view allows scrubbing as well */ + uniqueHapticTrigger.set(new HapticTrigger((HapticDevice)haptics, HapticTrigger.DisplayType.DISPLAY_SEQUENCE)); + uniqueHapticTrigger.addToListener(track.getSoundWave()); + + Sequence sequence = (Sequence)track.getSoundWave().getSequence(); + uniqueHapticTrigger.addToListener(sequence); + + Preferences prefs = Preferences.userNodeForPackage(HaptificationPrefsPanel.class); + String gridType = prefs.get("grid_type", HaptificationPrefsPanel.GRID_TYPES[0]); + int id = -1; + for (int i=0; i stop scrubbing + + /** + * Constructs a new ScrubAction + * + * @param resourceName the resource name from the action properties + * @param delta the amount of scrubbing. Positive value will shift to the right, negative + * value will scrub to the left and a 0 value will stop scrubbing. + */ + public ScrubAction(String resourceName, Integer delta) { + super(resourceName); + this.delta = delta; + } + + @Override + public void actionPerformed(ActionEvent evt) { + + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null) + return; + + SoundWave wave = track.getSoundWave(); + + if(delta == null){ // stop scrubbing + wave.scan(SoundWave.STOP_SCANNING); + return; + } + + if(wave.getCurrentChunkPosition()+delta < 0) // do nothing if the cursor hits the 0 + return; + + wave.scan(wave.getCurrentChunkPosition()+delta); + } + } // class ScrubAction + + private class GeneratePeaksAction extends MenuAction { + private static final long serialVersionUID = 1L; + boolean visible; + + GeneratePeaksAction(){ + super("view.peaks.show"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + return; + } + + SoundWave wave = track.getSoundWave(); + + visible = !visible; + setText("view.peaks."+(visible ? "dont_show" : "show")); + wave.generatePeakMeter(visible); + } + } // class GeneratePeaksAction + + + private class ShowDbViewAction extends MenuAction { + private static final long serialVersionUID = 1L; + private boolean visible; + + public ShowDbViewAction() { + super("view.db.show"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + return; + } + + visible = !visible; + resetFindIndex(); + setText("view.db."+ (visible ? "dont_show" : "show")); + track.showDbWave(visible); + + } + } // class ShowDbViewAction + + private class RemoveSelectionAction extends MenuAction { + private static final long serialVersionUID = 1L; + + RemoveSelectionAction(){ + super("track.cancel_selection"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track != null){ + track.getSoundWave().setSelection(null);// null = no selection + } + } + } // RemoveSelectionAction + + private class FindNextPointAction extends MenuAction { + private static final long serialVersionUID = 1L; + private final Direction direction; + + FindNextPointAction(Direction d){ + super(d == Direction.RIGHT ? "view.find.next_seq_val" : "view.find.prev_seq_val"); + direction = d; + resetFindIndex(); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + + if(track == null){ + sonification.play(SoundType.ERROR); + return; + } + + SoundWave wave = track.getSoundWave(); + + if(wave.hasSequence()){ + Automation automation = wave.getParametersControl().getCurrentAutomation(); + if(automation.getValuesNum() == 0){ + sonification.play(SoundType.ERROR); + return; + } + + if(direction == Direction.RIGHT){ + if(currentFindPosition == -1){ + currentFindPosition = 0; + }else{ + currentFindPosition = (currentFindPosition + 1) % automation.getValuesNum(); + if(currentFindPosition == 0){ + sonification.play(SoundType.LOOP); + } + } + }else if (direction == Direction.LEFT){ + if(currentFindPosition == -1){ + currentFindPosition = automation.getValuesNum() - 1; + sonification.play(SoundType.LOOP); + }else{ + currentFindPosition = currentFindPosition - 1; + if(currentFindPosition == -1){ + currentFindPosition = automation.getValuesNum() - 1; + sonification.play(SoundType.LOOP); + } + } + } + + Sequence.Value automVal = automation.getValueAt(currentFindPosition); + wave.setPosition((int) (automVal.getTimePosition()/wave.getMillisecPerChunk())); + if( showHapticsAction.uniqueHapticTrigger.isNull()) {// FIXME more elegant + haptics.sendMessage(Command.SEQUENCE_VALUE_FIND, Integer.toString(currentFindPosition) , 0); + } + + }else if(wave.getDbWave().hasSequence()){ + Sequence peakLevelMeter = wave.getDbWave().getSequence(); + if(peakLevelMeter.getValuesNum() == 0){ + sonification.play(SoundType.ERROR); + return; + } + + Preferences prefs = Preferences.userNodeForPackage(BeadsSequenceMapping.class); // FIXME remove beads dependency + float borderThreshold = prefs.getFloat("borders.threshold", 0.01f); + + if(direction == Direction.RIGHT){ + if(currentFindPosition == -1 ){ + currentFindPosition = 0; + Sequence.Value seqVal = peakLevelMeter.getValueAt(currentFindPosition); +// if(seqVal.getValue() > peakLevelMeter.getRange().getEnd() - borderThreshold ) { + if( peakLevelMeter.getRange().getEnd() - seqVal.getValue() <= borderThreshold ) { + wave.setPosition((int) (seqVal.getTimePosition()/wave.getMillisecPerChunk())); + return; + } + } + + int stopIndex = currentFindPosition; + + do { + currentFindPosition = (currentFindPosition + 1) % peakLevelMeter.getValuesNum(); + Sequence.Value seqVal = peakLevelMeter.getValueAt(currentFindPosition); + if(peakLevelMeter.getRange().getEnd() - seqVal.getValue() <= borderThreshold ) { // FIXME + if(currentFindPosition < stopIndex){ + sonification.play(SoundType.LOOP); + } + wave.setPosition((int) (seqVal.getTimePosition()/wave.getMillisecPerChunk())); + return; + } + }while(currentFindPosition != stopIndex); + + /* clipping not found */ + sonification.play(SoundType.ERROR); + + + }else if (direction == Direction.LEFT){ + if(currentFindPosition == -1 ){ + currentFindPosition = peakLevelMeter.getValuesNum()-1; + Sequence.Value seqVal = peakLevelMeter.getValueAt(currentFindPosition); + if(seqVal.getValue() > peakLevelMeter.getRange().getEnd() - 0.02f ) { + sonification.play(SoundType.LOOP); + wave.setPosition((int) (seqVal.getTimePosition()/wave.getMillisecPerChunk())); + return; + } + } + + int stopIndex = currentFindPosition; + + do { + currentFindPosition = (currentFindPosition - 1); + if(currentFindPosition == -1){ + currentFindPosition = peakLevelMeter.getValuesNum()-1; + } + Sequence.Value seqVal = peakLevelMeter.getValueAt(currentFindPosition); + if(seqVal.getValue() > peakLevelMeter.getRange().getEnd() - 0.02f ) { // clipping FIXME + if(currentFindPosition > stopIndex){ + sonification.play(SoundType.LOOP); + } + wave.setPosition((int) (seqVal.getTimePosition()/wave.getMillisecPerChunk())); + return; + } + }while(currentFindPosition != stopIndex); + + /* clipping not found */ + sonification.play(SoundType.ERROR); + } + }else{ + sonification.play(SoundType.ERROR); + } + } + } + + private class ToggleScrubAction extends MenuAction { + private static final long serialVersionUID = 1L; + private boolean scrub = true; + + ToggleScrubAction(){ + super("haptics.toggle_scrub"); + setEnabled(false); + + } + + @Override + public void actionPerformed(ActionEvent arg0) { + scrub = !scrub; + ((DawHapticListener)haptics.getListener()).setScrubEnabled(scrub); + + } + } + + private void resetFindIndex(){ + currentFindPosition = -1; + } +} // class Actions + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/EditActions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/actions/EditActions.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,139 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui.actions; + +import java.awt.event.ActionEvent; + +import javax.swing.Action; + +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrack; +import uk.ac.qmul.eecs.depic.daw.gui.MainFrame; + +class EditActions { + MainFrame frame; + Action cutAction; + Action pasteAction; + Action copyAction; + Action insertAction; + Sonification sonification; + + public EditActions(MainFrame frame) { + this.frame = frame; + sonification = Daw.getSoundEngineFactory().getSharedSonification(); + + cutAction = new CutAction(); + copyAction = new CopyAction(); + pasteAction = new PasteAction(); + insertAction = new InsertAction(); + } + + class CutAction extends MenuAction { + private static final long serialVersionUID = 1L; + + public CutAction() { + super("edit.cut"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + sonification.play(SoundType.ERROR); + return; + } + + boolean copied = track.getSoundWave().getEditor().cut(track.getSoundWave(), track.getSelection()); + + sonification.play(copied ? SoundType.OK : SoundType.ERROR); + } + } // class CutAction + + class CopyAction extends MenuAction { + private static final long serialVersionUID = 1L; + + public CopyAction(){ + super("edit.copy"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + sonification.play(SoundType.ERROR); + return; + } + + boolean copied = track.getSoundWave().getEditor().copy(track.getSoundWave(), track.getSelection()); + + sonification.play(copied ? SoundType.OK : SoundType.ERROR); + } + } // CopyAction + + class PasteAction extends MenuAction { + private static final long serialVersionUID = 1L; + + public PasteAction(){ + super("edit.paste"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + sonification.play(SoundType.ERROR); + return; + } + + boolean pasted = track.getSoundWave().getEditor().paste(track.getSoundWave(), track.getSoundWave().getCurrentChunkPosition()); + if(!pasted){ + sonification.play(SoundType.ERROR); + }else{ + sonification.play(SoundType.OK); + } + } + } + + class InsertAction extends MenuAction { + private static final long serialVersionUID = 1L; + + public InsertAction(){ + super("edit.insert"); + } + + @Override + public void actionPerformed(ActionEvent evt) { + AudioTrack track = frame.getArrangeWindow().getCurrentTrack(); + if(track == null){ + sonification.play(SoundType.ERROR); + return; + } + + boolean inserted = track.getSoundWave().getEditor().insert(track.getSoundWave(), track.getSoundWave().getCurrentChunkPosition()); + if(!inserted){ + sonification.play(SoundType.ERROR); + }else{ + sonification.play(SoundType.OK); + } + } + } + +} // class EditAction diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/MenuAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/actions/MenuAction.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,59 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui.actions; + +import java.awt.event.KeyEvent; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; + + +abstract class MenuAction extends AbstractAction { + private static final long serialVersionUID = 1L; + + public MenuAction(String resourceName){ + ResourceBundle bundle = ResourceBundle.getBundle(MenuAction.class.getName()); + /* set the menu label */ + String name = bundle.getString(resourceName+".text"); + putValue(AbstractAction.NAME,name); + /* try and set the accelerator and mnemonic */ + try{ + String accelerator = bundle.getString(resourceName+".accelerator"); + putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accelerator)); + }catch(MissingResourceException mre){ + // do nothing, accelerator just won't be set + } + + try{ + String mnemonic = bundle.getString(resourceName+".mnemonic"); + putValue(AbstractAction.MNEMONIC_KEY,KeyEvent.getExtendedKeyCodeForChar(mnemonic.charAt(0))); + }catch(MissingResourceException mre){ + // do nothing, mnmonic just won't be set + } + } + + public void setText(String resourceName){ + ResourceBundle bundle = ResourceBundle.getBundle(MenuAction.class.getName()); + String name = bundle.getString(resourceName+".text"); + putValue(AbstractAction.NAME,name); + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/MenuAction.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/actions/MenuAction.properties Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,116 @@ + +### FILE ### +file.text=File +file.mnemonic=F + +file.open.text=Open +file.open.accelerator=ctrl O +file.open.mnemonic=O + +file.close.text=Close +file.close.mnemonic=C + +file.properties.text=Properties +file.properties.mnemonic=P + +file.exit.text=Exit +file.exit.menmonic=x + +### EDIT ### + +edit.text=Edit +edit.mnemonic=E + +edit.cut.text=Cut +edit.cut.accelerator=ctrl X + +edit.copy.text=Copy +edit.copy.accelerator=ctrl C + +edit.paste.text=Paste +edit.paste.accelerator=ctrl V + +edit.insert.text=Insert +edit.insert.accelerator=ctrl I + + +### VIEW ### + +view.text=View +view.mnemonic=V + +view.zoom_in.text=Zoom In +view.zoom_in.accelerator=ctrl EQUALS +view.zoom_in.mnemonic=I + +view.zoom_out.text=Zoom Out +view.zoom_out.accelerator=ctrl MINUS +view.zoom_out.mnemonic=O + +view.peaks.show.text=Generate Peaks Curve +view.peaks.dont_show.text=Remove Peaks Curve + +view.find.next_seq_val.text=Find next point +view.find.next_seq_val.accelerator=ctrl F +view.find.next_seq_val.mnemonic=F + +view.find.prev_seq_val.text=Find previous point +view.find.prev_seq_val.accelerator=ctrl shift F +view.find.prev_seq_val.mnemonic=p + +view.db.show.text=Show Db wave +view.db.dont_show.text=Show Amplitude wave + +### TRACK ### + +track.text=Track +track.mnemonic=T + +track.add_track.text=Add Track +track.add_track.mnemonic=A + +track.cancel_selection.text=Cancel Selection +track.cancel_selection.mnemonic=C +track.cancel_selection.accelerator=ESCAPE + +track.switch_automation.text=Switch Automation +track.switch_automation.mnemonic=S +track.switch_automation.accelerator=ctrl A + +track.scrub_forwards.text=Scrub Forwards +#track.scrub_forwards.accelera + +track.scrub_backwards.text=Scrub Backwards + +track.stop_scrub.text=Stop Scrubbing + + +### HAPTICS ### + +haptics.text=Haptics +haptics.mnemonic=H + +haptics.falcon_start.text=Start Haptic Device +haptics.falcon_start.mnemonic=F +haptics.falcon_stop.text = Stop Haptic Device + + +haptics.show.text=Activate Current view in Haptics +haptics.show.mnemonic=A +haptics.show.accelerator=ctrl H + +haptic.pause.text=Pause Haptic Device +haptic.pause.mnemonic=P +haptic.pause.accelerator=ctrl J + +haptics.rotate.y.text=Rotate Haptics to Depth +haptics.rotate.y.mnemonic = D + +haptics.rotate.z.text=Rotate Haptics to Vertical +haptics.rotate.z.mnemonic = V + +haptics.rotate.x.text=Rotate Haptics to Horizontal +haptics.rotate.x.mnemonic = H + +haptics.toggle_scrub.text = Toggle Scrub +haptics.toggle_scrub.accelerator = F1 diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/TransportControlActions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/gui/actions/TransportControlActions.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,168 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.gui.actions; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; + +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.gui.ArrangeWindow; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrack; +import uk.ac.qmul.eecs.depic.daw.gui.MainFrame; + +public class TransportControlActions { + private MainFrame frame; + private boolean isPlaying; + + Action playAction; + Action stopAction; + Action rewAction; + Action fwdAction; + Action loopAction; + + public TransportControlActions(MainFrame frame) { + this.frame = frame; + + playAction = new PlayAction(); + stopAction = new StopAction(); + rewAction = new RewAction(); + fwdAction = new FwdAction(); + loopAction = new LoopAction(); + } + + class PlayAction extends AbstractAction { + private static final long serialVersionUID = 1L; + + PlayAction(){ + super("play",new ImageIcon(TransportControlActions.class.getResource("play.png"))); + + } + + @Override + public void actionPerformed(ActionEvent e) { + ArrangeWindow arrangeWindow = frame.getArrangeWindow(); + + isPlaying = !isPlaying; + + if(isPlaying) { + AudioTrack currentTrack = arrangeWindow.getCurrentTrack(); + /* set all the tracks at the ame position */ + for(int i=0; i. +*/ +package uk.ac.qmul.eecs.depic.daw.gui.actions; + +import java.util.ArrayList; +import java.util.List; + +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.haptics.HapticTrigger; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +class UniqueHapticTrigger { + private HapticTrigger trigger; + private UniqueHapticTrigger uniqueTrigger; + private List observed = new ArrayList<>(2); + + public void set(HapticTrigger t){ + + /* remove the trigger from the observed objects */ + for(Object o : observed){ + if(o instanceof Sequence){ + ((Sequence) o).removeSequenceListener(trigger); + }else{ + ((SoundWave) o).removeSoundWaveListener(trigger); + } + } + + observed.clear(); + trigger = t; + } + + public UniqueHapticTrigger get(){ + return uniqueTrigger; + } + + UniqueHapticTrigger(){ + + } + + public void addToListener(Sequence s){ + s.addSequenceListener(trigger); + observed.add(s); + } + + public void addToListener(SoundWave sw){ + sw.addSoundWaveListener(trigger); + observed.add(sw); + } + + public boolean isNull() { + return uniqueTrigger == null; + } + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/fwd.png Binary file src/uk/ac/qmul/eecs/depic/daw/gui/actions/fwd.png has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/loop.png Binary file src/uk/ac/qmul/eecs/depic/daw/gui/actions/loop.png has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/play.png Binary file src/uk/ac/qmul/eecs/depic/daw/gui/actions/play.png has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/rew.png Binary file src/uk/ac/qmul/eecs/depic/daw/gui/actions/rew.png has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/gui/actions/stop.png Binary file src/uk/ac/qmul/eecs/depic/daw/gui/actions/stop.png has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/haptics/DawHapticListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/haptics/DawHapticListener.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,287 @@ +package uk.ac.qmul.eecs.depic.daw.haptics; + +import java.awt.geom.Point2D; + +import javax.swing.SwingUtilities; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.AutomationValue; +import uk.ac.qmul.eecs.depic.daw.Daw; +import uk.ac.qmul.eecs.depic.daw.Sonification; +import uk.ac.qmul.eecs.depic.daw.SoundType; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrack; +import uk.ac.qmul.eecs.depic.daw.gui.MainFrame; +import uk.ac.qmul.eecs.depic.daw.referencesound.GridSound; +import uk.ac.qmul.eecs.depic.jhapticgui.HapticListener; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; + +public class DawHapticListener extends HapticListener { + public static final boolean SUPPORT_VIEW_PORT = false; + public static final String SCRUB_OFF_MSG = "scrub.off"; + public static final String SCRUB_ON_MSG = "scrub.on"; + public static final String SCRUB_SHIFT = "scrub.shift"; + + public static final String ADD_SEQ_VAL = "seq.val.add" ; + public static final String REM_SEQ_VAL = "seq.val.rem" ; + public static final String MOVE_SEQ_VAL= "seq.val.move" ; + public static final String PICK_SEQ_VAL = "seq.val.pick"; + public static final String DROP_SEQ_VAL = "seq.val.drop"; + + public static final String LINE_TOUCHED = "line.touched"; + public static final String POINT_TOUCHED = "point.touched"; + + private MainFrame frame; + private Sonification sonification = Daw.getSoundEngineFactory().getSharedSonification(); + private GridSound gridSound; + private Sequence.Value line; + private float lineValue; + private AudioTrack track; + private boolean scrubEnabled; + + long lastTouchTime = 0; + + public DawHapticListener(MainFrame frame){ + this.frame = frame; + gridSound = GridSound.get(GridSound.Type.NONE); + scrubEnabled = true; + final Sequence sequence = new Sequence () { + + @Override + public float getBegin() { + return 0; + } + + @Override + public float getEnd() { + return 0; + } + + @Override + public int getValuesNum() { + return 1; + } + + @Override + public Value getValueAt(int index) { + return line; + } + + @Override + public Range getRange() { + return new Range(0.0f, 30.0f); + } + + @Override + public void addSequenceListener(SequenceListener l) {} + + @Override + public void removeSequenceListener(SequenceListener l) {} + + @Override + public float getLen() { + return 0; + } + + }; + + line = new Sequence.Value() { + @Override + public int index() { + return 0; + } + + @Override + public float getValue() { + return lineValue; + } + + @Override + public float getTimePosition() { + return 0; + } + + @Override + public Sequence getSequence() { + return sequence; + } + }; + } + + public void setGridSound(GridSound gs){ + System.out.println("setGridSound " + gs); + gridSound = gs; + } + + public void setScrubEnabled(boolean enabled){ + scrubEnabled = enabled; + + if(track != null) + track.getSoundWave().scan(SoundWave.STOP_SCANNING); + + sonification.play(SoundType.OK); // give feedback + } + + public void setTrack(AudioTrack track){ + this.track = track; + } + + + /** + * + * executes the messages in the Event Dispatching Thread + * + */ + @Override + protected void fireMessageReceived(final Message m){ + SwingUtilities.invokeLater(new Runnable(){ + @Override + public void run(){ + messageReceived(m.command, m.args, m.ID); + } + }); + } + + + @Override + public void messageReceived(String msg, String args, long ID) { + if(track == null) + return; + + switch(msg) + { + + case SCRUB_ON_MSG : { + if(!scrubEnabled) + return; + + SoundWave wave = track.getSoundWave(); + /* position of the haptic proxy in the horizontal * + * scrub line of the haptic space */ + float hapticPos = Float.parseFloat(args); + wave.scan(track.getHapticViewPort().getPosition(hapticPos)); + break; + } + + case SCRUB_OFF_MSG : { + track.getSoundWave().scan(SoundWave.STOP_SCANNING); + break; + } + + case SCRUB_SHIFT : { + /* calculate the step size in pixels starting from the step size in milliseconds */ + SoundWave wave = track.getSoundWave(); + float timeShift = Float.parseFloat(args); + + float chunkShift = timeShift/wave.getMillisecPerChunk(); + /* position to set for the cursor in the view port of the sound wave */ + track.getHapticViewPort().shift(chunkShift); + if(SUPPORT_VIEW_PORT){ + /* reduce the frequency of sound feedback to once every 200 ms */ + long now = System.currentTimeMillis(); + if( now - lastTouchTime >= 200){ + sonification.play(SoundType.HAPTIC_PORT_TOUCH, 1.0f, chunkShift>0 ? 1 : -1); + lastTouchTime = now; + } + } + break; + } + + case ADD_SEQ_VAL : { + String [] hapticCoord = args.split(" "); + int chunkPos = track.normToWidthconvert(Float.parseFloat(hapticCoord[0])); + int rangePos = track.normToHeightConvert(Float.parseFloat(hapticCoord[1])); + + + Point2D.Float p = AudioTrack.getAutomationCoord(track, chunkPos, rangePos); + + /* if user is trying to create an automation outside the bounds of the automation, notify the error */ + if(p == null){ + sonification.play(SoundType.ERROR); + return; + } + + SoundWave wave = track.getSoundWave(); + + + //p.y = new MathUtils.Interpolate(wave.getParametersControl().getCurrentAutomation()) + // .linear(p.y); + + /* add the automation point to the model and sonify it */ + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue( + wave.getParametersControl().getCurrentAutomation().add(p.x, p.y)); + break; + } + + case REM_SEQ_VAL : { + /* find the automaton value with this hash code */ + Sequence s = track.getSoundWave().getSequence(); + AutomationValue aVal = null; + for(int i=0 ; i< s.getValuesNum(); i++){ + if(s.getValueAt(i).hashCode() == ID){ + aVal = (AutomationValue)s.getValueAt(i); + break; + } + } + + ((Automation)s).remove(aVal); + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(aVal); +// sonification.play(SoundType.ERROR); + break; + } + + case MOVE_SEQ_VAL : { + String [] hapticCoord = args.split(" "); + + /* get the automation point */ + Sequence s = track.getSoundWave().getSequence(); + AutomationValue aVal = fetchAutomationValue(ID); + + int chunkPos = track.normToWidthconvert(Float.parseFloat(hapticCoord[0])); + int rangePos = track.normToHeightConvert(Float.parseFloat(hapticCoord[1])); + + Point2D.Float pointCoord = AudioTrack.getAutomationCoord(track,chunkPos,rangePos); + + if(pointCoord != null){ + aVal.setLocation(pointCoord.x, pointCoord.y); + track.getSoundWave().scan(chunkPos); + + }else{ + sonification.play(SoundType.ERROR); + } + + break; + } + + + case LINE_TOUCHED : { + lineValue = ID; + gridSound.sound(line); + break; + } + + case POINT_TOUCHED : { + AutomationValue aVal = fetchAutomationValue(ID); + + sonification.getSequenceMapping(SoundType.AUTOMATION).renderValue(aVal); + } + + + } + } + + private AutomationValue fetchAutomationValue(long ID){ + /* find the automaton value with this hash code */ + Sequence s = track.getSoundWave().getSequence(); + for(int i=0 ; i< s.getValuesNum(); i++){ + if(s.getValueAt(i).hashCode() == ID){ + return (AutomationValue)s.getValueAt(i); + } + } + + return null; + } + +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/haptics/DawHaptics.dll Binary file src/uk/ac/qmul/eecs/depic/daw/haptics/DawHaptics.dll has changed diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/haptics/HapticTrigger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/haptics/HapticTrigger.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,158 @@ +package uk.ac.qmul.eecs.depic.daw.haptics; + +import uk.ac.qmul.eecs.depic.daw.Chunk; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; +import uk.ac.qmul.eecs.depic.jhapticgui.Haptics; +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.Sequence.Value; +import uk.ac.qmul.eecs.depic.patterns.SequenceEvent; +import uk.ac.qmul.eecs.depic.patterns.SequenceListener; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + + +public class HapticTrigger implements SequenceMapping, SequenceListener, SoundWaveListener { + public enum DisplayType { + DISPLAY_SEQUENCE, + DISPLAY_NONE, + DISPLAY_PEAKS, + DISPLAY_RENDER_CURVE_AT, + DISPLAY_RENDER_VALUE + } + + public abstract class Command { + /** + * Message to display a graph + * + * Arguments: intitial y value, max y value, min y value, max x value, x viewport size + */ + public static final String DISPLAY_SEQUENCE = "display.seq"; + public static final String DISPLAY_RENDER_CURVE_AT = "display.curve_at"; + public static final String DISPLAY_RENDER_VALUE = "display.value"; + /** + * display nothing. takes no args + */ + public static final String DISPLAY_NONE = "display.none"; + public static final String DISPLAY_PEAKS = "display.peaks"; + + /** + * Arguments : x time position, y in normalized form + */ + public static final String SEQUENCE_VALUE_ADD = "seq.value.add"; + public static final String SEQUENCE_VALUE_CHANGE = "seq.value.change"; + public static final String SEQUENCE_VALUE_REMOVE = "seq.value.rem"; + public static final String SEQUENCE_VALUE_FIND = "seq.value.find"; + public static final String SEQUENCE_SHIFT = "seq.shift"; + public static final String SEQUENCE_BEGIN = "seq.begin"; + + public static final String RENDER_VALUE = "render_value"; + public static final String RENDER_CURVE_AT = "render_curve_at"; + /** + * Message to rotate the viscosity scrub line in the haptic space + * + * Argument is the degree of rotation + */ + public static final String ROTATE_Z = "rotate.z"; + public static final String ROTATE_Y = "rotate.y"; + public static final String ROTATE_X = "rotate.x"; + } + + private DisplayType displayType; + private Haptics haptics; + + public HapticTrigger(Haptics haptics, DisplayType type){ + if(haptics == null) + throw new IllegalArgumentException("haptics cannot be null"); + this.haptics = haptics; + displayType = type; + haptics.sendMessage(Command.DISPLAY_NONE,"",0); + } + + @Override + public void renderValue(Value val) { + /* sends a normalized value of the chunk size */ + haptics.sendMessage(Command.RENDER_VALUE, Float.toString(val.getValue()) , val.hashCode()); + } + + @Override + public void renderCurve(Sequence m, float startTime) { + throw new UnsupportedOperationException("Only update implemented"); + } + + @Override + public void renderCurveAt(Sequence sequence, float time, float duration) { + float val = new MathUtils.Interpolate(sequence).linear(time); + + /* normalize the value to [0-1] range */ + float normalizedValue = new MathUtils.Scale(sequence.getRange(), new Range(0.0f,1.0f)).linear(val); + haptics.sendMessage(Command.RENDER_CURVE_AT, ""+normalizedValue, sequence.hashCode()); + } + + @Override + public void sequenceUpdated(T t) { + SequenceEvent.What evtType = t.getWhat(); + + if(displayType == DisplayType.DISPLAY_SEQUENCE) { + if(SequenceEvent.What.VALUE_ADDED.equals(evtType)){ + Sequence.Value aVal = t.getValue(); + + /* since the haptic graph y value are in normalized form normalize first */ + float verticalValue = new MathUtils.Scale(t.getSource().getRange(), Range.NORMALIZED_RANGE_F).linear(aVal.getValue()); + + haptics.sendMessage(Command.SEQUENCE_VALUE_ADD, aVal.getTimePosition()+" "+verticalValue, aVal.hashCode()); + }else if(SequenceEvent.What.VALUE_CHANGED.equals(evtType)){ + Sequence.Value aVal = t.getValue(); + + float verticalValue = new MathUtils.Scale(t.getSource().getRange(), Range.NORMALIZED_RANGE_F).linear(aVal.getValue()); + + haptics.sendMessage(Command.SEQUENCE_VALUE_CHANGE, aVal.getTimePosition() +" "+verticalValue, aVal.hashCode()); + }else if(SequenceEvent.What.VALUE_REMOVED.equals(evtType)){ + Sequence.Value aVal = t.getValue(); + haptics.sendMessage(Command.SEQUENCE_VALUE_REMOVE, "", aVal.hashCode()); + }else if(SequenceEvent.What.BEGIN_CHANGED.equals(evtType)){ + Sequence sequence = t.getSource(); + + haptics.sendMessage(Command.SEQUENCE_BEGIN, + ""+new MathUtils.Scale(sequence.getRange(),Range.NORMALIZED_RANGE_F).linear(sequence.getBegin()), + 0 + ); + } + } + + } + + /** + * Gets an update from a {@code SoundWave} this class is a listener of, and send a message to the {@code HapticDevice} + * thread accordingly. + * + * @param evt the new event + */ + @Override + public void update(SoundWaveEvent evt) { + String evtType = evt.getType(); + + if(SoundWaveEvent.POSITION_CHANGED.equals(evtType)){ + SoundWave wave = evt.getSource(); + int pos = (Integer)evt.getArgs(); + + if(pos < wave.getChunkNum()){ + Chunk chunk = wave.getChunkAt(pos); + float chunkSize = chunk.getNormEnd() - chunk.getNormStart(); + /* sends a normalized value of the chunk size */ + haptics.sendMessage(Command.RENDER_VALUE, Float.toString(chunkSize) , chunk.hashCode()); + }else{ + /* if the scub goes past the audio wave just send wave amp = 0 */ + haptics.sendMessage(Command.RENDER_VALUE, "0", 0); + } + }else if(SoundWaveEvent.CLOSE.equals(evtType)){ + /* removes itself from listeners */ + evt.getSource().removeSoundWaveListener(this); + } + } + + + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/haptics/HapticViewPort.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/haptics/HapticViewPort.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,78 @@ +package uk.ac.qmul.eecs.depic.daw.haptics; + +public class HapticViewPort { + public static final int DEFAULT_WIDTH = 2320; // 2 secs at scale factor =1 + //with 8000 Hz frame rate. x = 4000/frameRate * minChunksize => 2 = 4000/8000 * 4 + public final static float STEP_SIZE = 5.0f;// in milliseconds + private static final double LOG_2 = Math.log(2); + + /* the width of the viewport */ + private int width; + private float offset; + private int trackSize; + private int scaleFactor; + + public HapticViewPort(int scaleFactor){ + offset = 0.0f; + width = DEFAULT_WIDTH; + this.scaleFactor = scaleFactor; + trackSize = 0; + } + + + /** + * + * @param amount the amount of shift in pixels (or chunks) + */ + public void shift(float amount){ + if(width > trackSize) // shifts only if viewport is smaller than the whole track + return; + + offset += amount; + if(offset < 0) // cannot shift more left than 0 + offset = 0; + else if(offset > (trackSize - width)) // cannot more right than the track size-VIEWPORT_SIZE + offset = (trackSize - width); // (as the offset counts for the viewport start) + } + + /** + * Returns the position in the haptic view port that is mapped to {@code ratio}. + * {@code ratio} is a normalized value ranging from 0 to 1. + * + * + * @param ratio the position on the haptic view port as a ratio of the haptic view port width + * @return the the position on the haptic view port as chunk/pixel position + */ + public int getPosition(float ratio){ + return (int)(offset+(width*ratio)); + } + + public void setTrackSize(int trackSize){ + this.trackSize = trackSize; + } + + public int getScaleFactor() { + return scaleFactor; + } + + public int getWidth(){ + return width; + } + + public void setScaleFactor(int scaleFactor) { + if(scaleFactor <= 0) + throw new IllegalArgumentException("scaleFactor must be greater than 0"); + this.scaleFactor = scaleFactor; + + /* this formula makes the viewport size grow linearly-ish * + * It grows as a function of scaleFactor : (2 * (2^(scaleFactor - 1)) / scaleFactor) + 2*log_2(scaleFactor)) */ + double w = (DEFAULT_WIDTH / scaleFactor ) + (log2(scaleFactor) * DEFAULT_WIDTH/Math.pow(2, scaleFactor-1)) ; + width = (int)w; + } + + public static double log2(double x) { + return Math.log(x)/LOG_2; + } +} + + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/haptics/HaptificationPrefsPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/haptics/HaptificationPrefsPanel.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,131 @@ +package uk.ac.qmul.eecs.depic.daw.haptics; + + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.prefs.Preferences; + +import javax.swing.JLabel; + +import uk.ac.qmul.eecs.depic.daw.gui.PreferencesPanel; +import uk.ac.qmul.eecs.depic.daw.referencesound.GridSound; + +import javax.swing.JComboBox; + +public class HaptificationPrefsPanel extends PreferencesPanel { + + public static GridSound.Type [] SOUND_TYPES = { + GridSound.Type.NONE, + GridSound.Type.PITCH, + GridSound.Type.PITCH_ONE_REF, + GridSound.Type.PITCH_MUL_REF + }; + + public static String [] GRID_TYPES = { + "freeform","grid0","grid1", "grid3" + }; + + private static String [] keys = new String [] { + "sound_type", + "grid_type", + }; + + private static final long serialVersionUID = 1L; + + + private static HaptificationPrefsPanel singleton; + private JComboBox soundComboBox; + private JComboBox gridComboBox; + public static HaptificationPrefsPanel getInstance(){ + if(singleton == null) + singleton = new HaptificationPrefsPanel(); + return singleton; + } + + /** + * Create the panel. + */ + private HaptificationPrefsPanel() { + + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{30, 70, 78, 189, 0, 0}; + gridBagLayout.rowHeights = new int[]{30, 18, 0, 0, 0, 0, 0}; + gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 1.0, 0.0, Double.MIN_VALUE}; + gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + setLayout(gridBagLayout); + + JLabel selectionFilterLabel = new JLabel("Sound Type:"); + GridBagConstraints gbc_selectionFilterLabel = new GridBagConstraints(); + gbc_selectionFilterLabel.anchor = GridBagConstraints.WEST; + gbc_selectionFilterLabel.insets = new Insets(0, 0, 5, 5); + gbc_selectionFilterLabel.gridx = 1; + gbc_selectionFilterLabel.gridy = 1; + add(selectionFilterLabel, gbc_selectionFilterLabel); + + /* prefs are used to initialize the spinners and the grain ugens */ + Preferences prefs = Preferences.userNodeForPackage(this.getClass()); + + soundComboBox = new JComboBox<>(SOUND_TYPES); + soundComboBox.setSelectedItem(prefs.get(keys[1], SOUND_TYPES[0].toString())); + GridBagConstraints gbc_soundComboBox = new GridBagConstraints(); + gbc_soundComboBox.fill = GridBagConstraints.HORIZONTAL; + gbc_soundComboBox.insets = new Insets(0, 0, 5, 5); + gbc_soundComboBox.gridx = 3; + gbc_soundComboBox.gridy = 1; + add(soundComboBox, gbc_soundComboBox); + + JLabel borderThreshLabel = new JLabel("Grid Type :"); // FIXME boudles + GridBagConstraints gbc_borderThreshLabel = new GridBagConstraints(); + gbc_borderThreshLabel.anchor = GridBagConstraints.WEST; + gbc_borderThreshLabel.insets = new Insets(0, 0, 5, 5); + gbc_borderThreshLabel.gridx = 1; + gbc_borderThreshLabel.gridy = 2; + add(borderThreshLabel, gbc_borderThreshLabel); + + gridComboBox = new JComboBox<>(GRID_TYPES); + gridComboBox.setSelectedItem(prefs.get(keys[1], GRID_TYPES[0])); + GridBagConstraints gbc_gridComboBox = new GridBagConstraints(); + gbc_gridComboBox.insets = new Insets(0, 0, 5, 5); + gbc_gridComboBox.fill = GridBagConstraints.HORIZONTAL; + gbc_gridComboBox.gridx = 3; + gbc_gridComboBox.gridy = 2; + add(gridComboBox, gbc_gridComboBox); + + /* sets all the strings for the screen reader */ + configureAccessibleContext(); + } + + + private void configureAccessibleContext(){ + } + + @Override + public void savePrefs() { + Preferences prefs = Preferences.userNodeForPackage(this.getClass()); + + prefs.put(getPrefsList()[0], soundComboBox.getItemAt(soundComboBox.getSelectedIndex()).toString()); + prefs.put(getPrefsList()[1], gridComboBox.getItemAt(gridComboBox.getSelectedIndex())); + + } + + public Preferences getPrefs(){ + Preferences prefs = Preferences.userNodeForPackage(this.getClass()); + + prefs.put(getPrefsList()[0], prefs.get(getPrefsList()[0], SOUND_TYPES[0].toString())); + prefs.put(getPrefsList()[1], prefs.get(getPrefsList()[1], GRID_TYPES[0])); + + return prefs; + } + + @Override + public String getTitle(){ + return "Haptics"; + } + + @Override + public String[] getPrefsList() { + return keys; + } +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/referencesound/BeadsSequenceMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/referencesound/BeadsSequenceMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,315 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.referencesound; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.ugens.Gain; +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.Sequence.Value; +import uk.ac.qmul.eecs.depic.patterns.SequenceMapping; + +/** + * Implements the sequence mapping using the Beads sound engine + * + * + * + */ +abstract class BeadsSequenceMapping implements SequenceMapping { + public static final float DEFAULT_VALUE_LEN_MS = 50.0f; + public static final float DEFAULT_VALUE_REF_LEN_MS = 50.0f; + public static final float DEFAULT_VALUE_AMP = .5f; + public static boolean verboseMode = true; + + protected static final float RATE_STEP = 0.1f; + + protected AudioContext ac; + /* the master gain */ + protected Gain masterGain; + + private PitchedUGen renderCurveAtUGen; + private Gain renderCurveGain; + private float renderValueLen; + private float renderValueRefLen; + private float renderValueAmp; + + private Bead lastTrail; + + + /** + * + * @param ac an audio context. The audio context must be started outside this class + */ + protected BeadsSequenceMapping(AudioContext ac){ + this.ac = ac; + masterGain = new Gain(ac, 1, 1.0f); + ac.out.addInput(masterGain); + renderValueLen = DEFAULT_VALUE_LEN_MS; + renderValueRefLen = DEFAULT_VALUE_REF_LEN_MS; + + renderValueAmp = DEFAULT_VALUE_AMP; + + } + + + /** + * + * returns the ugen used to render a sequence value + * + * @return + */ + protected abstract PitchedUGen createRenderValueUGen(); + + + /** + * returns a + * @return + */ + protected abstract PitchedUGen createRenderCurveUGen(); + + protected abstract Range getRenderCurvePitchSpan(); + + protected abstract Range getRenderValuePitchSpan(); + + public void setRenderValueRefLen(float renderValueRefLen) { + this.renderValueRefLen = renderValueRefLen; + } + + + public float getRenderValueRefLen() { + return renderValueRefLen; + } + + + public void setRenderValueLen(float milliseconds){ + renderValueLen = milliseconds; + } + + public float getRenderValueLen(){ + return renderValueLen; + } + + public float getRenderValueAmp() { + return renderValueAmp; + } + + public void setRenderValueAmp(float renderValueAmp) { + this.renderValueAmp = renderValueAmp; + } + + + @Override + public void renderValue(Value val) { + + if(lastTrail != null){ + lastTrail.kill(); + lastTrail = null; + } + + float v = val.getValue(); + + Sequence sequence = val.getSequence(); + Range range = sequence.getRange(); + + /* Map the value to the pitch: */ + MathUtils.Scale scale = new MathUtils.Scale( + range, + getRenderValuePitchSpan() + ); + + float pitch = scale.exponential(v); + + PitchedUGen valuePlayer = createRenderValueUGen(); + valuePlayer.setLen(renderValueAmp, renderValueLen); + valuePlayer.setPitch(pitch); + + masterGain.addInput(valuePlayer); + } + + public void renderValueOneRef(Value val){ + if(lastTrail != null){ + lastTrail.kill(); + lastTrail = null; + } + + Range r = val.getSequence().getRange(); + float v = r.getStart() + (r.getEnd() - r.getStart())/2 ; + + Sequence sequence = val.getSequence(); + Range range = sequence.getRange(); + + /* Map the value to the pitch: */ + MathUtils.Scale scale = new MathUtils.Scale( + range, + getRenderValuePitchSpan() + ); + + float pitch = scale.exponential(v); + + PitchedUGen valuePlayer = createRenderValueUGen(); + valuePlayer.setLen(renderValueAmp, renderValueLen); + valuePlayer.setPitch(pitch); + + masterGain.addInput(valuePlayer); + } + + public void renderValueMulRef(Value val, float resolution) { + + if(lastTrail != null){ + lastTrail.kill(); + lastTrail = null; + } + + Range r = val.getSequence().getRange(); + float reference = r.getStart() + (r.getEnd() - r.getStart())/2; + + float v = val.getValue(); + Sequence sequence = val.getSequence(); + Range range = sequence.getRange(); + + if(!rangeContains(range,v) || !rangeContains(range,reference)){ + throw new IllegalArgumentException("One or more values not in range val:"+ v + " reference:" +reference); + } + + if(Float.compare(resolution, 0) <= 0){ + throw new IllegalArgumentException("Resolution must be positive. resolution:"+resolution); + } + + /* if the step is bigger than the start and end make the step = end-start */ + if(resolution > Math.abs(v-reference) ){ + resolution = Math.abs(v-reference); + } + + /* Map the value to the pitch: */ + MathUtils.Scale scale = new MathUtils.Scale( + range, + getRenderValuePitchSpan() + ); + + final PitchedUGen valuePlayer = createRenderValueUGen(); + + boolean onePitchOnly = true; + if(v > reference){ // scale from v down to reference + /* goes all the way from v-resolution to the last value before reaching the + * reference, each step is of resolution */ + for(float i=v-resolution; i>=reference; i = i-resolution){ + final float nextPitch = scale.exponential(i); + + if(i == v-resolution){ // first run + valuePlayer.setPitch(nextPitch); + }else{ + onePitchOnly = false; + valuePlayer.addLen(renderValueAmp, renderValueRefLen,new Bead(){ + @Override + public void messageReceived(Bead bead){ + valuePlayer.setPitch(nextPitch); + } + }); + } + } + + /* add the last bit of envelope that will kill the UGen */ + /* if the envelope has many pitches already used addLen which, if env is != null + * will bring it to 0. Otherwise use setLen that brings the envelope to 0 */ + if(onePitchOnly){ + valuePlayer.setLen(renderValueAmp, renderValueRefLen); + }else{ + valuePlayer.addLen(renderValueAmp, renderValueRefLen); + } + + lastTrail = valuePlayer; + masterGain.addInput(valuePlayer); + }else if(v < reference){ // scale from v up to reference + + for(float i=v+resolution; i<=reference; i = i+resolution){ + final float nextPitch = scale.exponential(i); + + if(i == v+resolution){ // first run + valuePlayer.setPitch(nextPitch); + }else{ + onePitchOnly = false; + valuePlayer.addLen(renderValueAmp, renderValueRefLen,new Bead(){ + @Override + public void messageReceived(Bead bead){ + valuePlayer.setPitch(nextPitch); + } + }); + } + } + + /* add the last bit of envelope that will kill the UGen */ + /* if the envelope has many pitches already used addLen which, if env is != null + * will bring it to 0. Otherwise use setLen that brings the envelope to 0 */ + if(onePitchOnly){ + valuePlayer.setLen(renderValueAmp, renderValueRefLen); + }else{ + valuePlayer.addLen(renderValueAmp, renderValueRefLen); + } + + lastTrail = valuePlayer; + masterGain.addInput(valuePlayer); + + } + } + + private static boolean rangeContains(Range range, float f){ + return (Float.compare(f, range.getStart()) >= 0 && Float.compare(f, range.getEnd()) <= 0); + } + + @Override + public void renderCurveAt(Sequence sequence, float time, float duration){ + /* negative value means stop now */ + if(duration < 0.0f ){ + if(renderCurveAtUGen != null) + renderCurveAtUGen.kill(); + renderCurveAtUGen = null; + return; + } + + /* look for the two sequence values, whose times contain time in between */ + float valueAtTime = new MathUtils.Interpolate(sequence).linear(time); + float pitch = new MathUtils.Scale(sequence.getRange(),getRenderCurvePitchSpan()).linear(valueAtTime); + + /* infinite duration means play until renderCurveAt is called again with duration = -1 */ + if(Float.isInfinite(duration)){ + if(renderCurveAtUGen == null){ + renderCurveAtUGen = createRenderCurveUGen(); + renderCurveAtUGen.setPitch(pitch); + masterGain.addInput(renderCurveAtUGen); + renderCurveAtUGen.start(); + }else{ + renderCurveAtUGen.setPitch(pitch); + } + }else if(duration > 0.0f){ + if(renderCurveAtUGen == null){ + renderCurveAtUGen = createRenderCurveUGen(); + renderCurveAtUGen.setPitch(pitch); + renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f); + ac.out.addInput(renderCurveAtUGen); + renderCurveAtUGen.start(); + }else{ + renderCurveAtUGen.setLen(duration*0.8f, duration*0.2f); + } + } + } + +} + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/referencesound/BeadsWaveTableSequenceMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/referencesound/BeadsWaveTableSequenceMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,199 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.referencesound; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.data.Buffer; +import net.beadsproject.beads.ugens.Envelope; +import net.beadsproject.beads.ugens.Gain; +import net.beadsproject.beads.ugens.WavePlayer; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; + +/** + * Implements a sequence mapping using beads engine and simple wave tables ugens + * + * + */ +class BeadsWaveTableSequenceMapping extends BeadsSequenceMapping { + private Range renderCurvePitchSpan; + private Range renderValuePitchSpan; + private static final float ATTACK_TIME_MS = 5.0f; + + static { + /* loads the buffer for the PeakLevelPitchedUgen and stores it into staticBuffs */ + int bufferLen = 4096; + Buffer pulse = new Buffer(bufferLen); + int pulseWidth = bufferLen/10; + + for(int i=0; i curvePitchSpan, Range valuePitchSpan){ + super(ac); + renderCurvePitchSpan = curvePitchSpan; + renderValuePitchSpan = valuePitchSpan; + } + + @Override + protected PitchedUGen createRenderValueUGen(){ + return new BufferPitchedUGen(ac, renderValuePitchSpan, Buffer.SINE); + } + + @Override + protected PitchedUGen createRenderCurveUGen(){ + return new BufferPitchedUGen(ac, renderCurvePitchSpan, Buffer.SINE); + } + + + @Override + protected Range getRenderValuePitchSpan() { + return renderValuePitchSpan; + } + + @Override + protected Range getRenderCurvePitchSpan() { + return renderCurvePitchSpan; + } + + private class BufferPitchedUGen extends PitchedUGen { + private Gain gain; + private WavePlayer player; + private float maxFreq; + private float minFreq; + private Envelope env; + + /** + * + * Pulse TriangularBuffer Square Saw Noise Sine + * + * @param ac + * @param pitchSpan + * @param buffer + */ + public BufferPitchedUGen(AudioContext ac, Range pitchSpan, Buffer buffer) { + super(ac); + if(maxFreq < minFreq){ + throw new IllegalArgumentException("minFreq must be lower than maxFreq. minFreq: "+minFreq+" maxFreq:"+maxFreq ); + } + + this.minFreq = pitchSpan.getStart(); + this.maxFreq = pitchSpan.getEnd(); + gain = new Gain(ac, 1, 0.5f); + player = new WavePlayer(ac,minFreq,buffer); + + gain.addInput(player); + + addToChainOutput(gain); + } + + @Override + public void setPitch(float pitch){ + /* set the frequency for both the actual curve sound and the threshold sound */ + player.setFrequency(pitch); + + } + + @Override + public void setPitch(UGen pitch) { + player.setFrequency(pitch); + } + + public float getFrequency(float freq){ + return player.getFrequency(); + } + + @Override + public void start(){ + player.start(); + } + + + @Override + public void setLen(float sustainValue, float decayTime){ + /* if decayTime is long enough, smooth out the attack to avoid glitchy sound */ + if(Float.compare(decayTime,ATTACK_TIME_MS) > 0){ + env = new Envelope(context,0.0f); // starts from 0 + env.addSegment(sustainValue,ATTACK_TIME_MS,2); // attack + env.addSegment(0.0f,decayTime-ATTACK_TIME_MS, this); // decay + }else{ + env = new Envelope(context,sustainValue); + env.addSegment(0.0f, decayTime, this); // decay + } + gain.setGain(env); + } + + @Override + public void addLen(float sustainValue, float duration, final Bead bead){ + if(env == null){ + if(Float.compare(duration,ATTACK_TIME_MS) > 0){ + env = new Envelope(context,0.0f); // starts from 0 + env.addSegment(sustainValue,ATTACK_TIME_MS,2); // attack + env.addSegment(sustainValue,duration-ATTACK_TIME_MS, bead); // decay + }else{ + env = new Envelope(context,sustainValue); + env.addSegment(0.0f, duration, bead); // decay + } + gain.setGain(env); + }else{ + env.addSegment(sustainValue,duration, bead); + } + + } + + @Override + public void addLen(float sustainValue, float duration ){ + if(env == null){ + if(Float.compare(duration,ATTACK_TIME_MS) > 0){ + env = new Envelope(context,0.0f); // starts from 0 + env.addSegment(sustainValue,ATTACK_TIME_MS,2); // attack + env.addSegment(sustainValue,duration-ATTACK_TIME_MS, this); // decay + }else{ + env = new Envelope(context,sustainValue); + env.addSegment(sustainValue, duration, this); // decay + } + gain.setGain(env); + }else{ + env.addSegment(0.0f,duration, this); + } + + } + + @Override + public void messageReceived(Bead b){ + kill(); + } + + } + + @Override + public void renderCurve(Sequence m, float startTime) { + throw new UnsupportedOperationException(); + } +};; \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/referencesound/GridSound.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/referencesound/GridSound.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,140 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.referencesound; + +import java.util.Timer; +import java.util.TimerTask; + +import net.beadsproject.beads.core.AudioContext; +import uk.ac.qmul.eecs.depic.patterns.Range; +import uk.ac.qmul.eecs.depic.patterns.Sequence; +import uk.ac.qmul.eecs.depic.patterns.Sequence.Value; + +public abstract class GridSound { + public static final long REFERENCE_DELAY_MS = 200; + protected BeadsSequenceMapping mapping; + protected Timer timer; + public enum Type { + NONE("none"), + PITCH("pitch"), + PITCH_ONE_REF("pitch one ref"), + PITCH_MUL_REF("pitch mul ref"); + + String toStr; + + Type(String s){ + toStr = s; + } + + @Override + public String toString(){ + return toStr; + } + } + + public static GridSound get(GridSound.Type type){ + + GridSound newRepres; + + switch(type){ + case NONE : newRepres = new None(); break; + case PITCH: newRepres = new Single(); break; + case PITCH_ONE_REF : newRepres = new Ref(); break; + case PITCH_MUL_REF : newRepres = new MulRef(); break; + default : throw new IllegalArgumentException("Type string not recognized: " + type); + } + + return newRepres; + } + + protected GridSound(){ + AudioContext ac = new AudioContext(); + ac.start(); + mapping = new BeadsWaveTableSequenceMapping(ac, new Range(0.0f,0.0f),new Range(120.0f,5000.0f)); + timer = new Timer("Ref Point Timer"); + } + + public abstract void sound(Sequence.Value val); + + public abstract void ref(Sequence.Value val); + + + private static class None extends GridSound { + + @Override + public void sound(Value val) { + // does nothing + } + + @Override + public void ref(Value val) { + // does nothing + } + } + + private static class Single extends GridSound { + + float previousVal = Float.NaN; + + @Override + public void sound(Value val) { + if(Float.compare(val.getValue(), previousVal) != 0){ + mapping.renderValue(val); + previousVal = val.getValue(); + }; + } + + @Override + public void ref(Value val) { + //does nothing + } + } + + private static class Ref extends GridSound { + protected TimerTask lastTimerTask = new TimerTask(){public void run(){}}; + @Override + public void sound(final Value val) { + mapping.renderValue(val); + + lastTimerTask.cancel(); + + lastTimerTask = new TimerTask(){ + + @Override + public void run() { + ref(val); + } + }; + timer.schedule(lastTimerTask, REFERENCE_DELAY_MS); + } + + @Override + public void ref(Value val) { + mapping.renderValueOneRef(val); + } + } + + private static class MulRef extends Ref { + + @Override + public void ref(Value val) { + mapping.renderValueMulRef(val, /*resolution =*/ 1.0f); + } + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/daw/referencesound/PitchedUGen.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/daw/referencesound/PitchedUGen.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,49 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.referencesound; + +import net.beadsproject.beads.core.AudioContext; +import net.beadsproject.beads.core.Bead; +import net.beadsproject.beads.core.UGen; +import net.beadsproject.beads.core.UGenChain; + +abstract class PitchedUGen extends UGenChain { + + public PitchedUGen(AudioContext ac) { + super(ac, 0, 2); + } + + public abstract void setPitch(float pitch); + + public abstract void setPitch(UGen pitch); + + /** + * + * Kills the UGen when decayTime has elapsed + * + * @param sustain + * @param decay + */ + public abstract void setLen(float sustain, float decayTime); + + public abstract void addLen(float sustain, float decayTime, Bead bead); + + public abstract void addLen(float sustain, float decayTime); + +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/MathUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/MathUtils.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,151 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + + +public class MathUtils { + + public static boolean equal(float a, float b){ + return Float.compare(a, b) == 0; + } + + public static boolean equal(double a, double b){ + return Double.compare(a, b) == 0 ; + + } + + public static boolean equal(float a, float b , float epsilon){ + return Math.abs(a-b) < epsilon; + } + + public static boolean equal(double a, double b , double epsilon){ + return Math.abs(a-b) < epsilon; + } + + + /** + * + * @param amp amplitude value in the normalized form (ranging from 0 to 1) + * + * @return the decibel conversion of {@code amp} + */ + public static float toDb(float amp){ + if(Float.compare(amp,0) < 0){ + throw new RuntimeException(); + } + + return (float)(20.0 * Math.log10(amp)); + } + + public static float toAmp(float db){ + return (float) (Math.pow(10, db/ 20.0)); + } + + + public static class Scale { + private Range from; + private Range to; + + public Scale(Range rangeFrom, Range rangeTo) { + this.from = rangeFrom; + this.to = rangeTo; + } + + public Scale(float startFrom, float endFrom,float startTo, float endTo) { + this(new Range(startFrom,endFrom),new Range(startTo,endTo)); + } + + public float linear(float num){ + if(Float.compare(num, from.getStart()) < 0 || Float.compare(num,from.getEnd()) > 0) + throw new IllegalArgumentException("num must be in rangeFrom interval. num:"+num+" rangeFrom:"+from); + + float ratio = (num - from.getStart()) / from.lenght(); + + return to.getStart() + (ratio * to.lenght()); + } + + public float exponential(float num){ + if(Float.compare(num, from.getStart()) < 0 || Float.compare(num,from.getEnd()) > 0) + throw new IllegalArgumentException("num must be in rangeFrom interval. num:"+num+" rangeFrom:"+from); + + if(Float.compare(to.getStart(),0) == 0 ) + throw new IllegalArgumentException("Cannot call esponential when destination range starts from 0"); + + num = (num - from.getStart())/ from.lenght(); + + float toRatio = to.getEnd() / to.getStart(); + return (float) (Math.pow(toRatio,num) * to.getStart()); + } + } + + /** + * + * Calculates values at any time in a sequence by interpolating the sequence values. + * + * A sequence is a finite series of float values at given times. In order to get values + * at any times, interpolation is necessary. This class can be useful to build a graph out of + * the sequence values. Of course, as the interpolation changes, the graph changes as well, as + * values at times different from those specified by the sequence will be calculated differently. + * + */ + public static class Interpolate { + private Sequence sequence; + + public Interpolate(Sequence sequence){ + this.sequence = sequence; + } + + public float linear(float time){ + /* initialise with start and end of the sequence */ + float startValue = sequence.getBegin(); + float endValue = sequence.getEnd(); + float startTimePos = 0; + float endTimePos = sequence.getLen(); + + + for(int i=0; i= 0){ + startValue = sv.getValue(); + startTimePos = sv.getTimePosition(); + } + + if(Float.compare(time, sv.getTimePosition()) <= 0){ + endValue = sv.getValue(); + endTimePos = sv.getTimePosition(); + break; // can break because values are ordered by time + } + } + + /* let D be the distance between time and start, then ratio * + * is the ratio between D and the start-end interval length * + * if startTimepos and endtimePos the ratio is going to be 0*/ + float ratio = MathUtils.equal(startTimePos, endTimePos) ? 0.0f : ((time - startTimePos) / (endTimePos-startTimePos)); + + + /* this comes from the following formula: + * valueAtTime = start.getValue + (ratio * (end.getValue() - start.getValue()) */ + float valueAtTime = (1.0f - ratio) * startValue + ratio * endValue; + + return valueAtTime; + } + + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/Range.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/Range.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,87 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + +/** + * + * Immutable class containing two comparable number which represent the start and the end of a range. + * + * @param + */ + public class Range > { + protected T start; + protected T end; + + public Range(T t1, T t2){ + if(t1.compareTo(t2) < 1){ + start = t1; + end = t2; + }else{ + start = t2; + end = t1; + } + } + + /** + * Creates a new instance by comparing {@code mm1} and {@code mm2}. + * + * The minimum of this object will be the minimum value between {@code mm1.getMin()} and {@code mm2.getMin()}. + * The maximum of this object will be the maximum value between {@code mm1.getMax()} and {@code mm2.getMax()}. + * + * @param r1 the former Range whose min and max are to be compared + * @param r2 the latter Range whose min and max are to be compared + */ + public Range(Range r1, Range r2){ + if(r1.getStart().compareTo(r2.getStart()) < 0){ // if the minimum of mm1 is less than the minimum of mm2 + start = r1.getStart(); // then min is the minimum of mm1 + }else{ // else + start = r2.getStart(); // min is the minimum of mm2 + } + + if(r1.getEnd().compareTo(r2.getEnd()) > 0){ // if the maximum of mm1 is greater than the maximum of mm2 + end = r1.getEnd(); // then max is the maximum of mm1 + }else{ // else + end = r2.getEnd(); // max is the maximum of mm2 + } + } + + protected Range(){ + // empty range + } + + public T getStart() { + return start; + } + public T getEnd() { + return end; + } + + public float lenght(){ + return getEnd().floatValue() - getStart().floatValue(); + } + + @Override + public String toString(){ + return "Range ["+ getStart()+","+getEnd()+"]"; + } + + public static final Range NORMALIZED_RANGE_F = new Range<>(0.0f,1.0f); + public static final Range NORMALIZED_RANGE_D = new Range<>(0.0,1.0); + } + diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/Sequence.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/Sequence.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,62 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + +/** + * + * A sequence of values arranged over time. + * + * The values are ordered by time, therefore given @ {@code i} and {@code j} such that {@code (i < j)}, the following expression is always true : + * {@code sequence.getValueAt(i).getTimePosition() <= sequence.getValueAt(j).getTimePosition()} + * + * The values are limited in range. {@code getRange()) returns the range + * of float values that a sequence value can assume. + * + */ +public interface Sequence { + public float getBegin(); + + public float getEnd(); + + public int getValuesNum(); + + public Value getValueAt(int index); + + public Range getRange(); + + public void addSequenceListener(SequenceListener l); + + public void removeSequenceListener(SequenceListener l); + + /** + * The length of the sequence in millisecons + * @return + */ + public float getLen(); + + public interface Value { + public float getValue(); + + public float getTimePosition(); + + public Sequence getSequence(); + + public int index(); + } +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/SequenceEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/SequenceEvent.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,61 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + +import java.util.EventObject; + + +public class SequenceEvent extends EventObject{ + private static final long serialVersionUID = 1L; + + public enum What { + VALUE_ADDED, + VALUE_REMOVED, + VALUE_CHANGED, + END_CHANGED, + BEGIN_CHANGED + }; + private Sequence.Value value; + private SequenceEvent.What what; + + public SequenceEvent(Sequence seq,Sequence.Value val, What what){ + super(seq); + this.value = val; + this.what = what; + } + + public What getWhat(){ + return what; + } + + @Override + public Sequence getSource(){ + return (Sequence)super.getSource(); + } + + /** + * If the event concerns a particular value, it returns a reference to that value. + * If the event concerns the whole sequence it returns {@code null}. + * + * @return a reference to the value, or {@code null} if the event concerns no value + */ + public Sequence.Value getValue(){ + return value; + } +} \ No newline at end of file diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/SequenceListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/SequenceListener.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,33 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + +public interface SequenceListener { + /** + * This method has to be implemented by classes that provide a continuous + * representation of the sequence. + * + * The method a callback that is called as soon as one of the values of the the + * sequence is added, deleted or modified. Implementors can thus promptly update their representation + * to reflect the changes in the sequence. + * + * @param t an event holding information about the sequence that has been modfied. + */ + public void sequenceUpdated(T t); +} diff -r 000000000000 -r 3074a84ef81e src/uk/ac/qmul/eecs/depic/patterns/SequenceMapping.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uk/ac/qmul/eecs/depic/patterns/SequenceMapping.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,61 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.patterns; + +/** + * + * A mapping of a sequence into some form of representation, e.g. visual, audio, audio-visual etc. + * + * The methods of this interface represent different ways a sequence can be rendered and + * need not be all implemented in classes implementing this interface. + * + */ +public interface SequenceMapping { + + public float DURATION_INF = Float.POSITIVE_INFINITY; + public float DURATION_STOP = -1.0f; + + public void renderValue(Sequence.Value val); + + /** + * + * @param m + * @param startTime the time in millisecond the curve rendering has to start from, if + * -1 will immediately stop the rendering. + */ + public void renderCurve(Sequence m, float startTime); + + /** + * + * Render a curve at precise time in sound. + * + * The sound lasts {@code duration} milliseconds and then fades out. + * If {@code INF} is passed as duration the sound will go on forever. Successive calls with {@code INF} as duration + * will have the effect of changing the sound in order to represent the curve at the new {@code time} passed as argument. + * Successive calls with {@code duration} different from {@code DURATION_INF} will carry on the sound for {@code duration} + * millisecond (and therefore setting {@code duration} to 0 will stop the sound). + * + * @param sequence the sequence to render + * @param time the time at which the sequence has to be rendered + * @param duration the duration of sound in millisecond. If (@code DURATION_INF) is used the sound + * will go on forever until it's stopped. To stop the sound use {@code duration = -1}; + */ + public void renderCurveAt(Sequence sequence, float time, float duration); + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/AllTests.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/AllTests.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,35 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ + SoundWaveChunkTest.class, + //AudioTrackTest.class, + ClipListTest.class, + ClipTest.class, + MathUtilsTest.class, + SoundWaveChunkTest.class}) +public class AllTests { + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/AudioFileLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/AudioFileLoader.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,125 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.SwingUtilities; + +import uk.ac.qmul.eecs.depic.daw.SoundWave; + +public class AudioFileLoader { + private static boolean swingIsDone; + + public static void loadFile(final SoundWave wave, final String resourcePath){ + long mill = System.currentTimeMillis(); + swingIsDone = false; + try { + SwingUtilities.invokeAndWait(new Runnable(){ + @Override + public void run() { + URI fileURI = null; + try { + fileURI = getClass().getResource(resourcePath).toURI(); + } catch (URISyntaxException e) { + new RuntimeException(e.getMessage()).printStackTrace(); + System.exit(-1); + } + wave.loadAudioData(new File(fileURI), new PropertyChangeListener(){ + @Override + public void propertyChange(PropertyChangeEvent evt) { + if("progress".equals(evt.getPropertyName())){ + if((Integer)evt.getNewValue() == SoundWave.FILE_LOAD_TOTAL_PROGRESS){ + synchronized(wave){ + swingIsDone = true; + wave.notify(); + } + } + }else if(SoundWave.FILE_ERROR_PROPERTY.equals(evt.getPropertyName())){ + new RuntimeException("could not open file:"+evt.getNewValue()).printStackTrace(); + System.exit(-1); + } + } + }); + } + }); + } catch (InvocationTargetException | InterruptedException e) { + e.printStackTrace(); + System.exit(-1); + } + + /* wait for the swing thread to finish. The swing thread will be finished when propertyChange with progress = * + * SoundWave.FILE_LOAD_TOTAL_PROGRESS will be scheduled for execution in the EDT, by the swing worker */ + synchronized(wave){ + while(!swingIsDone) + try { + wave.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + System.exit(-1); + } + } + System.out.println("File loaded in "+(System.currentTimeMillis()-mill)+" milliseconds"); + + } + + public static byte[] getAudioBuffer(URL fileURL){ + AudioInputStream inputStream = null; + try { + AudioInputStream originalInputStream = AudioSystem.getAudioInputStream(fileURL); + AudioFormat format = originalInputStream.getFormat(); + /* one byte format to check for silence in the wav file without risk of error from audio formats */ + AudioFormat newformat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, + format.getSampleRate(), + 8, + 1, + 1, + format.getFrameRate(), + true // big endian + ); + inputStream = AudioSystem.getAudioInputStream(newformat,originalInputStream); + byte[] buffer = new byte[1024 * newformat.getFrameSize()]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int numBytesRead = 0; + while((numBytesRead = inputStream.read(buffer)) != -1){ + out.write(buffer, 0, numBytesRead); + } + inputStream.close(); + return out.toByteArray(); + + } catch (UnsupportedAudioFileException | IOException e) { + e.printStackTrace(); + System.exit(-1); + } + + return null; + } +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/AudioTrackTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/AudioTrackTest.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,143 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import static org.junit.Assert.assertEquals; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import org.junit.Test; + +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent; +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSoundEngineFactory; +import uk.ac.qmul.eecs.depic.daw.gui.AudioTrack; + +public class AudioTrackTest { + private AudioTrack track; + private SoundWaveEvent evt; + private CursorPositionChecker checker; + + public AudioTrackTest(){ + SoundWave wave = new BeadsSoundEngineFactory() + .setSoundWaveParameters(128,10,5) + .createSoundWave(); // scaleFactor ranges from 1 to 10 + AudioFileLoader.loadFile(wave, "./audio/sound1.wav"); // need to load file to avoid null pointers + /* checker's expected value is the assert expected value */ + checker = new CursorPositionChecker(); + track = new AudioTrack(wave); + evt = new SoundWaveEvent(wave,SoundWaveEvent.POSITION_CHANGED); + } + + @Test + public void testPositionUpdate() { + checker.setExpectedValue(0); + // regardless of scale factor 0 will always map to 0 + for(int i = 1; i<=10; i++){ + resetCursorPos(-1); + track.setScaleFactor(i); + evt.setArgs(0); + track.update(evt); + } + + /* track scale factor = 3 */ + track.removePropertyChangeListener(checker); + track.setScaleFactor(3); + track.addPropertyChangeListener(checker); + + resetCursorPos(-1); + checker.setExpectedValue(0); + evt.setArgs(1); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(0); + evt.setArgs(3); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(1); + evt.setArgs(4); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(1); + evt.setArgs(5); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(1); + evt.setArgs(7); + track.update(evt); + + /* track scale factor = 1*/ + track.removePropertyChangeListener(checker); + track.setScaleFactor(1); + track.addPropertyChangeListener(checker); + + resetCursorPos(-1); + checker.setExpectedValue(0); + evt.setArgs(new Selection(0,3)); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(4); + evt.setArgs(new Selection(1,3)); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(8); + evt.setArgs(new Selection(2,3)); + track.update(evt); + + resetCursorPos(-1); + checker.setExpectedValue(12); + evt.setArgs(new Selection(3,3)); + track.update(evt); + + } + + private void resetCursorPos(int pos){ + track.removePropertyChangeListener(checker); // unregister the listener in order to ignore next instruction + track.setCursorPos(pos); // makes the previous value different in order to get the propertychange event triggered + track.addPropertyChangeListener(checker); + } + + /* + * Listens to "cursorPos" AudioTrack bounded property and perform a check on it + * against the expected value + */ + private class CursorPositionChecker implements PropertyChangeListener { + int expectedValue; + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if("cursorPos".equals(evt.getPropertyName())){ + assertEquals(expectedValue,((Integer)evt.getNewValue()).intValue()); + } + } + + /* checker's expected value is the assert expected value */ + public void setExpectedValue(int value){ + expectedValue = value; + } + } +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/ClipListTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/ClipListTest.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,90 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; + +import net.beadsproject.beads.data.SampleManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import uk.ac.qmul.eecs.depic.daw.Clip; +import uk.ac.qmul.eecs.depic.daw.ClipList; +import uk.ac.qmul.eecs.depic.daw.Sample; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.WavePeaks; +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSampleWrapper; + +public class ClipListTest { + Sample sample1; + Sample sample2; + ClipList clipList; + SoundWave wave = new DummySoundWave(); + + + @Before + public void setUp() throws Exception { + sample1 = new BeadsSampleWrapper(SampleManager.sample(getClass().getResourceAsStream("./audio/sound1.wav"))); + sample2 = new BeadsSampleWrapper(SampleManager.sample(getClass().getResourceAsStream("./audio/sound2.wav"))); + clipList = new ClipList(1, new HashMap (), wave); + + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testOrder() { + + /* insert in reverse order */ + for(int i = 99; i>=0; i--){ + clipList.add(new Clip(i,300,sample1,0,64.0f)); + } + + /* check if the order is correct */ + for(int i=0; i<100; i++) + assertEquals(i,clipList.get(i).getStart().intValue()); + + /* remove first ten items and check if the order is mantained */ + for(int i=1;i<=10;i++){ + clipList.remove(0); + assertEquals(i,clipList.get(0).getStart().intValue()); + } + + /*add another item at beginning and check if the order is mantained */ + clipList.add(new Clip(0,300,sample1,0,64.0f)); + assertEquals(0,clipList.get(0).getStart().intValue()); + } + + @Test + public void testCut(){ + /* cut on a [n,n] selection has no effect */ + clipList.add(new Clip(0,300,sample1,0,64.0f)); + clipList.getClipEditor().cut(wave, new Selection(5,5,clipList.getScaleFactor())); + assertEquals(1,clipList.size()); + } + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/ClipTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/ClipTest.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,92 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import static org.junit.Assert.*; + +import net.beadsproject.beads.data.SampleManager; + +import org.junit.Before; +import org.junit.Test; + +import uk.ac.qmul.eecs.depic.daw.Clip; +import uk.ac.qmul.eecs.depic.daw.Sample; +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSampleWrapper; + +public class ClipTest { + Sample sample; + + @Before + public void setUp() throws Exception { + sample = new BeadsSampleWrapper(SampleManager.sample(getClass().getResourceAsStream("./audio/sound1.wav"))); + } + + @Test + public void testSplit() { + Clip clip = new Clip(0,100,sample,0,64.0f); + + /* splits outside edges of selection */ + assertEquals(1,clip.split(-1).length); + assertEquals(2,clip.split(1).length); + assertEquals(2,clip.split(100).length); + assertEquals(1,clip.split(101).length); + + { + Clip [] split = clip.split(30); + assertEquals(2,split.length); + + assertEquals(0,split[0].getStart().intValue()); + assertEquals(29,split[0].getEnd().intValue()); + assertEquals(100,split[1].getEnd().intValue()); + assertEquals(30,split[1].getStart().intValue()); + } + { + /* split at edges of selection */ + Clip [] split = clip.split(0); + assertEquals(1,split.length); + assertEquals(0,split[0].getStart().intValue()); + assertEquals(100,split[0].getEnd().intValue()); + + } + + { + /* split at edges of selection */ + Clip [] split = clip.split(100); + assertEquals(2,split.length); + assertEquals(0,split[0].getStart().intValue()); + assertEquals(99,split[0].getEnd().intValue()); + assertEquals(100,split[1].getStart().intValue()); + assertEquals(100,split[1].getEnd().intValue()); + } + } + + + @Test + public void testJoin(){ + Clip clip = new Clip(0,100,sample,0,64.0f); + + Clip [] split = clip.split(30); + + Clip joined = Clip.join(split[0], split[1]); + + assertNotNull(joined); + assertEquals(clip.getStart().intValue(),joined.getStart().intValue()); + assertEquals(clip.getEnd().intValue(),joined.getEnd().intValue()); + } +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/DummySoundWave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/DummySoundWave.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,194 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.List; + +import javax.swing.JComponent; + +import uk.ac.qmul.eecs.depic.daw.Automation; +import uk.ac.qmul.eecs.depic.daw.Chunk; +import uk.ac.qmul.eecs.depic.daw.DbWave; +import uk.ac.qmul.eecs.depic.daw.ParametersControl; +import uk.ac.qmul.eecs.depic.daw.Selection; +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.SoundWaveEditor; +import uk.ac.qmul.eecs.depic.daw.SoundWaveListener; + +public class DummySoundWave implements SoundWave { + + public DummySoundWave() { + // TODO Auto-generated constructor stub + } + + @Override + public void addSoundWaveListener(SoundWaveListener l) { + // TODO Auto-generated method stub + + } + + @Override + public void removeSoundWaveListener(SoundWaveListener l) { + // TODO Auto-generated method stub + + } + + @Override + public void loadAudioData(File audioFile, + PropertyChangeListener propertyChangelistener) { + // TODO Auto-generated method stub + + } + + @Override + public void stopLoading(boolean mayInterruptIfRunning) { + // TODO Auto-generated method stub + + } + + @Override + public void close() { + // TODO Auto-generated method stub + + } + + + @Override + public SoundWave.TransportControl getTransportControl() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getCurrentChunkPosition() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void scan(int position) { + // TODO Auto-generated method stub + + } + + @Override + public void setSelection(Selection selection) { + // TODO Auto-generated method stub + + } + + @Override + public void setPosition(int position) { + // TODO Auto-generated method stub + + } + + @Override + public void setScaleFactor(int scaleFactor) { + // TODO Auto-generated method stub + + } + + @Override + public int getScaleFactor() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getMillisecPerChunk() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public List getFramesPositionAt(int chuckPosition) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getChunkNum() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Chunk getChunkAt(int i) { + // TODO Auto-generated method stub + return null; + } + + @Override + public float getWaveTime() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxScaleFactor() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public SoundWaveEditor getEditor() { + // TODO Auto-generated method stub + return null; + } + + @Override + public JComponent[] getPreferencesPanels() { + // TODO Auto-generated method stub + return new JComponent [] {} ; + } + + @Override + public Automation getSequence() { + // TODO Auto-generated method stub + return null; + } + + @Override + public DbWave getDbWave() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void generatePeakMeter(boolean generate) { + // TODO Auto-generated method stub + + } + + @Override + public boolean hasSequence() { + // TODO Auto-generated method stub + return false; + } + + @Override + public ParametersControl getParametersControl() { + // TODO Auto-generated method stub + return null; + } + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/MathUtilsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/MathUtilsTest.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,66 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import uk.ac.qmul.eecs.depic.patterns.MathUtils; +import uk.ac.qmul.eecs.depic.patterns.Range; + +public class MathUtilsTest { + private static float DELTA = 0.05f; + + @Test + public void testLinearScale() { + MathUtils.Scale f = new MathUtils.Scale(new Range(-1.0f,1.0f),new Range(0.0f, 100.0f)); + assertEquals(50.0f,f.linear(0.0f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,1.0f),new Range(-1.0f, 0.0f)); + assertEquals(-0.5,f.linear(0.0f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,1.0f),new Range(0.0f, 0.0f)); + assertEquals(0.0f,f.linear(0.0f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,1.0f),new Range(-100.0f, -100.0f)); + assertEquals(-100.0f,f.linear(0.0f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,1.0f),new Range(-0.6f, -0.2f)); + assertEquals(-0.4f,f.linear(0.0f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,0.0f),new Range(-150.0f, 100.0f)); + assertEquals(37.5f,f.linear(-0.25f),DELTA); + f = new MathUtils.Scale(new Range(-1.0f,0.0f),new Range(0.0f, 1.0f)); + assertEquals(0.75f,f.linear(-0.25f),DELTA); + f = new MathUtils.Scale(new Range(0.0f,2.0f),new Range(0.0f, 400.0f)); + assertEquals(400.0f,f.linear(2.0f),DELTA); + + } + + @Test + public void testAmpToDb (){ + assertEquals(-6.0,MathUtils.toDb(0.5f),DELTA); + assertEquals(6.0,MathUtils.toDb(2.0f),DELTA); + assertEquals(-10.0,MathUtils.toDb(0.3162f),DELTA); + assertEquals(0.0f,MathUtils.toDb(1.0f),DELTA); + assertEquals(-60.0f, MathUtils.toDb(0.001f),DELTA); + + assertEquals(0.501f,MathUtils.toAmp(-6.0f),DELTA); + assertEquals(1.0f,MathUtils.toAmp(0.0f),DELTA); + } + + + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/SoundWaveChunkTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/uk/ac/qmul/eecs/depic/daw/test/SoundWaveChunkTest.java Wed Aug 26 16:16:53 2015 +0100 @@ -0,0 +1,182 @@ +/* + Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. + + Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) + + This program is free software: you can 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 . +*/ +package uk.ac.qmul.eecs.depic.daw.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.swing.SwingUtilities; + +import org.junit.Before; +import org.junit.Test; + +import uk.ac.qmul.eecs.depic.daw.SoundWave; +import uk.ac.qmul.eecs.depic.daw.beads.BeadsSoundEngineFactory; + +@SuppressWarnings("unused") +public class SoundWaveChunkTest { + BeadsSoundEngineFactory factory; + SoundWave wave; + boolean swingIsDone; + + @Before + public void setUp() throws Exception { + swingIsDone = false; + factory = new BeadsSoundEngineFactory(); + } + + @Test + public void testConstructor(){ + int[] scaleFactors = new int[] {-1,0}; + for(int scaleFactor : scaleFactors) + try { + + SoundWave w1 = factory + .setSoundWaveParameters(SoundWave.MIN_SUPPORTED_CHUNK_SIZE,scaleFactor,scaleFactor) + .createSoundWave(); + fail(); + }catch(RuntimeException e){ + assertTrue(true); + } + + /* test chunks size < MIN_SUPPORTED_CHUNK_SIZE and not a power of 2*/ + int[] chunkSizes = new int[] {-1,-16,0,10,100,SoundWave.MIN_SUPPORTED_CHUNK_SIZE+2}; + for(int size : chunkSizes) + try { + SoundWave w2 = factory + .setSoundWaveParameters(size,1,1) + .createSoundWave(); + fail("Size "+size+" allowed"); + }catch(RuntimeException e){ + assertTrue(true); + } + + /* check correct chunk size > MIN_SUPPORTED_CHUNK_SIZE and power of 2*/ + chunkSizes = new int[] {SoundWave.MIN_SUPPORTED_CHUNK_SIZE, + SoundWave.MIN_SUPPORTED_CHUNK_SIZE*2, + SoundWave.MIN_SUPPORTED_CHUNK_SIZE*4}; + for(int size : chunkSizes) + try { + factory.setSoundWaveParameters(size,1,1).createSoundWave(); + }catch(RuntimeException e){ + e.printStackTrace(); + assertTrue(false); + } + } + + @Test + public void testGetFrameAtChunk() { + /* 128 samples per chunk, scale factor = 1 and 2 */ + wave = factory.setSoundWaveParameters(128,3,1).createSoundWave(); + /* by-pass the final constrain using an array */ + final AssertionError [] swingError = {null}; + try { + SwingUtilities.invokeAndWait(new Runnable(){ + @Override + public void run() { + URI fileURI = null; + try { + fileURI = getClass().getResource("./audio/sound1.wav").toURI(); + } catch (URISyntaxException e) { + new RuntimeException(e.getMessage()).printStackTrace(); + System.exit(-1); + } + wave.loadAudioData(new File(fileURI), new PropertyChangeListener(){ + @Override + public void propertyChange(PropertyChangeEvent evt) { + if("progress".equals(evt.getPropertyName())){ + /* only test when the file is fully loaded */ + if((Integer)evt.getNewValue() == SoundWave.FILE_LOAD_TOTAL_PROGRESS){ + try { + /* sound1.wav file is 44100Hz, 2 bytes per frame, mono * + * 0-1 and 7-8 seconds are silenced */ + + byte[] audioData = AudioFileLoader.getAudioBuffer(getClass().getResource("./audio/sound1.wav")); + /* first second @ 8000Hz is silence */ + for(int i = 0; i * 128 < 8000; i++){ + assertEquals("chunk "+i,0,audioData[(int)wave.getFramesPositionAt(i).get(0)]); + assertEquals("chunk "+i+" (scale = 2)",0,audioData[(int)wave.getFramesPositionAt(i).get(0)/2]); + assertEquals("chunk "+i+" (scale = 2)",0,audioData[(int)wave.getFramesPositionAt(i).get(0)/4]); + } + + /* (16min *44100)/128 = 5512.5 */ + /* 1min * 44100 = 344 chunks and 68 frames */ + /* 7th seconds @ 8000Hz is at (7 * 8000)/128 = 437 chunks + 64 frames */ + /* check a neighbourhood of 10 samples */ + for(int i=10; i<20;i++){ + wave.setScaleFactor(1); + assertEquals("chunk at second 7",0,audioData[(int)wave.getFramesPositionAt(437).get(0)+64+i]); + wave.setScaleFactor(2); + assertEquals("chunk at second 7 (scale = 2)",0,audioData[((int)wave.getFramesPositionAt(437).get(0)+64+i)/2]); + wave.setScaleFactor(3); + assertEquals("chunk at second 7 (scale = 3)",0,audioData[((int)wave.getFramesPositionAt(437).get(0)+64+i)/4]); + } + + }catch(AssertionError e){ + swingError[0] = e; + }finally{ //System.out.println("just after assert Equals"); + synchronized(wave){ + swingIsDone = true; + wave.notify(); + } + } + } + }else if(SoundWave.FILE_ERROR_PROPERTY.equals(evt.getPropertyName())){ + new RuntimeException("could not open file:"+evt.getNewValue()).printStackTrace(); + System.exit(-1); + } + } + }); + } + }); + } catch (InvocationTargetException | InterruptedException e) { + e.printStackTrace(); + System.exit(-1); + } + + /* wait for the swing thread to finish. The swing thread will be finished when propertyChange with progress = * + * SoundWave.FILE_LOAD_TOTAL_PROGRESS will be scheduled for execution in the EDT, by the swing worker */ + synchronized(wave){ + while(!swingIsDone) + try { + wave.wait(); + /* throw the error in the JUnit thread so that it's counted */ + if(swingError[0] != null){ + assertTrue(swingError[0].getMessage(),false); + } + } catch (InterruptedException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + + } + + + +} diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/audio/sound1.wav Binary file test/uk/ac/qmul/eecs/depic/daw/test/audio/sound1.wav has changed diff -r 000000000000 -r 3074a84ef81e test/uk/ac/qmul/eecs/depic/daw/test/audio/sound2.wav Binary file test/uk/ac/qmul/eecs/depic/daw/test/audio/sound2.wav has changed