You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
767 lines
26 KiB
767 lines
26 KiB
/*
|
|
* 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.levien.synthesizer.android.widgets.score;
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
import com.levien.synthesizer.R;
|
|
import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer;
|
|
import com.levien.synthesizer.core.music.Music.Event;
|
|
import com.levien.synthesizer.core.music.Music.Score;
|
|
import com.levien.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_;
|
|
}
|
|
|