Initial import of source.

bklimt
Bryan Klimt 13 years ago
commit 72190eccf2
  1. 202
      COPYING
  2. 11
      android/.classpath
  3. 10
      android/.externalToolBuilders/Proto Builder.launch
  4. 65
      android/.project
  5. 280
      android/.settings/org.eclipse.jdt.core.prefs
  6. 8
      android/.settings/org.eclipse.jdt.ui.prefs
  7. 73
      android/AndroidManifest.xml
  8. 11
      android/default.properties
  9. BIN
      android/res/drawable-hdpi/add.png
  10. BIN
      android/res/drawable-hdpi/arrow.png
  11. BIN
      android/res/drawable-hdpi/bass.png
  12. BIN
      android/res/drawable-hdpi/closed_eye.png
  13. BIN
      android/res/drawable-hdpi/down.png
  14. BIN
      android/res/drawable-hdpi/drums.png
  15. BIN
      android/res/drawable-hdpi/eighth_note.png
  16. BIN
      android/res/drawable-hdpi/flute.png
  17. BIN
      android/res/drawable-hdpi/guitar.png
  18. BIN
      android/res/drawable-hdpi/half_note.png
  19. BIN
      android/res/drawable-hdpi/icon.png
  20. BIN
      android/res/drawable-hdpi/loop.png
  21. BIN
      android/res/drawable-hdpi/no_note.png
  22. BIN
      android/res/drawable-hdpi/open_eye.png
  23. BIN
      android/res/drawable-hdpi/play.png
  24. BIN
      android/res/drawable-hdpi/play_piano.png
  25. BIN
      android/res/drawable-hdpi/quarter_note.png
  26. BIN
      android/res/drawable-hdpi/sixteenth_note.png
  27. BIN
      android/res/drawable-hdpi/stop.png
  28. BIN
      android/res/drawable-hdpi/thirtysecond_note.png
  29. BIN
      android/res/drawable-hdpi/trash.png
  30. BIN
      android/res/drawable-hdpi/unknown_note.png
  31. BIN
      android/res/drawable-hdpi/up.png
  32. BIN
      android/res/drawable-hdpi/voice.png
  33. BIN
      android/res/drawable-hdpi/whole_note.png
  34. BIN
      android/res/drawable-hdpi/zoom.png
  35. BIN
      android/res/drawable-ldpi/icon.png
  36. BIN
      android/res/drawable-mdpi/icon.png
  37. 81
      android/res/layout/amplification.xml
  38. 32
      android/res/layout/chord_grid.xml
  39. 76
      android/res/layout/effects.xml
  40. 79
      android/res/layout/karplus_strong.xml
  41. 88
      android/res/layout/low_pass_filter.xml
  42. 45
      android/res/layout/main.xml
  43. 90
      android/res/layout/oscillator.xml
  44. 63
      android/res/layout/piano.xml
  45. 29
      android/res/layout/score.xml
  46. 93
      android/res/layout/tremolo.xml
  47. 90
      android/res/layout/vibrato.xml
  48. 7
      android/res/menu/options_menu.xml
  49. 9
      android/res/menu/score_menu.xml
  50. BIN
      android/res/raw/drums.sf2
  51. 380
      android/res/raw/presets.txt
  52. 15
      android/res/values/attrs.xml
  53. 79
      android/res/values/strings.xml
  54. 279
      android/src/com/google/synthesizer/android/Storage.java
  55. 150
      android/src/com/google/synthesizer/android/service/SynthesizerService.java
  56. 230
      android/src/com/google/synthesizer/android/service/SynthesizerThread.java
  57. 61
      android/src/com/google/synthesizer/android/ui/AmplificationActivity.java
  58. 73
      android/src/com/google/synthesizer/android/ui/ChordGridActivity.java
  59. 141
      android/src/com/google/synthesizer/android/ui/EditInstrumentActivity.java
  60. 52
      android/src/com/google/synthesizer/android/ui/EffectsActivity.java
  61. 81
      android/src/com/google/synthesizer/android/ui/InstrumentListActivity.java
  62. 59
      android/src/com/google/synthesizer/android/ui/KarplusStrongActivity.java
  63. 67
      android/src/com/google/synthesizer/android/ui/LowPassFilterActivity.java
  64. 107
      android/src/com/google/synthesizer/android/ui/MainActivity.java
  65. 66
      android/src/com/google/synthesizer/android/ui/OscillatorActivity.java
  66. 79
      android/src/com/google/synthesizer/android/ui/PianoActivity.java
  67. 141
      android/src/com/google/synthesizer/android/ui/ScoreActivity.java
  68. 189
      android/src/com/google/synthesizer/android/ui/SynthesizerActivity.java
  69. 68
      android/src/com/google/synthesizer/android/ui/TremoloActivity.java
  70. 65
      android/src/com/google/synthesizer/android/ui/VibratoActivity.java
  71. 328
      android/src/com/google/synthesizer/android/widgets/ChordGridView.java
  72. 27
      android/src/com/google/synthesizer/android/widgets/knob/KnobListener.java
  73. 78
      android/src/com/google/synthesizer/android/widgets/knob/KnobPlaceholderView.java
  74. 390
      android/src/com/google/synthesizer/android/widgets/knob/KnobView.java
  75. 95
      android/src/com/google/synthesizer/android/widgets/piano/BlackPianoKey.java
  76. 52
      android/src/com/google/synthesizer/android/widgets/piano/NotePianoKey.java
  77. 111
      android/src/com/google/synthesizer/android/widgets/piano/OctavePianoKey.java
  78. 177
      android/src/com/google/synthesizer/android/widgets/piano/PianoKey.java
  79. 373
      android/src/com/google/synthesizer/android/widgets/piano/PianoView.java
  80. 32
      android/src/com/google/synthesizer/android/widgets/piano/PianoViewListener.java
  81. 72
      android/src/com/google/synthesizer/android/widgets/piano/WhitePianoKey.java
  82. 490
      android/src/com/google/synthesizer/android/widgets/score/EditEventTool.java
  83. 97
      android/src/com/google/synthesizer/android/widgets/score/HideChannelButton.java
  84. 118
      android/src/com/google/synthesizer/android/widgets/score/NewEventTool.java
  85. 118
      android/src/com/google/synthesizer/android/widgets/score/NewLoopTool.java
  86. 156
      android/src/com/google/synthesizer/android/widgets/score/PlayButton.java
  87. 229
      android/src/com/google/synthesizer/android/widgets/score/PlayTool.java
  88. 767
      android/src/com/google/synthesizer/android/widgets/score/ScoreView.java
  89. 27
      android/src/com/google/synthesizer/android/widgets/score/ScoreViewListener.java
  90. 81
      android/src/com/google/synthesizer/android/widgets/score/ScoreViewTool.java
  91. 222
      android/src/com/google/synthesizer/android/widgets/score/ScoreViewToolbar.java
  92. 100
      android/src/com/google/synthesizer/android/widgets/score/SelectChannelButton.java
  93. 134
      android/src/com/google/synthesizer/android/widgets/score/SnapTool.java
  94. 259
      android/src/com/google/synthesizer/android/widgets/score/ViewportTool.java
  95. 27
      android/src/com/google/synthesizer/android/widgets/waveform/WaveformListener.java
  96. 167
      android/src/com/google/synthesizer/android/widgets/waveform/WaveformRowView.java
  97. 470
      android/src/com/google/synthesizer/android/widgets/waveform/WaveformView.java
  98. 62
      build.xml
  99. 73
      core/build.xml
  100. 284
      core/src/com/google/synthesizer/core/midi/MessageInputProcessor.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="**/.svn/*" kind="src" path="core"/>
<classpathentry excluding="**/.svn/*" kind="src" path="core-gen"/>
<classpathentry excluding="**/.svn/*" kind="src" path="src"/>
<classpathentry excluding="**/.svn/*" kind="src" path="test"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="core-lib/libprotobuf.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/MusicSynthesizer/core/com/google/synthesizer/core/model/composite/Presets.proto&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/MusicSynthesizer/core/com/google/synthesizer/core/model/sample/Proto.proto&quot; type=&quot;1&quot;/&gt;&#10;&lt;item path=&quot;/MusicSynthesizer/core/com/google/synthesizer/core/music/Music.proto&quot; type=&quot;1&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/../core/bin/protoc"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--java_out=../core/gen src/com/google/synthesizer/core/model/composite/Presets.proto src/com/google/synthesizer/core/music/Music.proto"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${project_loc}/../core"/>
</launchConfiguration>

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MusicSynthesizer</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/Proto Builder.launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<linkedResources>
<link>
<name>core</name>
<type>2</type>
<locationURI>PARENT-1-PROJECT_LOC/core/src</locationURI>
</link>
<link>
<name>core-gen</name>
<type>2</type>
<locationURI>PARENT-1-PROJECT_LOC/core/gen</locationURI>
</link>
<link>
<name>core-lib</name>
<type>2</type>
<locationURI>PARENT-1-PROJECT_LOC/core/lib</locationURI>
</link>
<link>
<name>test</name>
<type>2</type>
<locationURI>PARENT-1-PROJECT_LOC/test/src</locationURI>
</link>
</linkedResources>
</projectDescription>

@ -0,0 +1,280 @@
#Thu Jan 27 01:33:26 PST 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
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.5
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=100
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=4
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=4
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=2
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=true
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=true
org.eclipse.jdt.core.formatter.lineSplit=100
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

@ -0,0 +1,8 @@
#Thu Jan 27 01:35:20 PST 2011
eclipse.preferences.version=1
formatter_profile=_MusicSynthesizer
formatter_settings_version=11
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=java;javax;org;com;
org.eclipse.jdt.ui.ondemandthreshold=99
org.eclipse.jdt.ui.staticondemandthreshold=99

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.synthesizer"
android:versionCode="2"
android:versionName="0.9">
<uses-sdk android:minSdkVersion="8" /> <!-- 8 = Froyo 2.2 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity
android:name=".android.ui.PianoActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".android.ui.MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.ScoreActivity"
android:label="@string/app_name"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.ChordGridActivity"
android:label="@string/chord_grid"
android:screenOrientation="portrait" />
<activity
android:name=".android.ui.InstrumentListActivity"
android:label="@string/instrument_list"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.EditInstrumentActivity"
android:label="@string/edit_instrument"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.VibratoActivity"
android:label="@string/vibrato"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.OscillatorActivity"
android:label="@string/oscillator"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.TremoloActivity"
android:label="@string/tremolo"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.LowPassFilterActivity"
android:label="@string/low_pass_filter"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.AmplificationActivity"
android:label="@string/amplification"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.EffectsActivity"
android:label="@string/effects"
android:screenOrientation="landscape" />
<activity
android:name=".android.ui.KarplusStrongActivity"
android:label="@string/karplus_strong"
android:screenOrientation="landscape" />
<service android:name=".android.service.SynthesizerService">
<intent-filter>
<action android:name=".android.service.ISynthesizerService" />
</intent-filter>
</service>
</application>
</manifest>

@ -0,0 +1,11 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-8

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/attack"
android:id="@+id/attackLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/decay"
android:id="@+id/decayLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/sustain"
android:id="@+id/sustainLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/release"
android:id="@+id/releaseLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/volume"
android:id="@+id/volumeLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/attackKnob"
app:value="0.0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/decayKnob"
app:value="0.0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/sustainKnob"
app:value="1.0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/releaseKnob"
app:value="0.0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/emptyKnob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/volumeKnob"
app:value="1.0"
app:min="0.0"
app:max="25.0"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:orientation="horizontal">
<Spinner
android:id="@+id/presetSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.ChordGridView
android:id="@+id/chord_grid"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/echo_mix"
android:id="@+id/echoMixLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/echo_delay"
android:id="@+id/echoDelayLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty1Label"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty2Label"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty3Label"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty4Label"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/echoMixKnob"
app:value="0.0"
app:min="0.0"
app:max="1.0"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/echoDelayKnob"
app:value="0.5"
app:min="0.1"
app:max="2.0"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty1Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty2Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty3Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty4Knob"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/blend"
android:id="@+id/blendLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/stretch"
android:id="@+id/stretchLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/excitement"
android:id="@+id/excitementLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty1Label"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty2Label"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty3Label"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/blendKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/stretchKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/excitementKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty1Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty2Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty3Knob"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4" />
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/cutoff"
android:id="@+id/cutoffLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/depth"
android:id="@+id/depthLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/attack"
android:id="@+id/attackLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/decay"
android:id="@+id/decayLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/sustain"
android:id="@+id/sustainLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/release"
android:id="@+id/releaseLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/cutoffKnob"
app:value="1"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/depthKnob"
app:value="0"
app:min="-1"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/attackKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/decayKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/sustainKnob"
app:value="1.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/releaseKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:orientation="horizontal">
<Button
android:text="@string/record"
android:id="@+id/recordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="@string/play"
android:id="@+id/playButton"
android:enabled="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/presetSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/glide"
android:id="@+id/glideLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/coarse"
android:id="@+id/coarseLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/fine"
android:id="@+id/fineLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/vibrato_depth"
android:id="@+id/releaseLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/empty1Label"
android:gravity="center_horizontal" />
<TextView
android:text="@string/balance"
android:id="@+id/balanceLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/glideKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/coarseKnob"
app:value="0.0"
app:min="-1.0"
app:max="1.0"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/fineKnob"
app:value="0.0"
app:min="-0.0833333333"
app:max="0.0833333333"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/vibratoDepthKnob"
app:value="0"
app:min="0"
app:max="0.1666666667"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/empty1Knob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/balanceKnob"
app:value="0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.waveform.WaveformRowView
android:id="@+id/waveform"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4" />
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/volume"
android:id="@+id/volumeLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/volumeKnob"
app:value="0.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<Spinner
android:id="@+id/presetSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_span="5" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4" />
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.score.ScoreView
android:id="@+id/score"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4" />
<com.google.synthesizer.android.widgets.score.ScoreViewToolbar
android:id="@+id/toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/rate"
android:id="@+id/rateLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/depth"
android:id="@+id/depthLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/attack"
android:id="@+id/attackLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/decay"
android:id="@+id/decayLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/sustain"
android:id="@+id/sustainLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/release"
android:id="@+id/releaseLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/rateKnob"
app:value="0"
app:min="0"
app:max="10"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/depthKnob"
app:value="0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/attackKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/decayKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/sustainKnob"
app:value="1.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/releaseKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.waveform.WaveformRowView
android:id="@+id/waveform"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.google.synthesizer"
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*"
android:shrinkColumns="*">
<TableRow>
<TextView
android:text="@string/rate"
android:id="@+id/rateLabel"
android:gravity="center_horizontal" />
<TextView
android:text=""
android:id="@+id/emptyLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/attack"
android:id="@+id/attackLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/decay"
android:id="@+id/decayLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/sustain"
android:id="@+id/sustainLabel"
android:gravity="center_horizontal" />
<TextView
android:text="@string/release"
android:id="@+id/releaseLabel"
android:gravity="center_horizontal" />
</TableRow>
<TableRow>
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/rateKnob"
app:value="0.0"
app:min="0"
app:max="10"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobPlaceholderView
android:id="@+id/depthKnob"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/attackKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/decayKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/sustainKnob"
app:value="1.0"
app:min="0"
app:max="1"
android:layout_margin="2px" />
<com.google.synthesizer.android.widgets.knob.KnobView
android:id="@+id/releaseKnob"
app:value="0.0"
app:min="0"
app:max="5"
android:layout_margin="2px" />
</TableRow>
<TableRow>
<LinearLayout
android:layout_height="fill_parent"
android:layout_span="6"
android:orientation="vertical">
<com.google.synthesizer.android.widgets.waveform.WaveformRowView
android:id="@+id/waveform"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.google.synthesizer.android.widgets.piano.PianoView
android:id="@+id/piano"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
app:octaves="2"
app:first_octave="4"/>
</LinearLayout>
</TableRow>
</TableLayout>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/piano" android:title="@string/piano" />
<item android:id="@+id/chord_grid" android:title="@string/chord_grid" />
<item android:id="@+id/edit_instrument" android:title="@string/edit_instrument" />
<item android:id="@+id/compose" android:title="@string/compose" />
</menu>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_score" android:title="@string/new_score" />
<item android:id="@+id/save_score" android:title="@string/save_score" />
<item android:id="@+id/open_score" android:title="@string/open_score" />
<item android:id="@+id/piano" android:title="@string/piano" />
<item android:id="@+id/chord_grid" android:title="@string/chord_grid" />
<item android:id="@+id/edit_instrument" android:title="@string/edit_instrument" />
</menu>

Binary file not shown.

@ -0,0 +1,380 @@
#
# Copyright 2011 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
preset <
name: "organ"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 1.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.02 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "square" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: -1.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.01 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "square" >
input_setting < setting: BALANCE value: 0.75 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.2 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 0.15 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: FILTER_ATTACK value: 0.0 >
input_setting < setting: FILTER_DECAY value: 0.0 >
input_setting < setting: FILTER_SUSTAIN value: 0.0 >
input_setting < setting: FILTER_RELEASE value: 0.0 >
input_setting < setting: ATTACK value: 0.01 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 0.01 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.10 >
input_setting < setting: ECHO_DELAY value: 0.10 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "rubberband"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "karplus-strong string" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "sine" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.0 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: ATTACK value: 0.0 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 5.0 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "voice"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 5.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "triangle" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.05 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.17 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "sine" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.0 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: ATTACK value: 0.01 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 0.5 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "flute"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "sine" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.3 >
input_setting < setting: TREMOLO_ATTACK value: 2.5 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.5 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: ATTACK value: 0.1 >
input_setting < setting: DECAY value: 0.2 >
input_setting < setting: SUSTAIN value: 0.9 >
input_setting < setting: RELEASE value: 0.1 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "drums"
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "drums" >
input_setting < setting: ATTACK value: 0.01 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 5.0 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: TREMOLO_DEPTH value: 0.0 >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: FILTER_ATTACK value: 0.0 >
input_setting < setting: FILTER_DECAY value: 0.0 >
input_setting < setting: FILTER_SUSTAIN value: 0.0 >
input_setting < setting: FILTER_RELEASE value: 0.0 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "noise"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.01 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.01 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "noise" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "sine" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.0 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 0.25 >
input_setting < setting: FILTER_DEPTH value: -1.0 >
input_setting < setting: TREMOLO_ATTACK value: 0.01 >
input_setting < setting: TREMOLO_DECAY value: 0.01 >
input_setting < setting: TREMOLO_SUSTAIN value: 0.25 >
input_setting < setting: TREMOLO_RELEASE value: 0.01 >
input_setting < setting: ATTACK value: 0.01 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 0.01 >
input_setting < setting: VOLUME value: 0.7 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "square"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "square" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "sine" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.0 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: ATTACK value: 0.0 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 0.0 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.0 >
input_setting < setting: ECHO_DELAY value: 0.0 >
input_setting < setting: DELAY_MIX value: 0.5 >
>
preset <
name: "drawbar organ"
input_setting < setting: VIBRATO_RATE value: 2.5 >
input_setting < setting: VIBRATO_ATTACK value: 0.0 >
input_setting < setting: VIBRATO_DECAY value: 0.0 >
input_setting < setting: VIBRATO_SUSTAIN value: 1.0 >
input_setting < setting: VIBRATO_RELEASE value: 0.0 >
waveform_setting < setting: VIBRATO_WAVEFORM waveform: "sine" >
input_setting < setting: OSCILLATOR_1_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_1_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_1_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_1_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_1_WAVEFORM waveform: "drawbar organ" >
input_setting < setting: OSCILLATOR_2_GLIDE value: 0.0 >
input_setting < setting: OSCILLATOR_2_COARSE value: 0.0 >
input_setting < setting: OSCILLATOR_2_FINE value: 0.0 >
input_setting < setting: OSCILLATOR_2_VIBRATO value: 0.0 >
waveform_setting < setting: OSCILLATOR_2_WAVEFORM waveform: "noise" >
input_setting < setting: BALANCE value: 0.0 >
input_setting < setting: TREMOLO_RATE value: 2.2 >
input_setting < setting: TREMOLO_DEPTH value: 0.5 >
input_setting < setting: TREMOLO_ATTACK value: 0.0 >
input_setting < setting: TREMOLO_DECAY value: 0.0 >
input_setting < setting: TREMOLO_SUSTAIN value: 1.0 >
input_setting < setting: TREMOLO_RELEASE value: 0.0 >
waveform_setting < setting: TREMOLO_WAVEFORM waveform: "sine" >
input_setting < setting: FILTER_CUTOFF value: 1.0 >
input_setting < setting: FILTER_DEPTH value: 0.0 >
input_setting < setting: FILTER_ATTACK value: 0.0 >
input_setting < setting: FILTER_DECAY value: 0.0 >
input_setting < setting: FILTER_SUSTAIN value: 0.0 >
input_setting < setting: FILTER_RELEASE value: 0.0 >
input_setting < setting: ATTACK value: 0.01 >
input_setting < setting: DECAY value: 0.0 >
input_setting < setting: SUSTAIN value: 1.0 >
input_setting < setting: RELEASE value: 0.01 >
input_setting < setting: VOLUME value: 1.0 >
input_setting < setting: ECHO_MIX value: 0.10 >
input_setting < setting: ECHO_DELAY value: 0.10 >
input_setting < setting: DELAY_MIX value: 0.5 >
>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ChordGridView">
<attr name="octave" format="integer" />
</declare-styleable>
<declare-styleable name="KnobView">
<attr name="value" format="float" />
<attr name="min" format="float" />
<attr name="max" format="float" />
</declare-styleable>
<declare-styleable name="PianoView">
<attr name="octaves" format="integer" />
<attr name="first_octave" format="integer" />
</declare-styleable>
</resources>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Music Synthesizer</string>
<!-- Sections -->
<string name="new_score">New score</string>
<string name="save_score">Save score as...</string>
<string name="open_score">Open score...</string>
<string name="compose">Compose</string>
<string name="piano">Piano</string>
<string name="edit_instrument">Edit instrument</string>
<string name="instrument_list">Instrument List</string>
<string name="chord_grid">Chord Grid</string>
<string name="vibrato">Vibrato</string>
<string name="oscillator">Oscillator</string>
<string name="oscillator1">Oscillator 1</string>
<string name="oscillator2">Oscillator 2</string>
<string name="karplus_strong">Karplus-Strong</string>
<string name="karplus_strong1">Karplus-Strong 1</string>
<string name="karplus_strong2">Karplus-Strong 2</string>
<string name="tremolo">Tremolo</string>
<string name="low_pass_filter">Low Pass Filter</string>
<string name="amplification">Amplification</string>
<string name="effects">Effects</string>
<!-- Vibrato -->
<string name="rate">Rate</string>
<!-- Karplus-Strong -->
<string name="blend">Blend</string>
<string name="stretch">Stretch</string>
<string name="excitement">Excitement</string>
<!-- Oscillator -->
<string name="glide">Glide</string>
<string name="coarse">Coarse</string>
<string name="fine">Fine</string>
<string name="vibrato_depth">Vibrato</string>
<!-- Mixing -->
<string name="balance">Balance</string>
<!-- Tremolo -->
<string name="depth">Depth</string>
<!-- Low Pass Filter -->
<string name="cutoff">Cutoff</string>
<!-- Amp -->
<string name="attack">Attack</string>
<string name="decay">Decay</string>
<string name="sustain">Sustain</string>
<string name="release">Release</string>
<string name="volume">Volume</string>
<!-- Effects -->
<string name="echo_mix">Echo Mix</string>
<string name="echo_delay">Echo Delay</string>
<!-- Delay -->
<string name="play">Play</string>
<string name="record">Record</string>
<string name="stop">Stop</string>
<!-- Array of Sections -->
<string-array name="sections">
<item>Vibrato</item>
<item>Oscillator 1</item>
<item>Oscillator 2</item>
<item>Karplus-Strong 1</item>
<item>Karplus-Strong 2</item>
<item>Tremolo</item>
<item>Low Pass Filter</item>
<item>Amplification</item>
<item>Effects</item>
</string-array>
</resources>

