reversound/src/gui/graphs/SpectrogramView.java
2014-06-16 16:48:34 +02:00

481 lines
13 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 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<TemporalBuffer<Spectre>> {
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<String> 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<Number>() {
@Override
public void changed(ObservableValue<? extends Number> 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<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldWidth, Number newWidth) {
resizeCursor();
}
});
pane.setOnMouseMoved(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
highlightLine(selectTickLine(event));
}
});
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if(event.isPrimaryButtonDown())
setFrame(event);
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
@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<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)));
}
}
this.categories = FXCollections.observableArrayList(categories);
yAxis.setCategories(this.categories);
yAxis.setAutoRanging(false);
yAxis.invalidateRange(categories);
}
/**
* Call setWindow() of BufferViewState
*/
private void setBufferWindow(){
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
float upper = (float)xAxis.getUpperBound();
upper = PlayerControl.frame().fromTime(upper).mapToIndex(buffer());
float lower = (float)xAxis.getLowerBound();
lower = PlayerControl.frame().fromTime(lower).mapToIndex(buffer());
state().setWindow((int)lower,(int)upper);
setBoundsAndInc();
}
private void setBoundsAndInc(){
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
final int RESOLUTION = 50;
final int sizeMS = (int)PlayerControl.frame().fromIndex(buffer().size(),buffer()).toMs();
boundMax = xAxis.getUpperBound()>sizeMS ? 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));
}
}