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.
470 lines
15 KiB
470 lines
15 KiB
/*
|
|
* 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.levien.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.levien.synthesizer.core.model.WaveformInput;
|
|
import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer;
|
|
import com.levien.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_;
|
|
}
|
|
|