@ -0,0 +1,279 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Environment;
import android.widget.EditText;
import android.widget.Toast;
import com.google.synthesizer.core.music.Music.Score;
/**
* A collection of functions for storing and retrieving scores.
*/
public class Storage {
/**
* Opens the score stored with the name "_default", which should always be the current score
* being edited. This may be called at any time to restore saved state, since Android Activities
* can come and go.
*
* @param score - The mutable score to populate with the stored data.
* @param context - Android application context.
*/
public static void openDefaultScore(Score.Builder score, Context context) throws IOException {
openScore(score, "_default", context);
}
/**
* Saves a score with the name "_default", which should always be the current score being edited.
* This may be called at any time to save state, since Android Activities can come and go.
*
* @param score - The score data to save.
* @param context - Android application context.
*/
public static void saveDefaultScore(Score score, Context context) throws IOException {
saveScore(score, "_default", true, context);
}
/**
* Opens the score with the given name. The name should be name of a valid score file in storage.
* The file must be in the root external files directory for the app.
*
* @param score - The mutable score to update with the data from storage.
* @param name - The name of the file, minus the ".pb" extension.
* @param context - The Android application context.
*/
public static void openScore(Score.Builder score, String name, Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
!Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not readable.");
}
File path = context.getExternalFilesDir(null);
File file = new File(path, name + ".pb");
FileInputStream in = new FileInputStream(file);
score.clear();
score.mergeFrom(in);
in.close();
}
/**
* Saves the score with the given name. Files are stored in the root external files directory for
* the app.
*
* @param score - The score to save.
* @param name - The name of the file, without any extension.
* @param overwrite - If true, replace the existing file, if one already exists.
* @param context - The Android application context.
* @throws IOException - On any kind of IO error, or if name is "", or the file already exists.
*/
public static void saveScore(Score score,
String name,
boolean overwrite,
Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not writeable.");
}
File path = context.getExternalFilesDir(null);
name = cleanupName(name);
if (name.length() == 0) {
throw new IOException("Can't save score without a name.");
}
File file = new File(path, name + ".pb");
if (!overwrite && file.exists()) {
throw new IOException("File already exists.");
}
FileOutputStream out = new FileOutputStream(file);
score.writeTo(out);
out.close();
}
/**
* Returns the list of all valid names of scores that are currently in storage.
*/
public static String[] getScoreNames(Context context) throws IOException {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
!Environment.MEDIA_MOUNTED_READ_ONLY.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not readable.");
}
File path = context.getExternalFilesDir(null);
File[] files = path.listFiles();
ArrayList<String> names = new ArrayList<String>();
for (File file : files) {
names.add(file.getName().replaceAll("\\.pb", ""));
}
return names.toArray(new String[0]);
}
/**
* Returns true iff there is a score in storage with the given name.
*/
public static boolean scoreExists(String name, Context context) throws IOException {
String[] names = getScoreNames(context);
for (String existingName : names) {
if (name.equals(existingName)) {
return true;
}
}
return false;
}
/**
* Interface used to notify callers of openScoreWithDialog() when the opening has completed.
*/
public interface OpenScoreListener {
void onOpenScore(Score.Builder score);
}
/**
* Shows UI to allow the user to pick one of the current scores in storage, and then populates
* score with the data from that file.
*
* @param score - The mutable score to update.
* @param listener - A listener that is notified after the score is updated. Can be null.
* @param context - An Android application context.
*/
public static void openScoreWithDialog(final Score.Builder score,
final OpenScoreListener listener,
final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Open score...");
try {
final String[] scoreNames = getScoreNames(context);
builder.setItems(scoreNames, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
openScore(score, scoreNames[which], context);
if (listener != null) {
listener.onOpenScore(score);
}
dialog.dismiss();
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error opening score \"" + scoreNames[which] + "\" with dialog.", e);
Toast.makeText(context,
"Unable to open \"" + scoreNames[which] + "\".",
Toast.LENGTH_SHORT).show();
}
}
});
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error getting score names.", e);
Toast.makeText(context,
"Unable to get existing score names.",
Toast.LENGTH_SHORT).show();
}
AlertDialog dialog = builder.create();
dialog.show();
}
/**
* Shows UI to allow the user to pick a name and save the given score in storage.
*
* @param score - The score to save.
* @param context - An Android application context.
*/
public static void saveScoreWithDialog(final Score score,
final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Save score as...");
builder.setMessage("Name: ");
final EditText input = new EditText(context);
builder.setView(input);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface nameDialog, int which) {
final String name = cleanupName(input.getText().toString());
if (name.length() == 0) {
Toast.makeText(context,
"Name must not be empty.",
Toast.LENGTH_SHORT).show();
} else {
try {
if (scoreExists(name, context)) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Overwrite?");
builder.setMessage(
"A score named " + name + " already exists. Would you like to overwrite it?");
builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface confirmDialog, int which) {
try {
saveScore(score, name, true, context);
confirmDialog.dismiss();
nameDialog.dismiss();
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error saving score \"" + name + "\" with dialog.", e);
Toast.makeText(context,
"Unable to save \"" + name + "\".",
Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog confirmDialog = builder.create();
confirmDialog.show();
} else {
saveScore(score, name, false, context);
nameDialog.dismiss();
}
} catch (IOException e) {
Logger logger = Logger.getLogger(Storage.class.getName());
logger.log(Level.SEVERE,
"Error saving score \"" + name + "\" with dialog.", e);
Toast.makeText(context,
"Unable to save \"" + name + "\".",
Toast.LENGTH_SHORT).show();
}
}
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
/**
* Internal method to turn a user input string into a valid file name.
*/
private static String cleanupName(String name) {
name = name.trim();
name = name.replaceAll("[^A-Za-z0-9_-]", "_");
return name;
}
}

@ -0,0 +1,150 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.service;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import android.app.Service;
import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import com.google.synthesizer.R;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.soundfont.SoundFontReader;
/**
* An Android Service that plays audio from a synthesizer.
* The Service is created when the first Activity binds to it, and destroys itself when no more
* Activities are bound to it.
*/
public class SynthesizerService extends Service {
// Class for local client access.
public class LocalBinder extends Binder {
public SynthesizerService getService() {
return SynthesizerService.this;
}
/**
* Gets the underlying synthesizer powering this service.
*/
public MultiChannelSynthesizer getSynthesizer() {
return SynthesizerService.this.synthesizer_;
}
}
public SynthesizerService() {
logger_ = Logger.getLogger(getClass().getName());
}
/**
* Run when the Service is first created.
*/
@Override
public void onCreate() {
super.onCreate();
// Get the native audio settings.
int sampleRateInHz = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
// For now, cap the sample rate to reduce cpu requirements.
sampleRateInHz = Math.min(sampleRateInHz, 11025);
SoundFontReader sampleLibrary = null;
InputStream sampleLibraryFile = getResources().openRawResource(R.raw.drums);
try {
sampleLibrary = new SoundFontReader(sampleLibraryFile);
} catch (IOException e) {
logger_.log(Level.SEVERE, "Unable to load sample library.", e);
sampleLibrary = null;
}
synthesizer_ = new MultiChannelSynthesizer(CHANNELS, FINGERS, sampleRateInHz, sampleLibrary);
// Load the presets from a file.
InputStream presetsFile = getResources().openRawResource(R.raw.presets);
try {
synthesizer_.loadLibraryFromText(presetsFile);
} catch (IOException e) {
Log.e(getClass().getName(), "Unable to load presets from raw file.", e);
}
// Start a synthsizer thread playing the data.
synthesizerThread_ = new SynthesizerThread(synthesizer_, sampleRateInHz);
synthesizerThread_.play();
// No Activities are yet bound to this Service.
referenceCount_ = 0;
}
/**
* Run when this Service is finally destroyed.
*/
@Override
public void onDestroy() {
super.onDestroy();
// Free up the underlying data structures.
synthesizerThread_.stop();
synthesizerThread_ = null;
synthesizer_ = null;
}
/**
* Run when an Activity binds to this Service.
*/
@Override
public IBinder onBind(Intent intent) {
++referenceCount_;
return binder_;
}
/**
* Run when any Activity unbinds from this Service.
*/
@Override
public boolean onUnbind(Intent intent) {
if (--referenceCount_ == 0) {
// No more Activities are using this Service, so kill it.
stopSelf();
}
return super.onUnbind(intent);
}
// Binder to use for Activities in this process.
private final IBinder binder_ = new LocalBinder();
// The module that provides the sampled audio data.
private MultiChannelSynthesizer synthesizer_;
// The thread that actually does the work of playing the audio data.
private SynthesizerThread synthesizerThread_;
// How many Activities are currently bound to this Service.
private int referenceCount_;
// The number of channels the synthesizer supports.
private static final int CHANNELS = 8;
// The number of fingers the synthesizer supports.
private static final int FINGERS = 5;
private Logger logger_;
}

@ -0,0 +1,230 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.service;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import com.google.synthesizer.core.model.SynthesisTime;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
/**
* SynthesizerThread is a thread-safe interface to a thread that constantly plays sampled audio data
* from a given SignalProvider.
*/
public class SynthesizerThread {
/**
* Creates a new SynthesizerThread that will play audio from synthesizer.
* @param synthesizer - the source of the audio data to output.
*/
public SynthesizerThread(MultiChannelSynthesizer synthesizer, int sampleRateInHz) {
playingLock_ = new Object();
playing_ = false;
shouldDie_ = false;
audioTrackLock_ = new Object();
audioTrack_ = null;
synthesizer_ = synthesizer;
sampleRateInHz_ = sampleRateInHz;
time_ = new SynthesisTime();
time_.setSampleRate(sampleRateInHz);
}
/**
* Starts the thread taking sampled audio data from the synthesizer and playing it.
*/
public void play() {
synchronized (playingLock_) {
if (playing_) {
// It's already playing.
return;
}
// Set up the initial state.
playing_ = true;
shouldDie_ = false;
initSound();
fillBuffer();
// Start the audio track playing from the buffer.
startSound();
// Start the thread that grabs the output from the synthesizer and fills the output buffer.
speakerThread_ = new Thread(new Runnable() {
public void run() {
while (true) {
synchronized (playingLock_) {
// Check if the thread should die.
if (shouldDie_) {
// This thread has been signalled to stop.
Log.i(getClass().getName(), "Dying now.");
playing_ = false;
shouldDie_ = false;
cleanupSound();
return;
}
}
// Do the actual work.
fillBuffer();
}
}
});
speakerThread_.setName(getClass().getName());
speakerThread_.setPriority(Thread.MAX_PRIORITY);
speakerThread_.setDaemon(true);
speakerThread_.start();
}
}
/**
* Tells the thread it should stop getting sampled data and outputting it as soon as possible.
*/
public void stop() {
Log.i(getClass().getName(), "stop() is waiting for playingLock_.");
synchronized (playingLock_) {
Log.i(getClass().getName(), "stop() received.");
shouldDie_ = true;
}
}
/**
* Blocks until the thread is no longer playing.
* You better call stop() before calling this.
*/
public void waitForStop() {
while (true) {
synchronized (playingLock_) {
if (!playing_) {
return;
}
}
}
}
/**
* Initializes the data structures needed for playing.
*/
private void initSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
cleanupSound();
}
// Get the smallest buffer to minimize latency.
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz_,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
Log.i(getClass().getName(), "Output sample rate: " + sampleRateInHz_);
Log.i(getClass().getName(), "Minimum buffer size: " + bufferSizeInBytes);
Log.i(getClass().getName(), "Minimum volume: " + AudioTrack.getMinVolume());
Log.i(getClass().getName(), "Maximum volume: " + AudioTrack.getMaxVolume());
audioTrack_ = new AudioTrack(
AudioManager.STREAM_MUSIC, // int streamType,
sampleRateInHz_, // int sampleRateInHz,
AudioFormat.CHANNEL_OUT_MONO, // int channelConfig,
AudioFormat.ENCODING_PCM_16BIT, // int audioFormat,
bufferSizeInBytes, // int bufferSizeInBytes,
AudioTrack.MODE_STREAM); // int mode);
buffer_ = new short[bufferSizeInBytes / 8];
time_.reset();
}
}
/**
* Starts the AudioTrack playing.
*/
private void startSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
audioTrack_.play();
}
}
}
/**
* Cleans up the internal data structures.
*/
private void cleanupSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
audioTrack_.stop();
audioTrack_.release();
audioTrack_ = null;
buffer_ = null;
}
}
}
/**
* Fills the buffer once, and then sends it off to the AudioTrack.
*/
private void fillBuffer() {
for (int i = 0; i < buffer_.length; ++i) {
// Change the output range from [-1, 1] to [-32767, 32767].
// 16-bit signed output is fairly standard, and hard-coded.
double output = synthesizer_.getValue(time_);
// Clamp values out of range.
if (output < -1.0) {
output = -1.0;
}
if (output > 1.0) {
output = 1.0;
}
buffer_[i] = (short)(32767 * output);
time_.advance();
}
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
// This call will block until the buffer has been copied, but will return before the
// sampled audio data has actually been output.
audioTrack_.write(buffer_, 0, buffer_.length);
}
}
}
// The actual thread that constantly loops, taking the output from the synthesizer and sending it
// to Android's audio output buffer.
Thread speakerThread_;
// Lock guarding playing_, shouldDie_, and speakerThread_.
private Object playingLock_;
// True iff the thread is in the "playing" state.
private boolean playing_;
// Variable used to signal to the speakerThread that playing should stop.
private boolean shouldDie_;
// Lock guarding audioTrack_, buffer_, and time_.
private Object audioTrackLock_;
// Android object for outputting audio.
private AudioTrack audioTrack_;
// Buffer for collecting output from the synthesizer until it is ready to be output.
private short[] buffer_;
// The sample rate of the synthesizer.
private int sampleRateInHz_;
// Tracker for time since synthesis started.
private SynthesisTime time_;
// Module to provide sampled audio data to be output.
private MultiChannelSynthesizer synthesizer_;
}

@ -0,0 +1,61 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying amplification/adsr envelope.
* TODO(klimt): Add the ability to switch channels.
*/
public class AmplificationActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.amplification);
attackKnob_ = (KnobView)findViewById(R.id.attackKnob);
decayKnob_ = (KnobView)findViewById(R.id.decayKnob);
sustainKnob_ = (KnobView)findViewById(R.id.sustainKnob);
releaseKnob_ = (KnobView)findViewById(R.id.releaseKnob);
volumeKnob_ = (KnobView)findViewById(R.id.volumeKnob);
piano_ = (PianoView)findViewById(R.id.piano);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
attackKnob_.bindTo(synthesizer_, channel, Setting.ATTACK);
decayKnob_.bindTo(synthesizer_, channel, Setting.DECAY);
sustainKnob_.bindTo(synthesizer_, channel, Setting.SUSTAIN);
releaseKnob_.bindTo(synthesizer_, channel, Setting.RELEASE);
volumeKnob_.bindTo(synthesizer_, channel, Setting.VOLUME);
piano_.bindTo(synthesizer_, channel);
}
private KnobView attackKnob_;
private KnobView decayKnob_;
private KnobView sustainKnob_;
private KnobView releaseKnob_;
private KnobView volumeKnob_;
private PianoView piano_;
}

@ -0,0 +1,73 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import java.util.ArrayList;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.ChordGridView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
/**
* Activity for playing whole chords at a time, arranged in a circle of fifths.
*/
public class ChordGridActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chord_grid);
chordGrid_ = (ChordGridView)findViewById(R.id.chord_grid);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner);
presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (synthesizer_ == null) {
return;
}
if (position > 0) {
chordGrid_.bindTo(synthesizer_, position - 1);
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
chordGrid_.bindTo(synthesizer_, 0);
ArrayList<String> presetNames = new ArrayList<String>();
presetNames.add("");
synthesizer_.getPresetNames(presetNames);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this, android.R.layout.simple_spinner_item, presetNames);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
presetSpinner_.setAdapter(adapter);
}
private ChordGridView chordGrid_;
private Spinner presetSpinner_;
}

@ -0,0 +1,141 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.google.synthesizer.R;
import com.google.synthesizer.android.service.SynthesizerService;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* An Activity to let the user choose a subset of a presets settings in order to edit them.
*/
public class EditInstrumentActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
String[] sections = getResources().getStringArray(R.array.sections);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this, android.R.layout.simple_list_item_1, sections);
setListAdapter(adapter);
}
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, SynthesizerService.class),
synthesizerConnection_, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(synthesizerConnection_);
}
@Override
protected void onListItemClick(ListView list, View view, int position, long id) {
int channel = SynthesizerActivity.getIntentChannel(this);
switch (position) {
case 0:
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(channel),
this,
VibratoActivity.class));
break;
case 1:
this.startActivity(new Intent(null, SynthesizerActivity.makeUri(
channel,
Setting.OSCILLATOR_1_WAVEFORM,
Setting.OSCILLATOR_1_GLIDE,
Setting.OSCILLATOR_1_COARSE,
Setting.OSCILLATOR_1_FINE,
Setting.OSCILLATOR_1_VIBRATO,
Setting.BALANCE),
this, OscillatorActivity.class));
break;
case 2:
this.startActivity(new Intent(null, SynthesizerActivity.makeUri(
channel,
Setting.OSCILLATOR_2_WAVEFORM,
Setting.OSCILLATOR_2_GLIDE,
Setting.OSCILLATOR_2_COARSE,
Setting.OSCILLATOR_2_FINE,
Setting.OSCILLATOR_2_VIBRATO,
Setting.BALANCE),
this, OscillatorActivity.class));
break;
case 3:
this.startActivity(new Intent(null, SynthesizerActivity.makeUri(
channel,
Setting.OSCILLATOR_1_BLEND,
Setting.OSCILLATOR_1_STRETCH,
Setting.OSCILLATOR_1_EXCITEMENT),
this, KarplusStrongActivity.class));
break;
case 4:
this.startActivity(new Intent(null, SynthesizerActivity.makeUri(
channel,
Setting.OSCILLATOR_2_BLEND,
Setting.OSCILLATOR_2_STRETCH,
Setting.OSCILLATOR_2_EXCITEMENT),
this, KarplusStrongActivity.class));
break;
case 5:
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(channel),
this,
TremoloActivity.class));
break;
case 6:
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(channel),
this,
LowPassFilterActivity.class));
break;
case 7:
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(channel),
this,
AmplificationActivity.class));
break;
case 8:
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(channel),
this,
EffectsActivity.class));
break;
}
}
private ServiceConnection synthesizerConnection_ = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
}
public void onServiceDisconnected(ComponentName className) {
}
};
}

@ -0,0 +1,52 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying effects like echo.
* TODO(klimt): Add the ability to switch channels.
*/
public class EffectsActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.effects);
piano_ = (PianoView)findViewById(R.id.piano);
echoMixKnob_ = (KnobView)findViewById(R.id.echoMixKnob);
echoDelayKnob_ = (KnobView)findViewById(R.id.echoDelayKnob);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
piano_.bindTo(synthesizer_, channel);
echoMixKnob_.bindTo(synthesizer_, channel, Setting.ECHO_MIX);
echoDelayKnob_.bindTo(synthesizer_, channel, Setting.ECHO_DELAY);
}
private PianoView piano_;
private KnobView echoMixKnob_;
private KnobView echoDelayKnob_;
}

@ -0,0 +1,81 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import java.util.ArrayList;
import com.google.synthesizer.android.service.SynthesizerService;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* An activity that shows the list of available presets (aka instruments), and let's the user click
* on one of them to begin editing it.
*/
public class InstrumentListActivity extends ListActivity {
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, SynthesizerService.class),
synthesizerConnection_, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(synthesizerConnection_);
}
@Override
protected void onListItemClick(ListView list, View view, int position, long id) {
this.startActivity(new Intent(null,
SynthesizerActivity.makeUri(position),
this,
EditInstrumentActivity.class));
}
private ServiceConnection synthesizerConnection_ = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
MultiChannelSynthesizer synthesizer =
((SynthesizerService.LocalBinder)service).getSynthesizer();
ArrayList<String> presets = new ArrayList<String>();
synthesizer.getPresetNames(presets);
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
InstrumentListActivity.this,
android.R.layout.simple_list_item_1,
presets.toArray(new String[0]));
InstrumentListActivity.this.runOnUiThread(new Runnable() {
public void run() {
InstrumentListActivity.this.setListAdapter(adapter);
InstrumentListActivity.this.getListView().invalidate();
}
});
}
public void onServiceDisconnected(ComponentName className) {
}
};
}

@ -0,0 +1,59 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying Karplus-Strong parameters.
* TODO(klimt): Add the ability to switch channels.
*/
public class KarplusStrongActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.karplus_strong);
piano_ = (PianoView)findViewById(R.id.piano);
blendKnob_ = (KnobView)findViewById(R.id.blendKnob);
stretchKnob_ = (KnobView)findViewById(R.id.stretchKnob);
excitementKnob_ = (KnobView)findViewById(R.id.excitementKnob);
PianoView piano = (PianoView)findViewById(R.id.piano);
piano.bindTo(synthesizer_, 0);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
Setting[] settings = getIntentSettings(this);
piano_.bindTo(synthesizer_, channel);
blendKnob_.bindTo(synthesizer_, channel, settings[0]);
stretchKnob_.bindTo(synthesizer_, channel, settings[1]);
excitementKnob_.bindTo(synthesizer_, channel, settings[2]);
}
private PianoView piano_;
private KnobView blendKnob_;
private KnobView stretchKnob_;
private KnobView excitementKnob_;
}

