/* * Copyright (C) 2013-2017 たんらる */ package fourthline.mabiicco.ui; import javax.sound.midi.Sequencer; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JViewport; import javax.swing.ScrollPaneConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import fourthline.mabiicco.ActionDispatcher; import fourthline.mabiicco.AppResource; import fourthline.mabiicco.IEditState; import fourthline.mabiicco.IFileState; import fourthline.mabiicco.MabiIccoProperties; import fourthline.mabiicco.midi.InstClass; import fourthline.mabiicco.midi.MabiDLS; import fourthline.mabiicco.ui.PianoRollView.PaintMode; import fourthline.mabiicco.ui.editor.KeyboardEditor; import fourthline.mabiicco.ui.editor.MMLEditor; import fourthline.mabiicco.ui.editor.MMLScoreUndoEdit; import fourthline.mabiicco.ui.mml.MMLInputPanel; import fourthline.mabiicco.ui.mml.MMLOutputPanel; import fourthline.mabiicco.ui.mml.MMLPartChangePanel; import fourthline.mabiicco.ui.mml.TrackPropertyPanel; import fourthline.mmlTools.MMLEventList; import fourthline.mmlTools.MMLNoteEvent; import fourthline.mmlTools.MMLScore; import fourthline.mmlTools.MMLTempoEvent; import fourthline.mmlTools.MMLTrack; import fourthline.mmlTools.core.MMLTicks; import fourthline.mmlTools.core.NanoTime; import fourthline.mmlTools.core.UndefinedTickException; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Frame; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.IntConsumer; /** * 主表示部. * * <pre> * MMLSeqView * | * +- {@link PianoRollView} (JScrollPane 内) * | * +- {@link KeyboardView} (JScrollPane の行ヘッダ) * | * +- {@link ColumnPanel} (JScrollPane の列ヘッダ) * | * +- {@link MMLTrackView} ({@link TrackTabbedPane} extends JTabbedPane 内) * </pre> */ public final class MMLSeqView implements IMMLManager, ChangeListener, ActionListener, MouseWheelListener { private static final int INITIAL_TRACK_COUNT = 1; private int trackCounter; private final JScrollPane scrollPane; private final PianoRollView pianoRollView; private final KeyboardView keyboardView; private final JTabbedPane tabbedPane; private final ColumnPanel columnView; private MMLScore mmlScore = new MMLScore(); private final MMLScoreUndoEdit undoEdit = new MMLScoreUndoEdit(this); private final MMLInputPanel mmlInputDialog = new MMLInputPanel(this); private final MMLEditor editor; private final KeyboardEditor keyboardEditor; private final JPanel panel; private JLabel timeView; private ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(8); private final Frame parentFrame; /** * Create the panel. * @param parentFrame 関連付けるFrame */ public MMLSeqView(Frame parentFrame) { this.parentFrame = parentFrame; panel = new JPanel(false); panel.setLayout(new BorderLayout(0, 0)); // Scroll View (KeyboardView, PianoRollView) - CENTER pianoRollView = new PianoRollView(); keyboardView = new KeyboardView(this, pianoRollView); scrollPane = new JScrollPane(pianoRollView); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); scrollPane.getVerticalScrollBar().setUnitIncrement(pianoRollView.getNoteHeight()); panel.add(scrollPane, BorderLayout.CENTER); pianoRollView.setViewportAndParent(scrollPane.getViewport(), this); // MMLTrackView (tab) - SOUTH tabbedPane = new TrackTabbedPane(this); tabbedPane.addChangeListener(this); tabbedPane.setPreferredSize(new Dimension(0, 200)); panel.add(tabbedPane, BorderLayout.SOUTH); // create mml editor editor = new MMLEditor(parentFrame, keyboardView, pianoRollView, this); pianoRollView.addMouseInputListener(editor); pianoRollView.addMouseWheelListener(this); columnView = new ColumnPanel(parentFrame, pianoRollView, this, editor); // create keyboard editor keyboardEditor = new KeyboardEditor(parentFrame, this, keyboardView, editor, pianoRollView); scrollPane.setRowHeaderView(keyboardView); scrollPane.setColumnHeaderView(columnView); initialSetView(); initializeMMLTrack(); startSequenceThread(); startTimeViewUpdateThread(); } public void setNoteAlignChanger(IntConsumer noteAlignChanger) { keyboardEditor.setNoteAlignChanger(noteAlignChanger); } public boolean recovery(String s) { boolean result = undoEdit.recover(s); System.out.println("recover: "+result); if (result) { mmlScore = mmlScore.toGeneratedScore(); undoEdit.revertState(); updateTrackTabIcon(); updateActivePart(false); updateProgramSelect(); } return result; } public String getRecoveryData() { return undoEdit.getBackupString(); } private boolean currentEditMode = true; public void repaint() { boolean editMode = MabiIccoProperties.getInstance().enableEdit.get(); if (currentEditMode != editMode) { currentEditMode = editMode; for (int i = 0; i < tabbedPane.getTabCount(); i++) { MMLTrackView view = (MMLTrackView) tabbedPane.getComponentAt(i); view.setVisibleMMLTextPanel(currentEditMode); } resetTrackView(); tabbedPane.setPreferredSize(new Dimension(0, currentEditMode ? 200 : 60)); } panel.repaint(); } public JPanel getPanel() { return panel; } private void initialSetView() { EventQueue.invokeLater(() -> { // (ピアノロール全体の高さ / 2) - (表示領域 / 2)=真ん中の座標。 int y = (pianoRollView.getTotalHeight() / 2) - (scrollPane.getHeight() / 2); // 初期のView位置 scrollPane.getViewport().setViewPosition(new Point(0, y)); }); } public void initializeMMLTrack() { mmlScore = new MMLScore(); tabbedPane.removeAll(); trackCounter = 0; for (int i = 0; i < INITIAL_TRACK_COUNT; i++) { addMMLTrack(null); } panel.repaint(); undoEdit.initState(); } private String getNewTrackName() { trackCounter++; return "Track" + trackCounter; } /** * トラックの追加。作成したトラックを選択状態にします。 */ @Override public void addMMLTrack(MMLTrack newTrack) { if (newTrack == null) { newTrack = new MMLTrack(); newTrack.setTrackName( getNewTrackName() ); } int trackIndex = mmlScore.addTrack(newTrack); if (trackIndex < 0) { return; } // トラックビューの追加 tabbedPane.add(newTrack.getTrackName(), MMLTrackView.getInstance(trackIndex, this, this)); tabbedPane.setSelectedIndex(trackIndex); updateTrackTabIcon(); MabiDLS.getInstance().setMute(trackIndex, false); // エディタ更新 updateActivePart(false); updateSelectedTrackAndMMLPart(); updateProgramSelect(); } /** * 現在選択中のトラックを移動します. * @param toIndex 移動先index */ @Override public void moveTrack(int toIndex) { int fromIndex = tabbedPane.getSelectedIndex(); MabiDLS mabiDLS = MabiDLS.getInstance(); boolean mute = mabiDLS.getMute(fromIndex); mmlScore.moveTrack(fromIndex, toIndex); if (fromIndex < toIndex) { for (int i = fromIndex+1; i < toIndex; i++) { mabiDLS.setMute(i, mabiDLS.getMute(i+1)); } } else { for (int i = mmlScore.getTrackCount()-1; i > toIndex; i--) { mabiDLS.setMute(i, mabiDLS.getMute(i-1)); } } mabiDLS.setMute(toIndex, mute); tabbedPane.setSelectedIndex(toIndex); resetTrackView(); updateActivePart(false); } /** * トラックの削除 * 現在選択中のトラックを削除します。 */ public void removeMMLTrack() { int index = tabbedPane.getSelectedIndex(); mmlScore.removeTrack(index); resetTrackView(); // mute設定へ反映. for (int i = index; i < mmlScore.getTrackCount(); i++) { MabiDLS mabiDLS = MabiDLS.getInstance(); mabiDLS.setMute(i, mabiDLS.getMute(i+1)); } if (mmlScore.getTrackCount() == 0) { addMMLTrack(null); } else { // ピアノロール更新 pianoRollView.repaint(); // エディタ更新 updateSelectedTrackAndMMLPart(); } undoEdit.saveState(); } private void updateTrackTabIcon() { int len = tabbedPane.getTabCount(); for (int i = 0; i < len; i++) { tabbedPane.setIconAt(i, PartButtonIcon.getInstance(1, i)); } } /** * 再生スタート(現在のシーケンス位置を使用) */ public void startSequence() { new Thread(() -> { NanoTime time = NanoTime.start(); long startTick = pianoRollView.getSequencePosition(); MabiDLS.getInstance().createSequenceAndStart(mmlScore, startTick); ActionDispatcher.getInstance().showTime("play", time); }).start(); } /** * 新規で複数のトラックをセットする。 */ @Override public void setMMLScore(MMLScore score) { mmlScore = score; pianoRollView.repaint(); tabbedPane.removeAll(); int trackCount = 0; for (MMLTrack track : score.getTrackList()) { String name = track.getTrackName(); if (name == null) { name = "Track"+(trackCount+1); } tabbedPane.add(name, MMLTrackView.getInstance(trackCount, this, this)); trackCount++; } undoEdit.initState(); initialSetView(); pianoRollView.setSequenceTick(0); updateTrackTabIcon(); updateActivePart(false); updateProgramSelect(); } /** * 現在のトラックにMMLを設定する。 */ @Override public void setMMLselectedTrack(MMLTrack mml) { if (mmlScore.getTrackCount() == 1) { mmlScore.getTempoEventList().clear(); } int index = tabbedPane.getSelectedIndex(); mmlScore.setTrack(index, mml); tabbedPane.setTitleAt(index, mml.getTrackName()); // 表示を更新 MMLTrackView view = (MMLTrackView)tabbedPane.getComponentAt(index); view.updateTrack(); updateSelectedTrackAndMMLPart(); updateActivePart(false); } /** * 現在選択中のトラックを取得する。 * @return 選択中のMMLTrack */ public MMLTrack getSelectedTrack() { int index = tabbedPane.getSelectedIndex(); if (index < 0) { index = 0; } return mmlScore.getTrack(index); } @Override public MMLScore getMMLScore() { return mmlScore; } public PaintMode getPaintMode() { return pianoRollView.getPaintMode(); } public void setPaintMode(PaintMode mode) { pianoRollView.setPaintMode(mode); } public void editTrackPropertyAction() { MMLTrack track = getSelectedTrack(); new TrackPropertyPanel(track, this).showDialog(parentFrame); tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), track.getTrackName()); } private void setViewPosition(int x) { JViewport viewport = scrollPane.getViewport(); Point point = viewport.getViewPosition(); Dimension dim = viewport.getExtentSize(); double x1 = point.getX(); double x2 = x1 + dim.getWidth(); if ( (x < x1) || (x > x2) ) { point.setLocation(x, point.getY()); viewport.setViewPosition(point); } } public void setPianoRollHeightScaleIndex(int index) { pianoRollView.setNoteHeightIndex(index); keyboardView.updateHeight(); panel.repaint(); } /** * 表示ライン表示を現在のシーケンス位置に戻す */ public void resetViewPosition() { setViewPosition(pianoRollView.getSequenceX()); } /** * シーケンスの現在位置を先頭に戻す */ public void setStartPosition() { Sequencer sequencer = MabiDLS.getInstance().getSequencer(); if (!sequencer.isRunning()) { setViewPosition(0); pianoRollView.setSequenceTick(0); panel.repaint(); } else { sequencer.setTempoInBPM(120); sequencer.setTickPosition(0); } } /** * 現在のTickにシーケンスを設定する。(一時停止用) */ public void pauseTickPosition() { Sequencer sequencer = MabiDLS.getInstance().getSequencer(); long tick = sequencer.getTickPosition(); tick -= tick % MMLTicks.minimumTick(); pianoRollView.setSequenceTick(tick); } public void inputClipBoardAction() { mmlInputDialog.showDialog(parentFrame, getNewTrackName()); } public void outputClipBoardAction() { MMLOutputPanel outputPanel = new MMLOutputPanel(parentFrame, mmlScore.getTrackList()); outputPanel.showDialog(); } public void mmlImport() { String text = MMLInputPanel.getClipboardString(); if (new MMLTrack().setMML(text).isEmpty()) { return; } getSelectedTrack().setMML(text); resetTrackView(); undoEdit.saveState(); panel.repaint(); } public void mmlExport() { int index = getActiveTrackIndex(); String text = getMMLScore().getTrack(index).getMabiMML(); MMLOutputPanel.copyToClipboard(parentFrame, text); } private void updateSelectedTrackAndMMLPart() { MMLTrackView view = (MMLTrackView) tabbedPane.getSelectedComponent(); if (view != null) { view.updateMuteButton(); int program = getActivePartProgram(); editor.reset(); pianoRollView.setPitchRange(MabiDLS.getInstance().getInstByProgram(program)); pianoRollView.repaint(); columnView.repaint(); } } @Override public int getActiveTrackIndex() { int trackIndex = tabbedPane.getSelectedIndex(); return trackIndex; } @Override public MMLEventList getActiveMMLPart() { if (!currentEditMode) { return null; } MMLTrackView view = (MMLTrackView) tabbedPane.getSelectedComponent(); int mmlPartIndex = view.getSelectedMMLPartIndex(); MMLTrack track = mmlScore.getTrack(getActiveTrackIndex()); return track.getMMLEventAtIndex(mmlPartIndex); } /** * 編集時の音符基準長を設定します. * @param alignTick 基準tick */ public void setEditAlign(int alignTick) { editor.setEditAlign(alignTick); } private int viewScaleIndex = 0; private final double viewScaleTable[] = { 6, 5, 4, 3, 2, 1.5, 1, 0.75, 0.5, 0.375, 0.25 }; /** * ピアノロールビューの表示を1段階拡大します. * @param xOffset 拡大基準 */ public void expandPianoViewWide(int xOffset) { if (viewScaleIndex+1 < viewScaleTable.length) { viewScaleIndex++; } double scale1 = pianoRollView.getWideScale(); pianoRollView.setWideScale(viewScaleTable[viewScaleIndex]); repositionChangeScaleView(scale1, pianoRollView.getWideScale(), xOffset); } /** * ピアノロールビューの表示を1段階縮小します. * @param xOffset 縮小基準 */ public void reducePianoViewWide(int xOffset) { if (viewScaleIndex-1 >= 0) { viewScaleIndex--; } double scale1 = pianoRollView.getWideScale(); pianoRollView.setWideScale(viewScaleTable[viewScaleIndex]); repositionChangeScaleView(scale1, pianoRollView.getWideScale(), xOffset); } // TODO: 応急措置, 拡大時に表示位置を保持できていない. private void repositionChangeScaleView(double scale1, double scale2, int xOffset) { JViewport viewport = scrollPane.getViewport(); Point p = viewport.getViewPosition(); // 拡大/縮小したときの表示位置を調整します. p.x = (int)((p.x + xOffset) * scale1 / scale2) - xOffset; repaint(); viewport.updateUI(); viewport.setViewPosition(p); if ( (viewport.getViewPosition().x != p.x) || (viewport.getViewPosition().y != p.y)) { viewport.setViewPosition(p); } } @Override public void mouseWheelMoved(MouseWheelEvent e) { JViewport viewport = scrollPane.getViewport(); Point p = viewport.getViewPosition(); int modifiers = e.getModifiers(); int rotation = e.getWheelRotation(); if (modifiers == InputEvent.CTRL_MASK) { if (rotation < 0) { expandPianoViewWide( e.getX() - p.x ); } else { reducePianoViewWide( e.getX() - p.x ); } } else if (modifiers == InputEvent.SHIFT_MASK) { p.x += (rotation * 16); if (p.x < 0) { p.x = 0; } scrollPane.getViewport().setViewPosition(p); repaint(); } else { for (MouseWheelListener listener : scrollPane.getMouseWheelListeners()) { listener.mouseWheelMoved(e); } } } /** * MMLTrackViewのタブを再構築する. * トラック追加/削除のundo, redo時に実行される. */ private void resetTrackView() { int selectedTab = tabbedPane.getSelectedIndex(); int selectedPart = ((MMLTrackView) tabbedPane.getSelectedComponent()).getSelectedMMLPartIndex(); tabbedPane.removeAll(); int i = 0; for (MMLTrack track : mmlScore.getTrackList()) { tabbedPane.add(track.getTrackName(), MMLTrackView.getInstance(i, this, this)); i++; } if (selectedTab >= i) { selectedTab = i-1; } if (mmlScore.getTrackCount() > 0) { MMLTrack track = mmlScore.getTrack(selectedTab); int program = track.getProgram(); if (InstClass.getEnablePartByProgram(program)[selectedPart] == false) { if ( (selectedPart == 3) && (track.getSongProgram() >= 0) ) { } else { selectedPart = InstClass.getFirstPartNumberOnProgram(program); } } tabbedPane.setSelectedIndex(selectedTab); ((MMLTrackView) tabbedPane.getSelectedComponent()).setSelectMMLPartOfIndex(selectedPart); updateTrackTabIcon(); } } public void undo() { if (undoEdit.canUndo()) { undoEdit.undo(); mmlScore = mmlScore.toGeneratedScore(); resetTrackView(); updateSelectedTrackAndMMLPart(); updateActivePart(false); } } public void redo() { if (undoEdit.canRedo()) { undoEdit.redo(); mmlScore = mmlScore.toGeneratedScore(); resetTrackView(); updateSelectedTrackAndMMLPart(); updateActivePart(false); } } public void nextStepTimeTo(boolean next) { try { int step = MMLTicks.getTick(mmlScore.getBaseOnly()); int deltaTick = mmlScore.getTimeCountOnly() * step; Sequencer sequencer = MabiDLS.getInstance().getSequencer(); long tick = pianoRollView.getSequencePlayPosition(); if (next) { tick += deltaTick; } else { tick -= step; } tick -= tick % deltaTick; if (!sequencer.isRunning()) { pianoRollView.setSequenceTick(tick); panel.repaint(); } else { // 移動先のテンポに設定する. int tempo = mmlScore.getTempoOnTick(tick); sequencer.setTickPosition(tick); sequencer.setTempoInBPM(tempo); } updatePianoRollView(); } catch (UndefinedTickException e) {} } public void partChange() { MMLPartChangePanel panel = new MMLPartChangePanel(parentFrame, this, editor); panel.showDialog(); } @Override public void stateChanged(ChangeEvent e) { Object sourceObject = e.getSource(); if (sourceObject == tabbedPane) { updateSelectedTrackAndMMLPart(); } } @Override public void actionPerformed(ActionEvent arg0) { updateSelectedTrackAndMMLPart(); } @Override public void updateActivePart(boolean generate) { NanoTime time = NanoTime.start(); if (generate) { try { mmlScore.generateAll(); } catch (UndefinedTickException e) { EventQueue.invokeLater(() -> { String msg = AppResource.appText("fail.mml_modify") + "\n" + e.getMessage(); JOptionPane.showMessageDialog(parentFrame, msg, AppResource.getAppTitle(), JOptionPane.WARNING_MESSAGE); }); System.err.println("REVERT: " + e.getMessage()); undoEdit.revertState(); editor.reset(); } } updateAllMMLPart(); ActionDispatcher.getInstance().showTime("update", time); } private void updateAllMMLPart() { if (tabbedPane.getTabCount() != mmlScore.getTrackCount()) { resetTrackView(); updateProgramSelect(); } // すべての全パートMMLテキストを更新します. int count = tabbedPane.getTabCount(); for (int i = 0; i < count; i++) { MMLTrackView view = (MMLTrackView) tabbedPane.getComponentAt(i); view.updateTrack(); } MabiDLS.getInstance().updatePanpot(mmlScore); undoEdit.saveState(); panel.repaint(); } @Override public void updateActiveTrackProgram(int trackIndex, int program, int songProgram) { mmlScore.getTrack(trackIndex).setProgram(program); mmlScore.getTrack(trackIndex).setSongProgram(songProgram); updateSelectedTrackAndMMLPart(); undoEdit.saveState(); updateProgramSelect(); } @Override public boolean selectTrackOnExistNote(int note, int tickOffset) { PaintMode mode = pianoRollView.getPaintMode(); int activeTrackIndex = tabbedPane.getSelectedIndex(); int trackCount = mmlScore.getTrackCount(); for (int i = 0; i < trackCount; i++) { int partIndex = 0; int trackIndex = (i + activeTrackIndex) % trackCount; MMLTrack track = mmlScore.getTrack(trackIndex); if ( (mode == PaintMode.ALL_TRACK) || ( (mode == PaintMode.ACTIVE_TRACK) && (track == getSelectedTrack()))) { for (MMLEventList eventList : track.getMMLEventList()) { MMLNoteEvent noteEvent = eventList.searchOnTickOffset(tickOffset); if ( (noteEvent != null) && (note == noteEvent.getNote()) ) { tabbedPane.setSelectedIndex(trackIndex); MMLTrackView view = (MMLTrackView) tabbedPane.getSelectedComponent(); view.setSelectMMLPartOfIndex(partIndex); updateSelectedTrackAndMMLPart(); return true; } partIndex++; } } } return false; } public void switchTrack(boolean toNext) { int trackIndex = tabbedPane.getSelectedIndex(); if (toNext) { if (trackIndex+1 < tabbedPane.getTabCount()) { trackIndex++; } } else { if (trackIndex-1 >= 0) { trackIndex--; } } tabbedPane.setSelectedIndex(trackIndex); updateSelectedTrackAndMMLPart(); } public void switchMMLPart(boolean toNext) { MMLTrackView view = (MMLTrackView) tabbedPane.getSelectedComponent(); view.switchMMLPart(toNext); updateSelectedTrackAndMMLPart(); } public void setTimeView(JLabel timeView) { this.timeView = timeView; } public IFileState getFileState() { return undoEdit; } public IEditState getEditState() { return editor; } public long getEditSequencePosition() { return pianoRollView.getSequencePosition(); } public void addTicks(int tick) { int tickPosition = (int) pianoRollView.getSequencePosition(); mmlScore.addTicks(tickPosition, tick); updateActivePart(true); } public void removeTicks(int tick) { int tickPosition = (int) pianoRollView.getSequencePosition(); mmlScore.removeTicks(tickPosition, tick); updateActivePart(true); } // TimeViewを更新するためのスレッドを開始します. private void startTimeViewUpdateThread() { scheduledExecutor.scheduleWithFixedDelay(this::updateTimeView, 500, 100, TimeUnit.MILLISECONDS); } private void updateTimeView() { long position = pianoRollView.getSequencePlayPosition(); List<MMLTempoEvent> tempoList = mmlScore.getTempoEventList(); long time = MMLTempoEvent.getTimeOnTickOffset(tempoList, (int)position); long totalTime = mmlScore.getTotalTime(); int tempo = MMLTempoEvent.searchOnTick(tempoList, (int)position); String str = String.format("time %d:%02d.%d/%d:%02d.%d (t%d)", (time/60/1000), (time/1000%60), (time/100%10), (totalTime/60/1000), (totalTime/1000%60), (totalTime/100%10), tempo); if (timeView != null) { timeView.setText(str); } } // PianoRoll, Sequence系の描画を行うスレッドを開始します. private void startSequenceThread() { scheduledExecutor.scheduleWithFixedDelay(() -> { if (MabiDLS.getInstance().getSequencer().isRunning()) { EventQueue.invokeLater(() -> { updatePianoRollView(); }); } }, 500, 25, TimeUnit.MILLISECONDS); } @Override public void updatePianoRollView() { JViewport viewport = scrollPane.getViewport(); Point point = viewport.getViewPosition(); int note = pianoRollView.convertY2Note(point.y)-1; updatePianoRollView(note); } @Override public void updatePianoRollView(int note) { pianoRollView.updateRunningSequencePosition(); int measure = pianoRollView.getMeasureWidth(); long positionX = pianoRollView.getSequencePlayPosition(); positionX = pianoRollView.convertTicktoX(positionX); positionX -= positionX % measure; JViewport viewport = scrollPane.getViewport(); Point point = viewport.getViewPosition(); Dimension dim = viewport.getExtentSize(); int x1 = point.x; int x2 = x1 + dim.width - measure; if ( (positionX < x1) || (positionX > x2) ) { /* ビュー外にあるので、現在のポジションにあわせる */ if (positionX + dim.width > pianoRollView.getWidth()) { positionX = (pianoRollView.getWidth() - dim.width); positionX -= positionX % measure; } } else { positionX = point.x; } long positionY = pianoRollView.convertNote2Y(note); int y1 = point.y; int y2 = y1 + dim.height - pianoRollView.getNoteHeight() * 2; if (positionY < y1) { } else if (positionY > y2) { positionY -= dim.height; positionY += pianoRollView.getNoteHeight() * 2; } else { positionY = point.y; } point.setLocation(positionX, positionY); viewport.setViewPosition(point); scrollPane.repaint(); } @Override public int getActivePartProgram() { MMLTrackView view = (MMLTrackView) tabbedPane.getSelectedComponent(); MMLTrack track = getSelectedTrack(); if (view != null) { int part = view.getSelectedMMLPartIndex(); if ( (part == 3) && (track.getSongProgram() >= 0) ) { return track.getSongProgram(); } return track.getProgram(); } return 0; } private void updateProgramSelect() { scheduledExecutor.submit(() -> MabiDLS.getInstance().loadRequiredInstruments(mmlScore)); } public void showKeyboardInput() { editor.reset(); repaint(); keyboardEditor.setVisible(true); } }