512 lines
15 KiB
Java
512 lines
15 KiB
Java
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
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<T extends TemporalBuffer<?>> extends AbstractBufferGraph<T>{
|
|
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<Number>() {
|
|
@Override
|
|
public void changed(ObservableValue<? extends Number> observableValue, Number oldHeight, Number newHeight) {
|
|
moveCursor(true);
|
|
}
|
|
});
|
|
|
|
//quand la hauteur de la fenêtre change, redimensionner le curseur
|
|
pane.heightProperty().addListener(new ChangeListener<Number>() {
|
|
@Override
|
|
public void changed(ObservableValue<? extends Number> observableValue, Number oldWidth, Number newWidth) {
|
|
resizeCursor();
|
|
}
|
|
});
|
|
|
|
//quand la souris bouge, redessiner la ligne de surbrillance
|
|
pane.setOnMouseMoved(new EventHandler<MouseEvent>() {
|
|
@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<MouseEvent>() {
|
|
@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<MouseEvent>() {
|
|
@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<ScrollEvent>() {
|
|
@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<MouseEvent>() {
|
|
@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<MouseEvent>() {
|
|
@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<String> categories = new ArrayList();
|
|
|
|
for(int i=0 ; i<yAxisGamme.gammeSize() ; i++){
|
|
if(yAxisGamme instanceof NoteGamme) {
|
|
categories.add(((NoteGamme)yAxisGamme).getNoteName(i));
|
|
} else {
|
|
categories.add(String.valueOf(yAxisGamme.getFreq(i)));
|
|
}
|
|
}
|
|
yAxis.setCategories(FXCollections.<String>observableArrayList(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<String> 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()));
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|