@ -0,0 +1,67 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying low-pass filter settings.
* TODO(klimt): Add the ability to switch channels.
*/
public class LowPassFilterActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.low_pass_filter);
piano_ = (PianoView)findViewById(R.id.piano);
cutoffKnob_ = (KnobView)findViewById(R.id.cutoffKnob);
depthKnob_ = (KnobView)findViewById(R.id.depthKnob);
attackKnob_ = (KnobView)findViewById(R.id.attackKnob);
decayKnob_ = (KnobView)findViewById(R.id.decayKnob);
sustainKnob_ = (KnobView)findViewById(R.id.sustainKnob);
releaseKnob_ = (KnobView)findViewById(R.id.releaseKnob);
PianoView piano = (PianoView)findViewById(R.id.piano);
piano.bindTo(synthesizer_, 0);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
piano_.bindTo(synthesizer_, channel);
cutoffKnob_.bindTo(synthesizer_, channel, Setting.FILTER_CUTOFF);
depthKnob_.bindTo(synthesizer_, channel, Setting.FILTER_DEPTH);
attackKnob_.bindTo(synthesizer_, channel, Setting.FILTER_ATTACK);
decayKnob_.bindTo(synthesizer_, channel, Setting.FILTER_DECAY);
sustainKnob_.bindTo(synthesizer_, channel, Setting.FILTER_SUSTAIN);
releaseKnob_.bindTo(synthesizer_, channel, Setting.FILTER_RELEASE);
}
private PianoView piano_;
private KnobView cutoffKnob_;
private KnobView depthKnob_;
private KnobView attackKnob_;
private KnobView decayKnob_;
private KnobView sustainKnob_;
private KnobView releaseKnob_;
}

@ -0,0 +1,107 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import java.util.ArrayList;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
// TODO(klimt): Add the ability to switch channels.
public class MainActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
piano_ = (PianoView)findViewById(R.id.piano);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner);
final Button playButton = (Button)findViewById(R.id.playButton);
final Button recordButton = (Button)findViewById(R.id.recordButton);
playButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (synthesizer_.getChannel(0).isPlaying()) {
synthesizer_.getChannel(0).stopPlaying();
playButton.setText(R.string.play);
recordButton.setText(R.string.record);
} else {
synthesizer_.getChannel(0).startPlaying();
playButton.setText(R.string.stop);
recordButton.setText(R.string.record);
}
}
});
recordButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (synthesizer_.getChannel(0).isRecording()) {
synthesizer_.getChannel(0).stopRecording();
playButton.setText(R.string.play);
recordButton.setText(R.string.record);
} else {
synthesizer_.getChannel(0).startRecording();
playButton.setEnabled(true);
playButton.setText(R.string.play);
recordButton.setText(R.string.stop);
}
}
});
presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (synthesizer_ == null) {
return;
}
String preset = presetSpinner_.getItemAtPosition(position).toString();
if (!preset.equals("")) {
synthesizer_.getChannel(0).setPreset(preset);
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
piano_.bindTo(synthesizer_, 0);
ArrayList<String> presetNames = new ArrayList<String>();
presetNames.add("");
synthesizer_.getPresetNames(presetNames);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this, android.R.layout.simple_spinner_item, presetNames);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
presetSpinner_.setAdapter(adapter);
}
private PianoView piano_;
private Spinner presetSpinner_;
}

@ -0,0 +1,66 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.android.widgets.waveform.WaveformRowView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying oscillator parameters.
* TODO(klimt): Add the ability to switch channels.
*/
public class OscillatorActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.oscillator);
piano_ = (PianoView)findViewById(R.id.piano);
waveformView_ = (WaveformRowView)findViewById(R.id.waveform);
glideKnob_ = (KnobView)findViewById(R.id.glideKnob);
coarseKnob_ = (KnobView)findViewById(R.id.coarseKnob);
fineKnob_ = (KnobView)findViewById(R.id.fineKnob);
vibratoDepthKnob_ = (KnobView)findViewById(R.id.vibratoDepthKnob);
balanceKnob_ = (KnobView)findViewById(R.id.balanceKnob);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
Setting[] settings = getIntentSettings(this);
piano_.bindTo(synthesizer_, channel);
waveformView_.bindTo(synthesizer_, channel, settings[0]);
glideKnob_.bindTo(synthesizer_, channel, settings[1]);
coarseKnob_.bindTo(synthesizer_, channel, settings[2]);
fineKnob_.bindTo(synthesizer_, channel, settings[3]);
vibratoDepthKnob_.bindTo(synthesizer_, channel, settings[4]);
balanceKnob_.bindTo(synthesizer_, channel, settings[5]);
}
private PianoView piano_;
private WaveformRowView waveformView_;
private KnobView glideKnob_;
private KnobView coarseKnob_;
private KnobView fineKnob_;
private KnobView vibratoDepthKnob_;
private KnobView balanceKnob_;
}

@ -0,0 +1,79 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import java.util.ArrayList;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for simply playing the piano.
*/
public class PianoActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.piano);
piano_ = (PianoView)findViewById(R.id.piano);
volumeKnob_ = (KnobView)findViewById(R.id.volumeKnob);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner);
presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (synthesizer_ == null) {
return;
}
if (position > 0) {
piano_.bindTo(synthesizer_, position - 1);
volumeKnob_.bindTo(synthesizer_, position - 1, Setting.VOLUME);
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
piano_.bindTo(synthesizer_, 0);
volumeKnob_.bindTo(synthesizer_, 0, Setting.VOLUME);
ArrayList<String> presetNames = new ArrayList<String>();
presetNames.add("");
synthesizer_.getPresetNames(presetNames);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this, android.R.layout.simple_spinner_item, presetNames);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
presetSpinner_.setAdapter(adapter);
}
private PianoView piano_;
private KnobView volumeKnob_;
private Spinner presetSpinner_;
}

@ -0,0 +1,141 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import com.google.synthesizer.R;
import com.google.synthesizer.android.Storage;
import com.google.synthesizer.android.widgets.score.ScoreView;
import com.google.synthesizer.android.widgets.score.ScoreViewToolbar;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.music.Music.Score.Builder;
/**
* An Activity for editing or playing a score.
*/
public class ScoreActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.score);
logger_ = Logger.getLogger(getClass().getName());
scoreView_ = (ScoreView)findViewById(R.id.score);
scoreViewToolbar_ = (ScoreViewToolbar)findViewById(R.id.toolbar);
scoreViewToolbar_.setScoreView(scoreView_);
}
@Override
protected void onStart() {
super.onStart();
try {
Storage.openDefaultScore(scoreView_.getScore(), this.getApplicationContext());
scoreView_.invalidate();
scoreViewToolbar_.invalidate();
} catch (IOException e) {
logger_.log(Level.SEVERE, "Unable to open score.", e);
}
}
@Override
protected void onStop() {
try {
Storage.saveDefaultScore(scoreView_.getScore().build(), this.getApplicationContext());
} catch (IOException e) {
logger_.log(Level.SEVERE, "Unable to save score.", e);
}
super.onStop();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.score_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.new_score:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("New score...");
builder.setMessage("This will erase any unsaved work. Are you sure?");
builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
scoreView_.getScore().clear();
scoreView_.invalidate();
scoreViewToolbar_.invalidate();
dialog.dismiss();
}
});
builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
return true;
case R.id.open_score:
Storage.openScoreWithDialog(scoreView_.getScore(), new Storage.OpenScoreListener() {
public void onOpenScore(Builder score) {
scoreView_.invalidate();
scoreViewToolbar_.invalidate();
}
}, this);
return true;
case R.id.save_score:
Storage.saveScoreWithDialog(scoreView_.getScore().build(), this);
return true;
case R.id.piano:
this.startActivity(new Intent(this, PianoActivity.class));
return true;
case R.id.chord_grid:
this.startActivity(new Intent(this, ChordGridActivity.class));
return true;
case R.id.edit_instrument:
this.startActivity(new Intent(this, InstrumentListActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
scoreView_.bindTo(synth);
}
private ScoreView scoreView_;
private ScoreViewToolbar scoreViewToolbar_;
private Logger logger_;
}

@ -0,0 +1,189 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import com.google.synthesizer.R;
import com.google.synthesizer.android.service.SynthesizerService;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* A base class for any Android Activity that wants to interact with the SynthesizerService.
*/
public abstract class SynthesizerActivity extends Activity {
/**
* Called when the synthesizer model changes.
*/
protected abstract void onSynthesizerUpdate(MultiChannelSynthesizer synth);
/**
* Creates a URI for a specific synthesizer path.
* @param path - The absolute path of the component.
*/
public static Uri makeUri(int channel, Setting... settings) {
StringBuilder uri = new StringBuilder("content://com.google.synthesizer/" + channel + "/");
boolean first = true;
for (Setting setting : settings) {
if (!first) {
uri.append(',');
}
uri.append(setting.getNumber());
first = false;
}
return Uri.parse(uri.toString());
}
/**
* Returns the path part of the URI that invoked an activity.
*/
private static String getPath(Activity activity) {
Intent intent = activity.getIntent();
if (intent == null) {
Log.e(SynthesizerActivity.class.getName(),
"Attempted to get Intent module for SynthesizerActivity with no Intent.");
return null;
}
Uri uri = intent.getData();
if (uri == null) {
Log.e(SynthesizerActivity.class.getName(),
"Attempted to get Intent module for Intent with no URI: " + intent);
return null;
}
String path = uri.getPath();
if (path == null) {
Log.e(SynthesizerActivity.class.getName(),
"Attempted to get Intent module for URI with no path: " + uri);
return null;
}
if (path.startsWith("/")) {
path = path.substring(1);
}
return path;
}
/**
* Gets the modules specified by the URI for the intent given to this Activity.
* @return - The list of settings found, if any. Otherwise, null.
*/
public static Setting[] getIntentSettings(Activity activity) {
Setting[] settings = null;
String path = getPath(activity);
// Clip off the channel, if it's there...
if (path.indexOf('/') >= 0) {
path = path.substring(path.indexOf('/') + 1);
}
String[] parts = path.split(", *");
settings = new Setting[parts.length];
for (int i = 0; i < parts.length; ++i) {
try {
int id = Integer.parseInt(parts[i]);
settings[i] = Setting.valueOf(id);
} catch (NumberFormatException e) {
Log.e(SynthesizerActivity.class.getName(),
"Unable to convert number \"" + parts[i] + "\" in path: " + path);
}
}
return settings;
}
/**
* Gets the channel specified by the URI for the intent given to this Activity.
* @return - The channel in the intent, or 0 if none was found.
*/
public static int getIntentChannel(Activity activity) {
String path = getPath(activity);
int firstSlash = path.indexOf('/');
if (firstSlash < 0) {
Log.e(SynthesizerActivity.class.getName(),
"Unable to find channel number in path: " + path);
return 0;
}
String channelString = path.substring(0, firstSlash);
try {
return Integer.parseInt(channelString);
} catch (NumberFormatException e) {
Log.e(SynthesizerActivity.class.getName(),
"Unable to convert channel number \"" + channelString + "\" in path: " + path);
return 0;
}
}
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, SynthesizerService.class),
synthesizerConnection_, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(synthesizerConnection_);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.piano:
this.startActivity(new Intent(this, PianoActivity.class));
return true;
case R.id.chord_grid:
this.startActivity(new Intent(this, ChordGridActivity.class));
return true;
case R.id.edit_instrument:
this.startActivity(new Intent(this, InstrumentListActivity.class));
return true;
case R.id.compose:
this.startActivity(new Intent(this, ScoreActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private ServiceConnection synthesizerConnection_ = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// synthesizer_ = ISynthesizerService.Stub.asInterface(service);
synthesizer_ = ((SynthesizerService.LocalBinder)service).getSynthesizer();
SynthesizerActivity.this.onSynthesizerUpdate(synthesizer_);
}
public void onServiceDisconnected(ComponentName className) {
synthesizer_ = null;
SynthesizerActivity.this.onSynthesizerUpdate(synthesizer_);
}
};
protected MultiChannelSynthesizer synthesizer_ = null;
}

@ -0,0 +1,68 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.android.widgets.waveform.WaveformRowView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying tremolo.
* TODO(klimt): Add the ability to switch channels.
*/
public class TremoloActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tremolo);
piano_ = (PianoView)findViewById(R.id.piano);
waveformView_ = (WaveformRowView)findViewById(R.id.waveform);
rateKnob_ = (KnobView)findViewById(R.id.rateKnob);
depthKnob_ = (KnobView)findViewById(R.id.depthKnob);
attackKnob_ = (KnobView)findViewById(R.id.attackKnob);
decayKnob_ = (KnobView)findViewById(R.id.decayKnob);
sustainKnob_ = (KnobView)findViewById(R.id.sustainKnob);
releaseKnob_ = (KnobView)findViewById(R.id.releaseKnob);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
piano_.bindTo(synthesizer_, channel);
waveformView_.bindTo(synthesizer_, channel, Setting.TREMOLO_WAVEFORM);
rateKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_RATE);
depthKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_DEPTH);
attackKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_ATTACK);
decayKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_DECAY);
sustainKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_SUSTAIN);
releaseKnob_.bindTo(synthesizer_, channel, Setting.TREMOLO_RELEASE);
}
private PianoView piano_;
private WaveformRowView waveformView_;
private KnobView rateKnob_;
private KnobView depthKnob_;
private KnobView attackKnob_;
private KnobView decayKnob_;
private KnobView sustainKnob_;
private KnobView releaseKnob_;
}

@ -0,0 +1,65 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.ui;
import android.os.Bundle;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.knob.KnobView;
import com.google.synthesizer.android.widgets.piano.PianoView;
import com.google.synthesizer.android.widgets.waveform.WaveformRowView;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* Activity for modifying vibrato.
* TODO(klimt): Add the ability to switch channels.
*/
public class VibratoActivity extends SynthesizerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.vibrato);
piano_ = (PianoView)findViewById(R.id.piano);
waveformView_ = (WaveformRowView)findViewById(R.id.waveform);
rateKnob_ = (KnobView)findViewById(R.id.rateKnob);
attackKnob_ = (KnobView)findViewById(R.id.attackKnob);
decayKnob_ = (KnobView)findViewById(R.id.decayKnob);
sustainKnob_ = (KnobView)findViewById(R.id.sustainKnob);
releaseKnob_ = (KnobView)findViewById(R.id.releaseKnob);
}
@Override
protected void onSynthesizerUpdate(MultiChannelSynthesizer synth) {
int channel = getIntentChannel(this);
piano_.bindTo(synthesizer_, channel);
waveformView_.bindTo(synthesizer_, channel, Setting.VIBRATO_WAVEFORM);
rateKnob_.bindTo(synthesizer_, channel, Setting.VIBRATO_RATE);
attackKnob_.bindTo(synthesizer_, channel, Setting.VIBRATO_ATTACK);
decayKnob_.bindTo(synthesizer_, channel, Setting.VIBRATO_DECAY);
sustainKnob_.bindTo(synthesizer_, channel, Setting.VIBRATO_SUSTAIN);
releaseKnob_.bindTo(synthesizer_, channel, Setting.VIBRATO_RELEASE);
}
private PianoView piano_;
private WaveformRowView waveformView_;
private KnobView rateKnob_;
private KnobView attackKnob_;
private KnobView decayKnob_;
private KnobView sustainKnob_;
private KnobView releaseKnob_;
}

@ -0,0 +1,328 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets;
import com.google.synthesizer.R;
import com.google.synthesizer.android.widgets.piano.PianoViewListener;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.music.Note;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* ChordGridView is an alternative interface for performing that has keys for chords amongst
* fundamentals arranged in a circle of fifths.
*/
public class ChordGridView extends View {
/**
* Basic android widget constructor.
*/
public ChordGridView(Context context, AttributeSet attrs) {
super(context, attrs);
// Get the xml attributes for this instance.
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChordGridView);
firstOctave_ = a.getInteger(R.styleable.ChordGridView_octave, 4);
pressedRow_ = -1;
pressedColumn_ = -1;
drawingRect_ = new Rect();
path_ = new Path();
strokePaint_ = new Paint();
fillPaint_ = new Paint();
strokePaint_.setStyle(Style.STROKE);
fillPaint_.setStyle(Style.FILL);
}
/**
* Sets the listener that will receive events from this widget.
*/
public void setPianoViewListener(PianoViewListener pianoViewListener) {
pianoViewListener_ = pianoViewListener;
}
/**
* Signals the listener that a new note was pressed.
* @param logFrequency - the log frequency of the new note.
* @param retriggerIfOn - true if this is a new touch, rather than just moving.
*/
private void notifyNoteDown(double logFrequency, int finger, boolean retriggerIfOn) {
if (pianoViewListener_ != null) {
pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn);
}
}
/**
* Signals the listener that a note was released.
*/
private void notifyNoteUp(int finger) {
if (pianoViewListener_ != null) {
pianoViewListener_.noteUp(finger);
}
}
/**
* Handler for all touch events.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
boolean redraw = false;
if (actionCode == MotionEvent.ACTION_DOWN) {
int tileWidth = drawingRect_.width() / COLUMNS;
int tileHeight = drawingRect_.height() / ROWS;
pressedColumn_ = (int)(event.getX() - drawingRect_.left) / tileWidth;
pressedRow_ = (int)(event.getY() - drawingRect_.top) / tileHeight;
redraw = true;
getTileInfo(pressedRow_, pressedColumn_);
notifyNoteDown(Note.computeLog12TET(tileNote1_, tileOctave1_), 0, true);
notifyNoteDown(Note.computeLog12TET(tileNote2_, tileOctave2_), 1, true);
notifyNoteDown(Note.computeLog12TET(tileNote3_, tileOctave3_), 2, true);
} else if (actionCode == MotionEvent.ACTION_MOVE) {
int tileWidth = drawingRect_.width() / COLUMNS;
int tileHeight = drawingRect_.height() / ROWS;
int newPressedColumn_ = (int)(event.getX() - drawingRect_.left) / tileWidth;
int newPressedRow_ = (int)(event.getY() - drawingRect_.top) / tileHeight;
if (pressedColumn_ != newPressedColumn_ || pressedRow_ != newPressedRow_) {
pressedColumn_ = newPressedColumn_;
pressedRow_ = newPressedRow_;
redraw = true;
getTileInfo(pressedRow_, pressedColumn_);
notifyNoteDown(Note.computeLog12TET(tileNote1_, tileOctave1_), 0, false);
notifyNoteDown(Note.computeLog12TET(tileNote2_, tileOctave2_), 1, false);
notifyNoteDown(Note.computeLog12TET(tileNote3_, tileOctave3_), 2, false);
}
} else if (actionCode == MotionEvent.ACTION_UP) {
pressedColumn_ = -1;
pressedRow_ = -1;
notifyNoteUp(0);
notifyNoteUp(1);
notifyNoteUp(2);
redraw = true;
} else {
return super.onTouchEvent(event);
}
if (redraw) {
invalidate();
}
return true;
}
/**
* Draws the widget.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(drawingRect_);
int tileWidth = drawingRect_.width() / COLUMNS;
int tileHeight = drawingRect_.height() / ROWS;
for (int row = 0; row < ROWS; ++row) {
for (int column = 0; column < COLUMNS; ++column) {
getTileInfo(row, column);
int foreground;
int background;
if (Note.isNatural(tileNote1_)) {
if (row == pressedRow_ && column == pressedColumn_) {
foreground = Color.GREEN;
} else {
foreground = Color.WHITE;
}
background = Color.BLACK;
} else {
foreground = Color.BLACK;
if (row == pressedRow_ && column == pressedColumn_) {
background = Color.GREEN;
} else {
background = Color.WHITE;
}
}
int textColor = background;
fillPaint_.setColor(foreground);
strokePaint_.setColor(background);
canvas.drawRect(tileWidth * column,
tileHeight * row,
tileWidth * (column + 1),
tileHeight * (row + 1),
fillPaint_);
canvas.drawRect(tileWidth * column + 1,
tileHeight * row + 1,
tileWidth * (column + 1) - 2,
tileHeight * (row + 1) - 2,
strokePaint_);
if (tileIsMajor_) {
textColor = foreground;
strokePaint_.setColor(foreground);
fillPaint_.setColor(background);
path_.reset();
path_.moveTo(tileWidth * column + 15, tileHeight * (row + 1) - 15);
path_.lineTo(tileWidth * (column + 1) - 15, tileHeight * (row + 1) - 15);
path_.lineTo(tileWidth * column + tileWidth / 2, tileHeight * row + 15);
path_.close();
canvas.drawPath(path_, fillPaint_);
}
if (tileIsMinor_) {
textColor = foreground;
strokePaint_.setColor(foreground);
fillPaint_.setColor(background);
path_.reset();
path_.moveTo(tileWidth * column + 15, tileHeight * row + tileHeight / 2);
path_.lineTo(tileWidth * (column + 1) - 15, tileHeight * (row + 1) - 15);
path_.lineTo(tileWidth * (column + 1) - 15, tileHeight * row + 15);
path_.close();
canvas.drawPath(path_, fillPaint_);
}
if (tileNote1_ == Note.C && !tileIsMinor_ && !tileIsMajor_) {
fillPaint_.setColor(background);
canvas.drawCircle(tileWidth * column + tileWidth / 2,
tileHeight * row + tileHeight / 2,
10,
fillPaint_);
}
strokePaint_.setColor(textColor);
canvas.drawText(Note.getName(tileNote1_),
tileWidth * column + tileWidth / 2,
tileHeight * row + tileHeight / 2,
strokePaint_);
}
}
}
/**
* Populates the tile* fields for the tile at the given row and column.
*/
private void getTileInfo(int row, int column) {
int startIndex = 5 + firstOctave_ * 36;
int absoluteIndex = startIndex + row + column * 14;
if (absoluteIndex % 3 == 0) {
// Fundamental key.
tileOctave1_ = absoluteIndex / 36;
tileNote1_ = (absoluteIndex % 36) / 3;
tileOctave2_ = tileOctave1_;
tileNote2_ = tileNote1_;
tileOctave3_ = tileOctave1_;
tileNote3_ = tileNote1_;
tileIsMajor_ = false;
tileIsMinor_ = false;
} else if (absoluteIndex % 3 == 1) {
// Minor chord key.
tileOctave3_ = (int)((absoluteIndex - 1) / 36) + 1;
tileNote3_ = ((absoluteIndex - 1) % 36) / 3;
if (tileNote3_ >= 4) {
tileOctave2_ = tileOctave3_;
tileNote2_ = tileNote3_ - 4;
} else {
tileOctave2_ = tileOctave3_ - 1;
tileNote2_ = tileNote3_ + 8;
}
if (tileNote3_ >= 7) {
tileOctave1_ = tileOctave3_;
tileNote1_ = tileNote3_ - 7;
} else {
tileOctave1_ = tileOctave3_ - 1;
tileNote1_ = tileNote3_ + 5;
}
tileIsMajor_ = false;
tileIsMinor_ = true;
} else if (absoluteIndex % 3 == 2) {
// Major chord key.
tileOctave1_ = (int)((absoluteIndex + 1) / 36);
tileNote1_ = ((absoluteIndex + 1) % 36) / 3;
if (tileNote1_ < 8) {
tileOctave2_ = tileOctave1_;
tileNote2_ = tileNote1_ + 4;
} else {
tileOctave2_ = tileOctave1_ + 1;
tileNote2_ = tileNote1_ - 8;
}
if (tileNote1_ >= 7) {
tileOctave3_ = tileOctave1_;
tileNote3_ = tileNote1_ + 7;
} else {
tileOctave3_ = tileOctave1_ + 1;
tileNote3_ = tileNote1_ - 5;
}
tileIsMajor_ = true;
tileIsMinor_ = false;
}
}
/**
* Connects the ChordGridView to a Synthesizer.
* @synth - The synthesizer to connect to.
*/
public void bindTo(final MultiChannelSynthesizer synth, final int channel) {
this.setPianoViewListener(new PianoViewListener() {
public void noteDown(double logFrequency, int finger, boolean retriggerIfOn) {
synth.getChannel(channel).setPitch(logFrequency, finger);
synth.getChannel(channel).turnOn(retriggerIfOn, finger);
}
public void noteUp(int finger) {
synth.getChannel(channel).turnOff(finger);
}
});
}
/**
* Populated by getTileInfo, these fields will contain the info for a particular key.
*/
private int tileOctave1_;
private int tileOctave2_;
private int tileOctave3_;
private int tileNote1_;
private int tileNote2_;
private int tileNote3_;
private boolean tileIsMajor_;
private boolean tileIsMinor_;
// The coordinates of the key currently being pressed.
private int pressedColumn_;
private int pressedRow_;
// The current octave the keyboard is on.
private int firstOctave_;
// The listener to receive key events.
private PianoViewListener pianoViewListener_;
// These are basically stack variables for onDraw. They're member variables only so that we can
// avoid reallocating them every time the keyboard is redrawn.
//
// The most recent screen rect that this keyboard was drawn into.
private Rect drawingRect_;
private Path path_;
private Paint strokePaint_;
private Paint fillPaint_;
private static final int ROWS = 8;
private static final int COLUMNS = 5;
}

