/* Reversound is used to get the music sheet of a piece from a music file. Copyright (C) 2014 Gabriel AUGENDRE Copyright (C) 2014 Gabriel DIENY Copyright (C) 2014 Arthur GAUCHER Copyright (C) 2014 Gabriel LEPETIT-AIMON This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package gui.graphs; import conteneurs.Gamme; import conteneurs.NoteGamme; import conteneurs.RegularGamme; import conteneurs.Spectre; import generictools.Instant; import gui.PlayerControl; import gui.PlayerControlEvent; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Parent; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import processing.buffer.TemporalBuffer; import javax.swing.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; /** * Created by arthur on 14/05/14. */ public class SpectrogramView extends AbstractBufferGraph> { private TraceChart chart; private StackPane pane; private Gamme yAxisGamme; private Rectangle cursorRect; private int inc; private int boundMax; private int gammeSize; private Rectangle highlightedLine; private ObservableList categories; public SpectrogramView(){ super("Spectrogramme",Spectre.class); } public Parent createContent(){ CategoryAxis yAxis = new CategoryAxis(); yAxis.setTickLabelFont(Font.font(8)); setYAxisGamme(NoteGamme.gamme()); NumberAxis xAxis = new NumberAxis(0, 10000,1000); //creating the chart chart = new TraceChart(xAxis,yAxis); chart.setAnimated(false); chart.setVerticalGridLinesVisible(false); chart.setHorizontalGridLinesVisible(false); chart.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); yAxisGamme = NoteGamme.gamme(); cursorRect = new Rectangle(); cursorRect.setManaged(false); //cursorRect.setFill(Color.RED.deriveColor(0, 1, 1, 0.5)); cursorRect.setFill(Color.rgb(39,93,153)); cursorRect.setWidth(1); cursorRect.setHeight(215); moveCursor(true); highlightedLine = new Rectangle(); highlightedLine.setManaged(false); highlightedLine.setFill((Color.GREEN.deriveColor(0,1,1,0.2))); highlightedLine.setWidth(150); highlightedLine.setHeight(4); //note = new Label(); pane = new StackPane(); pane.getChildren().add(chart); pane.getChildren().add(cursorRect); pane.getChildren().add(highlightedLine); //pane.getChildren().addNote(note); //when the window height changes, resize the cursor pane.widthProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observableValue, Number oldHeight, Number newHeight) { moveCursor(true); //resizeHiglightedLine(); } }); //when the window height changes, resize the controls and replace the cursor pane.heightProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observableValue, Number oldWidth, Number newWidth) { resizeCursor(); } }); pane.setOnMouseMoved(new EventHandler() { @Override public void handle(MouseEvent event) { highlightLine(selectTickLine(event)); } }); pane.setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent event) { if(event.isPrimaryButtonDown()) setFrame(event); } }); pane.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent event) { if(event.isPrimaryButtonDown()) setFrame(event); } }); setBoundsAndInc(); setBufferWindow(); return pane; } private void setFrame(MouseEvent event){ final NumberAxis xAxis = (NumberAxis)chart.getXAxis(); final CategoryAxis yAxis = (CategoryAxis) chart.getYAxis(); final double yAxisInScene = yAxis.localToScene(0, 0).getX() + yAxis.getWidth(); final double mouseX = event.getX(); final float timeToSet = xAxis.getValueForDisplay(mouseX - yAxisInScene).floatValue(); if(timeToSet >= xAxis.getLowerBound() && timeToSet <= boundMax) PlayerControl.instance().setFrame(Instant.fromTime(timeToSet)); } private void highlightLine(String category) { Platform.runLater(new Runnable() { @Override public void run() { final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); double tickPosition = yAxis.getDisplayPosition(category) + yAxis.localToScene(0,0).getY(); //note.setText(category); highlightedLine.setY(tickPosition - highlightedLine.getHeight()/2); resizeHiglightedLine(); } }); } private void resizeHiglightedLine(){ final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); highlightedLine.setX(0); highlightedLine.setWidth(pane.getWidth() > 0 ? pane.getWidth() : 100); highlightedLine.setHeight(yAxis.getHeight()/categories.size()); } private String selectTickLine(MouseEvent event){ final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); return yAxis.getValueForDisplay(event.getY() - yAxis.localToScene(0,0).getY()); } /** * Resize the cursor */ private void resizeCursor(){ Platform.runLater(() -> { final CategoryAxis yAxis = (CategoryAxis) chart.getYAxis(); //if yAxis is not totally initialized, move it correctly // and prevent from setting height to 0 if (yAxis.getHeight() == 0){ cursorRect.setY(17); moveCursor(true); } else { cursorRect.setY(yAxis.localToScene(0, 0).getY()); cursorRect.setHeight(yAxis.getHeight()); } }); } /** * Move the cursor according to the frameMS * @param locked Boolean indicating if the cursor has to be followed */ private void moveCursor(final Boolean locked){ final NumberAxis xAxis = (NumberAxis) chart.getXAxis(); final CategoryAxis yAxis = (CategoryAxis) chart.getYAxis(); final double yAxisInScene = yAxis.localToScene(0, 0).getX() + yAxis.getWidth(); if (locked) { final int interval = (int) (xAxis.getUpperBound() - xAxis.getLowerBound()); //if frame greater than upper bound, move the window to the right if (PlayerControl.frame().toMs() > xAxis.getUpperBound()) { xAxis.setLowerBound(xAxis.getUpperBound()); xAxis.setUpperBound(xAxis.getLowerBound() + interval); setBufferWindow(); } //if frame lower than lower bound, move the window to the left if (PlayerControl.frame().toMs() < xAxis.getLowerBound()) { if (xAxis.getLowerBound() < interval) { xAxis.setLowerBound(0); xAxis.setUpperBound(interval); setBufferWindow(); } else { xAxis.setUpperBound(xAxis.getLowerBound()); xAxis.setLowerBound(xAxis.getUpperBound() - interval); setBufferWindow(); } } } Platform.runLater(new Runnable() { @Override public void run() { cursorRect.setX((PlayerControl.frame().toMs() - xAxis.getLowerBound()) * xAxis.getScale() + yAxisInScene); } }); } public void setYAxisGamme(Gamme yAxisGamme){ this.yAxisGamme = yAxisGamme; Platform.runLater(new Runnable(){ @Override public void run() { changeYAxis(); state().invalidate(); } }); } private void changeYAxis(){ CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); ArrayList categories = new ArrayList(); for(int i=0 ; isizeMS ? sizeMS : (int)xAxis.getUpperBound(); final int boundMin = (int) xAxis.getLowerBound(); inc = (boundMax - boundMin) > RESOLUTION ? Math.round((boundMax - boundMin) / (float)RESOLUTION) : 1; } public void playerControlEvent(PlayerControlEvent e) { if(!visibility()) return; switch (e.getType()) { case FRAME: moveCursor(true); break; // case PLAY_STATE: // switch(e.getPlayingState()) { // case PLAYING: // //Workaround to reload buffer to show if new values after // // opening window // // Is there a way to know if buffer finished loading ? // setBufferWindow(); // break; // case STOPPED: // /*NumberAxis xAxis = (NumberAxis)chart.getXAxis(); // xAxis.setLowerBound(0); // xAxis.setUpperBound(10000); // setBufferWindow();*/ // break; // } } } @Override protected AbstractBufferGraph getGenericGraph() { return new SpectrogramView(); } @Override public Parent getMainNode() { return pane; } /** * Revoie le liste des éléments du menu contextuel * * @param contextMenu */ @Override public void createContextMenu(JPopupMenu contextMenu) { JMenu choixAxesY = new JMenu("Axe fréquence"); JRadioButtonMenuItem noteGamme = new JRadioButtonMenuItem("Gamme chromatique"); noteGamme.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if(e.getStateChange()==ItemEvent.SELECTED) setYAxisGamme(NoteGamme.gamme()); } }); JRadioButtonMenuItem regularGamme = new JRadioButtonMenuItem("Gamme linéaire"); regularGamme.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if(e.getStateChange()==ItemEvent.SELECTED) setYAxisGamme(new RegularGamme(0f, 20000f, 1000)); } }); ButtonGroup b = new ButtonGroup(); b.add(noteGamme); b.add(regularGamme); choixAxesY.add(noteGamme); choixAxesY.add(regularGamme); contextMenu.add(choixAxesY); } @Override protected void initView() { createContent(); cleanView(); state().setSlowingFactorOnDataChange(5); } @Override public void updateView() { XYChart.Series series = new XYChart.Series<>(); final int MAX = 4000000; NumberAxis xAxis = (NumberAxis)chart.getXAxis(); final int sizeMS = (int)PlayerControl.frame().fromIndex(buffer().size(),buffer()).toMs(); boundMax = xAxis.getUpperBound()>sizeMS ? sizeMS : (int)xAxis.getUpperBound(); for (float j = (float)xAxis.getLowerBound() ; j < boundMax ; j+=inc) { if (buffer().get(j) != null) { if (buffer().get(j).getGamme().gammeSize() != gammeSize) { gammeSize = buffer().get(j).getGamme().gammeSize(); } for (int i = 0; i < gammeSize; i++) { if (buffer().get(j).getGamme() != yAxisGamme) buffer().get(j).castToGamme(yAxisGamme); if (buffer().get(j).getAmplitude(i) > 1000) { Float ampli =buffer().get(j).getAmplitude(i); if(ampli == null) ampli = 0f; series.getData().add(new XYChart.Data(j, categories.get(i), new BarExtraValues(j + inc, colorRamp(ampli, MAX)))); } } } } Platform.runLater(new Runnable() { @Override public void run() { chart.getData().set(0, series); } }); Platform.runLater(new Runnable() { @Override public void run() { } }); } @Override public void cleanView() { Platform.runLater(new Runnable() { @Override public void run() { chart.setData(FXCollections.observableArrayList(new XYChart.Series())); } }); } @Override protected void visibilityChange(boolean isVisible) { super.visibilityChange(isVisible); if(isVisible==true) setBufferWindow(); } public Color colorRamp(float pos, float maxPos){ if(pos == 0 || maxPos == 0) return Color.hsb(0,0,1); pos = Math.min(pos, maxPos); float x = pos/maxPos; x = x*x; if(x <= .08) { x/=.08; return Color.hsb(0, x*.45f, 1); } x-=.08; if(x <= .52) { x/=.52; return Color.hsb(1 - x * 360 + (x * 336), (1-x) * .45f + (x * .62f), 1 - x + (x * .75f)); } x-=.52; x/=.4; return Color.hsb(1-x *336 + (x*264), 1-x *.62f + (x*.100f), 1-x*.75f +(x*.53f)); } }