481 lines
13 KiB
Java
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));
|
|
}
|
|
}
|