@ -0,0 +1,27 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.knob;
/**
* KnobListener is an interface for getting events when a KnobView's value changes.
*/
public interface KnobListener {
/**
* Called when the value changes.
*/
void onKnobChanged(double newValue);
}

@ -0,0 +1,78 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.knob;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* KnobPlaceholderView is an empty widget that behaves the same way as a KnobView when sizing.
* It's useful for layouts that vary how many knobs they have, but want them sized consistently.
*/
public class KnobPlaceholderView extends View {
public KnobPlaceholderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Controls how the knob is sized; it is square, and prefers to be 100x100 pixels.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Prefer 100 for both dimensions.
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 100;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 100;
break;
}
// Make it square.
if (width > height && widthMode != MeasureSpec.EXACTLY) {
width = height;
}
if (height > width && heightMode != MeasureSpec.EXACTLY) {
height = width;
}
setMeasuredDimension(width, height);
}
}

@ -0,0 +1,390 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.knob;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.google.synthesizer.R;
import com.google.synthesizer.core.model.SynthesizerInput;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* KnobView is a widget for setting a real value by turning a virtual "knob".
*/
public class KnobView extends View {
/** Basic constructor for an Android widget. */
public KnobView(Context context, AttributeSet attrs) {
super(context, attrs);
// Load the xml attributes.
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KnobView);
knobValue_ = a.getFloat(R.styleable.KnobView_value, 0.5f);
min_ = a.getFloat(R.styleable.KnobView_min, 0.0f);
max_ = a.getFloat(R.styleable.KnobView_max, 1.0f);
// Set up the drawing structures.
knobPaint_ = new Paint();
knobPaint_.setAntiAlias(true);
knobPaint_.setColor(Color.WHITE);
rect_ = new Rect();
rectF_ = new RectF();
textRect_ = new Rect();
// The listener has to be set later.
listener_ = null;
setPadding(3, 3, 3, 3);
}
/**
* Touch event handler.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN: {
// Just record the current finger position.
getDrawingRect(rect_);
previousX_ = event.getX() - rect_.centerX();
previousY_ = event.getY() - rect_.centerY();
currentTouchAngle_ = knobValue_ * 2 * Math.PI * 0.8 + (Math.PI / 5.0);
diffAngle_ = 0;
break;
}
case MotionEvent.ACTION_MOVE: {
getDrawingRect(rect_);
// Compare the previous angle of the finger position (relative to the center of the control)
// to the new angle, and update the value accordingly.
currentTouchAngle_ = knobValue_ * 2 * Math.PI * 0.8 + (Math.PI / 5.0);
currentX_ = event.getX() - rect_.centerX();
currentY_ = event.getY() - rect_.centerY();
diffAngle_ = Math.atan2(currentY_, currentX_) - Math.atan2(previousY_, previousX_);
if (diffAngle_ > Math.PI) {
diffAngle_ -= Math.PI * 2;
} else if (diffAngle_ < -Math.PI) {
diffAngle_ += Math.PI * 2;
}
currentTouchAngle_ += diffAngle_;
knobValue_ = currentTouchAngle_ / (2.0 * Math.PI);
if (knobValue_ < 0.1) knobValue_ = 0.1;
if (knobValue_ > 0.9) knobValue_ = 0.9;
knobValue_ -= 0.1;
knobValue_ /= 0.8;
previousX_ = currentX_;
previousY_ = currentY_;
// Notify listener and redraw.
if (listener_ != null) {
listener_.onKnobChanged(getValue());
}
invalidate();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
break;
}
}
return true;
}
/**
* Sets the listener to receive events when the knob's value changes.
*/
public void setKnobListener(KnobListener listener) {
listener_ = listener;
}
/**
* Sets the value for the knob when it is turned all the way counter-clockwise.
*/
public void setMin(double min) {
min_ = min;
invalidate();
}
/**
* Sets the value for the knob when it is turned all the way clockwise.
*/
public void setMax(double max) {
max_ = max;
invalidate();
}
/**
* Sets the current value of the knob.
*/
public void setValue(double value) {
if (value < min_) {
knobValue_ = 0.0;
} else if (value > max_) {
knobValue_ = 1.0;
} else {
knobValue_ = (value - min_) / (max_ - min_);
}
if (listener_ != null) {
listener_.onKnobChanged(value);
}
invalidate();
}
/**
* Returns the current value of the knob.
*/
public double getValue() {
return min_ + (knobValue_ * (max_ - min_));
}
/**
* Drawing handler.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(rect_);
rectF_.set(rect_);
// Make it square.
if (rectF_.height() > rectF_.width()) {
float center = rectF_.centerY();
rectF_.top = center - rectF_.width() / 2;
rectF_.bottom = center + rectF_.width() / 2;
} else {
float center = rectF_.centerX();
rectF_.left = center - rectF_.height() / 2;
rectF_.right = center + rectF_.height() / 2;
}
// Draw outer white glow.
int[] radialGradientColors = {Color.WHITE, Color.WHITE, 0x00000000};
float[] radialGradientPositions = {0.0f, 0.87f, 1.0f};
radialGradient_ = new RadialGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2,
radialGradientColors,
radialGradientPositions,
TileMode.CLAMP);
knobPaint_.setShader(radialGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2,
knobPaint_);
// Draw outer gauge.
final int fullDark = Color.BLACK;
final int guageStartColor = 0xff202050;
final int guageEndColor = 0xff4040A0;
final int adjustedStartColor = Color.argb(
0xFF,
(int)(0.1875 * Color.red(guageStartColor) + (1.0 - 0.1875) * Color.red(guageEndColor)),
(int)(0.1875 * Color.green(guageStartColor) + (1.0 - 0.1875) * Color.green(guageEndColor)),
(int)(0.1875 * Color.blue(guageStartColor) + (1.0 - 0.1875) * Color.blue(guageEndColor)));
int[] sweepGradientColors = {
adjustedStartColor,
guageEndColor,
fullDark,
fullDark,
guageStartColor,
adjustedStartColor};
float[] sweepGradientPositions = { 0.0f, 0.16f, 0.16f, 0.35f, 0.35f, 1.0f };
sweepGradient_ = new SweepGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
sweepGradientColors,
sweepGradientPositions);
knobPaint_.setShader(sweepGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2.4f,
knobPaint_);
// Draw inner gauge.
knobPaint_.setShader(null);
knobPaint_.setStyle(Style.FILL);
knobPaint_.setColor(Color.BLACK);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 4,
knobPaint_);
// Draw inner white glow.
int[] innerRadialGradientColors = { 0x00000000, 0x00000000, Color.WHITE };
float[] innerRadialGradientPositions = { 0.0f, 0.6f, 1.0f };
innerRadialGradient_ = new RadialGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 4f,
innerRadialGradientColors,
innerRadialGradientPositions,
TileMode.CLAMP);
knobPaint_.setShader(innerRadialGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(),rect_.height()) / 4,
knobPaint_);
// Draw indicator.
knobPaint_.setShader(null);
knobPaint_.setColor(Color.WHITE);
knobPaint_.setStyle(Style.STROKE);
knobPaint_.setStrokeWidth(4.0f);
final float arcWidth = 15.0f;
canvas.drawArc(rectF_,
(float)(knobValue_ * 360 * 0.8 + 90 - arcWidth / 2 + 36),
(float)arcWidth,
false,
knobPaint_);
// Draw text.
String knobValueString = String.format("%.2f", getValue());
Typeface typeface = Typeface.create(knobPaint_.getTypeface(), Typeface.BOLD);
knobPaint_.setTypeface(typeface);
knobPaint_.setTextAlign(Align.CENTER);
knobPaint_.setTextSize(rectF_.width() / 8);
knobPaint_.setSubpixelText(true);
knobPaint_.setStyle(Style.FILL);
knobPaint_.getTextBounds(knobValueString, 0, knobValueString.length(), textRect_);
canvas.drawText(knobValueString,
rect_.centerX(),
rect_.centerY() + textRect_.height() / 2,
knobPaint_);
}
/**
* Controls how the knob is sized; it is square, and prefers to be 100x100 pixels.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Specify that 100 is preferred for both dimensions.
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 100;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 100;
break;
}
// Make it square.
if (width > height && widthMode != MeasureSpec.EXACTLY) {
width = height;
}
if (height > width && heightMode != MeasureSpec.EXACTLY) {
height = width;
}
setMeasuredDimension(width, height);
}
/**
* Connects knob to a SynthesizerInput.
* @input - The synthesizer input to connect to.
*/
public void bindTo(final SynthesizerInput input) {
setValue(input.getSynthesizerInputValue());
setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) {
input.setValue(newValue);
}
});
}
/**
* Connects knob to a SynthesizerInput.
* @synth - The synthesizer to connect to.
* @path - The setting to connect to.
* @return - True on success, false on failure.
*/
public boolean bindTo(final MultiChannelSynthesizer synth, int channel, Setting setting) {
SynthesizerInput input = synth.getChannel(channel).getSynthesizerInput(setting);
if (input != null) {
bindTo(input);
return true;
} else {
Log.e(getClass().getName(), "Unable to bind to setting " + setting.name() + ".");
return false;
}
}
// Knob's current value, ranges from 0 - 1.0.
private double knobValue_;
private double min_;
private double max_;
// Structures used in drawing that we don't want to reallocate every time we draw.
private Paint knobPaint_;
private Rect rect_;
private Rect textRect_;
private RectF rectF_;
private SweepGradient sweepGradient_;
private RadialGradient radialGradient_;
private RadialGradient innerRadialGradient_;
// Position of the finger relative to the knob.
private double previousX_ = 0;
private double previousY_ = 0;
private double currentX_ = 0;
private double currentY_ = 0;
private double currentTouchAngle_ = 0;
private double diffAngle_ = 0;
// Object listening for events when the knob's value changes.
private KnobListener listener_;
}

@ -0,0 +1,95 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import com.google.synthesizer.core.music.Note;
/**
* One of the black (non-natural) keys on the piano.
*/
public class BlackPianoKey extends NotePianoKey {
/**
* Creates a new key.
* @param piano - the piano this key is on.
* @param octaveOffset - octave of the key, relative to the leftmost octave of the piano.
* @param key - offset of the key from the start of the octave.
*/
public BlackPianoKey(PianoView piano, int octave, int key) {
super(piano, octave, key);
}
/**
* Sets rect_ to the position of this key, based on the drawing rect of the piano it's on.
* @param drawingRect - the position of the piano itself.
* @param octaves - the number of octaves visible on the piano keyboard.
*/
public void layout(Rect drawingRect, int octaves) {
int whiteKeyWidth = getWhiteKeyWidth(drawingRect, octaves);
int blackKeyWidth = getBlackKeyWidth(drawingRect, octaves);
rect_.top = 0;
rect_.bottom = rect_.top + getBlackKeyHeight(drawingRect);
rect_.left = ((octaveOffset_ * WHITE_KEYS.length + key_ + 2) * whiteKeyWidth) -
(blackKeyWidth/2);
rect_.right = rect_.left + blackKeyWidth;
}
/**
* Returns the log frequency of the note of the key.
*/
public double getLogFrequency() {
return Note.computeLog12TET(BLACK_KEYS[key_], octaveOffset_ + piano_.getFirstOctave());
}
/**
* Draws the key in the current rect_.
*/
public void draw(Canvas canvas) {
strokePaint_.setColor(Color.BLACK);
if (isPressed()) {
fillPaint_.setColor(Color.GREEN);
} else {
fillPaint_.setColor(Color.BLACK);
}
canvas.drawRect(rect_, fillPaint_);
canvas.drawRect(rect_, strokePaint_);
}
/**
* Returns true if this is one of the black key positions that should actually have a key.
*/
public static boolean isValid(int note) {
return BLACK_KEYS[note] != Note.NONE;
}
/**
* Utility function to calculate the width that a standard black key on this keyboard should be.
*/
protected static int getBlackKeyWidth(Rect drawingRect, int octaves) {
return (getWhiteKeyWidth(drawingRect, octaves) * 2) / 3;
}
/**
* Utility function to calculate the height that a standard black key on this keyboard should be.
*/
protected static int getBlackKeyHeight(Rect drawingRect) {
return getWhiteKeyHeight(drawingRect) / 2;
}
}

@ -0,0 +1,52 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
/**
* Abstract base class for keys on the piano that play a note when pressed.
*/
public abstract class NotePianoKey extends PianoKey {
/**
* Creates a new key.
* @param piano - the piano this key is on.
* @param octaveOffset - octave of the key, relative to the leftmost octave of the piano.
* @param key - offset of the key from the start of the octave.
*/
public NotePianoKey(PianoView piano, int octaveOffset, int key) {
super(piano);
octaveOffset_ = octaveOffset;
key_ = key;
}
/**
* Called when the pressed_ state has changed.
*/
protected void onPressedChanged(boolean move) {
}
/**
* Returns the log frequency of the note of the key.
*/
abstract protected double getLogFrequency();
// Octave of the key, relative to the leftmost octave of the piano.
protected int octaveOffset_;
// Offset of the key from the start of the octave.
protected int key_;
}

@ -0,0 +1,111 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Rect;
/**
* A key on the piano for changing the octave up or down.
*/
public class OctavePianoKey extends PianoKey {
/**
* Creates the key.
* @param piano - the piano this key is on.
* @param delta - how the octave should change when this key is pressed.
*/
public OctavePianoKey(PianoView piano, int delta) {
super(piano);
arrow_ = new Path();
delta_ = delta;
}
/**
* Sets rect_ to the position of this key, based on the drawing rect of the piano it's on.
* @param drawingRect - the position of the piano itself.
* @param octaves - the number of octaves visible on the piano keyboard.
*/
public void layout(Rect drawingRect, int octaves) {
int whiteKeyWidth = getWhiteKeyWidth(drawingRect, octaves);
rect_.top = 0;
rect_.bottom = getWhiteKeyHeight(drawingRect);
if (delta_ <= 0) {
rect_.left = 0;
rect_.right = rect_.left + whiteKeyWidth;
} else {
rect_.right = drawingRect.right;
rect_.left = rect_.right - whiteKeyWidth;
}
}
/**
* Returns true if the current octave of the piano could be changed by delta and still be valid.
*/
private boolean isValid() {
return (piano_.getFirstOctave() + delta_ >= 0 &&
piano_.getFirstOctave() + piano_.getOctaves() + delta_ <= 8);
}
/**
* Draws the key in the current rect_.
*/
public void draw(Canvas canvas) {
strokePaint_.setColor(Color.BLACK);
fillPaint_.setColor(Color.BLACK);
if (isPressed() && isValid()) {
fillPaint_.setColor(Color.GREEN);
}
canvas.drawRect(rect_, fillPaint_);
canvas.drawRect(rect_, strokePaint_);
// Draw an arrow in the direction of the delta.
if (isValid()) {
arrow_.reset();
if (delta_ <= 0) {
arrow_.moveTo(rect_.left + 2, rect_.height() / 2);
arrow_.lineTo(rect_.right - 2, rect_.height() / 2 - 20);
arrow_.lineTo(rect_.right - 2, rect_.height() / 2 + 20);
} else {
arrow_.moveTo(rect_.right - 2, rect_.height() / 2);
arrow_.lineTo(rect_.left + 2, rect_.height() / 2 - 20);
arrow_.lineTo(rect_.left + 2, rect_.height() / 2 + 20);
}
arrow_.close();
fillPaint_.setColor(Color.WHITE);
canvas.drawPath(arrow_, fillPaint_);
}
}
/**
* Called when the pressed_ state has changed.
*/
@Override
protected void onPressedChanged(boolean move) {
if (isPressed() && isValid()) {
piano_.changeOctave(delta_);
}
}
// This is just used for drawing, but we don't want to pay to reallocate it every time.
private Path arrow_;
// How the octave should change when this key is pressed.
private int delta_;
}

@ -0,0 +1,177 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
import com.google.synthesizer.core.music.Note;
/**
* PianoKey is the abstract base class for any key on the piano.
* It keeps track of whether the key is currently being pressed.
*/
public abstract class PianoKey {
public PianoKey(PianoView piano) {
piano_ = piano;
pressed_ = new boolean[PianoView.FINGERS];
rect_ = new Rect();
for (int i = 0; i < pressed_.length; ++i) {
pressed_[i] = false;
}
// Set up some default objects for the key to draw itself with.
fillPaint_ = new Paint();
strokePaint_ = new Paint();
fillPaint_.setStyle(Paint.Style.FILL);
strokePaint_.setStyle(Paint.Style.STROKE);
strokePaint_.setColor(Color.BLACK);
}
/**
* Sets rect_ to the position of this key, based on the drawing rect of the piano it's on.
* @param drawingRect - the position of the piano itself.
* @param octaves - the number of octaves visible on the piano keyboard.
*/
abstract public void layout(Rect drawingRect, int octaves);
/**
* Draws the key in the current rect_.
*/
abstract public void draw(Canvas canvas);
/**
* Called when the key's pressed_ state has changed.
* @param move - true if the key became pressed because the touch moved onto it.
*/
abstract protected void onPressedChanged(boolean move);
/**
* Returns true if the given co-ordinate is inside the key's current rect_.
*/
public boolean contains(int x_, int y_) {
return rect_.contains(x_, y_);
}
/**
* Returns true if any finger is pressing this key.
*/
public boolean isPressed() {
for (int i = 0; i < pressed_.length; ++i) {
if (pressed_[i]) {
return true;
}
}
return false;
}
/**
* Called when a finger has touched down onto this key.
* Returns true iff whether the pressed state changed.
*/
final public boolean onTouchDown(int finger) {
if (finger >= pressed_.length) {
Log.e(getClass().getName(),
"Finger " + finger + " was pressed down, but PianoKey only supports " +
pressed_.length + " fingers.");
}
boolean wasPressed = isPressed();
pressed_[finger] = true;
if (!wasPressed) {
onPressedChanged(false);
return true;
}
return false;
}
/**
* Called on a touch event where this key is not being touched. It may already be up.
* Returns true iff whether the pressed state changed.
*/
final public boolean onTouchUp(int finger) {
if (finger >= pressed_.length) {
Log.e(getClass().getName(),
"Finger " + finger + " was released, but PianoKey only supports " +
pressed_.length + " fingers.");
}
boolean wasPressed = isPressed();
pressed_[finger] = false;
boolean isPressed = isPressed();
if (wasPressed && !isPressed) {
onPressedChanged(false);
return true;
}
return false;
}
/**
* Called when there's a touch event where the finger was moved onto this key.
* Returns true iff whether the pressed state changed.
*/
final public boolean onTouchMoved(int finger) {
if (finger >= pressed_.length) {
Log.e(getClass().getName(),
"Finger " + finger + " was pressed down, but PianoKey only supports " +
pressed_.length + " fingers.");
}
boolean wasPressed = isPressed();
pressed_[finger] = true;
if (!wasPressed) {
onPressedChanged(true);
return true;
}
return false;
}
/**
* Utility function to calculate the width that a standard white key on this keyboard should be.
*/
protected static int getWhiteKeyWidth(Rect drawingRect, int octaves) {
// It's +2 to reserve space for the octave-up/down buttons.
return drawingRect.width() / ((WHITE_KEYS.length * octaves) + 2);
}
/**
* Utility function to calculate the height that a standard white key on this keyboard should be.
*/
protected static int getWhiteKeyHeight(Rect drawingRect) {
return drawingRect.height();
}
// The piano this key is on.
protected PianoView piano_;
// Is each keys currently being pressed?
protected boolean[] pressed_;
// The area this key occupies.
protected Rect rect_;
// Objects for subclasses to use for painting, just so they don't have to reallocate every time.
protected Paint fillPaint_;
protected Paint strokePaint_;
// Constants to map notes onto the keys.
protected static final int WHITE_KEYS[] = {
Note.C, Note.D, Note.E, Note.F, Note.G, Note.A, Note.B };
protected static final int BLACK_KEYS[] = {
Note.C_SHARP, Note.E_FLAT, Note.NONE, Note.F_SHARP, Note.A_FLAT, Note.B_FLAT, Note.NONE };
}

