/* 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 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.input.ScrollEvent; 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; /** * Affiche un graphe en "barres" représentant l'amplitude d'une fréquence * donnée en fonction du temps. * Created by arthur on 20/05/14. */ public abstract class BargramView> extends AbstractBufferGraph{ protected TraceChart chart; private StackPane pane; private Rectangle cursorRect; private Rectangle highlightedLine; final int RESOLUTION = 50; private float windowWidthMs = 5000; private Instant boundMin = new Instant(0); protected Gamme yAxisGamme; /** * Constructeur * @param name Le nom du graphe * @param type Le type de buffer à afficher */ public BargramView(String name, Class type){ super(name, type); } /** * Crée tous les éléments de l'affichage * @return Le panel */ Parent createContent(){ //définition des axes CategoryAxis yAxis = new CategoryAxis(); yAxis.setAutoRanging(false); yAxis.setTickLabelFont(Font.font(8)); yAxis.setTickLabelRotation(0); setYAxisGamme(NoteGamme.gamme()); NumberAxis xAxis = new NumberAxis(0, 5000,500); xAxis.setLabel("Temps (ms)"); //création d'un graphe TraceChart 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))); //création du curseur cursorRect = new Rectangle(); cursorRect.setManaged(false); cursorRect.setFill(Color.rgb(39, 93, 153)); cursorRect.setWidth(1); cursorRect.setHeight(215); //création d'une ligne de couleur horizontale pour mettre en //surbrillance une ligne de fréquence highlightedLine = new Rectangle(); highlightedLine.setManaged(false); highlightedLine.setFill((Color.GREEN.deriveColor(0,1,1,0.2))); highlightedLine.setWidth(150); highlightedLine.setHeight(4); highlightedLine.setMouseTransparent(true); //creation du panel qui contient tous les éléments de l'affichage pane = new StackPane(); pane.getChildren().add(chart); pane.getChildren().add(cursorRect); pane.getChildren().add(highlightedLine); //quand la largeur de la fenêtre change, déplacer le curseur pane.widthProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observableValue, Number oldHeight, Number newHeight) { moveCursor(true); } }); //quand la hauteur de la fenêtre change, redimensionner le curseur pane.heightProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observableValue, Number oldWidth, Number newWidth) { resizeCursor(); } }); //quand la souris bouge, redessiner la ligne de surbrillance pane.setOnMouseMoved(new EventHandler() { @Override public void handle(MouseEvent event) { highlightLine(selectTickLine(event)); float time = readTime(event); getOnMouseMoved(readFreqID(event), Instant.fromTime(time - time % (windowWidthMs / RESOLUTION))).run(); } }); //quand le bouton gauche de la souris est appuyé, changer la frame pane.setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent event) { if (event.isPrimaryButtonDown()) { setFrame(event); float time = readTime(event); getOnMouseClicked(readFreqID(event), Instant.fromTime(time - time % (windowWidthMs / RESOLUTION))).run(); } } }); pane.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent event) { if (event.isPrimaryButtonDown()) { setFrame(event); float time = readTime(event); getOnMouseClicked(readFreqID(event), Instant.fromTime(time - time % (windowWidthMs / RESOLUTION))).run(); } } }); //avec ctrl+molette, zoomer ou dézoomer pane.setOnScroll(new EventHandler() { @Override public void handle(ScrollEvent scrollEvent) { if(scrollEvent.isControlDown()){ setWindowWidthMs((float) -scrollEvent.getDeltaY() + windowWidthMs); } } }); //rendre la ligne de surbrillance visible quand la souris rentre //dans le panel chart.setOnMouseEntered(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { highlightedLine.setVisible(true); } }); //rendre la ligne de surbrillance invisible quand la souris sort //dans le panel chart.setOnMouseExited(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { highlightedLine.setVisible(false); } }); highlightedLine.setVisible(false); //donne une série vide à afficher au chart pour éviter les erreurs chart.setData(FXCollections.observableArrayList(new XYChart.Series())); return pane; } /** * Définit une nouvelle frame * @param event L'événement de la souris */ private void setFrame(MouseEvent event){ final Instant timeToSet = Instant.fromTime(readTime(event)); if(timeToSet.isGreaterThan(boundMin) && timeToSet.isLowerThan(boundMin.translated(windowWidthMs))) PlayerControl.instance().setFrame(timeToSet); } /** * Déplace le curseur en fonction de la frame actuelle * @param locked Booléen indiquant si le curseur doit être suivi */ 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) { //si la frame est plus grande que la limite sup, translater la //fenêtre vers la droite if (PlayerControl.frame().toMs() >= boundMin.toMs()+windowWidthMs) setBoundMin(boundMin.translated(windowWidthMs)); //si la frame est plus petite que la limite inf, translater la //fenêtre vers la gauche if (PlayerControl.frame().toMs() < boundMin.toMs()) setBoundMin(boundMin.translated(-windowWidthMs)); } Platform.runLater(new Runnable() { @Override public void run() { //positionne le curseur cursorRect.setX((PlayerControl.frame().toMs() - boundMin.toMs()) * xAxis.getScale() + yAxisInScene); } }); } /** * Met en surbrillance une ligne horizontale correspondant à une * valeur sur l'axe y * @param category La valeur de la donnée sur l'axe y à mettre en * surbrillance */ 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(); highlightedLine.setY(tickPosition - highlightedLine.getHeight()/2); resizeHiglightedLine(); } }); } /** * Redimensionne la ligne de surbrillance */ private void resizeHiglightedLine(){ final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); highlightedLine.setX(0); highlightedLine.setWidth(pane.getWidth() > 0 ? pane.getWidth() : 100); highlightedLine.setHeight(yAxis.getHeight() / yAxis.getCategories().size()); } /** * Sélectionne la donnée de l'axe y sous le curseur de la souris * @param event L'événement de la souris * @return La donnée de l'axe y */ private String selectTickLine(MouseEvent event){ final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); return yAxis.getValueForDisplay(event.getY() - yAxis.localToScene(0,0).getY()); } private int readFreqID(MouseEvent event){ final CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); return yAxis.getCategories().indexOf(yAxis.getValueForDisplay(event.getY() - yAxis.localToScene(0,0).getY())); } private float readTime(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(); return xAxis.getValueForDisplay(mouseX - yAxisInScene).floatValue(); } /** * Redimensionne le curseur */ private void resizeCursor(){ Platform.runLater(() -> { final CategoryAxis yAxis = (CategoryAxis) chart.getYAxis(); /*si l'axe y n'est pas complètement initialisé, cela peut arriver si le graphe met un peu plus de temps à s'initialiser, positionner le curseur avec des valeurs fixes*/ if (yAxis.getHeight() == 0){ cursorRect.setY(17); moveCursor(true); } else { cursorRect.setY(yAxis.localToScene(0, 0).getY()); cursorRect.setHeight(yAxis.getHeight()); } }); } /** * Définit une limite minimale pour dessiner * @param bound */ private void setBoundMin(Instant bound){ if(bound.toMs() < 0) bound = Instant.fromTime(0); boundMin = bound; applyWindowChange(); } /** * Définit la taille de la fenêtre du signal à afficher * @param windowWidthMs La taille de la fenêtre */ private void setWindowWidthMs(float windowWidthMs){ this.windowWidthMs = windowWidthMs; applyWindowChange(); } /** * Appelle setWindow() de BufferViewerState */ private void applyWindowChange(){ final NumberAxis xAxis = (NumberAxis)chart.getXAxis(); xAxis.setLowerBound(boundMin.toMs()); xAxis.setUpperBound(boundMin.toMs()+windowWidthMs); cleanView(); state().setWindow(boundMin.mapToIndex(buffer()), boundMin.translated(windowWidthMs).mapToIndex(buffer())); } /** * Définit la gamme qui peut alterner entre regular et chromaatique * @param yAxisGamme La gamme */ public void setYAxisGamme(Gamme yAxisGamme){ this.yAxisGamme = yAxisGamme; Platform.runLater(new Runnable(){ @Override public void run() { changeYAxis(); state().invalidate(); } }); } public Gamme getGamme(){ return yAxisGamme; } /** * Alterne la gamme à afficher entre regular et chromatic */ private void changeYAxis(){ CategoryAxis yAxis = (CategoryAxis)chart.getYAxis(); ArrayList categories = new ArrayList(); for(int i=0 ; iobservableArrayList(categories)); yAxis.invalidateRange(categories); } public void playerControlEvent(PlayerControlEvent e) { if(!visibility()) return; switch (e.getType()) { case FRAME: moveCursor(true); break; } } @Override public Parent getMainNode() { return pane; } /** * Revoie la liste des éléments du menu contextuel * * @param contextMenu Le menu */ @Override public void createContextMenu(JPopupMenu contextMenu) { JMenu choixAxesX = 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(NoteGamme.getMaxFreq(), NoteGamme.getMaxFreq(), 1000)); } }); ButtonGroup b = new ButtonGroup(); b.add(noteGamme); b.add(regularGamme); choixAxesX.add(noteGamme); choixAxesX.add(regularGamme); contextMenu.add(choixAxesX); } @Override protected void initView() { createContent(); cleanView(); state().setSlowingFactorOnDataChange(5); setBoundMin(new Instant(0)); } @Override public void updateView(){ XYChart.Series series = new XYChart.Series<>(); ObservableList categories = ((CategoryAxis)chart.getYAxis()).getCategories(); final float INC = windowWidthMs/RESOLUTION; // On parcours la fenêtre selon le temps et si l'échantillon à l'instant considéré est non nul ... for (float time = boundMin.toMs(); time <= boundMin.toMs()+windowWidthMs ; time+=INC) { if (buffer().get(time) != null) { //On parcours l'échantillon fréquence par fréquence for (int idFreq = 0; idFreq < categories.size(); idFreq++) { Color color = getColor(idFreq, Instant.fromTime(time)); if (color != null && (color.getBrightness() < .95 ||color.getSaturation() > .05 )) { series.getData().add(new XYChart.Data(time, categories.get(idFreq), new BarExtraValues(time+INC, color))); } } } } Platform.runLater(new Runnable() { @Override public void run() { chart.getData().set(0, series); } }); } protected abstract Color getColor(int idFreq, Instant time); protected Runnable getOnMouseMoved(int idFreq, Instant time){return new Runnable() { @Override public void run() { } };} protected Runnable getOnMouseClicked(int idFreq, Instant time){return 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())); } }); } }