package com.xenoage.zong.demos.simplegui; import com.xenoage.utils.document.io.FileOutput; import com.xenoage.utils.error.Err; import com.xenoage.utils.log.Report; import com.xenoage.utils.math.Units; import com.xenoage.utils.math.geom.Point2f; import com.xenoage.zong.core.Score; import com.xenoage.zong.core.music.MusicElement; import com.xenoage.zong.core.position.MPElement; import com.xenoage.zong.core.position.Time; import com.xenoage.zong.desktop.io.DocumentIO; import com.xenoage.zong.desktop.io.midi.out.MidiScoreDocFileOutput; import com.xenoage.zong.desktop.io.musicxml.in.MusicXmlScoreDocFileInput; import com.xenoage.zong.desktop.io.ogg.out.OggScoreDocFileOutput; import com.xenoage.zong.desktop.io.pdf.out.PdfScoreDocFileOutput; import com.xenoage.zong.desktop.io.png.out.PngScoreDocFileOutput; import com.xenoage.zong.documents.ScoreDoc; import com.xenoage.zong.io.midi.out.PlaybackListener; import com.xenoage.zong.layout.Layout; import com.xenoage.zong.layout.frames.ScoreFrame; import com.xenoage.zong.musiclayout.ScoreFrameLayout; import com.xenoage.zong.musiclayout.layouter.PlaybackLayouter; import com.xenoage.zong.musiclayout.stampings.Stamping; import lombok.Getter; import java.io.File; import java.util.Arrays; /** * The loaded document, its layout and playback capabilities. * * @author Andreas Wenger */ public class Content implements PlaybackListener { private MainWindow mainWindow; private int scoreIndex = 0; @Getter private ScoreDoc scoreDoc = null; @Getter private Layout layout = null; private PlaybackLayouter playbackLayouter = null; public Content(MainWindow mainWindow) { this.mainWindow = mainWindow; //listen for playback events (see method playbackAtMP) Playback.registerListener(this); } /** * Loads the next MusicXML demo score from the scores directory. */ public void loadNextScore() { File[] files = new File("scores").listFiles((d, n) -> n.endsWith(".xml")); Arrays.sort(files, (f1, f2) -> (f1.compareTo(f2))); loadScore("scores/" + files[scoreIndex].getName()); scoreIndex = (scoreIndex + 1) % files.length; } /** * Loads the MusicXML score from the given file path. */ public void loadScore(String filePath) { try { ScoreDoc scoreDoc = DocumentIO.read(new File(filePath), new MusicXmlScoreDocFileInput()); loadScore(scoreDoc); } catch (Exception ex) { Err.handle(Report.error(ex)); } } /** * Loads the given MusicXML score document. */ public void loadScore(ScoreDoc scoreDoc) { //stop current playback Playback.stop(); //load the score this.scoreDoc = scoreDoc; //update the view and playback onScoreUpdated(); } /** * Call this method when the score was modified. * The layout and playback is recomputed. */ public void onScoreUpdated() { //layout the first page layout = scoreDoc.getLayout(); Score score = scoreDoc.getScore(); layout.updateScoreLayouts(score); //create playback layouter for the playback cursor playbackLayouter = new PlaybackLayouter(layout.getScoreFrameChain(score).getScoreLayout()); //set image to view mainWindow.renderLayout(layout); //load score into MIDI playback Playback.openScore(scoreDoc.getScore()); } /** * Saves the current score using the given format. * pdf, png, mid and ogg is supported by this demo. */ public void saveAs(String format) { FileOutput<ScoreDoc> out = null; switch (format) { case "pdf": out = new PdfScoreDocFileOutput(); break; case "png": out = new PngScoreDocFileOutput(); break; case "mid": out = new MidiScoreDocFileOutput(); break; case "ogg": out = new OggScoreDocFileOutput(); break; default: return; } String filePath = "demo." + format; try { DocumentIO.write(scoreDoc, new File(filePath), out); mainWindow.showMessageDialog(filePath + " saved."); } catch (Exception ex) { Err.handle(Report.error(ex)); } } /** * This method is called when the mouse was clicked on the content. * A message with the clicked element is shown to the user. */ public void onClick(Point2f positionPx) { if (layout.getScoreFrames().size() == 0) return; //get the layout of first score frame ScoreFrame frame = layout.getScoreFrames().get(0); ScoreFrameLayout frameLayout = frame.getScoreFrameLayout(); //convert position from screen space to page space, then from page space //to frame space, and them from frame space to score frame space Point2f positionMm = positionPx.scale(Units.pxToMm(1, mainWindow.getZoom())); Point2f framePositionMm = positionMm.sub(frame.getAbsolutePosition()); Point2f scorePositionMm = frame.getScoreLayoutPosition(framePositionMm); //find elements under this position for (Stamping stamping : frameLayout.getAllStampings()) { if (stamping.getBoundingShape() != null && stamping.getBoundingShape().contains(scorePositionMm)) { MusicElement element = stamping.getMusicElement(); if (element != null) { //music element found String message = "An element was clicked: " + element; if (element instanceof MPElement) { //music element with a known musical position found MPElement mpElement = (MPElement) element; if (mpElement.getParent() != null) message += " at " + mpElement.getMP(); } mainWindow.showMessageDialog(message); } } } } /** * This method is called by the MIDI playback whenever a new * musical position is reached. */ @Override public void playbackAtTime(Time time, long ms) { //update cursor position and redraw the layout playbackLayouter.setCursorAt(time); mainWindow.renderLayout(layout); } @Override public void playbackAtMs(long ms) { } @Override public void playbackStarted() { } @Override public void playbackPaused() { } @Override public void playbackStopped() { } @Override public void playbackAtEnd() { } }