@ -0,0 +1,373 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.google.synthesizer.R;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
/**
* PianoView is a UI widget that simulates a music keyboard.
*/
public class PianoView extends View {
/**
* Basic android widget constructor.
*/
public PianoView(Context context, AttributeSet attrs) {
super(context, attrs);
// Get the xml attributes for this instance.
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PianoView);
octaves_ = a.getInteger(R.styleable.PianoView_octaves, 1);
firstOctave_ = a.getInteger(R.styleable.PianoView_first_octave, 4);
// Set up basic drawing structs, just so we don't have to allocate this later when we draw.
drawingRect_ = new Rect();
// Generate the set of keys. There are 12 music keys per octave, plus the octave change button
// on either end.
keys_ = new PianoKey[12 * octaves_ + 2];
int key = 0;
// Create the white keys.
for (int octave = 0; octave < octaves_; ++octave) {
for (int note = 0; note < 7; ++note) {
keys_[key++] = new WhitePianoKey(this, octave, note);
}
}
// Create the black keys.
for (int octave = 0; octave < octaves_; ++octave) {
for (int note = 0; note < 7; ++note) {
if (BlackPianoKey.isValid(note)) {
keys_[key++] = new BlackPianoKey(this, octave, note);
}
}
}
// Create the octave changing keys.
keys_[key++] = new OctavePianoKey(this, -1);
keys_[key++] = new OctavePianoKey(this, 1);
// The listener will have to be set later.
pianoViewListener_ = null;
}
/**
* Returns the absolute octave of the left-most key.
*/
public int getFirstOctave() {
return firstOctave_;
}
/**
* Returns the number of octaves covered by all of the keys.
*/
public int getOctaves() {
return octaves_;
}
/**
* Shifts the octave of all of the keys.
* @param delta - The number (and direction) of octaves to shift by.
*/
public void changeOctave(int delta) {
firstOctave_ += delta;
}
/**
* Sets the listener that will receive events from this widget.
*/
public void setPianoViewListener(PianoViewListener pianoViewListener) {
pianoViewListener_ = pianoViewListener;
}
/**
* Signals the listener that a new note was pressed.
* @param logFrequency - the log frequency of the new note.
* @param retriggerIfOn - true if this is a new touch, rather than just moving.
*/
private void notifyNoteDown(double logFrequency, int finger, boolean retriggerIfOn) {
if (pianoViewListener_ != null) {
pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn);
}
}
/**
* Signals the listener that a note was released.
*/
private void notifyNoteUp(int finger) {
if (pianoViewListener_ != null) {
pianoViewListener_.noteUp(finger);
}
}
/**
* Draws the widget.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(drawingRect_);
for (int i = 0; i < keys_.length; ++i) {
keys_[i].layout(drawingRect_, octaves_);
}
for (int i = 0; i < keys_.length; ++i) {
keys_[i].draw(canvas);
}
}
/**
* Called to handle touch down events.
* Returns true iff we need to redraw.
*/
protected boolean onTouchDown(int finger, int x, int y) {
// Look through keys from top to bottom, and set the first one found as down, the rest as up.
PianoKey keyDown = null;
boolean redraw = false;
for (int i = keys_.length - 1; i >= 0; --i) {
if (keyDown != null) {
// If we already found a key that's being touched, then none of the rest can be.
redraw |= keys_[i].onTouchUp(finger);
} else if (keys_[i].contains(x, y)) {
// This key is being touched.
redraw |= keys_[i].onTouchDown(finger);
keyDown = keys_[i];
} else {
// This key is not being touched.
redraw |= keys_[i].onTouchUp(finger);
}
}
if (keyDown instanceof NotePianoKey) {
notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, true);
}
return redraw;
}
/**
* Called to handle touch move events.
*/
protected boolean onTouchMove(int finger, int x, int y) {
// Look through keys from top to bottom, and set the first one found as moved, the rest as up.
PianoKey keyDown = null;
boolean redraw = false;
boolean wasPressed = false;
for (int i = keys_.length - 1; i >= 0; --i) {
if (keyDown != null) {
// If we already found a key that's being touched, then none of the rest can be.
redraw |= keys_[i].onTouchUp(finger);
} else if (keys_[i].contains(x, y)) {
// This key is being pressed.
wasPressed = keys_[i].isPressed();
redraw |= keys_[i].onTouchMoved(finger);
keyDown = keys_[i];
} else {
// This key is not being pressed.
redraw |= keys_[i].onTouchUp(finger);
}
}
if (keyDown instanceof NotePianoKey) {
if (!wasPressed) {
notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, false);
}
} else {
notifyNoteUp(finger);
}
return redraw;
}
/**
* Called to handle touch up events.
*/
protected boolean onTouchUp(int finger) {
// Set all keys as up.
boolean redraw = false;
for (int i = 0; i < keys_.length; ++i) {
redraw |= keys_[i].onTouchUp(finger);
}
notifyNoteUp(finger);
return redraw;
}
/**
* Handler for all touch events.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
boolean redraw = false;
if (actionCode == MotionEvent.ACTION_DOWN) {
int pointerId = event.getPointerId(0);
if (pointerId < FINGERS) {
int x = (int)event.getX();
int y = (int)event.getY();
redraw |= onTouchDown(pointerId, x, y);
}
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
if (pointerId < FINGERS) {
int pointerIndex = event.findPointerIndex(pointerId);
if (pointerIndex >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
redraw |= onTouchDown(pointerId, x, y);
}
}
} else if (actionCode == MotionEvent.ACTION_MOVE) {
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
int pointerId = event.getPointerId(pointerIndex);
if (pointerId >= FINGERS) {
continue;
}
if (pointerIndex >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
redraw |= onTouchMove(pointerId, x, y);
}
}
} else if (actionCode == MotionEvent.ACTION_UP) {
int pointerId = event.getPointerId(0);
if (pointerId < FINGERS) {
redraw |= onTouchUp(pointerId);
}
// Clean up any other pointers that have disappeared.
for (pointerId = 0; pointerId < FINGERS; ++pointerId) {
boolean found = false;
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
if (pointerId == event.getPointerId(pointerIndex)) {
found = true;
break;
}
}
if (!found) {
redraw |= onTouchUp(pointerId);
}
}
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
if (pointerId < FINGERS) {
redraw |= onTouchUp(pointerId);
}
// Clean up any other pointers that have disappeared.
for (pointerId = 0; pointerId < FINGERS; ++pointerId) {
boolean found = false;
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
if (pointerId == event.getPointerId(pointerIndex)) {
found = true;
break;
}
}
if (!found) {
redraw |= onTouchUp(pointerId);
}
}
} else {
return super.onTouchEvent(event);
}
if (redraw) {
invalidate();
}
return true;
}
/**
* Layout measurement for this widget.
* This method just sets a basic minimum size and makes the widget maximized otherwise.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 10;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 10;
break;
}
setMeasuredDimension(width, height);
}
/**
* Connects the PianoView to a Synthesizer.
* @synth - The synthesizer to connect to.
* @channel - Which of the synthesizer's channels to bind to.
*/
public void bindTo(final MultiChannelSynthesizer synth, final int channel) {
this.setPianoViewListener(new PianoViewListener() {
public void noteDown(double logFrequency, int finger, boolean retriggerIfOn) {
synth.getChannel(channel).setPitch(logFrequency, finger);
synth.getChannel(channel).turnOn(retriggerIfOn, finger);
}
public void noteUp(int finger) {
synth.getChannel(channel).turnOff(finger);
}
});
}
// The most recent screen rect that this keyboard was drawn into.
//
// This is basically a stack variable for onDraw. It's a member variable only so that we can
// avoid reallocating them every time the keyboard is redrawn.
private Rect drawingRect_;
// The set of keys on the keyboard.
private PianoKey[] keys_;
// The current octave the keyboard is on.
private int firstOctave_;
// The total number of octaves the keyboard displays at any one time.
private final int octaves_;
// The listener to receive key events.
private PianoViewListener pianoViewListener_;
// The number of simultaneous fingers supported by this control.
protected static final int FINGERS = 5;
}

@ -0,0 +1,32 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
// Simple interface for listening to piano widget events.
public interface PianoViewListener {
/**
* A note was pressed.
* @param logFrequency - the log frequency of the note pressed.
* @param retriggerIfOn - true if this is a new touch, rather than just moving.
*/
void noteDown(double logFrequency, int finger, boolean retriggerIfOn);
/**
* The note was released.
*/
void noteUp(int finger);
}

@ -0,0 +1,72 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.piano;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import com.google.synthesizer.core.music.Note;
/**
* A white key on the piano.
*/
public class WhitePianoKey extends NotePianoKey {
/**
* Creates a new key.
* @param piano - the piano this key is on.
* @param octaveOffset - octave of the key, relative to the leftmost octave of the piano.
* @param key - offset of the key from the start of the octave.
*/
public WhitePianoKey(PianoView piano, int octave, int key) {
super(piano, octave, key);
}
/**
* Sets rect_ to the position of this key, based on the drawing rect of the piano it's on.
* @param drawingRect - the position of the piano itself.
* @param octaves - the number of octaves visible on the piano keyboard.
*/
public void layout(Rect drawingRect, int octaves) {
int whiteKeyWidth = getWhiteKeyWidth(drawingRect, octaves);
rect_.top = 0;
rect_.bottom = getWhiteKeyHeight(drawingRect);
rect_.left = ((octaveOffset_ * WHITE_KEYS.length + key_ + 1) * whiteKeyWidth);
rect_.right = rect_.left + whiteKeyWidth;
}
/**
* Returns the log frequency of the note of the key.
*/
public double getLogFrequency() {
return Note.computeLog12TET(WHITE_KEYS[key_], octaveOffset_ + piano_.getFirstOctave());
}
/**
* Draws the key in the current rect_.
*/
public void draw(Canvas canvas) {
strokePaint_.setColor(Color.BLACK);
if (isPressed()) {
fillPaint_.setColor(Color.GREEN);
} else {
fillPaint_.setColor(Color.WHITE);
}
canvas.drawRect(rect_, fillPaint_);
canvas.drawRect(rect_, strokePaint_);
}
}

