/*
* Copyright (C) 2013-2017 たんらる
*/
package fourthline.mabiicco.fx;
import java.util.List;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import fourthline.mabiicco.AppResource;
import fourthline.mabiicco.MabiIccoProperties;
import fourthline.mabiicco.midi.InstClass;
import fourthline.mabiicco.midi.MabiDLS;
import fourthline.mabiicco.ui.IMMLManager;
import fourthline.mabiicco.fx.color.ColorManager;
import fourthline.mmlTools.MMLEventList;
import fourthline.mmlTools.MMLNoteEvent;
import fourthline.mmlTools.MMLScore;
import fourthline.mmlTools.MMLTrack;
import fourthline.mmlTools.core.MMLTicks;
import fourthline.mmlTools.core.UndefinedTickException;
/**
* ピアノロール表示を行うためのビューです.
*/
public final class PianoRollView {
public static final int OCTNUM = 9;
private final Canvas canvas;
private final IMMLManager mmlManager;
public PianoRollView(Canvas canvas, IMMLManager mmlManager) {
this.canvas = canvas;
this.mmlManager = mmlManager;
setNoteHeightIndex( MabiIccoProperties.getInstance().getPianoRollViewHeightScaleProperty() );
paint();
}
public void paint() {
GraphicsContext gc = canvas.getGraphicsContext2D();
paintComponent(gc);
}
/**
* ノートの表示高さ
*/
public static int NOTE_HEIGHT_TABLE[] = { 6, 8, 10, 12, 14 };
private int noteHeight = NOTE_HEIGHT_TABLE[3];
public int getNoteHeight() {
return noteHeight;
}
public void setNoteHeightIndex(int index) {
if ( (index >= 0) && (index < NOTE_HEIGHT_TABLE.length) ) {
noteHeight = NOTE_HEIGHT_TABLE[index];
}
canvas.setHeight( getTotalHeight() );
}
public int getTotalHeight() {
return (12*OCTNUM*noteHeight+1);
}
private double wideScale = 6; // ピアノロールの拡大/縮小率 (1~6)
private long sequencePosition = 0;
private long runningSequencePosition = 0;
// 選択中のノートイベント
private List<MMLNoteEvent> selectNoteList;
// draw pitch range
private int lowerNote = 0;
private int upperNote = 14;
private static final Color wKeyColor = Color.color(0.9f, 0.9f, 0.9f); // 白鍵盤用
private static final Color bKeyColor = Color.color(0.8f, 0.8f, 0.8f); // 黒鍵盤用
private static final Color borderColor = Color.color(0.6f, 0.6f, 0.6f); // 境界線用
private static final Color pitchRangeBorderColor = Color.RED; // pitch range
private static final Color keyColors[] = new Color[] {
wKeyColor,
bKeyColor,
wKeyColor,
bKeyColor,
wKeyColor,
bKeyColor,
wKeyColor,
wKeyColor,
bKeyColor,
wKeyColor,
bKeyColor,
wKeyColor
};
private static final Color noSoundColor = Color.color(0.9f, 0.8f, 0.8f);
private static final Color barBorder = Color.color(0.5f, 0.5f, 0.5f);
private static final Color darkBarBorder = Color.color(0.3f, 0.2f, 0.3f);
private static final Color shadowColor = Color.GRAY;
public enum PaintMode {
ALL_TRACK("paintMode.all_track"),
ACTIVE_TRACK("paintMode.active_track"),
ACTIVE_PART("paintMode.active_part");
final private String resourceName;
private PaintMode(String name) {
resourceName = name;
}
public String toString() {
return AppResource.appText(resourceName);
}
}
private PaintMode paintMode = PaintMode.ALL_TRACK;
public PaintMode getPaintMode() {
return paintMode;
}
public void setPaintMode(PaintMode mode) {
paintMode = mode;
}
public void setSelectNote(List<MMLNoteEvent> list) {
selectNoteList = list;
}
/**
* 現在のトラックの内容に合わせた幅に設定します.
*/
private void updateViewWidthTrackLength() {
long tickLength = 0;
MMLScore mmlScore = mmlManager.getMMLScore();
for (MMLTrack track : mmlScore.getTrackList()) {
long length = track.getMaxTickLength();
if (tickLength < length) {
tickLength = length;
}
}
try {
// 最後に4小節分のマージンを作成します.
int t1 = MMLTicks.getTick("1");
tickLength += t1*4;
tickLength -= tickLength % t1;
} catch (UndefinedTickException e) {
e.printStackTrace();
}
canvas.setWidth( convertTicktoX(tickLength) );
}
/**
* ピアノロールの横方向の拡大/縮小スケールを設定します.
* @param scale
*/
public void setWideScale(double scale) {
if ( (scale > 6.0) || (scale < 0.1) ) {
return;
}
// 拡大/縮小したときの表示幅を調整します.
this.wideScale = scale;
updateViewWidthTrackLength();
}
public double getWideScale() {
return this.wideScale;
}
public long convertXtoTick(int x) {
return (long)(x * wideScale);
}
public int convertTicktoX(long tick) {
return (int)(tick / wideScale);
}
/**
* Panel上のy座標をnote番号に変換します.
* @param y
* @return
*/
public final int convertY2Note(int y) {
int note = -1;
if (y >= 0) {
note = (OCTNUM*12-(y/noteHeight)) -1;
}
return note;
}
/**
* note番号をPanel上のy座標に変換します.
* @param note
* @return
*/
public final int convertNote2Y(int note) {
int y = OCTNUM*12 - note - 1;
y *= noteHeight;
return y;
}
public long getSequencePosition() {
return sequencePosition;
}
public int getSequenceX() {
return convertTicktoX( sequencePosition );
}
public void updateRunningSequencePosition() {
runningSequencePosition = MabiDLS.getInstance().getSequencer().getTickPosition();
}
public long getSequencePlayPosition() {
long position = sequencePosition;
if (MabiDLS.getInstance().getSequencer().isRunning()) {
return runningSequencePosition;
}
return position;
}
public void setSequenceTick(long tick) {
if (!MabiDLS.getInstance().getSequencer().isRunning()) {
sequencePosition = tick;
}
}
/**
* 1オクターブ 12 x 6
* 9オクターブ分つくると、648
*/
public void paintComponent(GraphicsContext gc) {
updateViewWidthTrackLength();
for (int i = 0; i < OCTNUM; i++) {
paintOctPianoLine(gc, i, (char)('0'+OCTNUM-i-1));
}
paintMeasure(gc);
paintPitchRangeBorder(gc);
paintOtherTrack(gc);
paintActiveTrack(gc);
paintSelectedNote(gc);
paintSelectingArea(gc);
paintSequenceLine(gc, convertNote2Y(-1));
}
private void paintOctPianoLine(GraphicsContext gc, int pos, char posText) {
int startY = 12 * noteHeight * pos;
int octave = OCTNUM - pos - 1;
// グリッド
int y = startY;
double width = canvas.getWidth();
for (int i = 0; i < 12; i++) {
int line = octave*12 + (11-i);
Color fillColor = keyColors[i];
if ( (line < lowerNote) || (line > upperNote) ) {
if (MabiIccoProperties.getInstance().viewRange.get()) {
fillColor = noSoundColor;
}
}
gc.setFill(fillColor);
gc.fillRect(0, i*noteHeight+y, width, noteHeight);
if (i == 0) {
gc.setFill(darkBarBorder);
} else {
gc.setFill(borderColor);
}
gc.fillRect(0, i*noteHeight+y, width, 1);
}
gc.setFill(darkBarBorder);
gc.fillRect(0, 12*noteHeight+y, width, 1);
}
private void paintPitchRangeBorder(GraphicsContext gc) {
if (MabiIccoProperties.getInstance().viewRange.get()) {
double width = canvas.getWidth();
int y1 = convertNote2Y(lowerNote-1);
int y2 = convertNote2Y(upperNote);
gc.setFill(pitchRangeBorderColor);
gc.fillRect(0, y1, width, 1);
gc.fillRect(0, y2, width, 1);
}
}
public void paintSequenceLine(GraphicsContext gc, int height) {
long position = getSequencePlayPosition();
Color color = Color.RED;
int x = convertTicktoX(position);
gc.setFill(color);
gc.fillRect(x, 0, 1, height);
}
/**
* 現在のスコアを基準とした1小節の幅を取得する.
* @return
*/
public int getMeasureWidth() {
try {
int sect = MMLTicks.getTick(mmlManager.getMMLScore().getBaseOnly());
sect = convertTicktoX(sect);
int borderCount = mmlManager.getMMLScore().getTimeCountOnly();
return (sect * borderCount);
} catch (UndefinedTickException e) {
throw new AssertionError();
}
}
/**
* 補助線の描画.
*/
private void paintHalfMeasure(GraphicsContext gc, int offset, int w) {}
/**
* メジャーを表示します。
*/
private void paintMeasure(GraphicsContext gc) {
int width = (int)convertXtoTick((int)canvas.getWidth());
try {
int sect = MMLTicks.getTick(mmlManager.getMMLScore().getBaseOnly());
int borderCount = mmlManager.getMMLScore().getTimeCountOnly();
for (int i = 0; i*sect < width; i++) {
if (i%borderCount == 0) {
gc.setFill(darkBarBorder);
} else {
gc.setFill(barBorder);
}
int x = convertTicktoX(i*sect);
int y = convertNote2Y(-1);
gc.fillRect(x, 0, 1, y);
paintHalfMeasure(gc, x, convertTicktoX(sect));
}
} catch (UndefinedTickException e) {
e.printStackTrace();
}
}
private void drawRect(GraphicsContext gc, Color rectColor, Color fillColor, int x, int y, int width, int height) {
gc.setFill(fillColor);
gc.fillRect(x+1, y+1, width, height-1);
gc.setFill(rectColor);
gc.fillRect(x+1, y+1, 1, height-1);
gc.fillRect(x+width+1, y+1, 1, height-1);
if (width > 1) {
gc.fillRect(x+2, y, width-1, 1);
gc.fillRect(x+2, y+height, width-1, 1);
} else if (width == 1) {
gc.fillRect(x+1, y, 2, 1);
gc.fillRect(x+1, y+height, 2, 1);
} else if (width == 0) {
gc.fillRect(x+0, y, 3, 1);
gc.fillRect(x+0, y+height, 3, 1);
}
}
private void drawNote(GraphicsContext gc, MMLNoteEvent noteEvent, Color rectColor, Color fillColor, boolean drawOption, MMLNoteEvent prevNote) {
int note = noteEvent.getNote();
int tick = noteEvent.getTick();
int offset = noteEvent.getTickOffset();
int x = convertTicktoX(offset);
int y = convertNote2Y(note) +1;
int width = convertTicktoX(tick) -1;
int height = noteHeight-2;
if (width > 1) width--;
if (drawOption) {
// shadow
drawRect(gc, shadowColor, shadowColor, x+2, y+2, width, height);
}
drawRect(gc, rectColor, fillColor, x, y, width, height);
if (drawOption) {
// velocityの描画.
int velocity = noteEvent.getVelocity();
if ( (prevNote == null) || (prevNote.getVelocity() != velocity) ) {
String s = "V" + velocity;
gc.setFont(Font.font(10));
gc.setFill(Color.BLACK);
gc.fillText(s, x, y);
}
}
}
/**
* MMLEventリストのロールを表示します。
* @param gc
* @param mmlPart
* @param rectColor
* @param fillColor
* @param drawOption
*/
private void paintMMLPart(GraphicsContext gc, List<MMLNoteEvent> mmlPart, Color rectColor, Color fillColor, boolean drawOption) {
MMLNoteEvent prevNote = new MMLNoteEvent(0, 0, 0, MMLNoteEvent.INIT_VOL);
// 現在のView範囲のみを描画する.
for (MMLNoteEvent noteEvent : mmlPart) {
drawNote(gc, noteEvent, rectColor, fillColor, drawOption, prevNote);
prevNote = noteEvent;
}
}
/**
* 1トラック分のロールを表示します。(アクティブトラックは表示しない)
* @param gc
* @param index トラックindex
* @param track
*/
private void paintMMLTrack(GraphicsContext gc, int index, MMLTrack track) {
boolean instEnable[] = InstClass.getEnablePartByProgram(track.getProgram());
boolean songExEnable[] = InstClass.getEnablePartByProgram(track.getSongProgram());
MMLEventList activePart = mmlManager.getActiveMMLPart();
int colorIndex = 0;
for (int i = 0; i < track.getMMLEventList().size(); i++) {
MMLEventList targetPart = track.getMMLEventAtIndex(i);
if (targetPart == activePart) {
colorIndex++;
continue;
}
Color rectColor = ColorManager.defaultColor().getPartRectColor(index, colorIndex);
Color fillColor = ColorManager.defaultColor().getPartFillColor(index, colorIndex);
if ( !instEnable[i] && !songExEnable[i] ) {
fillColor = ColorManager.defaultColor().getUnusedFillColor();
} else {
colorIndex++;
}
paintMMLPart(gc, track.getMMLEventList().get(i).getMMLNoteEventList(), rectColor, fillColor, false);
}
}
private void paintActiveTrack(GraphicsContext gc) {
int trackIndex = mmlManager.getActiveTrackIndex();
if (paintMode != PaintMode.ACTIVE_PART) {
paintMMLTrack(gc, trackIndex, mmlManager.getMMLScore().getTrack(trackIndex));
}
MMLEventList activePart = mmlManager.getActiveMMLPart();
if (activePart != null) {
Color rectColor = ColorManager.defaultColor().getActiveRectColor(trackIndex);
Color fillColor = ColorManager.defaultColor().getActiveFillColor(trackIndex);
paintMMLPart(gc, activePart.getMMLNoteEventList(), rectColor, fillColor, true);
}
}
private void paintOtherTrack(GraphicsContext gc) {
MMLScore mmlScore = mmlManager.getMMLScore();
if (paintMode != PaintMode.ALL_TRACK) {
return;
}
if (mmlScore != null) {
for (int i = 0; i < mmlScore.getTrackCount(); i++) {
MMLTrack track = mmlScore.getTrack(i);
if (track != mmlScore.getTrack(mmlManager.getActiveTrackIndex())) {
paintMMLTrack(gc, i, track);
}
}
}
}
private void paintSelectedNote(GraphicsContext gc) {
// 選択中ノートの表示
if (selectNoteList != null) {
paintMMLPart(gc, selectNoteList, Color.YELLOW, Color.YELLOW, false);
}
}
private void paintSelectingArea(GraphicsContext gc) {}
public void setPitchRange(InstClass inst) {
if (inst == null) {
return;
}
this.lowerNote = inst.getLowerNote();
this.upperNote = inst.getUpperNote();
}
}