@ -0,0 +1,490 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.music.Music.Event;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
/**
* A tool for editing the events in a score. If the user touches an event, they can move it or
* resize it. If the event is already selected, all selected events will be moved or resized in the
* same way. If the user touches outside any events, a selection rectangle allows them to select
* one or more events.
*/
public class EditEventTool extends ScoreViewTool {
/**
* Creates a new EditEventTool with the given context for loading resources.
*/
EditEventTool(Context context) {
logger_ = Logger.getLogger(getClass().getName());
// Set up basic drawing structs, just so we don't have to allocate this later when we draw.
paint_ = new Paint();
path_ = new Path();
selection_ = new Rect();
trashVisible_ = false;
trashRect_ = new Rect();
trashIcon_ = context.getResources().getDrawable(R.drawable.trash);
icon_ = context.getResources().getDrawable(R.drawable.arrow);
}
/**
* Starts this tool editing a particular event, and sets it to invoke another tool when done.
* Called by NewEventTool to handle sizing the tool as it's being created.
* @param view - The ScoreView for this tool.
* @param event - The event to start editing.
* @param physicalX - The current x of the user's finger, in screen coordinates.
* @param physicalY -The current y of the user's finger, in screen coordinates.
* @param nextTool - The tool to select when the user is done editing this event. (on touch up)
*/
public void pickupEvent(ScoreView view,
Event.Builder event,
int physicalX,
int physicalY,
ScoreViewTool nextTool) {
// Save the tool to bring up after this operation is complete.
nextTool_ = nextTool;
// De-select everything.
for (int j = 0; j < view.getScore().getEventCount(); ++j) {
view.getScore().getEventBuilder(j).setSelected(false);
}
// Select just the one that was picked up.
event.setSelected(true);
mode_ = RESIZING_RIGHT;
// Make sure to snap the event.
if (!event.hasUnsnappedStart()) {
event.setUnsnappedStart(event.getStart());
}
if (!event.hasUnsnappedEnd()) {
event.setUnsnappedEnd(event.getEnd());
}
if (view.getSnapTo() != 0) {
event.setStart(((int)(event.getUnsnappedStart() / view.getSnapTo())) * view.getSnapTo());
event.setEnd(((int)(event.getUnsnappedEnd() / view.getSnapTo())) * view.getSnapTo());
} else {
event.setStart(event.getUnsnappedStart());
event.setEnd(event.getUnsnappedEnd());
}
// Store the coordinates where it was picked up.
double time = view.getTimeAt(physicalX);
double note = view.getNoteAt(physicalY);
previousTime_ = time;
previousNote_ = note;
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
icon_.setBounds(rect);
icon_.draw(canvas);
}
/**
* Called on finger down.
*/
private void onTouchDown(ScoreView view, int physicalX, int physicalY) {
double time = view.getTimeAt(physicalX);
double note = view.getNoteAt(physicalY);
// When we're done editing this item, this tool stays selected.
nextTool_ = this;
// See if there's an event being touched.
Event.Builder event = view.getEventAt(physicalX, physicalY);
if (event != null) {
if (!event.getSelected()) {
// De-select everything.
for (int j = 0; j < view.getScore().getEventCount(); ++j) {
view.getScore().getEventBuilder(j).setSelected(false);
}
// Select just the one that was pressed.
event.setSelected(true);
}
// Compute the handle size.
int physicalHandleSize = view.getNoteY(0) - view.getNoteY(1);
double handleWidth = view.getTimeAt(physicalHandleSize) - view.getTimeAt(0);
boolean largeEnough = event.getEnd() - event.getStart() > handleWidth * 2;
// See if any handle was touched.
if (largeEnough && time <= event.getStart() + handleWidth) {
mode_ = RESIZING_LEFT;
} else if (largeEnough && time >= event.getEnd() - handleWidth) {
mode_ = RESIZING_RIGHT;
} else {
mode_ = MOVING;
}
} else {
// De-select everything.
for (int j = 0; j < view.getScore().getEventCount(); ++j) {
view.getScore().getEventBuilder(j).setSelected(false);
}
selection_.left = physicalX;
selection_.right = physicalX;
selection_.top = physicalY;
selection_.bottom = physicalY;
mode_ = SELECTING;
}
view.invalidate();
previousTime_ = time;
previousNote_ = note;
}
/**
* Called on finger movements.
*/
private void onTouchMove(ScoreView view, int physicalX, int physicalY) {
double time = view.getTimeAt(physicalX);
double note = view.getNoteAt(physicalY);
double deltaTime = time - previousTime_;
double deltaNote = note - previousNote_;
switch (mode_) {
case RESIZING_RIGHT: {
for (int i = 0; i < view.getScore().getEventCount(); ++i) {
Event.Builder event = view.getScore().getEventBuilder(i);
if (!event.getSelected()) {
continue;
}
if (!event.hasUnsnappedEnd()) {
event.setUnsnappedEnd(event.getEnd());
}
event.setUnsnappedEnd(event.getUnsnappedEnd() + deltaTime);
if (view.getSnapTo() != 0) {
event.setEnd(((int)(event.getUnsnappedEnd() / view.getSnapTo())) * view.getSnapTo());
} else {
event.setEnd(event.getUnsnappedEnd());
}
if (event.getEnd() <= event.getStart()) {
event.setEnd(event.getStart() + 1/32.0f);
}
}
break;
}
case RESIZING_LEFT: {
for (int i = 0; i < view.getScore().getEventCount(); ++i) {
Event.Builder event = view.getScore().getEventBuilder(i);
if (!event.getSelected()) {
continue;
}
if (!event.hasUnsnappedStart()) {
event.setUnsnappedStart(event.getStart());
}
event.setUnsnappedStart(event.getUnsnappedStart() + deltaTime);
if (view.getSnapTo() != 0) {
event.setStart(((int)(event.getUnsnappedStart() / view.getSnapTo())) * view.getSnapTo());
} else {
event.setStart(event.getUnsnappedStart());
}
if (event.getStart() >= event.getEnd()) {
event.setEnd(event.getEnd() - 1/32.0f);
}
}
break;
}
case MOVING: {
for (int i = 0; i < view.getScore().getEventCount(); ++i) {
Event.Builder event = view.getScore().getEventBuilder(i);
if (!event.getSelected()) {
continue;
}
if (!event.hasUnsnappedStart()) {
event.setUnsnappedStart(event.getStart());
}
event.setUnsnappedStart(event.getUnsnappedStart() + deltaTime);
if (view.getSnapTo() != 0) {
event.setStart(((int)(event.getUnsnappedStart() / view.getSnapTo())) *
view.getSnapTo());
} else {
event.setStart(event.getUnsnappedStart());
}
if (!event.hasUnsnappedEnd()) {
event.setUnsnappedEnd(event.getEnd());
}
event.setUnsnappedEnd(event.getUnsnappedEnd() + deltaTime);
if (view.getSnapTo() != 0) {
event.setEnd(((int)(event.getUnsnappedEnd() / view.getSnapTo())) * view.getSnapTo());
} else {
event.setEnd(event.getUnsnappedEnd());
}
if (event.getEnd() <= event.getStart()) {
event.setEnd(event.getStart() + 1/32.0f);
}
if (!event.hasUnsnappedKey()) {
event.setUnsnappedKey(event.getKey());
}
event.setUnsnappedKey(event.getUnsnappedKey() + deltaNote);
event.setKey((int)event.getUnsnappedKey());
trashVisible_ = true;
trashRect_.top = view.getDrawingRect().top;
trashRect_.bottom = view.getDrawingRect().top +
Math.max(200, trashIcon_.getIntrinsicHeight());
trashRect_.left = view.getDrawingRect().right -
Math.max(200, trashIcon_.getIntrinsicWidth());
trashRect_.right = view.getDrawingRect().right;
trashIcon_.setBounds(trashRect_);
}
break;
}
case SELECTING: {
// Update the selection rectangle.
selection_.right = physicalX;
selection_.bottom = physicalY;
// Update event selections.
for (int i = 0; i < view.getScore().getEventCount(); ++i) {
Event.Builder event = view.getScore().getEventBuilder(i);
if (selection_.intersects(view.getTimeX(event.getStart()),
view.getNoteY(event.getKey() + 1),
view.getTimeX(event.getEnd()),
view.getNoteY(event.getKey()))) {
view.getScore().getEventBuilder(i).setSelected(true);
} else {
view.getScore().getEventBuilder(i).setSelected(false);
}
}
break;
}
}
view.invalidate();
previousTime_ = time;
previousNote_ = note;
}
/**
* Called on finger up.
*/
private void onTouchUp(ScoreView view, int physicalX, int physicalY) {
trashVisible_ = false;
if (trashRect_.contains(physicalX, physicalY)) {
for (int i = view.getScore().getEventCount() - 1; i >= 0; --i) {
if (view.getScore().getEventBuilder(i).getSelected()) {
view.getScore().removeEvent(i);
}
}
}
// Make all snapping permanent.
for (int i = 0; i < view.getScore().getEventCount(); ++i) {
Event.Builder event = view.getScore().getEventBuilder(i);
event.clearUnsnappedStart();
event.clearUnsnappedEnd();
event.clearUnsnappedKey();
}
mode_ = NONE;
view.setTool(nextTool_);
view.invalidate();
}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
@Override
public boolean onTouch(ScoreView view, MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
if (actionCode == MotionEvent.ACTION_DOWN) {
pointerId_ = event.getPointerId(0);
onTouchDown(view, (int)event.getX(), (int)event.getY());
return true;
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
return false;
} else if (actionCode == MotionEvent.ACTION_MOVE) {
// Find the current positions of the fingers.
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
int pointerId = event.getPointerId(pointerIndex);
if (pointerId >= 0 && pointerId == pointerId_) {
int physicalX = (int)event.getX(pointerIndex);
int physicalY = (int)event.getY(pointerIndex);
onTouchMove(view, physicalX, physicalY);
return true;
}
}
return false;
} else if (actionCode == MotionEvent.ACTION_UP) {
onTouchUp(view, (int)event.getX(), (int)event.getY());
return true;
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
return false;
} else {
return false;
}
}
/**
* Called after each event is drawn, to give this tool a chance to draw over it.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param event - The event that was drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
@Override
public void afterDrawEvent(Event event,
Canvas canvas,
Rect rect) {
if (rect.right >= rect.left + rect.height() * 2) {
// Draw left arrow.
float margin = 1.0f;
paint_.setStrokeWidth(1.0f);
paint_.setStyle(Paint.Style.STROKE);
if (event.hasKeyEvent()) {
paint_.setColor(Color.BLACK);
} else {
paint_.setColor(Color.WHITE);
}
canvas.drawRect(rect.left, rect.top,
rect.left + rect.height(), rect.top + rect.height(),
paint_);
paint_.setStyle(Paint.Style.FILL);
path_.reset();
path_.moveTo(rect.left + rect.height() - margin, rect.top + margin);
path_.lineTo(rect.left + rect.height() - margin, rect.bottom - margin);
path_.lineTo(rect.left + margin, (rect.top + rect.bottom) / 2.0f);
path_.close();
canvas.drawPath(path_, paint_);
// Draw right arrow.
paint_.setStyle(Paint.Style.STROKE);
if (event.hasKeyEvent()) {
paint_.setColor(Color.BLACK);
} else {
paint_.setColor(Color.WHITE);
}
canvas.drawRect(rect.right - rect.height(), rect.top, rect.right, rect.bottom, paint_);
paint_.setStyle(Paint.Style.FILL);
path_.reset();
path_.moveTo(rect.right - (rect.height() - (margin + 1)), rect.top + margin);
path_.lineTo(rect.right - (rect.height() - (margin + 1)), rect.bottom - margin);
path_.lineTo(rect.right - margin, (rect.top + rect.bottom) / 2.0f);
path_.close();
canvas.drawPath(path_, paint_);
}
}
/**
* Called after the entire score is drawn, to give this tool a chance to draw over it.
* Draws the selection box, and possibly the trash icon.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param view - The ScoreView being drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
@Override
public void afterDrawScore(ScoreView view, Canvas canvas, Rect rect) {
if (mode_ == SELECTING) {
paint_.setStyle(Paint.Style.FILL);
paint_.setColor(Color.CYAN);
paint_.setAlpha(127);
canvas.drawRect(selection_, paint_);
paint_.setAlpha(255);
}
// Draw the trash can.
if (trashVisible_) {
trashIcon_.draw(canvas);
}
}
// The id of the finger doing the editing.
private int pointerId_;
// The most recent previous position of the finger.
private double previousTime_;
private double previousNote_;
// A tool to select the next time the user finishes editing an event.
// Can be "this", but not null.
private ScoreViewTool nextTool_;
// While the user is drawing a selection rectangle, this is it, in screen (physical) coordinates.
private Rect selection_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
protected Paint paint_;
protected Path path_;
private Drawable icon_;
// The mode of the tool, depending mostly on where the user's finger was when they first touched.
private int mode_;
private static final int NONE = 0;
private static final int RESIZING_LEFT = 1;
private static final int RESIZING_RIGHT = 2;
private static final int MOVING = 3;
private static final int SELECTING = 4;
// Members for controlling how the trash can icon gets drawn while moving events.
private boolean trashVisible_;
private Rect trashRect_; // in screen (physical) coordinates.
private Drawable trashIcon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,97 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
* A button that toggles between showing the events for all of the synthesizer channels or showing
* just the currently selected channel.
*/
public class HideChannelButton extends ScoreViewTool {
/**
* Creates a new HideChannelButton, using the given context for loading resources.
*/
HideChannelButton(Context context) {
logger_ = Logger.getLogger(getClass().getName());
visibleIcon_ = context.getResources().getDrawable(R.drawable.open_eye);
hiddenIcon_ = context.getResources().getDrawable(R.drawable.closed_eye);
paint_ = new Paint();
}
/**
* Called when this tool is selected.
* Changes the channel visibility and then reselects the previous tool.
* @param view - The ScoreView that this toolbar is for.
* @param previousTool - The tool that was selected when this one was chosen.
*/
@Override
public void onSelect(ScoreView view, ScoreViewTool previousTool) {
view.setOtherChannelsVisible(!view.getOtherChannelsVisible());
view.setTool(previousTool);
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
if (score.getOtherChannelsVisible()) {
visibleIcon_.setBounds(rect);
visibleIcon_.draw(canvas);
} else {
hiddenIcon_.setBounds(rect);
hiddenIcon_.draw(canvas);
}
}
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable visibleIcon_;
private Drawable hiddenIcon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,118 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.music.Music.Event;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
/**
* A tool for creating new events in the score.
*/
public class NewEventTool extends ScoreViewTool {
/**
* Creates a new NewEventTool.
* @param context - The context to use for loading resources.
* @param eventTool - The tool to use for editing the event as it's created.
*/
NewEventTool(Context context, EditEventTool eventTool) {
logger_ = Logger.getLogger(getClass().getName());
eventTool_ = eventTool;
icon_ = context.getResources().getDrawable(R.drawable.add);
paint_ = new Paint();
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
icon_.setBounds(rect);
icon_.draw(canvas);
}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
@Override
public boolean onTouch(ScoreView view, MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
if (actionCode == MotionEvent.ACTION_DOWN) {
double time = view.getTimeAt((int)event.getX());
double note = view.getNoteAt((int)event.getY());
if (view.getSnapTo() != 0) {
time = ((int)(time / view.getSnapTo())) * view.getSnapTo();
}
Event.Builder e = view.getScore().addEventBuilder();
e.setStart(time);
e.setEnd(time + view.getSnapTo());
e.setKey((int)note);
e.getKeyEventBuilder().setChannel(view.getCurrentChannel());
eventTool_.pickupEvent(view, e, (int)event.getX(), (int)event.getY(), this);
view.setTool(eventTool_);
return true;
} else {
return false;
}
}
// The tool that's used to edit the event after it's created, but only until the finger is up.
// Then control returns back to this control.
private EditEventTool eventTool_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable icon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,118 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.music.Music.Event;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
/**
* A tool for creating new events in the score.
*/
public class NewLoopTool extends ScoreViewTool {
/**
* Creates a new NewEventTool.
* @param context - The context to use for loading resources.
* @param eventTool - The tool to use for editing the event as it's created.
*/
NewLoopTool(Context context, EditEventTool eventTool) {
logger_ = Logger.getLogger(getClass().getName());
eventTool_ = eventTool;
icon_ = context.getResources().getDrawable(R.drawable.loop);
paint_ = new Paint();
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
icon_.setBounds(rect);
icon_.draw(canvas);
}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
@Override
public boolean onTouch(ScoreView view, MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
if (actionCode == MotionEvent.ACTION_DOWN) {
double time = view.getTimeAt((int)event.getX());
double note = view.getNoteAt((int)event.getY());
if (view.getSnapTo() != 0) {
time = ((int)(time / view.getSnapTo())) * view.getSnapTo();
}
Event.Builder e = view.getScore().addEventBuilder();
e.setStart(time);
e.setEnd(time + view.getSnapTo());
e.setKey((int)note);
e.getLoopEventBuilder().setCount(1);
eventTool_.pickupEvent(view, e, (int)event.getX(), (int)event.getY(), this);
view.setTool(eventTool_);
return true;
} else {
return false;
}
}
// The tool that's used to edit the event after it's created, but only until the finger is up.
// Then control returns back to this control.
private EditEventTool eventTool_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable icon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,156 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.music.ScorePlayer;
import com.google.synthesizer.core.music.ScorePlayerListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
* A button to start the current score playing.
*/
public class PlayButton extends ScoreViewTool implements ScorePlayerListener {
/**
* Creates a new play button, loading resources from the given context.
*/
PlayButton(ScoreViewToolbar toolbar, Context context) {
toolbar_ = toolbar;
logger_ = Logger.getLogger(getClass().getName());
player_ = new ScorePlayer();
playing_ = false;
playingIcon_ = context.getResources().getDrawable(R.drawable.stop);
stoppedIcon_ = context.getResources().getDrawable(R.drawable.play);
paint_ = new Paint();
}
/**
* Called when this tool is selected. Starts the score playing.
* @param view - The ScoreView that this toolbar is for.
* @param previousTool - The tool that was selected when this one was chosen.
*/
@Override
public void onSelect(ScoreView view, ScoreViewTool previousTool) {
view_ = view;
if (playing_) {
player_.stopPlaying();
} else {
player_.startPlaying(view.getSynthesizer(), view.getScore().build(), 120.0, 4, this);
}
view.setTool(previousTool);
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
if (playing_) {
playingIcon_.setBounds(rect);
playingIcon_.draw(canvas);
} else {
stoppedIcon_.setBounds(rect);
stoppedIcon_.draw(canvas);
}
}
/**
* Called when the score starts playing.
*/
public void onStart() {
view_.post(new Thread("PlayButton.onStart()") {
public void run() {
playing_ = true;
view_.invalidate();
toolbar_.invalidate();
}
});
}
/**
* Called every so often during playback.
* @param time - the time in measures from the start of the song.
*/
public void onTimeUpdate(final double time) {
view_.post(new Thread("PlayButton.onTimeUpdate()") {
public void run() {
view_.setCursor(time);
view_.invalidate();
toolbar_.invalidate();
}
});
}
/**
* Called when the score stops playing.
*/
public void onStop() {
view_.post(new Thread("PlayButton.onStop()") {
public void run() {
playing_ = false;
view_.invalidate();
}
});
}
// The ScoreView that this button controls.
private ScoreView view_;
private ScoreViewToolbar toolbar_;
// ScorePlayer to play the score.
private ScorePlayer player_;
// Is the score playing?
private boolean playing_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable playingIcon_;
private Drawable stoppedIcon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,229 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.music.Note;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
/**
* A button to enable "play" mode of the ScoreView. In play mode, pressing anywhere plays the note
* for the key that's being pressed. @see ScoreView.
*/
public class PlayTool extends ScoreViewTool {
/**
* Creates a new PlayTool, loading resources from the given context.
*/
PlayTool(Context context) {
logger_ = Logger.getLogger(getClass().getName());
keysDown_ = new int[FINGERS];
for (int i = 0; i < keysDown_.length; ++i) {
keysDown_[i] = -1;
}
icon_ = context.getResources().getDrawable(R.drawable.play_piano);
paint_ = new Paint();
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
icon_.setBounds(rect);
icon_.draw(canvas);
}
/**
* Called after each key is drawn, to give this tool a chance to draw over it.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param key - The key that was drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
@Override
public void afterDrawKey(int key,
Canvas canvas,
Rect rect) {
for (int keyDown : keysDown_) {
if (key == keyDown) {
paint_.setColor(Color.GREEN);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
}
}
}
/**
* Called to handle touch down events.
* Returns true iff we need to redraw.
*/
private boolean onTouchDown(ScoreView view, int finger, int physicalX, int physicalY) {
double note = view.getNoteAt(physicalY);
double logFrequency = Note.computeLog12TET(((int)note) % 12, ((int)note) / 12);
view.getSynthesizer().getChannel(view.getCurrentChannel()).setPitch(logFrequency, finger);
view.getSynthesizer().getChannel(view.getCurrentChannel()).turnOn(true, finger);
keysDown_[finger] = (int)note;
return true;
}
/**
* Called to handle touch move events.
*/
private boolean onTouchMove(ScoreView view, int finger, int physicalX, int physicalY) {
double note = view.getNoteAt(physicalY);
double logFrequency = Note.computeLog12TET(((int)note) % 12, ((int)note) / 12);
view.getSynthesizer().getChannel(view.getCurrentChannel()).setPitch(logFrequency, finger);
keysDown_[finger] = (int)note;
return true;
}
/**
* Called to handle touch up events.
*/
protected boolean onTouchUp(ScoreView view, int finger) {
view.getSynthesizer().getChannel(view.getCurrentChannel()).turnOff(finger);
keysDown_[finger] = -1;
return true;
}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
@Override
public boolean onTouch(ScoreView view, MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
boolean redraw = false;
if (actionCode == MotionEvent.ACTION_DOWN) {
int pointerId = event.getPointerId(0);
if (pointerId < FINGERS) {
int x = (int)event.getX();
int y = (int)event.getY();
redraw |= onTouchDown(view, pointerId, x, y);
}
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
if (pointerId < FINGERS) {
int pointerIndex = event.findPointerIndex(pointerId);
if (pointerIndex >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
redraw |= onTouchDown(view, pointerId, x, y);
}
}
} else if (actionCode == MotionEvent.ACTION_MOVE) {
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
int pointerId = event.getPointerId(pointerIndex);
if (pointerId >= FINGERS) {
continue;
}
if (pointerIndex >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
redraw |= onTouchMove(view, pointerId, x, y);
}
}
} else if (actionCode == MotionEvent.ACTION_UP) {
int pointerId = event.getPointerId(0);
if (pointerId < FINGERS) {
redraw |= onTouchUp(view, pointerId);
}
// Clean up any other pointers that have disappeared.
for (pointerId = 0; pointerId < FINGERS; ++pointerId) {
boolean found = false;
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
if (pointerId == event.getPointerId(pointerIndex)) {
found = true;
break;
}
}
if (!found) {
redraw |= onTouchUp(view, pointerId);
}
}
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
if (pointerId < FINGERS) {
redraw |= onTouchUp(view, pointerId);
}
// Clean up any other pointers that have disappeared.
for (pointerId = 0; pointerId < FINGERS; ++pointerId) {
boolean found = false;
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
if (pointerId == event.getPointerId(pointerIndex)) {
found = true;
break;
}
}
if (!found) {
redraw |= onTouchUp(view, pointerId);
}
}
} else {
return false;
}
if (redraw) {
view.invalidate();
}
return true;
}
// The piano key each finger is holding down, or -1 if a finger is not pressing any key.
private int[] keysDown_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable icon_;
// The number of simultaneous fingers supported by this control.
protected static final int FINGERS = 5;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,767 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.music.Music.Event;
import com.google.synthesizer.core.music.Music.Score;
import com.google.synthesizer.core.music.Note;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* ScoreView is a UI widget that allows editing a musical score, as well as live playing. The
* majority of the ScoreView area shows a subsequence of the current musical score. Along the
* y-axis are the keys of a piano. Time is along the x-axis. Along the bottom, there is a toolbar,
* which allows selecting various "tools" to use on the score.
*
* A score is composed of various "events", such as playing a note for a certain duration at a
* certain time, using a certain channel. A ScoreView lets the user create or edit these events.
*
* PlayTool - When selected, pressing plays the note at that x using the selected channel.
* ViewportTool - Sets the currently visible part of the score by touching or dragging.
* NewEventTool - Creates new events.
* EditEventTool - Edits an existing event.
* PlayButton - Starts the score playing audibly.
* SelectChannelButton - Selects a particular channel (instrument) for editing or playing.
* HideChannelButton - Toggles whether to show/hide the channels not currently being edited.
* SnapTool - Changes the "snap to" setting for this ScoreView.
*/
public class ScoreView extends View {
/**
* Basic android widget constructor.
*/
public ScoreView(Context context, AttributeSet attrs) {
super(context, attrs);
logger_ = Logger.getLogger(getClass().getName());
// Set the default time to be 20 measures, and show 5 measures to start.
minTime_ = 0.0;
maxTime_ = 20.0;
timeZoom_ = 0.25;
timeOffset_ = 0.0;
// Set the piano keys to the range of a normal piano, showing one octave to start.
minNote_ = Note.A;
maxNote_ = Note.A + 88.0;
noteZoom_ = 8.0 / 88.0;
noteOffset_ = 44.0;
// Snap to eighth notes to start.
snapTo_ = 1.0 / 8.0;
// Create the score to edit.
score_ = Score.newBuilder();
// Setup the channels.
currentChannel_ = 0;
showOtherChannels_ = true;
// Load the icon to use for each channel.
iconForChannel_ = new Drawable[CHANNELS];
iconForChannel_[0] = context.getResources().getDrawable(R.drawable.guitar);
iconForChannel_[1] = context.getResources().getDrawable(R.drawable.bass);
iconForChannel_[2] = context.getResources().getDrawable(R.drawable.voice);
iconForChannel_[3] = context.getResources().getDrawable(R.drawable.flute);
iconForChannel_[4] = context.getResources().getDrawable(R.drawable.drums);
arrowsVisible_ = true;
upSelected_ = false;
downSelected_ = false;
upIcon_ = context.getResources().getDrawable(R.drawable.up);
downIcon_ = context.getResources().getDrawable(R.drawable.down);
// Set up basic drawing structs, just so we don't have to allocate them later when we draw.
drawingRect_ = new Rect();
keyRect_ = new Rect();
eventRect_ = new Rect();
fillPaint_ = new Paint();
strokePaint_ = new Paint();
marginPaint_ = new Paint();
fillPaint_.setStyle(Paint.Style.FILL);
strokePaint_.setStyle(Paint.Style.STROKE);
marginPaint_.setStyle(Paint.Style.FILL);
marginPaint_.setColor(Color.GRAY);
}
/**
* Returns a mutable copy of the score being edited.
*/
public Score.Builder getScore() {
return score_;
}
/**
* Returns the currently selected channel (instrument).
*/
public int getCurrentChannel() {
return currentChannel_;
}
/**
* Selects the given channel.
* @param channel - The channel to select.
*/
public void setCurrentChannel(int channel) {
currentChannel_ = channel;
}
/**
* Returns true iff the given channel is visible in the score.
*/
public boolean isChannelVisible(int channel) {
if (showOtherChannels_) {
return true;
} else {
return channel == currentChannel_;
}
}
/**
* Returns true iff channels that aren't the current one are visible in the score.
*/
public boolean getOtherChannelsVisible() {
return showOtherChannels_;
}
/**
* Sets whether channels that aren't the current one are visible in the score.
*/
public void setOtherChannelsVisible(boolean visible) {
showOtherChannels_ = visible;
}
/**
* Returns the currently selected tool for this ScoreView.
*/
public ScoreViewTool getTool() {
return currentTool_;
}
/**
* Sets a tool to be the current tool for this ScoreView. Informs listeners of the change.
*/
public void setTool(ScoreViewTool tool) {
ScoreViewTool previousTool = currentTool_;
currentTool_ = tool;
tool.onSelect(this, previousTool);
if (listener_ != null) {
listener_.onSetTool(tool);
}
invalidate();
}
/**
* Returns the "snap to" setting for this ScoreView. @see setSnapTo().
* @return the note that should be snapped to. For example, if editing should snap to the nearest
* quarter note, then returns 0.25. For a whole note, 1.0. For no snapping, returns 0.0.
*/
public double getSnapTo() {
return snapTo_;
}
/**
* Sets the "snap to" setting for this ScoreView. @see getSnapTo().
* @param snapTo - the note that should be snapped to. For example, if editing should snap to the
* nearest quarter note, then 0.25. For a whole note, 1.0. For no snapping, 0.0.
*/
public void setSnapTo(double snapTo) {
snapTo_ = snapTo;
}
/**
* Returns the zoom setting for this control on the x-axis. @see setTimeZoom().
* @return the multiplier for the x-axis on the viewport. 1.0 means the entire time of the score
* is visible. 0.5 means only half of the score (time-wise) is visible. 2.0 means that the
* entire score is shown, but only takes up half the screen. Any excess space is just "margin".
*/
public double getTimeZoom() {
return timeZoom_;
}
/**
* Sets the zoom level for this control on the x-axis. @see getTimeZoom().
* @param zoom - the multiplier for the x-axis on the viewport. 1.0 means the entire time of the
* score is visible. 0.5 means only half of the score (time-wise) is visible. 2.0 means that the
* entire score is shown, but only takes up half the screen. Any excess space is just "margin".
*/
public void setTimeZoom(double zoom) {
timeZoom_ = zoom;
}
/**
* Returns the zoom setting for this control on the y-axis. @see setNoteZoom().
* @return the multiplier for the y-axis on the viewport, which controls how many note keys are
* visible. 1.0 means show the entire 88 keys of the piano are visible. N/88 means exactly N
* keys are visible. Values larger than 1.0 means extra "margin" is shown at the top and bottom.
*/
public double getNoteZoom() {
return noteZoom_;
}
/**
* Sets the zoom setting for this control on the y-axis. @see getNoteZoom().
* @param zoom - the multiplier for the y-axis on the viewport, which controls how many note keys
* are visible. 1.0 means show the entire 88 keys of the piano are visible. N/88 means exactly N
* keys are visible. Values larger than 1.0 means extra "margin" is shown at the top and bottom.
*/
public void setNoteZoom(double zoom) {
noteZoom_ = zoom;
}
/**
* Returns the left-most time currently visible in this control. @see setTimeOffset().
* @return the time, in measures, from the beginning of the score to the first visible time
* in the ScoreView. For example, 5.25 in 4/4 time would mean one quarter note past the end of
* the 5th measure. Negative values mean margin is shown on the left side.
*/
public double getTimeOffset() {
return timeOffset_;
}
/**
* Sets the left-most time currently visible in this control. @see getTimeOffset().
* @param offset - The time, in measures, from the beginning of the score to the first visible
* time in the ScoreView. For example, 5.25 in 4/4 time would mean one quarter note past the end
* of the 5th measure. Negative values mean margin is shown on the left side.
*/
public void setTimeOffset(double offset) {
timeOffset_ = offset;
}
/**
* Returns the bottom-most note key currently visible in this control. @see setNoteOffset().
* @return the note number of the bottom key visible on the screen. 0.0 means the lowest note is
* fully visible, with its bottom along the bottom edge of the screen. 1.0 means the lowest note
* is not visible, but its top edge is along the bottom of the screen. 88.0 means no keys are
* visible, but the top edge of the highest key is along the bottom of the screen.
*/
public double getNoteOffset() {
return noteOffset_;
}
/**
* Sets the bottom-most note key currently visible in this control. @see getNoteOffset().
* @param offset - the note number of the bottom key visible on the screen. 0.0 means the lowest
* note is fully visible, with its bottom along the bottom edge of the screen. 1.0 means the
* lowest note is not visible, but its top edge is along the bottom of the screen. 88.0 means no
* keys are visible, but the top edge of the highest key is along the bottom of the screen.
*/
public void setNoteOffset(double offset) {
noteOffset_ = offset;
}
/**
* Returns the max time viewable or editable by this ScoreView. @see setMaxTime().
* @return the time, where 0.0 means no time, 1.0 means one measure, and 10 means ten measures.
*/
public double getMaxTime() {
return maxTime_;
}
/**
* Sets the max time viewable or editable by this ScoreView. @see getMaxTime().
* @param max - the time, where 0.0 means no time, 1.0 means one measure, and 10 for ten measures.
*/
public void setMaxTime(double max) {
maxTime_ = max;
}
/**
* Returns the max possible note viewable or editable by this ScoreView. @see setMaxNote().
* @return the note number. For a normal piano layout, this method should always return 88.0.
*/
public double getMaxNote() {
return maxNote_;
}
/**
* Sets the max possible note viewable or editable by this ScoreView. @see getMaxNote().
* @param max - the note number. For a normal piano layout, this should always be 88.0.
*/
public void setMaxNote(double max) {
maxNote_ = max;
}
/**
* Returns the rectangle, in screen coordinates, where this ScoreView was most recently drawn.
* @return a reference to the Rect.
*/
public Rect getDrawingRect() {
return drawingRect_;
}
/**
* Returns the time (logical x) that corresponds to the given pixel (physical x).
* @param pixelX - the x in screen coordinates.
* @return the x in logical coordinates (the time, in measures, from the score start).
*/
public double getTimeAt(int pixelX) {
return timeOffset_ + ((double)(pixelX - drawingRect_.left) / drawingRect_.width()) / timeZoom_;
}
/**
* Returns the pixel (physical x) that corresponds to the given time (logical x).
* @param time - the time, in measures, from the score start.
* @return the x offset of the given time, in screen coordinates.
*/
public int getTimeX(double time) {
return (int)(((time - timeOffset_) * timeZoom_) * drawingRect_.width() +
drawingRect_.left + 0.5);
}
/**
* Returns the note (logical y) that corresponds to the given pixel (physical y).
* @param pixelY - the y in screen coordinates.
* @return the y in logical coordinates (the note key, typically from 0 to 88.0).
*/
public double getNoteAt(int pixelY) {
return ((double)(drawingRect_.bottom - pixelY) / drawingRect_.height()) / noteZoom_ + noteOffset_;
}
/**
* Returns the pixel (physical y) that corresponds to the given note (logical y).
* @param note - the note key.
* @return the y offset of the given note, in screen coordinates.
*/
public int getNoteY(double note) {
return (int)(drawingRect_.bottom - (note - noteOffset_) * drawingRect_.height() * noteZoom_);
}
/**
* Returns the top-most event at the given coordinates.
* @param physicalX - the x in screen coordinates.
* @param physicalY - the y in screen coordinates.
* @return the mutable event.
*/
public Event.Builder getEventAt(int physicalX, int physicalY) {
double time = getTimeAt(physicalX);
double note = getNoteAt(physicalY);
for (int i = score_.getEventCount() - 1; i >= 0; --i) {
double eventStartTime = score_.getEvent(i).getStart();
double eventEndTime = score_.getEvent(i).getEnd();
double eventMinNote = score_.getEvent(i).getKey();
double eventMaxNote = score_.getEvent(i).getKey() + 1;
if (time >= eventStartTime &&
time < eventEndTime &&
note >= eventMinNote &&
note < eventMaxNote) {
return score_.getEventBuilder(i);
}
}
return null;
}
/**
* Sets the cursor position that shows where playback is in the score.
* This should only be called from the Android UI thread.
*/
public void setCursor(double cursor) {
cursor_ = cursor;
invalidate();
}
/**
* Returns the color to use for representing the given channel in this ScoreView.
* @param channel - the channel.
* @return the color to use, Android style.
*/
public int getColorForChannel(int channel) {
switch (channel % CHANNELS) {
case 0: return Color.rgb(0, 255, 255);
case 1: return Color.rgb(255, 0, 255);
case 2: return Color.rgb(255, 255, 0);
case 3: return Color.rgb(255, 0, 0);
case 4: return Color.rgb(0, 255, 0);
case 5: return Color.rgb(0, 0, 255);
}
return Color.BLACK;
}
/**
* Returns the icon to use for representing the given channel in this ScoreView.
* @param channel - the channel.
* @return the icon to use, as a Drawable.
*/
public Drawable getIconForChannel(int channel) {
return iconForChannel_[channel % iconForChannel_.length];
}
/**
* Called to draw the ScoreView widget.
* @param canvas - the canvas to draw the widget on.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(drawingRect_);
// Clear the rectangle.
fillPaint_.setColor(Color.WHITE);
canvas.drawRect(drawingRect_, fillPaint_);
strokePaint_.setStrokeWidth(1.0f);
// Draw piano keys to mark the frequencies.
for (int note = (int)minNote_; note < (int)maxNote_; ++note) {
// Draw a single key that fills up a row.
keyRect_.bottom = getNoteY(note);
keyRect_.top = getNoteY(note + 1);
keyRect_.left = getTimeX(0.0);
keyRect_.right = getTimeX(maxTime_);
strokePaint_.setStrokeWidth(2.0f);
strokePaint_.setColor(Color.LTGRAY);
if (Note.isNatural(note)) {
fillPaint_.setColor(Color.WHITE);
} else {
fillPaint_.setColor(Color.LTGRAY);
}
canvas.drawRect(keyRect_, fillPaint_);
canvas.drawRect(keyRect_, strokePaint_);
if (currentTool_ != null) {
currentTool_.afterDrawKey(note, canvas, keyRect_);
}
}
// Draw lines to mark the measures.
for (double i = minTime_ + 1; i < maxTime_; ++i) {
strokePaint_.setColor(Color.LTGRAY);
int x = getTimeX(i - 0.75);
canvas.drawLine(x, drawingRect_.top, x, drawingRect_.bottom, strokePaint_);
strokePaint_.setColor(Color.GRAY);
x = getTimeX(i - 0.5);
canvas.drawLine(x, drawingRect_.top, x, drawingRect_.bottom, strokePaint_);
strokePaint_.setColor(Color.LTGRAY);
x = getTimeX(i - 0.25);
canvas.drawLine(x, drawingRect_.top, x, drawingRect_.bottom, strokePaint_);
strokePaint_.setColor(Color.BLACK);
x = getTimeX(i);
canvas.drawLine(x, drawingRect_.top, x, drawingRect_.bottom, strokePaint_);
}
// Draw the margins.
double leftMargin = getTimeX(minTime_);
if (leftMargin > drawingRect_.left) {
canvas.drawRect(drawingRect_.left,
drawingRect_.top,
(float)leftMargin,
drawingRect_.bottom,
marginPaint_);
}
double rightMargin = getTimeX(maxTime_);
if (rightMargin < drawingRect_.right) {
canvas.drawRect((float)rightMargin,
drawingRect_.top,
drawingRect_.right,
drawingRect_.bottom,
marginPaint_);
}
double topMargin = getNoteY(maxNote_);
if (topMargin > drawingRect_.top) {
canvas.drawRect(drawingRect_.left,
drawingRect_.top,
drawingRect_.right,
(float)topMargin,
marginPaint_);
}
double bottomMargin = getNoteY(minNote_);
if (bottomMargin < drawingRect_.bottom) {
canvas.drawRect(drawingRect_.left,
(float)bottomMargin,
drawingRect_.right,
drawingRect_.bottom,
marginPaint_);
}
// Draw the sequence.
for (int i = 0; i < score_.getEventCount(); ++i) {
Event event = score_.getEvent(i);
eventRect_.left = getTimeX(event.getStart());
eventRect_.top = getNoteY(event.getKey() + 1);
eventRect_.right = getTimeX(event.getEnd());
eventRect_.bottom = getNoteY(event.getKey());
if (!event.hasKeyEvent() || isChannelVisible(event.getKeyEvent().getChannel())) {
if (event.getSelected()) {
if (event.hasKeyEvent()) {
fillPaint_.setColor(getColorForChannel(event.getKeyEvent().getChannel()));
strokePaint_.setColor(Color.BLACK);
} else {
fillPaint_.setColor(Color.BLACK);
strokePaint_.setColor(Color.WHITE);
}
fillPaint_.setAlpha(255);
strokePaint_.setStrokeWidth(5.0f);
} else {
if (event.hasKeyEvent()) {
fillPaint_.setColor(getColorForChannel(event.getKeyEvent().getChannel()));
strokePaint_.setColor(Color.BLACK);
} else {
fillPaint_.setColor(Color.BLACK);
strokePaint_.setColor(Color.WHITE);
}
fillPaint_.setAlpha(127);
strokePaint_.setStrokeWidth(1.0f);
}
canvas.drawRect(eventRect_, fillPaint_);
canvas.drawRect(eventRect_, strokePaint_);
if (currentTool_ != null) {
currentTool_.afterDrawEvent(event, canvas, eventRect_);
}
}
}
// Draw the cursor.
strokePaint_.setColor(Color.rgb(0, 175, 0));
strokePaint_.setStrokeWidth(8.0f);
canvas.drawLine(getTimeX(cursor_), drawingRect_.top,
getTimeX(cursor_), drawingRect_.bottom, strokePaint_);
// Draw the scroll arrows, if visible.
if (arrowsVisible_) {
upIcon_.setBounds(getDrawingRect().left + 50,
getDrawingRect().top + 50,
getDrawingRect().left + 50 + upIcon_.getIntrinsicWidth(),
getDrawingRect().top + 50 + downIcon_.getIntrinsicHeight());
downIcon_.setBounds(getDrawingRect().left + 50,
(getDrawingRect().bottom - 50) - downIcon_.getIntrinsicHeight(),
getDrawingRect().left + 50 + upIcon_.getIntrinsicWidth(),
getDrawingRect().bottom - 50);
if (upSelected_) {
fillPaint_.setColor(Color.WHITE);
} else {
fillPaint_.setColor(Color.BLACK);
}
canvas.drawCircle(upIcon_.getBounds().centerX(),
upIcon_.getBounds().centerY(),
upIcon_.getBounds().width(),
fillPaint_);
upIcon_.draw(canvas);
if (downSelected_) {
fillPaint_.setColor(Color.WHITE);
} else {
fillPaint_.setColor(Color.BLACK);
}
canvas.drawCircle(downIcon_.getBounds().centerX(),
downIcon_.getBounds().centerY(),
downIcon_.getBounds().width(),
fillPaint_);
downIcon_.draw(canvas);
// Make the bounds for the icons a little larger so they're easier to hit.
downIcon_.getBounds().inset(-50, -50);
upIcon_.getBounds().inset(-50, -50);
}
if (currentTool_ != null) {
currentTool_.afterDrawScore(this, canvas, drawingRect_);
}
}
/**
* Handler for all touch events.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// Check if they touched the arrows.
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
if (actionCode == MotionEvent.ACTION_DOWN) {
if (upIcon_.getBounds().contains((int)event.getX(), (int)event.getY())) {
upSelected_ = true;
invalidate();
return true;
} else if (downIcon_.getBounds().contains((int)event.getX(), (int)event.getY())) {
downSelected_ = true;
invalidate();
return true;
} else {
arrowsVisible_ = false;
invalidate();
}
} else if (actionCode == MotionEvent.ACTION_UP) {
arrowsVisible_ = true;
if (upSelected_) {
// Scroll up.
if (getNoteOffset() < maxNote_) {
setNoteOffset(getNoteOffset() + 1);
}
upSelected_ = false;
}
if (downSelected_) {
// Scroll down.
if (getNoteOffset() > minNote_) {
setNoteOffset(getNoteOffset() - 1);
}
downSelected_ = false;
}
invalidate();
}
// Delegate the touch to the current tool.
if (!upSelected_ && !downSelected_ && currentTool_ != null) {
return currentTool_.onTouch(this, event);
} else {
return false;
}
}
/**
* Layout measurement for this widget.
* This method just sets a basic minimum size and makes the widget maximized otherwise.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 10;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 10;
break;
}
setMeasuredDimension(width, height);
}
/**
* Connects the ScoreView to a Synthesizer for playback.
* @synth - The synthesizer to connect to.
*/
public void bindTo(final MultiChannelSynthesizer synth) {
synthesizer_ = synth;
}
/**
* Returns the synthesizer connected to this ScoreView.
* @return the connected synthesizer.
*/
public MultiChannelSynthesizer getSynthesizer() {
return synthesizer_;
}
/**
* Sets the listener to notify of events in this control.
* @param listener - the listener to notify.
*/
public void setListener(ScoreViewListener listener) {
listener_ = listener;
}
// The score being edited, played, etc by this control.
private Score.Builder score_;
// The current tool being used.
private ScoreViewTool currentTool_;
// The currently selected channel (instrument).
private int currentChannel_;
// Whether to show channels other than the currently selected one.
private boolean showOtherChannels_;
// The set of icons to use for each channel.
private Drawable[] iconForChannel_;
// What granularity of note to snap to when editing. See getSnapTo and setSnapTo().
private double snapTo_;
// The min, max and current viewport for the x and y axes.
private double timeZoom_;
private double timeOffset_;
private double minTime_;
private double maxTime_;
private double noteZoom_;
private double noteOffset_;
private double minNote_;
private double maxNote_;
// A cursor that indicates where playback is in the score, in logical coordinates.
private double cursor_;
// The synthesizer this control is bound to.
private MultiChannelSynthesizer synthesizer_;
// The listener to notify of events in this control.
private ScoreViewListener listener_;
// Buttons to let the user move up and down without switching to the viewport tool.
private boolean arrowsVisible_;
private boolean upSelected_;
private boolean downSelected_;
private Drawable upIcon_;
private Drawable downIcon_;
// These are basically stack variables for onDraw. They're member variables only so that we can
// avoid reallocating them every time the keyboard is redrawn.
//
// The most recent screen rect that this keyboard was drawn into.
private Rect drawingRect_;
private Rect keyRect_;
private Rect eventRect_;
private Paint fillPaint_;
private Paint strokePaint_;
private Paint marginPaint_;
// The number of channels (instruments) edittable by this control.
private static final int CHANNELS = 5;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,27 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
/**
* A listener for events happening in a ScoreView.
*/
public interface ScoreViewListener {
/**
* Called when a new tool is selected for the ScoreView.
*/
void onSetTool(ScoreViewTool tool);
}

@ -0,0 +1,81 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import com.google.synthesizer.core.music.Music.Event;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.MotionEvent;
/**
* A base class for tools on the ScoreView's toolbar.
*/
public abstract class ScoreViewTool {
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this tool is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
public abstract void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin);
/**
* Called when this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param previousTool - The tool that was selected when this one was chosen.
*/
public void onSelect(ScoreView view, ScoreViewTool previousTool) {}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
public boolean onTouch(ScoreView view, MotionEvent event) {
return false;
}
/**
* Called after each key is drawn, to give this tool a chance to draw over it.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param key - The key that was drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
public void afterDrawKey(int key, Canvas canvas, Rect rect) {}
/**
* Called after each event is drawn, to give this tool a chance to draw over it.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param event - The event that was drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
public void afterDrawEvent(Event event, Canvas canvas, Rect rect) {}
/**
* Called after the entire score is drawn, to give this tool a chance to draw over it.
* See ScoreView.onDraw() for more information on how ScoreView is drawn.
* @param view - The ScoreView being drawn.
* @param canvas - The canvas the key is drawn into.
* @param rect - The area of the key on the canvas.
*/
public void afterDrawScore(ScoreView view, Canvas canvas, Rect rect) {}
}

@ -0,0 +1,222 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* A toolbar that goes in a ScoreView and lets the user choose which tool to use on it.
*/
public class ScoreViewToolbar extends View implements ScoreViewListener {
/** Basic constructor for an Android widget. */
public ScoreViewToolbar(Context context, AttributeSet attrs) {
super(context, attrs);
paint_ = new Paint();
rect_ = new Rect();
textRect_ = new Rect();
// Just hard-code the list of tools.
tool_ = new ScoreViewTool[13];
tool_[0] = new PlayButton(this, context);
tool_[1] = new ViewportTool(context);
tool_[2] = new EditEventTool(context);
tool_[3] = new NewEventTool(context, (EditEventTool)tool_[2]);
tool_[4] = new NewLoopTool(context, (EditEventTool)tool_[2]);
tool_[5] = new HideChannelButton(context);
tool_[6] = new PlayTool(context);
tool_[7] = new SelectChannelButton(context, 0);
tool_[8] = new SelectChannelButton(context, 1);
tool_[9] = new SelectChannelButton(context, 2);
tool_[10] = new SelectChannelButton(context, 3);
tool_[11] = new SelectChannelButton(context, 4);
tool_[12] = new SnapTool(context);
toolRect_ = new Rect[tool_.length];
for (int i = 0; i < tool_.length; ++i) {
toolRect_[i] = new Rect();
}
}
/**
* Sets the ScoreView that this toolbar controls.
* @param score - the ScoreView.
*/
public void setScoreView(ScoreView score) {
score_ = score;
score_.setListener(this);
score_.setTool(tool_[1]);
invalidate();
}
/**
* Sets the current tool.
* @param tool - the tool to use.
*/
public void onSetTool(ScoreViewTool tool) {
invalidate();
}
/**
* Touch event handler.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
getDrawingRect(rect_);
for (int i = 0; i < tool_.length; ++i) {
if (toolRect_[i].contains((int)event.getX(), (int)event.getY())) {
score_.setTool(tool_[i]);
invalidate();
break;
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
break;
}
case MotionEvent.ACTION_UP: {
break;
}
}
return true;
}
/**
* Drawing handler.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(rect_);
//rect_.set(rect_);
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect_, paint_);
// Draw the buttons.
float margin = 15.0f;
float waveHeight = (rect_.height() - 2.0f * margin);
float waveWidth = waveHeight;
float xOffset = margin;
float yOffset = margin;
for (int i = 0; i < tool_.length; ++i) {
paint_.setTextSize(24.0f);
paint_.setColor(Color.WHITE);
if (i == 1) {
xOffset += 2 * margin;
paint_.getTextBounds("Tools ", 0, 6, textRect_);
canvas.drawText("Tools ", xOffset, yOffset + (waveHeight + textRect_.height()) / 2, paint_);
xOffset += textRect_.width();
xOffset += margin;
} else if (i == 7) {
xOffset += 2 * margin;
paint_.getTextBounds("Instruments ", 0, 12, textRect_);
canvas.drawText("Instruments ", xOffset, yOffset + (waveHeight + textRect_.height()) / 2, paint_);
xOffset += textRect_.width();
xOffset += margin;
} else if (i == 12) {
xOffset += 2 * margin;
paint_.getTextBounds("Snap to ", 0, 8, textRect_);
canvas.drawText("Snap to ", xOffset, yOffset + (waveHeight + textRect_.height()) / 2, paint_);
xOffset += textRect_.width();
xOffset += margin;
}
paint_.setColor(Color.BLACK);
toolRect_[i].left = (int)xOffset;
toolRect_[i].top = (int)yOffset;
toolRect_[i].right = (int)(xOffset + waveWidth);
toolRect_[i].bottom = (int)(yOffset + waveHeight);
tool_[i].drawButton(canvas, score_, toolRect_[i], margin);
xOffset += waveWidth;
xOffset += margin;
}
}
/**
* Layout measurement for this widget.
* This method just sets a basic minimum size and makes the width maximized otherwise.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 10;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = 150;
break;
case MeasureSpec.UNSPECIFIED:
height = 10;
break;
}
setMeasuredDimension(width, height);
}
// The score being edited.
private ScoreView score_;
// Structures used in drawing that we don't want to reallocate every time we draw.
private Paint paint_;
private Rect rect_;
private Rect textRect_;
// The set of available tools.
private ScoreViewTool[] tool_;
// The rect that each tool occupied the last time it was drawn.
private Rect[] toolRect_;
}

@ -0,0 +1,100 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
* A tool for selecting a particular channel in a ScoreView.
*/
public class SelectChannelButton extends ScoreViewTool {
/**
* Creates a tool for selecting the given channel, loading resources using the given context.
*/
SelectChannelButton(Context context, int channel) {
logger_ = Logger.getLogger(getClass().getName());
channel_ = channel;
paint_ = new Paint();
}
/**
* Returns the channel that this control selects.
*/
public int getChannel() {
return channel_;
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
if (getChannel() == score.getCurrentChannel()) {
paint_.setColor(score.getColorForChannel(getChannel()));
} else {
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
Drawable icon = score.getIconForChannel(getChannel());
icon.setBounds(rect);
icon.draw(canvas);
}
/**
* Called when this tool is selected.
* Changes the selected channel for the view and then reselects the previously selected tool.
* @param view - The ScoreView that this toolbar is for.
* @param previousTool - The tool that was selected when this one was chosen.
*/
@Override
public void onSelect(ScoreView view, ScoreViewTool previousTool) {
view.setCurrentChannel(channel_);
view.setTool(previousTool);
}
// The channel this control selects.
private int channel_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,134 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import com.google.synthesizer.R;
import java.util.logging.Logger;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
* A control for selecting the "snap to" setting of a ScoreView.
* @see ScoreView
*/
public class SnapTool extends ScoreViewTool {
/**
* Creates a new SnapTool, loading resources from the given context.
*/
SnapTool(Context context) {
logger_ = Logger.getLogger(getClass().getName());
customIcon_ = context.getResources().getDrawable(R.drawable.unknown_note);
noneIcon_ = context.getResources().getDrawable(R.drawable.no_note);
thirtySecondIcon_ = context.getResources().getDrawable(R.drawable.thirtysecond_note);
sixteenthIcon_ = context.getResources().getDrawable(R.drawable.sixteenth_note);
eighthIcon_ = context.getResources().getDrawable(R.drawable.eighth_note);
quarterIcon_ = context.getResources().getDrawable(R.drawable.quarter_note);
halfIcon_ = context.getResources().getDrawable(R.drawable.half_note);
wholeIcon_ = context.getResources().getDrawable(R.drawable.whole_note);
paint_ = new Paint();
}
/**
* Called when this tool is selected. Changes the "snap to" setting for the score view and then
* reselects the previously selected tool.
* @param view - The ScoreView that this toolbar is for.
* @param previousTool - The tool that was selected when this one was chosen.
*/
@Override
public void onSelect(ScoreView view, ScoreViewTool previousTool) {
if (view.getSnapTo() == 0.0) {
view.setSnapTo(1.0);
} else if (view.getSnapTo() <= 0.03125) {
view.setSnapTo(0.0);
} else {
view.setSnapTo(view.getSnapTo() / 2.0);
}
view.setTool(previousTool);
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
if (score.getSnapTo() == 1.0) {
wholeIcon_.setBounds(rect);
wholeIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.5) {
halfIcon_.setBounds(rect);
halfIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.25) {
quarterIcon_.setBounds(rect);
quarterIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.125) {
eighthIcon_.setBounds(rect);
eighthIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.0625) {
sixteenthIcon_.setBounds(rect);
sixteenthIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.03125) {
thirtySecondIcon_.setBounds(rect);
thirtySecondIcon_.draw(canvas);
} else if (score.getSnapTo() == 0.0) {
noneIcon_.setBounds(rect);
noneIcon_.draw(canvas);
} else {
customIcon_.setBounds(rect);
customIcon_.draw(canvas);
}
}
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable customIcon_;
private Drawable noneIcon_;
private Drawable thirtySecondIcon_;
private Drawable sixteenthIcon_;
private Drawable eighthIcon_;
private Drawable quarterIcon_;
private Drawable halfIcon_;
private Drawable wholeIcon_;
@SuppressWarnings("unused")
private Logger logger_;
}

@ -0,0 +1,259 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.score;
import java.util.logging.Logger;
import com.google.synthesizer.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
/**
* A tool for adjusting the visible logical area of a ScoreView.
* When this tool is selected, touching moves around the viewport, and pinching zooms in/out.
*/
public class ViewportTool extends ScoreViewTool {
/**
* Creates a new ViewportTool, loading resources from the given context.
*/
ViewportTool(Context context) {
logger_ = Logger.getLogger(getClass().getName());
startX1_ = 0;
startX2_ = 0;
startY1_ = 0;
startY2_ = 0;
icon_ = context.getResources().getDrawable(R.drawable.zoom);
paint_ = new Paint();
}
/**
* Draws the button on the toolbar.
* @param canvas - The canvas to draw the button on.
* @param score - The ScoreView that this toolbar is for.
* @param rect - The area of the button to be drawn, including any margin.
* @param margin - The preferred margin around the button, in screen coordinates.
*/
@Override
public void drawButton(Canvas canvas, ScoreView score, Rect rect, float margin) {
if (score.getTool() == this) {
paint_.setColor(Color.WHITE);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect.left - margin / 2,
rect.top - margin / 2,
rect.right + margin / 2,
rect.bottom + margin / 2,
paint_);
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect, paint_);
icon_.setBounds(rect);
icon_.draw(canvas);
}
/**
* Called when the user touches the ScoreView while this tool is selected.
* @param view - The ScoreView that this tool is for.
* @param event - The touch event that triggered this handler.
* @return true iff this tool handled the touch event.
*/
@Override
public boolean onTouch(ScoreView view, MotionEvent event) {
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
boolean redraw = false;
double timeZoom = view.getTimeZoom();
double timeOffset = view.getTimeOffset();
double noteZoom = view.getNoteZoom();
double noteOffset = view.getNoteOffset();
if (actionCode == MotionEvent.ACTION_DOWN) {
int pointerId = event.getPointerId(0);
startX1_ = (int)event.getX();
startY1_ = (int)event.getY();
if (pointerId != 0) {
logger_.severe("Initial pointer has id " + pointerId);
}
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
int pointerIndex = event.findPointerIndex(pointerId);
if (pointerId == 1 && pointerIndex >= 0) {
startX2_ = (int)event.getX(pointerIndex);
startY2_ = (int)event.getY(pointerIndex);
}
} else if (actionCode == MotionEvent.ACTION_MOVE) {
double currentX1 = 0;
double currentX2 = 0;
double currentY1 = 0;
double currentY2 = 0;
boolean has1 = false;
boolean has2 = false;
// Find the current positions of the fingers.
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
int pointerId = event.getPointerId(pointerIndex);
if (pointerId >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
if (pointerId == 0) {
currentX1 = x;
currentY1 = y;
has1 = true;
} else if (pointerId == 1) {
currentX2 = x;
currentY2 = y;
has2 = true;
}
}
}
if (has1 && has2) {
// Enforce that finger 1 is to the left of and above finger 2.
if (currentX2 < currentX1) {
double temp = currentX1;
currentX1 = currentX2;
currentX2 = temp;
}
if (currentY2 < currentY1) {
double temp = currentY1;
currentY1 = currentY2;
currentY2 = temp;
}
if (startX2_ < startX1_) {
double temp = startX1_;
startX1_ = startX2_;
startX2_ = temp;
}
if (startY2_ < startY1_) {
double temp = startY1_;
startY1_ = startY2_;
startY2_ = temp;
}
// Make sure the fingers aren't too close together.
if (startX2_ - startX1_ < 50.0) {
startX2_ = startX1_ + 50.0;
}
if (currentX2 - currentX1 < 50.0) {
currentX2 = currentX1 + 50.0;
}
if (startY2_ - startY1_ < 50.0) {
startY2_ = startY1_ + 50.0;
}
if (currentY2 - currentY1 < 50.0) {
currentY2 = currentY1 + 50.0;
}
// Figure out the parameters of the new viewport.
double scaleXFactor = Math.abs((currentX1 - currentX2) / (startX1_ - startX2_));
timeOffset += (startX1_ - currentX1 / scaleXFactor) /
(timeZoom * view.getDrawingRect().width());
timeZoom *= scaleXFactor;
double scaleYFactor = Math.abs((currentY1 - currentY2) / (startY1_ - startY2_));
noteOffset -= (startY1_ - currentY1/scaleYFactor -
view.getDrawingRect().bottom * (1 - 1/scaleYFactor)) /
(noteZoom * view.getDrawingRect().height());
noteZoom *= scaleYFactor;
// Update the tracking.
startX1_ = currentX1;
startX2_ = currentX2;
startY1_ = currentY1;
startY2_ = currentY2;
redraw = true;
} else if (has1) {
// Move the viewport.
timeOffset += (startX1_ - currentX1) / (timeZoom * view.getDrawingRect().width());
startX1_ = currentX1;
noteOffset -= (startY1_ - currentY1) / (noteZoom * view.getDrawingRect().height());
startY1_ = currentY1;
redraw = true;
}
} else if (actionCode == MotionEvent.ACTION_UP) {
// Snap back so we aren't showing much margin.
// This code is commented out because it's actually much more intuitive if it doesn't snap
// back but just shows some margin.
/*
if (scaleX < 1.0 / maxX) {
offsetX = 0.0;
scaleX = 1.0 / maxX;
redraw = true;
}
if (scaleY < 1.0 / maxY) {
offsetY = 0.0;
scaleY = 1.0 / maxY;
redraw = true;
}
if (offsetX < 0.0) {
offsetX = 0.0;
redraw = true;
}
if (offsetY < 0.0) {
offsetY = 0.0;
redraw = true;
}
if (offsetX > maxX - 1.0 / scaleX) {
offsetX = maxX - 1.0 / scaleX;
redraw = true;
}
if (offsetY > maxY - 1.0 / scaleY) {
offsetY = maxY - 1.0 / scaleY;
redraw = true;
}
*/
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
} else {
return view.onTouchEvent(event);
}
view.setTimeZoom(timeZoom);
view.setTimeOffset(timeOffset);
view.setNoteZoom(noteZoom);
view.setNoteOffset(noteOffset);
if (redraw) {
view.invalidate();
}
return true;
}
// The screen coordinates of the first and second fingers pressed on the ScoreView.
// These are updated every time the viewport is updated.
private double startX1_;
private double startX2_;
private double startY1_;
private double startY2_;
// Some objects used in drawing. They are owned here so that they don't have to be reallocated
// and garbage collected for every pass of drawing.
private Paint paint_;
private Drawable icon_;
private Logger logger_;
}

@ -0,0 +1,27 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.waveform;
/**
* WaveformListener is an interface for getting events when a WaveformView's value changes.
*/
public interface WaveformListener {
/**
* Called when the value changes.
*/
void onWaveformChanged(String waveform);
}

@ -0,0 +1,167 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.waveform;
import com.google.synthesizer.core.model.WaveformInput;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* WaveformRowView is like a WaveformView, but it arranges its buttons in a row.
*/
public class WaveformRowView extends WaveformView {
/** Basic constructor for an Android widget. */
public WaveformRowView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Touch event handler.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
getDrawingRect(rect_);
double x = (event.getX() - rect_.left) / rect_.width();
if (x < 1.0/6.0f) {
setWaveform(WaveformInput.SINE);
} else if (x < 2.0/6.0f) {
setWaveform(WaveformInput.TRIANGLE);
} else if (x < 3.0/6.0f) {
setWaveform(WaveformInput.SQUARE);
} else if (x < 4.0/6.0f) {
setWaveform(WaveformInput.SAWTOOTH);
} else if (x < 5.0/6.0f) {
setWaveform(WaveformInput.NOISE);
} else {
CharSequence[] items = new CharSequence[input_.getWaveformCount()];
for (int i = 0; i < items.length; ++i) {
items[i] = input_.getWaveform(i);
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setWaveform(input_.getWaveform(which));
}
});
builder.create().show();
}
invalidate();
break;
}
case MotionEvent.ACTION_MOVE: {
break;
}
case MotionEvent.ACTION_UP: {
break;
}
}
return true;
}
/**
* Drawing handler.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(rect_);
rect_.set(rect_);
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect_, paint_);
// Draw waveforms.
float lineWidth = 5.0f;
float margin = 15.0f;
float waveWidth = (rect_.width() - 7.0f * margin) / 6.0f;
float waveHeight = (rect_.height() - 2.0f * margin);
float xOffset = margin;
float yOffset = margin;
drawSine(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
xOffset += waveWidth;
xOffset += margin;
drawTriangle(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
xOffset += waveWidth;
xOffset += margin;
drawSquare(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
xOffset += waveWidth;
xOffset += margin;
drawSawtooth(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
xOffset += waveWidth;
xOffset += margin;
drawNoise(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
xOffset += waveWidth;
xOffset += margin;
drawOther(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
}
/**
* Layout measurement for this widget.
* This method just sets a basic minimum size and makes the width maximized otherwise.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 10;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = 150;
break;
case MeasureSpec.UNSPECIFIED:
height = 10;
break;
}
setMeasuredDimension(width, height);
}
}

@ -0,0 +1,470 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.android.widgets.waveform;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.google.synthesizer.core.model.WaveformInput;
import com.google.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.google.synthesizer.core.model.composite.Presets.Setting;
/**
* WaveformView is a control for selecting from among available waveforms.
* It's designed to occupy the same space as a KnobView.
*/
public class WaveformView extends View {
/** Basic constructor for an Android widget. */
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
waveform_ = WaveformInput.SINE;
// Set up the drawing structures.
paint_ = new Paint();
path_ = new Path();
rect_ = new Rect();
// The listener has to be set later.
listener_ = null;
setPadding(3, 3, 3, 3);
}
/**
* Touch event handler.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
getDrawingRect(rect_);
double x = (event.getX() - rect_.left) / rect_.width();
double y = (event.getY() - rect_.top) / rect_.height();
if (x < 0.5) {
if (y < 0.34) {
setWaveform(WaveformInput.SINE);
} else if (y < 0.67) {
setWaveform(WaveformInput.TRIANGLE);
} else {
setWaveform(WaveformInput.SQUARE);
}
} else {
if (y < 0.34) {
setWaveform(WaveformInput.SAWTOOTH);
} else if (y < 0.67) {
setWaveform(WaveformInput.NOISE);
}
}
invalidate();
break;
}
case MotionEvent.ACTION_MOVE: {
break;
}
case MotionEvent.ACTION_UP: {
break;
}
}
return true;
}
/**
* Sets the listener to receive events when the value changes.
*/
public void setWaveformListener(WaveformListener listener) {
listener_ = listener;
}
/**
* Sets the current value of the knob.
*/
public void setWaveform(String waveform) {
waveform_ = waveform;
if (listener_ != null) {
listener_.onWaveformChanged(waveform);
}
invalidate();
}
/**
* Returns the current value of the knob.
*/
public String getWaveform() {
return waveform_;
}
/**
* Draws a button for selecting a sine waveform.
*/
protected void drawSine(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
int steps = 12;
// Sine wave.
path_.reset();
path_.moveTo(x, y + (height / 2));
for (int i = 0; i < steps + 1; i++) {
float x1 = x + (i / (float)steps) * width;
float y1 = y + -1 * (height/2) * (float)Math.sin(2.0f/steps * Math.PI * i) + height/2;
path_.lineTo(x1, y1);
}
paint_.setColor(Color.WHITE);
if (waveform_.equals(WaveformInput.SINE)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path_, paint_);
}
/**
* Draws a button for selecting a triangle waveform.
*/
protected void drawTriangle(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
// Triangle Wave.
path_.reset();
path_.moveTo(x, y + (height / 2));
path_.lineTo(x + width / 4, y);
path_.lineTo(x + width * (3.0f / 4.0f), y + height);
path_.lineTo(x + width, y + (height / 2));
paint_.setColor(Color.WHITE);
if (waveform_.equals(WaveformInput.TRIANGLE)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path_, paint_);
}
/**
* Draws a button for selecting a square waveform.
*/
protected void drawSquare(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
// Square Wave.
path_.reset();
path_.moveTo(x, y + height);
path_.lineTo(x + width / 4, y + height);
path_.lineTo(x + width / 4, y);
path_.lineTo(x + width * (3.0f / 4.0f), y);
path_.lineTo(x + width * (3.0f / 4.0f), y + height);
path_.lineTo(x + width, y + height);
paint_.setColor(Color.WHITE);
if (waveform_.equals(WaveformInput.SQUARE)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path_, paint_);
}
/**
* Draws a button for selecting a sawtooth waveform.
*/
protected void drawSawtooth(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
// Sawtooth Wave.
path_.reset();
path_.moveTo(x, y + height);
path_.lineTo(x, y);
path_.lineTo(x + width / 2, y + height);
path_.lineTo(x + width / 2, y);
path_.lineTo(x + width, y + height);
paint_.setColor(Color.WHITE);
if (waveform_.equals(WaveformInput.SAWTOOTH)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path_, paint_);
}
/**
* Draws a button for selecting a noise waveform.
*/
protected void drawNoise(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
// Noise.
path_.reset();
path_.moveTo(x, y + height * 0.5f);
path_.lineTo(x + 0.125f * width, y + height * 0.4f);
path_.lineTo(x + 0.25f * width, y + height * 1.0f);
path_.lineTo(x + 0.375f * width, y + height * 0.3f);
path_.lineTo(x + 0.5f * width, y + height * 0.7f);
path_.lineTo(x + 0.625f * width, y + height * 0.0f);
path_.lineTo(x + 0.75f * width, y + height * 0.8f);
path_.lineTo(x + 0.875f * width, y + height * 0.2f);
path_.lineTo(x + 1.0f * width, y + height * 0.5f);
paint_.setColor(Color.WHITE);
if (waveform_.equals(WaveformInput.NOISE)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path_, paint_);
}
/**
* Draws a button for selecting a Karplus-Strong waveform.
*/
protected void drawOther(Canvas canvas,
float x, float y,
float width, float height,
float margin,
float lineWidth) {
int steps = 12;
paint_.setColor(Color.WHITE);
if (!waveform_.equals(WaveformInput.SINE) &&
!waveform_.equals(WaveformInput.TRIANGLE) &&
!waveform_.equals(WaveformInput.SAWTOOTH) &&
!waveform_.equals(WaveformInput.SQUARE) &&
!waveform_.equals(WaveformInput.NOISE)) {
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(x - margin / 2,
y - margin / 2,
x + width + margin / 2,
y + height + margin / 2,
paint_);
paint_.setColor(Color.BLACK);
}
paint_.setStyle(Paint.Style.STROKE);
paint_.setStrokeWidth(lineWidth);
paint_.setStrokeJoin(Paint.Join.ROUND);
path_.reset();
path_.moveTo(x, y + (height / 2));
for (int i = 0; i < steps + 1; i++) {
float x1 = x + (i / (float)steps) * width;
float y1 = y + -1 * (height/2) * (float)Math.sin(2.0f/steps * Math.PI * i) + height/2;
path_.lineTo(x1, y1);
}
canvas.drawPath(path_, paint_);
path_.reset();
path_.moveTo(x, y + (height / 2));
for (int i = 0; i < steps + 1; i++) {
float x1 = x + (i / (float)steps) * width;
float y1 = y + -0.6f * (height/2) * (float)Math.sin(2.0f/steps * Math.PI * (steps-i)) + height/2;
path_.lineTo(x1, y1);
}
canvas.drawPath(path_, paint_);
}
/**
* Drawing handler.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(rect_);
rect_.set(rect_);
// Make it square.
if (rect_.height() > rect_.width()) {
int center = rect_.centerY();
rect_.top = center - rect_.width() / 2;
rect_.bottom = center + rect_.width() / 2;
} else {
int center = rect_.centerX();
rect_.left = center - rect_.height() / 2;
rect_.right = center + rect_.height() / 2;
}
paint_.setColor(Color.BLACK);
paint_.setStyle(Paint.Style.FILL);
canvas.drawRect(rect_, paint_);
// Draw waveforms.
float lineWidth = 5.0f;
float margin = 15.0f;
float waveWidth = (rect_.width() - 3.0f * margin) / 2.0f;
float waveHeight = (rect_.height() - 4.0f * margin) / 3.0f;
float xOffset = margin;
float yOffset = margin;
drawSine(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
yOffset += waveHeight;
yOffset += margin;
drawTriangle(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
yOffset += waveHeight;
yOffset += margin;
drawSquare(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
yOffset = margin;
xOffset += waveWidth;
xOffset += margin;
drawSawtooth(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
yOffset += waveHeight;
yOffset += margin;
drawNoise(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
yOffset += waveHeight;
yOffset += margin;
drawOther(canvas, xOffset, yOffset, waveWidth, waveHeight, margin, lineWidth);
}
/**
* Controls how the knob is sized; it is square, and prefers to be 100x100 pixels.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Specify that 100 is preferred for both dimensions.
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 100;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 100;
break;
}
// Make it square.
if (width > height && widthMode != MeasureSpec.EXACTLY) {
width = height;
}
if (height > width && heightMode != MeasureSpec.EXACTLY) {
height = width;
}
setMeasuredDimension(width, height);
}
/**
* Connects control to a WaveformInput.
* @input - The synthesizer input to connect to.
*/
public void bindTo(WaveformInput waveform) {
input_ = waveform;
setWaveform(waveform.getWaveform(waveform.getSelected()));
setWaveformListener(new WaveformListener() {
public void onWaveformChanged(String newValue) {
input_.select(newValue);
}
});
}
/**
* Connects control to a WaveformSelector module.
* @synth - The synthesizer to connect to.
* @setting - The setting to connect to.
* @return - True on success, false on failure.
*/
public boolean bindTo(final MultiChannelSynthesizer synth, int channel, Setting setting) {
WaveformInput input = synth.getChannel(0).getWaveformInput(setting);
if (input != null) {
bindTo(input);
return true;
} else {
Log.e(getClass().getName(), "Unable to bind to setting " + setting.name() + ".");
return false;
}
}
// Currently selected waveform.
private String waveform_;
protected WaveformInput input_;
// Structures used in drawing that we don't want to reallocate every time we draw.
protected Paint paint_;
protected Path path_;
protected Rect rect_;
// Object listening for events when the knob's value changes.
private WaveformListener listener_;
}

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project name="music-synthesizer" default="build">
<property file="build.properties"/>
<target name="build">
<ant dir="core" target="build"/>
<ant dir="test" target="build"/>
<ant dir="j2se" target="build"/>
</target>
<target name="clean">
<ant dir="core" target="clean"/>
<ant dir="test" target="clean"/>
<ant dir="j2se" target="clean"/>
</target>
<target name="test" depends="build,clean">
<ant dir="test" target="test"/>
</target>
<target name="javadoc">
<mkdir dir="docs/javadoc"/>
<javadoc destdir="docs/javadoc" useexternalfile="true">
<sourcepath>
<pathelement location="android/src"/>
<pathelement location="core/src"/>
<pathelement location="test/src"/>
<pathelement location="j2se/src"/>
</sourcepath>
<classpath>
<!-- TODO(klimt): Add android. -->
</classpath>
</javadoc>
</target>
<target name="release" depends="build,clean,javadoc">
<zip file="music-synthesizer-${version}.zip">
<zipfileset dir="." prefix="music-synthesizer-${version}">
<exclude name="**/.*"/>
</zipfileset>
</zip>
</target>
</project>

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project name="core" default="build">
<property file="../build.properties"/>
<target name="compile">
<mkdir dir="gen"/>
<apply executable="bin/protoc" relative="true" failonerror="true">
<arg value="--java_out=gen/"/>
<fileset dir="." includes="**/*.proto"/>
</apply>
<mkdir dir="build"/>
<javac srcdir="gen"
destdir="build"
source="1.5"
target="1.5"
optimize="true"
deprecation="true"
debug="true"
includeantruntime="false">
<classpath>
<pathelement path="lib/libprotobuf.jar"/>
</classpath>
</javac>
<javac srcdir="src"
destdir="build"
source="1.5"
target="1.5"
optimize="true"
deprecation="true"
debug="true"
includeantruntime="false">
<classpath>
<pathelement path="build"/>
<pathelement path="lib/libprotobuf.jar"/>
</classpath>
</javac>
<jar jarfile="core.jar" basedir="build"/>
</target>
<target name="clean">
<delete dir="gen"/>
<mkdir dir="gen"/>
<delete dir="build"/>
<delete dir="core.jar"/>
</target>
<target name="build" depends="clean">
<antcall target="compile">
<param name="generate-debug" value="true"/>
</antcall>
</target>
<target name="test"/>
</project>

@ -0,0 +1,284 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.synthesizer.core.midi;
import java.io.IOException;
import java.io.InputStream;
/**
* MessageInputProcessor takes Midi messages from an input stream and dispatches them to a
* MidiListener.
* @see MidiListener
*/
public class MessageInputProcessor {
/**
* Creates a new MessageInputProcessor.
*/
public MessageInputProcessor() {
previousCode_ = 0;
}
/**
* Reads one Midi message from input and dispatches any events to a listener.
*/
public void process(InputStream input, MidiListener listener) throws IOException {
previousCode_ = process(input, previousCode_, listener);
}
/**
* Reads one Midi message from input and dispatches any events to a listener.
* @param input - The stream to read from.
* @param previousCode - The previous message code in the stream, for "running status" encoding.
* @param listener - The object to handle the event.
*/
public static int process(InputStream input,
int previousCode,
MidiListener listener) throws IOException {
if (!input.markSupported()) {
throw new IOException("process() requires an InputStream that supports mark().");
}
input.mark(1);
int code = MidiUtil.readByte(input);
if ((code & 0x80) == 0 && previousCode != 0) {
code = previousCode;
input.reset();
}
if (code == 0xFF) {
processMetaMessage(input, listener);
} else if (code == 0xFE) {
listener.onActiveSensing();
} else if (code == 0xF8) {
listener.onTimingClock();
} else if (code == 0xF0 || code == 0xF7) {
processSysExMessage(input, listener);
} else if ((code & 0x80) == 0x80 && (code & 0xF0) != 0xF0) {
processControlMessage(input, code, listener);
} else {
throw new IOException("Invalid midi event code " + code + ".");
}
return code;
}
/**
* Processes control messages.
* @param input - The stream to read from.
* @param code - The code of the event, which has the type and the channel.
* @param listener - The object to handle the event.
*/
private static void processControlMessage(InputStream input,
int code,
MidiListener listener) throws IOException {
int type = (code & 0xF0);
int channel = (code & 0x0F);
switch (type) {
case 0x80: {
int note = MidiUtil.readByte(input);
int velocity = MidiUtil.readByte(input);
listener.onNoteOff(channel, note, velocity);
break;
}
case 0x90: {
int note = MidiUtil.readByte(input);
int velocity = MidiUtil.readByte(input);
listener.onNoteOn(channel, note, velocity);
break;
}
case 0xA0: {
int note = MidiUtil.readByte(input);
int aftertouch = MidiUtil.readByte(input);
listener.onNoteAftertouch(channel, note, aftertouch);
break;
}
case 0xB0: {
int control = MidiUtil.readByte(input);
int value = MidiUtil.readByte(input);
listener.onController(channel, control, value);
break;
}
case 0xC0: {
int program = MidiUtil.readByte(input);
listener.onProgramChange(channel, program);
break;
}
case 0xD0: {
int aftertouch = MidiUtil.readByte(input);
listener.onChannelAftertouch(channel, aftertouch);
break;
}
case 0xE0: {
int value = MidiUtil.readWord(input);
listener.onPitchBend(channel, value);
break;
}
default: {
throw new IOException("Invalid midi control message type " + type + ".");
}
}
}
/**
* Processes meta messages.
* @param input - The stream to read from.
* @param listener - The object to handle the event.
*/
private static void processMetaMessage(InputStream input,
MidiListener listener) throws IOException {
int type = MidiUtil.readByte(input);
int size = MidiUtil.readVarInt(input);
switch (type) {
case 0x00: {
if (size != 2) {
throw new IOException("Invalid length for sequence meta event " + size + ".");
}
listener.onSequenceNumber(MidiUtil.readWord(input));
break;
}
case 0x01: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onText(text);
break;
}
case 0x02: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onCopyrightNotice(text);
break;
}
case 0x03: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onSequenceName(text);
break;
}
case 0x04: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onInstrumentName(text);
break;
}
case 0x05: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onLyrics(text);
break;
}
case 0x06: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onMarker(text);
break;
}
case 0x07: {
byte[] text = new byte[size];
MidiUtil.readBytes(input, size, text);
listener.onCuePoint(text);
break;
}
case 0x20: {
if (size != 1) {
throw new IOException("Invalid length for midi channel prefix " + size + ".");
}
listener.onChannelPrefix(MidiUtil.readByte(input));
break;
}
case 0x21: {
byte[] data = new byte[size];
MidiUtil.readBytes(input, size, data);
listener.onPort(data);
break;
}
case 0x2F: {
if (size != 0) {
throw new IOException("Invalid length for end of track " + size + ".");
}
listener.onEndOfTrack();
break;
}
case 0x51: {
if (size != 3) {
throw new IOException("Invalid length for set tempo event " + size + ".");
}
// Stupid 3-byte value.
int w = MidiUtil.readWord(input);
int b = MidiUtil.readByte(input);
int mspqn = (w << 8) | b;
listener.onSetTempo(mspqn);
break;
}
case 0x54: {
if (size != 5) {
throw new IOException("Invalid length for smpte offset event " + size + ".");
}
byte[] data = new byte[size];
MidiUtil.readBytes(input, size, data);
listener.onSmpteOffset(data);
break;
}
case 0x58: {
if (size != 4) {
throw new IOException("Invalid length for time signature event " + size + ".");
}
int numerator = MidiUtil.readByte(input);
int denominator = MidiUtil.readByte(input);
int metronomePulse = MidiUtil.readByte(input);
int thirtySecondNotesPerQuarterNote = MidiUtil.readByte(input);
listener.onTimeSignature(numerator,
denominator,
metronomePulse,
thirtySecondNotesPerQuarterNote);
break;
}
case 0x59: {
if (size != 2) {
throw new IOException("Invalid length for key signature event " + size + ".");
}
int key = MidiUtil.readByte(input);
boolean isMinor = (MidiUtil.readByte(input) != 0);
listener.onKeySignature(key, isMinor);
break;
}
case 0x7F: {
byte[] data = new byte[size];
MidiUtil.readBytes(input, size, data);
listener.onSequencerSpecificEvent(data);
break;
}
default: {
throw new IOException("Invalid midi meta message type " + type + ".");
}
}
}
/**
* Processes SysEx messages.
* @param input - The stream to read from.
* @param listener - The object to handle the event.
*/
private static void processSysExMessage(InputStream input,
MidiListener listener) throws IOException {
int size = MidiUtil.readVarInt(input);
byte[] data = new byte[size];
MidiUtil.readBytes(input, size, data);
listener.onSysEx(data);
}
// The most recent code seen in the stream, used for "running status" encoding.
int previousCode_;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save