adding code
Switching from INSA svn to GitHub
This commit is contained in:
parent
19d75499bb
commit
6b4ec42c01
90 changed files with 11908 additions and 0 deletions
21
src/JNLP/Reversound.jnlp
Normal file
21
src/JNLP/Reversound.jnlp
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jnlp spec="1.0+" codebase="http://crocmagnon.github.io/reversound" href="Reversound.jnlp">
|
||||
<information>
|
||||
<title>Reversound</title>
|
||||
<vendor>Gabriel Augendre</vendor>
|
||||
<offline-allowed/>
|
||||
</information>
|
||||
<resources>
|
||||
<!-- Application Resources -->
|
||||
<j2se version="1.8+"/>
|
||||
<jar href="Reversound.jar"
|
||||
main="true" download="eager"/>
|
||||
</resources>
|
||||
<security>
|
||||
<all-permissions />
|
||||
</security>
|
||||
<application-desc
|
||||
name="Reversound"
|
||||
main-class="gui.MainWindow">
|
||||
</application-desc>
|
||||
</jnlp>
|
3
src/META-INF/MANIFEST.MF
Normal file
3
src/META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: gui.MainWindow
|
||||
Permissions: all-permissions
|
30
src/actions/ExpertModeAction.java
Normal file
30
src/actions/ExpertModeAction.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package actions;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 28/03/2014.
|
||||
*/
|
||||
|
||||
public class ExpertModeAction extends AbstractAction {
|
||||
public static final String ACTIVE = "ExpertModeActive";
|
||||
|
||||
/**
|
||||
* Create the ExpertModeAction.
|
||||
* @param default_value Is the expert mode enabled ?
|
||||
*/
|
||||
public ExpertModeAction(boolean default_value) {
|
||||
super("Mode expert");
|
||||
putValue(ACTIVE,default_value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
putValue(ACTIVE, !((Boolean) getValue(ACTIVE)));
|
||||
}
|
||||
|
||||
/* public void setExpertMode(Boolean enabled) {
|
||||
putValue(ACTIVE,enabled);
|
||||
}*/
|
||||
}
|
48
src/actions/ImportFileAction.java
Normal file
48
src/actions/ImportFileAction.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package actions;
|
||||
|
||||
import generictools.Strings;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Ask to user to open a File and start reading.
|
||||
*/
|
||||
public class ImportFileAction extends AbstractAction {
|
||||
public static final String FILE = "Fichier";
|
||||
private final JComboBox<String> sourceList;
|
||||
private final JFileChooser chooser = new JFileChooser();
|
||||
private final JFrame frame;
|
||||
|
||||
public ImportFileAction(JFrame frame, JComboBox<String> sourceList){
|
||||
super("Importer un fichier...");
|
||||
this.frame = frame;
|
||||
this.sourceList = sourceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user clicks either on file import button or menu item.
|
||||
* Loads the file in memory
|
||||
* @param e An event.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter("Fichier Audio wav", "wav");
|
||||
chooser.setFileFilter(filter);
|
||||
int returnVal = chooser.showOpenDialog(frame);
|
||||
if(returnVal == JFileChooser.APPROVE_OPTION) {
|
||||
if (sourceList.getItemCount() > 1)
|
||||
sourceList.removeItemAt(1);
|
||||
File audioFile = new File(chooser.getSelectedFile().getAbsolutePath());
|
||||
putValue(FILE, audioFile);
|
||||
sourceList.addItem(audioFile.getName());
|
||||
sourceList.setPrototypeDisplayValue(Strings.longestString("Fichier", audioFile.getName()));
|
||||
sourceList.setSelectedIndex(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
23
src/actions/QuitAction.java
Normal file
23
src/actions/QuitAction.java
Normal file
|
@ -0,0 +1,23 @@
|
|||
package actions;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
/**
|
||||
* Created by harry2410 on 18/04/2014.
|
||||
*/
|
||||
public class QuitAction extends AbstractAction {
|
||||
|
||||
private final JFrame frame;
|
||||
|
||||
public QuitAction(JFrame frame) {
|
||||
super("Quitter");
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
}
|
34
src/actions/ShowInternalFrameAction.java
Normal file
34
src/actions/ShowInternalFrameAction.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package actions;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyVetoException;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 28/03/2014.
|
||||
*/
|
||||
public class ShowInternalFrameAction extends AbstractAction {
|
||||
private final JDesktopPane desktopPane;
|
||||
private final JInternalFrame internalFrame;
|
||||
|
||||
public ShowInternalFrameAction(JDesktopPane desktopPane, JInternalFrame internalFrame) {
|
||||
super(internalFrame.getTitle());
|
||||
this.desktopPane = desktopPane;
|
||||
this.internalFrame = internalFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!internalFrame.isVisible()) {
|
||||
desktopPane.add(internalFrame);
|
||||
internalFrame.setVisible(true);
|
||||
} else {
|
||||
try {
|
||||
internalFrame.setSelected(true);
|
||||
} catch (PropertyVetoException e1) {
|
||||
e1.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
src/conteneurs/CustomGamme.java
Normal file
69
src/conteneurs/CustomGamme.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
package conteneurs;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* CustomGamme permet de créer une gamme en ne sélectionnant uniquement les fréquences qui nous intéressent.
|
||||
*/
|
||||
public class CustomGamme extends Gamme{
|
||||
private final ArrayList<Float> freqID;
|
||||
|
||||
public CustomGamme(ArrayList<Float> listeFreq) {
|
||||
freqID = listeFreq;
|
||||
}
|
||||
|
||||
/** Cherche l'indice d'une fréquence donnée en paramètre. pour une gamme quelconque. La méthode de recherche des indices les plus proches utilise la dichotomie.
|
||||
* @param freq la fréquence donnée en paramètre
|
||||
* @return l'indice de la fréquence que l'on cherche */
|
||||
public ArrayList<Number> getIndex (float freq) {
|
||||
int indexPrec = 0;
|
||||
int indexSuiv = this.freqID.size()-1;
|
||||
int newIndex = 0;
|
||||
ArrayList<Number> res = new ArrayList<>();
|
||||
|
||||
|
||||
// Gère le cas où on demande une fréquence qui n'est pas comprise entre le minimum et le maximum des fréquences de cette gamme.
|
||||
if(freq > this.freqID.get(this.freqID.size()-1) || freq < this.freqID.get(0)){
|
||||
res.add(-1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//Trouve les 2 indices qui encadrent la fréquence
|
||||
while( (indexSuiv-indexPrec) > 1){
|
||||
newIndex = Math.round((indexSuiv + indexPrec)/2);
|
||||
// System.out.println("test : "+newIndex);
|
||||
if(freq > this.getFreq(newIndex)){
|
||||
indexPrec = newIndex;
|
||||
} else {
|
||||
indexSuiv = newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/** Retourne un seul indice si une des fréquences est exactement une de la liste. Sinon, retourne les deux indices les plus proches */
|
||||
if(freq == this.getFreq(indexSuiv)){
|
||||
res.add(indexSuiv);
|
||||
} else if(freq == this.getFreq(indexPrec)){
|
||||
res.add(indexPrec);
|
||||
} else {
|
||||
res.add(indexPrec);
|
||||
res.add(indexSuiv);
|
||||
float coeff = (freq - this.getFreq(indexPrec)) / (this.getFreq(indexSuiv)-this.getFreq(indexPrec));
|
||||
res.add(coeff);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Renvoie le nombre de fréquences que contient la gamme */
|
||||
public int gammeSize(){
|
||||
return this.freqID.size();
|
||||
}
|
||||
|
||||
/** Renvoie la fréquence à l'indice donné dans la liste. La fréquence est stockée en Hz
|
||||
* @param index l'indice de la fréquence à renvoyer
|
||||
* @return la fréquence en Hz */
|
||||
public float getFreq (int index) {
|
||||
return this.freqID.get(index);
|
||||
}
|
||||
}
|
24
src/conteneurs/Gamme.java
Normal file
24
src/conteneurs/Gamme.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class Gamme {
|
||||
/** Renvoie l'indice associé à une fréquence donnée
|
||||
* Si jamais la fréquence n'existe pas, on prend la plus proche */
|
||||
public abstract ArrayList <Number> getIndex(float freq);
|
||||
|
||||
/** Renvoie la fréquence associée à un indice donné */
|
||||
public abstract float getFreq(int index);
|
||||
|
||||
/** Renvoie le nombre de fréquences que contient la gamme */
|
||||
public abstract int gammeSize();
|
||||
|
||||
|
||||
public ArrayList<String> toStringList(){
|
||||
ArrayList<String> r = new ArrayList<>(gammeSize());
|
||||
for (int i = 0; i < gammeSize(); i++)
|
||||
r.add(String.valueOf(getFreq(i)));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
104
src/conteneurs/Note.java
Normal file
104
src/conteneurs/Note.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
package conteneurs;
|
||||
|
||||
import generictools.Instant;
|
||||
import processing.buffer.NoteBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 02/06/14.
|
||||
*/
|
||||
public class Note extends ObservableObject {
|
||||
|
||||
private int noteID;
|
||||
private int bufferID = -1;
|
||||
private Instant start;
|
||||
private Instant end;
|
||||
private NoteBuffer buffer;
|
||||
|
||||
//Caractérise la durée de la note (en diviseur de la ronde: 1 pour ronde, 2 pour blanche, 4 pour noire ...)
|
||||
private String unifiedDuration = null;
|
||||
|
||||
public Note(int noteID, Instant start, Instant end) {
|
||||
this.noteID = noteID;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/** Renvoie l'instant de départ de la note
|
||||
* @return l'instant sous forme d'Instant. */
|
||||
public Instant getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/** Définit l'instant de départ d'une note */
|
||||
public void setStart(Instant start) {
|
||||
this.start = start;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/** Permet de récupérer l'instant de fin de la note */
|
||||
public Instant getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/** Redefinit l'instant de fin de la note
|
||||
* @param end l'instant de fin */
|
||||
public void setEnd(Instant end) {
|
||||
this.end = end;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/** Permet de récupérer l'index de la note (exemple: la 49 ... )*/
|
||||
public int getNoteID() {
|
||||
return noteID;
|
||||
}
|
||||
|
||||
|
||||
/** @return le nom de la note*/
|
||||
public String getNoteName(){
|
||||
return NoteGamme.getNoteName(noteID);
|
||||
}
|
||||
|
||||
/**@return le nom anglais de la note*/
|
||||
public String getEnglishNoteName(){return NoteGamme.getEnglishNoteName(noteID);}
|
||||
|
||||
/** @return la fréquence de la note*/
|
||||
public float getFreq(){
|
||||
return NoteGamme.getStatFreq(noteID);
|
||||
}
|
||||
|
||||
/** @return Identifiant de la note dans le buffer de note */
|
||||
public int getBufferID() {
|
||||
return bufferID;
|
||||
}
|
||||
|
||||
public void setBufferID(int bufferID) {
|
||||
this.bufferID = bufferID;
|
||||
emit(ObservableObjectEvent.Type.ID_CHANGED);
|
||||
}
|
||||
|
||||
public NoteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setBuffer(NoteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
emit(ObservableObjectEvent.Type.ID_CHANGED);
|
||||
}
|
||||
|
||||
public String getUnifiedDuration() {
|
||||
return unifiedDuration;
|
||||
}
|
||||
|
||||
public void setUnifiedDuration(String unifiedDuration) {
|
||||
this.unifiedDuration = unifiedDuration;
|
||||
}
|
||||
|
||||
public float getDuration(){
|
||||
return end.substract(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return Note.class;
|
||||
}
|
||||
}
|
69
src/conteneurs/NoteEvent.java
Normal file
69
src/conteneurs/NoteEvent.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
package conteneurs;
|
||||
|
||||
/**
|
||||
* Created by gaby on 14/05/14.
|
||||
*/
|
||||
public class NoteEvent extends ObservableObject {
|
||||
private NoteProfiled note;
|
||||
private Type type;
|
||||
private float eventAmpli;
|
||||
|
||||
public NoteEvent(NoteProfiled note, Type type, float eventAmpli) {
|
||||
this.note = note;
|
||||
this.type = type;
|
||||
this.eventAmpli = eventAmpli;
|
||||
}
|
||||
|
||||
/** Permet de creer un evenement de type "Apparition de note"
|
||||
* @param eventAmpli l'amplitude du changement
|
||||
* @param note la note qui contient le profil */
|
||||
public static NoteEvent apparitionEvent(NoteProfiled note, float eventAmpli){
|
||||
return new NoteEvent(note, Type.APPARITION, eventAmpli);
|
||||
}
|
||||
|
||||
/** Permet de creer un evenement de type "Disparition de note
|
||||
* @param eventAmpli l'amplitude du changement
|
||||
* @param note la note qui contient le profil */
|
||||
public static NoteEvent disparitionEvent(NoteProfiled note, float eventAmpli){
|
||||
return new NoteEvent(note, Type.DISPARITION, eventAmpli);
|
||||
}
|
||||
|
||||
/** Renvoie la note associee a cette instance d'evenement */
|
||||
public NoteProfiled getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
/** Redefinit la note associee a cette instance d'evenement */
|
||||
public void setNote(NoteProfiled note) {
|
||||
this.note = note;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
public float getEventAmpli() {
|
||||
return eventAmpli;
|
||||
}
|
||||
|
||||
public void setEventAmpli(float eventAmpli) {
|
||||
this.eventAmpli = eventAmpli;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/** Renvoie le type de l'evenement : Disparition ou Apparition */
|
||||
public Type getEventType(){
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return NoteEvent.class;
|
||||
}
|
||||
|
||||
public enum Type{
|
||||
APPARITION,
|
||||
DISPARITION
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return note.toString() + (type==Type.APPARITION?" Apparition: ":" Disparation: ") +eventAmpli;
|
||||
}
|
||||
}
|
49
src/conteneurs/NoteEventList.java
Normal file
49
src/conteneurs/NoteEventList.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 21/05/14.
|
||||
* Contient une liste d'evenement de notes, a savoir une liste d'apparitions et de disparitions de notes
|
||||
*/
|
||||
public class NoteEventList extends ObservableList<NoteEvent> {
|
||||
public NoteEventList() {
|
||||
}
|
||||
|
||||
/** Renvoie l'evenement associé à la note donnee en paramètre
|
||||
* @param noteID l'identifiant de la note
|
||||
* @return l'evenement sous forme de NoteEvent */
|
||||
public NoteEvent getByNoteId(int noteID){
|
||||
int id = 0;
|
||||
for(id = 0; id < size(); id++)
|
||||
if(get(id).getNote().getIdNote()==noteID)
|
||||
break;
|
||||
if(id==size())
|
||||
return null;
|
||||
|
||||
return get(id);
|
||||
}
|
||||
|
||||
/** Renvoie la gamme dans laquelle les notes associées aux evenements de cette liste se trouvent */
|
||||
public Gamme getEventsGamme(){
|
||||
ArrayList<Float> list = new ArrayList<>(size());
|
||||
for (int i = 0; i < size(); i++)
|
||||
list.add(get(i).getNote().getFreq());
|
||||
|
||||
return new CustomGamme(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(NoteEvent observableItem) {
|
||||
int addId = 0;
|
||||
float freq = observableItem.getNote().getFreq();
|
||||
for(addId=size()-1; addId>=0; addId--){
|
||||
if(get(addId).getNote().getFreq() < freq){
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.add(addId+1, observableItem);
|
||||
|
||||
|
||||
}
|
||||
}
|
247
src/conteneurs/NoteGamme.java
Normal file
247
src/conteneurs/NoteGamme.java
Normal file
|
@ -0,0 +1,247 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 16/04/2014.
|
||||
* Gamme qui ne garde que les fréquences qui correspondent à des notes utilisées dans les gammes classiques tempérées
|
||||
*/
|
||||
public class NoteGamme extends Gamme {
|
||||
|
||||
static final float MAX_FREQ = 2100;
|
||||
static final float MIN_FREQ = 18.354002f;
|
||||
private ArrayList<Float> freqID;
|
||||
|
||||
private NoteGamme(){
|
||||
this.freqID = new ArrayList<Float>();
|
||||
|
||||
}
|
||||
|
||||
private static NoteGamme noteGamme = new NoteGamme();
|
||||
public static NoteGamme gamme(){
|
||||
return noteGamme;
|
||||
}
|
||||
|
||||
/** public void computeFreqNote(){
|
||||
/**for(float freq = 440; freq < this.MAX_FREQ; freq = freq*(float)Math.pow(2, 1/12.0) ){
|
||||
this.freqID.addNote(freq);
|
||||
System.out.println(freqID.get(freqID.size()-1));
|
||||
}
|
||||
for(float freq = 440; freq > MIN_FREQ; freq = freq/(float)Math.pow(2,1/12.0)){
|
||||
this.freqID.addNote(0,freq);
|
||||
System.out.println(freqID.get(0));
|
||||
}
|
||||
System.out.println(freqID.size());
|
||||
|
||||
for(float f = MIN_FREQ; f < MAX_FREQ; f = f*(float)Math.pow(2, 1/12.0)){
|
||||
freqID.addNote(f);
|
||||
System.out.println(f);
|
||||
}
|
||||
} */
|
||||
|
||||
@Override
|
||||
public ArrayList<Number> getIndex(float freq) {
|
||||
return getStatIndex(freq);
|
||||
}
|
||||
|
||||
public static ArrayList<Number> getStatIndex(float freq){
|
||||
ArrayList<Number> res = new ArrayList<>();
|
||||
|
||||
// Gère le cas où on demande une fréquence qui n'est pas comprise entre le minimum et le maximum des fréquences de cette gamme.
|
||||
if(freq < MIN_FREQ ||freq > MAX_FREQ){
|
||||
res.add(-1);
|
||||
return res;
|
||||
}
|
||||
|
||||
//Cherche l'indice du dessus
|
||||
float f = MIN_FREQ;
|
||||
int index = 0;
|
||||
while(f <= freq){
|
||||
f = f * (float)Math.pow(2, 1/12.0);
|
||||
index++;
|
||||
}
|
||||
//Calcul du coefficient d'interpolation: 0 si freq = fréquence inférieure, 1 si freq = freq supérieure
|
||||
float coeff = (freq - (f/(float)Math.pow(2,1/12.0)))/(f - (f/(float)Math.pow(2,1/12.0)));
|
||||
|
||||
res.add(index-1);
|
||||
res.add(index);
|
||||
res.add(coeff);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Renvoie l'indice de la note a partir de son nom
|
||||
* @param name son nom en tant que chaine de caractere
|
||||
* @return l'indice de la note */
|
||||
public static int getIndex(String name){
|
||||
int id = ('0'+name.charAt(name.length()-1))*12;
|
||||
|
||||
if(name.startsWith("RE#"))
|
||||
id+=1;
|
||||
else if(name.startsWith("MI"))
|
||||
id+=2;
|
||||
else if(name.startsWith("FA"))
|
||||
id+=3;
|
||||
else if(name.startsWith("FA#"))
|
||||
id+=4;
|
||||
else if(name.startsWith("SOL"))
|
||||
id+=5;
|
||||
else if(name.startsWith("SOL#"))
|
||||
id+=6;
|
||||
else if(name.startsWith("LA"))
|
||||
id+=7;
|
||||
else if(name.startsWith("LA#"))
|
||||
id+=8;
|
||||
else if(name.startsWith("SI"))
|
||||
id+=9;
|
||||
else if(name.startsWith("DO"))
|
||||
id+=10;
|
||||
else if(name.startsWith("DO#"))
|
||||
id+=11;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFreq(int index) {
|
||||
return getStatFreq(index);
|
||||
}
|
||||
|
||||
public static float getStatFreq(int index){
|
||||
return MIN_FREQ * (float)Math.pow(2,index/12.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int gammeSize() {
|
||||
return gammeStatSize();
|
||||
}
|
||||
|
||||
public static int gammeStatSize(){
|
||||
int size = 0;
|
||||
for(float f = MIN_FREQ; f < MAX_FREQ; f = f*(float)Math.pow(2,1/12.0)){
|
||||
size ++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/** Renvoie le nom d'une note en fonction de la fréquence
|
||||
* Si la fréquence n'est pas proche d'une note (coeff < 5%), l'indice renvoyé est -1
|
||||
* @param freq la fréquence dont on cherche la note qui correspond
|
||||
* @return le nom de la note
|
||||
*/
|
||||
public static String getNoteName(float freq){
|
||||
|
||||
ArrayList<Number> res = getStatIndex(freq);
|
||||
int index = -1;
|
||||
float coeff = (float)res.get(2);
|
||||
if(coeff < 0.05){
|
||||
index = (int)res.get(0);
|
||||
} else if (coeff > 0.95){
|
||||
index = (int)res.get(1);
|
||||
}
|
||||
if(index != -1)
|
||||
return getNoteName(index);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Renvoie la note en fonction de l'indice de la note, (indice 0 : RE) */
|
||||
public static String getNoteName(int index){
|
||||
String noteName = "";
|
||||
if (index >= gammeStatSize() || index <0)
|
||||
return null;
|
||||
|
||||
int octave = (index/12);
|
||||
index = index%12;
|
||||
switch(index){
|
||||
case 0: noteName = "RE";
|
||||
break;
|
||||
case 1: noteName = "RE#";
|
||||
break;
|
||||
case 2: noteName = "MI";
|
||||
break;
|
||||
case 3: noteName = "FA";
|
||||
break;
|
||||
case 4: noteName = "FA#";
|
||||
break;
|
||||
case 5: noteName = "SOL";
|
||||
break;
|
||||
case 6: noteName = "SOL#";
|
||||
break;
|
||||
case 7: noteName = "LA";
|
||||
break;
|
||||
case 8: noteName = "LA#";
|
||||
break;
|
||||
case 9: noteName = "SI";
|
||||
break;
|
||||
case 10: noteName = "DO";
|
||||
octave++;
|
||||
break;
|
||||
case 11: noteName = "DO#";
|
||||
octave++;
|
||||
break;
|
||||
}
|
||||
noteName = noteName + octave;
|
||||
return noteName;
|
||||
}
|
||||
|
||||
/** Renvoie le nom de la note en version anglaise */
|
||||
public static String getEnglishNoteName(int index){
|
||||
String noteName = "";
|
||||
if (index >= gammeStatSize() || index <0)
|
||||
return null;
|
||||
|
||||
int octave = (index/12);
|
||||
index = index%12;
|
||||
switch(index){
|
||||
case 0: noteName = "d";
|
||||
break;
|
||||
case 1: noteName = "dis";
|
||||
break;
|
||||
case 2: noteName = "c";
|
||||
break;
|
||||
case 3: noteName = "f";
|
||||
break;
|
||||
case 4: noteName = "fis";
|
||||
break;
|
||||
case 5: noteName = "e";
|
||||
break;
|
||||
case 6: noteName = "eis";
|
||||
break;
|
||||
case 7: noteName = "a";
|
||||
break;
|
||||
case 8: noteName = "ais";
|
||||
break;
|
||||
case 9: noteName = "b";
|
||||
break;
|
||||
case 10: noteName = "c";
|
||||
octave++;
|
||||
break;
|
||||
case 11: noteName = "cis";
|
||||
octave++;
|
||||
break;
|
||||
}
|
||||
for (int i = 2; i < octave; i++)
|
||||
noteName+="\'";
|
||||
|
||||
return noteName;
|
||||
}
|
||||
|
||||
/** Renvoie la frequence maximale de la NoteGamme */
|
||||
public static float getMaxFreq() {
|
||||
return MAX_FREQ;
|
||||
}
|
||||
|
||||
/** Renvoie la frequence minimale de la NoteGamme */
|
||||
public static float getMinFreq() {
|
||||
return MIN_FREQ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> toStringList(){
|
||||
ArrayList<String> r = new ArrayList<>(gammeSize());
|
||||
for (int i = 0; i < gammeSize(); i++)
|
||||
r.add(String.valueOf(getNoteName(i)));
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
51
src/conteneurs/NoteID.java
Normal file
51
src/conteneurs/NoteID.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
package conteneurs;
|
||||
|
||||
import processing.buffer.NoteBuffer;
|
||||
|
||||
/** Created by gaby on 02/06/14.
|
||||
* Contient l'identifiant d'une note. Une note jouée est déclarée à tous les instants où elle est jouée,
|
||||
* mais l'identifiant de la note est commun car il s'agit de la meme note. Fait le lien entre tous les instants
|
||||
* ou une note est jouee */
|
||||
public class NoteID extends ObservableObject implements ObservableObject.Listener{
|
||||
private NoteBuffer buffer;
|
||||
private int bufferID=-1;
|
||||
|
||||
public NoteID(Note note){
|
||||
bufferID = note.getBufferID();
|
||||
buffer = note.getBuffer();
|
||||
note.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return NoteID.class;
|
||||
}
|
||||
|
||||
public int getBufferID() {
|
||||
return bufferID;
|
||||
}
|
||||
|
||||
public NoteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public Note note(){
|
||||
if(buffer==null)
|
||||
return null;
|
||||
|
||||
return buffer.getNote(bufferID);
|
||||
}
|
||||
|
||||
public boolean isValid(){
|
||||
return buffer!=null && bufferID!=-1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
emit(e.getType());
|
||||
if(e.getType()== ObservableObjectEvent.Type.ID_CHANGED){
|
||||
buffer = ((Note)e.getEmmiter()).getBuffer();
|
||||
bufferID = ((Note)e.getEmmiter()).getBufferID();
|
||||
}
|
||||
}
|
||||
}
|
66
src/conteneurs/NoteList.java
Normal file
66
src/conteneurs/NoteList.java
Normal file
|
@ -0,0 +1,66 @@
|
|||
package conteneurs;
|
||||
|
||||
import processing.buffer.NoteBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 02/06/14.
|
||||
*/
|
||||
public class NoteList extends ObservableList<NoteID> {
|
||||
|
||||
|
||||
public NoteList() {
|
||||
}
|
||||
|
||||
|
||||
/** Renvoie la note dans la liste de note dont on donne l'identifiant en paramètre */
|
||||
public Note getNoteById(int noteID) {
|
||||
int id = getIdById(noteID);
|
||||
if(id>-1)
|
||||
return get(id).note();
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Renvoie l'identifiant de la note à partir de son index (exemple : la 49) */
|
||||
public int getIdById(int noteID){
|
||||
for (int id = 0; id < size() && get(id).note().getNoteID() <= noteID; id++)
|
||||
if (get(id).note().getNoteID() == noteID)
|
||||
return id;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Permet d'enlever une note à la liste de note */
|
||||
public boolean remove(Note note){
|
||||
int id = getIdById(note.getNoteID());
|
||||
|
||||
if(id>-1){
|
||||
remove(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Permet d'ajouter une note à la liste de note */
|
||||
public void add(Note note){
|
||||
add(new NoteID(note));
|
||||
}
|
||||
|
||||
/** Permet d'ajouter une note à partir de son identifiant de note */
|
||||
public void add(NoteID noteID) {
|
||||
|
||||
int addId = 0;
|
||||
float freq = noteID.note().getFreq();
|
||||
for(addId=size()-1; addId>=0; addId--){
|
||||
if(get(addId).note().getFreq() < freq){
|
||||
break;
|
||||
}else if(get(addId).note().getFreq() == freq){
|
||||
set(addId, noteID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
list.add(addId+1, noteID);
|
||||
}
|
||||
|
||||
}
|
50
src/conteneurs/NoteProfiled.java
Normal file
50
src/conteneurs/NoteProfiled.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
package conteneurs;
|
||||
|
||||
/**
|
||||
* Created by gaby on 14/05/14.
|
||||
*/
|
||||
public class NoteProfiled extends ObservableObject{
|
||||
private int idNote;
|
||||
private Profil profil;
|
||||
|
||||
public NoteProfiled(int idNote, Profil profil) {
|
||||
this.idNote = idNote;
|
||||
this.profil = profil;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return NoteProfiled.class;
|
||||
}
|
||||
|
||||
public Profil getProfil() {
|
||||
return profil;
|
||||
}
|
||||
|
||||
/** Permet de definir le profil d'une note, cad l'amplitude des harmoniques
|
||||
* @param profil le profil de la note */
|
||||
public void setProfil(Profil profil) {
|
||||
this.profil = profil;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/** Indice qui caracterise la hauteur de la note */
|
||||
public int getIdNote() {
|
||||
return idNote;
|
||||
}
|
||||
|
||||
/** Renvoie la frequence d'une note à partir de son indice (exemple: la 49) */
|
||||
public float getFreq(){
|
||||
return NoteGamme.getStatFreq(idNote);
|
||||
}
|
||||
|
||||
/** Renvoie le nom de la note a partir de sa hauteur */
|
||||
public String getNoteName(){
|
||||
return NoteGamme.getNoteName(idNote);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return getNoteName();
|
||||
}
|
||||
}
|
108
src/conteneurs/ObservableList.java
Normal file
108
src/conteneurs/ObservableList.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 14/05/14.
|
||||
* Liste écoutable d'objet écoutable
|
||||
*
|
||||
* Cet objet est l'adaptation d'une ArrayList classique à laquelle on ajoute la gestion évenementielle lorsqu'on lui
|
||||
* ajoute ou retire des échantillons ou quand ceux ci sont modifiés.
|
||||
*/
|
||||
public class ObservableList<T extends ObservableObject> extends ObservableObject implements ObservableObject.Listener {
|
||||
|
||||
ArrayList<T> list = new ArrayList<>();
|
||||
|
||||
public ObservableList() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un objet à la liste écoutable
|
||||
* @param observableItem objet écoutable à ajouter
|
||||
*/
|
||||
public void add(T observableItem){
|
||||
list.add(observableItem);
|
||||
observableItem.addListener(this);
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplace un objet dans la liste
|
||||
* @param idItem identifiant de l'objet à remplacer
|
||||
* @param observaleItem nouvelle objet écoutable
|
||||
* @return vrai si idItem est un identifiant correct
|
||||
*/
|
||||
public boolean set(int idItem, T observaleItem){
|
||||
if(!isIdCorrect(idItem))
|
||||
return false;
|
||||
|
||||
list.set(idItem, observaleItem).removeListener(this);
|
||||
observaleItem.addListener(this);
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un objet de la liste
|
||||
* @param observableItem Objet observable à supprimer
|
||||
* @return Vrai si l'objet était contenu dans la liste et a été supprimé
|
||||
*/
|
||||
public boolean remove(T observableItem){
|
||||
return remove(list.indexOf(observableItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un objet de la liste
|
||||
* @param idItem Identifiant de l*bjet observable à supprimer
|
||||
* @return Vrai si l'objet était contenu dans la liste et a été supprimé
|
||||
*/
|
||||
public boolean remove(int idItem){
|
||||
if(!isIdCorrect(idItem))
|
||||
return false;
|
||||
|
||||
list.remove(idItem).removeListener(this);
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'objet correspondant à l'index
|
||||
* @param idItem Index de l'objet à récupérer
|
||||
* @return L'objet observable correpondant. /!\ Peut renvoyer null /!\
|
||||
*/
|
||||
public T get(int idItem){
|
||||
if(!isIdCorrect(idItem))
|
||||
return null;
|
||||
|
||||
return list.get(idItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retourne la taille de la liste
|
||||
* @return Taille
|
||||
*/
|
||||
public int size(){
|
||||
return list.size();
|
||||
}
|
||||
|
||||
|
||||
private boolean isIdCorrect(int id){
|
||||
return id>=0&&id<list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Propage l'évenement e losrque un item de la liste a été modifié
|
||||
* @param e
|
||||
*/
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return ObservableList.class;
|
||||
}
|
||||
}
|
127
src/conteneurs/ObservableObject.java
Normal file
127
src/conteneurs/ObservableObject.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 06/05/14.
|
||||
*
|
||||
* Objet écoutable
|
||||
*
|
||||
* Classe abstraite qui doit hérité de tout objet dont on veut pouvoir recevoir un évenement
|
||||
* lorsqu'il subit une modification.
|
||||
*/
|
||||
public abstract class ObservableObject {
|
||||
|
||||
ArrayList<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public ObservableObject() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des écouteurs à l'objet écoutable
|
||||
* @param l Objet écouteur
|
||||
*/
|
||||
public void addListener(Listener l){
|
||||
if(listeners.contains(l))
|
||||
return;
|
||||
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Supprime un écouteurs
|
||||
* @param l écouteurs à supprimer
|
||||
*/
|
||||
public void removeListener(Listener l){
|
||||
if(!listeners.contains(l))
|
||||
return;
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un écouteur par sont index dans la liste des écouteurs
|
||||
* @param idListener index des écouteurs
|
||||
*/
|
||||
public void removeListener(int idListener){
|
||||
if(idListener<0 && idListener>=listeners.size())
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un écouteur selon sa signature
|
||||
* @param signature signature de l'écouteur à supprimer
|
||||
*/
|
||||
public void removeSignedListener(String signature){
|
||||
int id = getSignedListenerID(signature);
|
||||
|
||||
if(id==-1)
|
||||
return;
|
||||
removeListener(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'index d'un écouteur dans la liste des écouteurs en fonction de se signature
|
||||
* @param signature Signature de l'écouteur recherché
|
||||
* @return Index de l'écouteur
|
||||
*/
|
||||
public int getSignedListenerID(String signature){
|
||||
for (int i = 0; i < listeners.size(); i++)
|
||||
if (listeners.get(i) instanceof SignedListener)
|
||||
if (((SignedListener) listeners.get(i)).signature().equals(signature))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmet un évenement à tous les écouteurs
|
||||
* @param e évenement à propager
|
||||
*/
|
||||
public void emit(ObservableObjectEvent e){
|
||||
for (int i = 0; i < listeners.size(); i++) {
|
||||
listeners.get(i).observableOjectEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Créé et transmet un évenement d'un type à tous les écouteurs
|
||||
* @param t type de l'évenement à propager
|
||||
*/
|
||||
public void emit(ObservableObjectEvent.Type t){
|
||||
emit(new ObservableObjectEvent(this, t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface définissant les objets écouteurs
|
||||
*/
|
||||
static public interface Listener {
|
||||
/**
|
||||
* Traitement de l'évenement d'objet observable
|
||||
* @param e evenement
|
||||
*/
|
||||
public void observableOjectEvent(ObservableObjectEvent e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe abstraite définissant un objet écouteur auquelle on ajoute une signature pour pouvoir
|
||||
* retrouver sans le stocké un écouteur grace à un String unique par écouteurs.
|
||||
*/
|
||||
static public abstract class SignedListener implements Listener{
|
||||
public void SignedListener(){
|
||||
}
|
||||
|
||||
abstract public String signature();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return "Observable object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le type de l'objet écoutable
|
||||
* @return Type de l'objet
|
||||
*/
|
||||
public abstract Class getType();
|
||||
}
|
||||
|
57
src/conteneurs/ObservableObjectEvent.java
Normal file
57
src/conteneurs/ObservableObjectEvent.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package conteneurs;
|
||||
|
||||
/**
|
||||
* Created by gaby on 06/05/14.
|
||||
*
|
||||
* Evenement d(objet observable
|
||||
*/
|
||||
public class ObservableObjectEvent {
|
||||
ObservableObject emmiter;
|
||||
Type type;
|
||||
|
||||
/**
|
||||
* Constructeur de l'évenement
|
||||
* @param emmiter Objet écoutable source de l'évenement
|
||||
* @param type type de l'évenement
|
||||
*/
|
||||
public ObservableObjectEvent(ObservableObject emmiter, Type type) {
|
||||
this.emmiter = emmiter;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'objet écoutable à l'origine de l'évenement
|
||||
* @return emmeteur
|
||||
*/
|
||||
public ObservableObject getEmmiter() {
|
||||
return emmiter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return type de l'évenement
|
||||
*/
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
String r = emmiter.toString() + " Event: ";
|
||||
switch (type){
|
||||
case DATA_CHANGED:
|
||||
r+="Modified";
|
||||
break;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type d'évenement:
|
||||
* DATA_CHANGED: les données ont été modifiés
|
||||
* ID_CHANGED: les identifiants de localisation de l'objet ont été modifiées (propre aux Note)
|
||||
*/
|
||||
public enum Type {
|
||||
DATA_CHANGED,
|
||||
ID_CHANGED
|
||||
}
|
||||
}
|
73
src/conteneurs/Profil.java
Normal file
73
src/conteneurs/Profil.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Associe à un numéro d'harmonique, une amplitude
|
||||
*/
|
||||
public class Profil extends ObservableObject{
|
||||
private ArrayList<Float> amplitudes = new ArrayList<>();
|
||||
|
||||
public Profil() {
|
||||
|
||||
}
|
||||
|
||||
/** Définit/Modifie l'amplitude d'une fréquence à partir de son indice */
|
||||
public void setAmplitude (float newAmplitude, int idHarmonique) {
|
||||
|
||||
if(newAmplitude==0 && idHarmonique == amplitudes.size()-1) {
|
||||
amplitudes.remove(idHarmonique);
|
||||
return;
|
||||
}
|
||||
|
||||
ensureSize(idHarmonique+1);
|
||||
this.amplitudes.set(idHarmonique, newAmplitude);
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
|
||||
/** Renvoie l'amplitude de l'harmonique à l'indice considéré */
|
||||
public float getAmplitude (int idHarmonique) {
|
||||
return idHarmonique<amplitudes.size()?this.amplitudes.get(idHarmonique):0f;
|
||||
}
|
||||
|
||||
/** Renvoie le nombre d'harmoniques */
|
||||
public int size(){
|
||||
return amplitudes.size();
|
||||
}
|
||||
|
||||
/** Renvoie les identifiants des harmoniques non nulles */
|
||||
public ArrayList<Integer> getHarmoniqueID(){
|
||||
ArrayList<Integer> r = new ArrayList<>(amplitudes.size());
|
||||
for (int i = 0; i < amplitudes.size(); i++)
|
||||
if (amplitudes.get(i) != 0)
|
||||
r.add(i);
|
||||
|
||||
r.trimToSize();
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Vérifie le nombre d'harmoniques > la taille donnee en parametre
|
||||
* @param size la taille en entier
|
||||
* @return boolean vrai si size<au nombre d'harmoniques, faux sinon. */
|
||||
private boolean ensureSize(int size){
|
||||
if(size<=amplitudes.size())
|
||||
return true;
|
||||
|
||||
amplitudes.ensureCapacity(size);
|
||||
|
||||
while(amplitudes.size()<size)
|
||||
amplitudes.add(0f);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Profil";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return Profil.class;
|
||||
}
|
||||
}
|
74
src/conteneurs/RegularGamme.java
Normal file
74
src/conteneurs/RegularGamme.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
package conteneurs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 26/03/2014.
|
||||
* Crée une gamme avec des fréquences régulièrement réparties (intervalle constant entre 2 fréquences
|
||||
*/
|
||||
public class RegularGamme extends Gamme{
|
||||
|
||||
private final float startFreq;
|
||||
private final float step;
|
||||
private final int lengthGamme;
|
||||
|
||||
public RegularGamme(float startFreq, int lengthGamme, float step) {
|
||||
this.startFreq = startFreq;
|
||||
this.lengthGamme = lengthGamme;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public RegularGamme(float startFreq, float endFreq, int nbrPoint){
|
||||
this(startFreq, nbrPoint, (endFreq-startFreq)/(float)nbrPoint);
|
||||
}
|
||||
|
||||
/** Renvoie l'indice de la fréquence la plus proche si on tombe dessus,
|
||||
* sinon renvoie une ArrayList contenant l'indice précedent, l'indice suivant et un coefficient pour situer la note entre les 2. */
|
||||
public ArrayList<Number> getIndex(float freq) {
|
||||
if(freq > this.getEndFreq()){
|
||||
ArrayList<Number> res = new ArrayList<>();
|
||||
res.add(-1);
|
||||
return res;
|
||||
} else if( (freq-this.startFreq)%this.step == 0 ){
|
||||
ArrayList<Number> res = new ArrayList<>();
|
||||
res.add(Math.round(freq-this.startFreq)/step);
|
||||
return res;
|
||||
} else {
|
||||
int indexPrec = (int)Math.floor((freq-this.startFreq)/this.step);
|
||||
float coeff = (freq - this.getFreq(indexPrec))/ this.step;
|
||||
ArrayList<Number> res = new ArrayList<>();
|
||||
res.add(indexPrec);
|
||||
res.add(indexPrec+1);
|
||||
res.add(coeff);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Renvoie la fréquence associée à un indice donne en paramètre */
|
||||
public float getFreq(int index) {
|
||||
if(index < this.lengthGamme) {
|
||||
return this.startFreq + index * this.step;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Renvoie le nombre de fréquences contenues dans la gamme */
|
||||
public int gammeSize() {
|
||||
return this.lengthGamme;
|
||||
}
|
||||
|
||||
/** Renvoie la fréquence de début: par défaut c'est 0 */
|
||||
public float getStartFreq() {
|
||||
return this.startFreq;
|
||||
}
|
||||
|
||||
/** Renvoie la fréquence maximale de la gamme */
|
||||
float getEndFreq() {
|
||||
return (this.lengthGamme-1)*this.step + this.startFreq;
|
||||
}
|
||||
|
||||
public float getStep() {
|
||||
return this.step;
|
||||
}
|
||||
}
|
345
src/conteneurs/Spectre.java
Normal file
345
src/conteneurs/Spectre.java
Normal file
|
@ -0,0 +1,345 @@
|
|||
package conteneurs;
|
||||
|
||||
import generictools.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Spectre fait le lien entre les fréquences et leur amplitude. Les deux sont stockés dans des tableaux, par ordre de fréquences croissantes.
|
||||
* L'association fréquences - index est faite dans la gamme
|
||||
* L'association amplitude - index est faite dans le spectre
|
||||
* Les index sont communs, donc on a le lien directement entre les 2.
|
||||
*/
|
||||
public class Spectre extends ObservableObject{
|
||||
private Gamme gamme;
|
||||
private ArrayList<Float> amplitudes;
|
||||
|
||||
private float moyennePos;
|
||||
private float moyenneNeg;
|
||||
private float eTypePos;
|
||||
private float eTypeNeg;
|
||||
|
||||
private static final int INDEX_PREV = 0;
|
||||
private static final int INDEX_NEXT = 1;
|
||||
private static final int INDEX_COEFF = 2;
|
||||
private static final int INDEX_UNIQUE = 0;
|
||||
|
||||
public enum InterpolationType{
|
||||
DEFAULT,
|
||||
SQUARRED,
|
||||
CUBIC,
|
||||
MOYENNE
|
||||
}
|
||||
|
||||
public Spectre(Gamme gamme) {
|
||||
this.gamme = gamme;
|
||||
this.amplitudes = new ArrayList<>(gamme.gammeSize());
|
||||
for (int i = 0; i < gamme.gammeSize(); i++) {
|
||||
this.amplitudes.add(0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit toutes les amplitudes d'un coup
|
||||
* @param newAmplitudes amplitudes à charger
|
||||
*/
|
||||
public void setAllAmplitudes(ArrayList<Float> newAmplitudes){
|
||||
if(newAmplitudes.size() == this.gamme.gammeSize()) {
|
||||
this.amplitudes = newAmplitudes;
|
||||
} else if(newAmplitudes.size() > this.gamme.gammeSize()) {
|
||||
this.amplitudes = (ArrayList<Float>)newAmplitudes.subList(0,this.gamme.gammeSize());
|
||||
} else {
|
||||
this.amplitudes = newAmplitudes;
|
||||
while(this.amplitudes.size() != this.gamme.gammeSize()){
|
||||
this.amplitudes.add(0f);
|
||||
}
|
||||
}
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
|
||||
}
|
||||
|
||||
/** Change l'amplitude à un index donné
|
||||
* @param id l'indice de la fréquence dont on veut définir l'amplitude (rappel: elles sont classées dans l'ordre croissant
|
||||
* @param newAmplitude la valeur de l'amplitude de la fréquence à modifier
|
||||
*/
|
||||
public void setAmplitudeAt (int id, float newAmplitude) {
|
||||
if(id>=0 && id<this.amplitudes.size()) {
|
||||
this.amplitudes.set(id, newAmplitude);
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/** Change l'amplitude en dB à un index donné
|
||||
* @param id l'indice de la fréquence dont on veut définir l'amplitude (rappel: elles sont classées dans l'ordre croissant
|
||||
* @param dBAmplitude la valeur de l'amplitude de la fréquence à modifier en dB
|
||||
*/
|
||||
public void setdBAmplitudeAt (int id, float dBAmplitude) {
|
||||
setAmplitudeAt(id, dBToAmpli(dBAmplitude));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Change l'amplitude à d'une fréquence donnée.
|
||||
* Pour cela, on cherche l'indice correspondant à cette fréquence
|
||||
* @param freq la fréquence dont l'amplitude est à modifier/définir
|
||||
* @param newAmplitude la valeur de l'amplitude de la fréquence à modifier
|
||||
*/
|
||||
public void setAmplitudeAt (float freq, float newAmplitude) {
|
||||
ArrayList<Number> listRes = this.gamme.getIndex(freq);
|
||||
if(listRes.size() == 1){
|
||||
this.setAmplitudeAt((Integer)listRes.get(INDEX_UNIQUE), newAmplitude); // TEST
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/** Change l'amplitude à d'une fréquence donnée en dB.
|
||||
* Pour cela, on cherche l'indice correspondant à cette fréquence
|
||||
* @param freq la fréquence dont l'amplitude est à modifier/définir
|
||||
* @param dBAmplitude la valeur de l'amplitude de la fréquence à modifier en dB
|
||||
*/
|
||||
public void setdBAmplitudeAt (float freq, float dBAmplitude) { setAmplitudeAt(freq, dBToAmpli(dBAmplitude));}
|
||||
|
||||
/** Renvoie l'amplitude d'une fréquence
|
||||
* @param id l'indice de la fréquence dont on cherche l'amplitude
|
||||
* @return l'amplitude de la fréquence. */
|
||||
public float getAmplitude (int id) {
|
||||
return this.amplitudes.get(id);
|
||||
}
|
||||
|
||||
/** Renvoie l'amplitude d'une fréquence en dB
|
||||
* @param id l'indice de la fréquence dont on cherche l'amplitude
|
||||
* @return l'amplitude de la fréquence en dB. */
|
||||
public float getdBAmplitude (int id) {
|
||||
return ampliTodB(this.amplitudes.get(id));
|
||||
}
|
||||
|
||||
/** Renvoie l'amplitude d'une fréquence donnée en paramètre, -1 si la fréquence n'existe pas
|
||||
* @param freq la fréquence donnée dont on cherche l'amplitude
|
||||
* @return l'amplitude de la fréquence.
|
||||
*/
|
||||
public Pair<Float, Float> getAmplitude(float freq, InterpolationType iType) {
|
||||
ArrayList<Number> listRes = this.gamme.getIndex(freq);
|
||||
|
||||
if(listRes.size() == 1 && ((Integer)listRes.get(0)!=-1)){
|
||||
return new Pair<>(this.amplitudes.get((Integer)listRes.get(INDEX_UNIQUE)), 1f);
|
||||
} else if((Integer)listRes.get(0) == -1) {
|
||||
return new Pair<>(0f, 0f);
|
||||
} else {
|
||||
float amplitudePrev = this.amplitudes.get((Integer)listRes.get(INDEX_PREV));
|
||||
float amplitudeNext = this.amplitudes.get((Integer)listRes.get(INDEX_NEXT));
|
||||
float coeff = (Float)listRes.get(INDEX_COEFF);
|
||||
// Pondère en fonction du coefficient donné pour situer la fréquence entre les 2 les plus proches
|
||||
|
||||
Pair<Float, Float> res = null;
|
||||
switch(iType){
|
||||
case DEFAULT: res = new Pair<> ( ( amplitudePrev * (1-coeff) ) + (amplitudeNext * coeff), coeff);
|
||||
break;
|
||||
case SQUARRED: res = new Pair<> ( (float)Math.sqrt((amplitudePrev*amplitudePrev*(1-coeff)*(1-coeff) ) + (amplitudeNext*amplitudeNext*coeff*coeff)), coeff);
|
||||
break;
|
||||
case MOYENNE: res = new Pair<> ((amplitudeNext+amplitudePrev)/2, coeff);
|
||||
break;
|
||||
case CUBIC:
|
||||
float y1 = (Integer)listRes.get(INDEX_PREV)>1? this.amplitudes.get((Integer) listRes.get(INDEX_PREV) - 1):0;
|
||||
float y2 = amplitudePrev;
|
||||
float y3 = amplitudeNext;
|
||||
float y4 = this.amplitudes.get((Integer) listRes.get(INDEX_NEXT) + 1);
|
||||
float x1 = this.gamme.getFreq((Integer) listRes.get(INDEX_PREV)-1);
|
||||
float x2 = this.gamme.getFreq((Integer) listRes.get(INDEX_PREV));
|
||||
float x3 = this.gamme.getFreq((Integer) listRes.get(INDEX_NEXT));
|
||||
float x4 = (Integer)listRes.get(INDEX_NEXT)<gamme.gammeSize()? this.gamme.getFreq((Integer) listRes.get(INDEX_NEXT) + 1):0;
|
||||
res = new Pair<>(lagrangeInterp(x1, x2, x3, x4, y1, y2, y3, y4, freq), coeff);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/** Calcule la valeur en un point grâce à une interpolation par un polynome de degré 3,
|
||||
* qui doit passer par les 4 points dont les coordonnées sont données en paramètre */
|
||||
private float lagrangeInterp(float x1, float x2, float x3, float x4, float y1, float y2, float y3, float y4, float freq){
|
||||
float ampli = y1 * ( ((freq-x2)*(freq-x3)*(freq-x4))/((x1-x2)*(x1-x3)*(x1-x4)) );
|
||||
ampli += y2 * ( ((freq-x1)*(freq-x3)*(freq-x4))/((x2-x1)*(x2-x3)*(x2-x4)) );
|
||||
ampli += y3 * ( ((freq-x1)*(freq-x2)*(freq-x4))/((x3-x1)*(x3-x2)*(x3-x4)) );
|
||||
ampli += y4 * ( ((freq-x1)*(freq-x2)*(freq-x3))/((x4-x1)*(x4-x2)*(x4-x3)) );
|
||||
return ampli;
|
||||
}
|
||||
|
||||
/** Renvoie l'amplitude d'une fréquence donnée en paramètre, -1 si la fréquence n'existe pas
|
||||
* @param freq la fréquence donnée dont on cherche l'amplitude
|
||||
* @return l'amplitude de la fréquence en dB.
|
||||
*/
|
||||
public Pair<Float, Float> getdBAmplitude(float freq) { Pair<Float,Float> r = getAmplitude(freq, InterpolationType.DEFAULT); r.setFirst(ampliTodB(r.first())); return r;}
|
||||
|
||||
/** Change la gamme sur laquelle ce spectre travaille: on ne sélectionne que certaines fréquences, ou on ne prend pas les mêmes, ou bien de manière pas linéaire
|
||||
* La gamme donnée en paramètre contient certaines fréquences, pour trouver les amplitudes soit on les a directement, soit on les interpole suivant si on les avait déjà ou non.
|
||||
* Renvoie un indice sur la qualité de l'interpolation réalisée sur les fréquences.
|
||||
* @param newGamme la nouvelle Gamme contenant les nouvelles fréquences dont on doit calculer les amplitudes
|
||||
* @return l'indice de qualité de l'interpolation: 0 au plus mauvais (on tombe toujours pile au milieu entre les 2 fréquences), 1 si on tombe toujours sur une fréquence pré existante */
|
||||
public float castToGamme (Gamme newGamme) {
|
||||
ArrayList<Float> newAmplitudes = new ArrayList<>();
|
||||
float sumIndicesQ = 0;
|
||||
float indiceQualite = 1;
|
||||
|
||||
//System.out.println("newGamme size: "+newGamme.gammeSize());
|
||||
newAmplitudes.add(0f);
|
||||
for(int i = 1; i < newGamme.gammeSize()-1; i++){
|
||||
Pair<Float,Float> coupleFreqCoef = this.getAmplitude(newGamme.getFreq(i), InterpolationType.CUBIC);
|
||||
newAmplitudes.add(coupleFreqCoef.first());
|
||||
sumIndicesQ += Math.abs((coupleFreqCoef.second()-0.5)*2);
|
||||
}
|
||||
newAmplitudes.add(0f);
|
||||
float moyenneIndiceQ = sumIndicesQ / newGamme.gammeSize();
|
||||
//Calcul l'indice de qualité de l'ensemble du traitement
|
||||
//System.out.println("Moyenne indices qualités : "+moyenneIndiceQ);
|
||||
|
||||
indiceQualite = ((moyenneIndiceQ * newGamme.gammeSize()) + (newAmplitudes.size() - newGamme.gammeSize())) / newAmplitudes.size();
|
||||
indiceQualite = indiceQualite*indiceQualite;
|
||||
|
||||
this.gamme = newGamme;
|
||||
this.amplitudes = newAmplitudes;
|
||||
emit(ObservableObjectEvent.Type.DATA_CHANGED);
|
||||
|
||||
return indiceQualite;
|
||||
}
|
||||
|
||||
/** Crée une RegularGamme à partir de la gamme actuelle, sans aucune extrapolation.
|
||||
* Selectionne la fréquence la plus proche pour caler celle de la gamme existante
|
||||
*/
|
||||
//public void castToRegularGamme(RegularGamme newGamme){
|
||||
public void castToRegularGamme(){
|
||||
ArrayList<Float> newAmplitudes = new ArrayList<>();
|
||||
RegularGamme newGammeTest = new RegularGamme(0f, Math.round(this.gamme.getFreq(this.getGamme().gammeSize()-1)), 1f);
|
||||
for(int i = 0; i < newGammeTest.gammeSize(); i++){
|
||||
newAmplitudes.add(0f);
|
||||
}
|
||||
for (int i = 0; i < this.gamme.gammeSize()-1; i++) {
|
||||
//System.out.println(this.gamme.getFreq(i));
|
||||
newAmplitudes.set(Math.round(this.gamme.getFreq(i)), this.amplitudes.get(i));
|
||||
}
|
||||
this.gamme = newGammeTest;
|
||||
this.amplitudes = newAmplitudes;
|
||||
}
|
||||
|
||||
/** Permet de copier un spectre. Utile si jamais on ne veut pas modifier directement un buffer partagé par plusieurs algo,
|
||||
* et si on veut faire un traitement sur celui-là
|
||||
* @return la copie de ce spectre, avec une nouvelle adresse mémoire du coup */
|
||||
public Spectre copy(){
|
||||
Spectre newSpectre = new Spectre(this.gamme);
|
||||
ArrayList<Float> newAmpli = new ArrayList<Float>(this.amplitudes.size());
|
||||
|
||||
for (int i = 0; i < this.amplitudes.size(); i++) {
|
||||
newAmpli.add(this.amplitudes.get(i));
|
||||
}
|
||||
newSpectre.setAllAmplitudes(newAmpli);
|
||||
|
||||
return newSpectre;
|
||||
}
|
||||
|
||||
/** Renvoie la gamme liée à cette instance de spectre */
|
||||
public Gamme getGamme() {
|
||||
return this.gamme;
|
||||
}
|
||||
|
||||
public ArrayList<Float> getAmplitudes() {
|
||||
return amplitudes;
|
||||
}
|
||||
|
||||
/** Calcule les moyennes des amplitudes positives et négatives ainsi que leurs écarts-types.
|
||||
* Sert à faire un filtrage du bruit sur les spectres */
|
||||
public void calculStats(){
|
||||
int gammeSize = this.getGamme().gammeSize();
|
||||
|
||||
int nPos = 0;
|
||||
int nNeg = 0;
|
||||
// Calcul de la moyenne
|
||||
for (int i = 0; i < gammeSize; i++) {
|
||||
if(this.getAmplitude(i)>=0) {
|
||||
moyennePos += this.getAmplitude(i);
|
||||
nPos++;
|
||||
} else {
|
||||
moyenneNeg += this.getAmplitude(i);
|
||||
nNeg++;
|
||||
}
|
||||
}
|
||||
|
||||
moyennePos = moyennePos / nPos;
|
||||
moyenneNeg = moyenneNeg / nNeg;
|
||||
|
||||
// Calcul de l'écart-type
|
||||
for (int i = 0; i < gammeSize; i++) {
|
||||
if(this.getAmplitude(i)>=0)
|
||||
eTypePos += (this.getAmplitude(i) - moyennePos)*(this.getAmplitude(i) - moyennePos );
|
||||
else
|
||||
eTypeNeg += (this.getAmplitude(i) - moyenneNeg)*(this.getAmplitude(i) - moyenneNeg );
|
||||
}
|
||||
eTypePos = (float)Math.sqrt(eTypePos/nPos);
|
||||
eTypeNeg = (float)Math.sqrt(eTypeNeg/nNeg);
|
||||
}
|
||||
|
||||
/** Filtre le bruit sur le principe de moyenne + ou - écart-type, que l'on doit calculer
|
||||
* VALEUR_SEUIL : Moyenne + (2*écart-type) --> 95,4% des valeurs à l'intérieur.
|
||||
* @return le spectre sans le bruit */
|
||||
public Spectre noiseFilter(){
|
||||
this.calculStats();
|
||||
if(this == null){
|
||||
return null;
|
||||
}
|
||||
Spectre res = this.copy();
|
||||
|
||||
//final float VALEUR_SEUIL_POS = 0.7f*(float)(this.eTypePos)+this.moyennePos;
|
||||
//final float VALEUR_SEUIL_NEG = -0.7f*(float)(this.eTypeNeg)+this.moyenneNeg;
|
||||
|
||||
|
||||
final float VALEUR_SEUIL_POS = this.moyennePos;
|
||||
final float VALEUR_SEUIL_NEG = this.moyenneNeg;
|
||||
|
||||
for (int i = 1; i < getGamme().gammeSize(); i++) {
|
||||
if(res.getAmplitude(i) < VALEUR_SEUIL_POS && res.getAmplitude(i)>VALEUR_SEUIL_NEG){
|
||||
res.setAmplitudeAt(i, 0);
|
||||
}
|
||||
}
|
||||
res.setAmplitudeAt(0, VALEUR_SEUIL_POS);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur en dB d'une intensité en pression
|
||||
* @param ampli valeur en pression
|
||||
* @return valeur en dB
|
||||
*/
|
||||
static public float ampliTodB(float ampli){return 10f*(float)Math.log10(ampli);}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur en pression d'une intensité en dB
|
||||
* @param dB valeur en dB
|
||||
* @return valeur en pression
|
||||
*/
|
||||
static public float dBToAmpli(float dB){return (float)Math.pow(dB/10f, 10);}
|
||||
|
||||
public float getMoyennePos() {
|
||||
return moyennePos;
|
||||
}
|
||||
|
||||
public float getMoyenneNeg() {
|
||||
return moyenneNeg;
|
||||
}
|
||||
|
||||
public float geteTypePos() {
|
||||
return eTypePos;
|
||||
}
|
||||
|
||||
public float geteTypeNeg() {
|
||||
return eTypeNeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Spectre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getType() {
|
||||
return Spectre.class;
|
||||
}
|
||||
}
|
116
src/generictools/Complex.java
Normal file
116
src/generictools/Complex.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
package generictools;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 25/03/2014.
|
||||
* Permet de créer des nombres complexes, utiles pour la FFT notamment
|
||||
*/
|
||||
public class Complex {
|
||||
|
||||
private float realPart;
|
||||
private float imagPart;
|
||||
public Complex (float realPart, float imagPart){
|
||||
this.realPart = realPart;
|
||||
this.imagPart = imagPart;
|
||||
}
|
||||
|
||||
public Complex( Complex c){
|
||||
this(c.getRealPart(), c.getImagPart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforme un complexe sous la forme (module, argument) en (réel, imaginaire).
|
||||
* @param mod Le module du complexe à transformer
|
||||
* @param arg L'argument du complexe à transformer
|
||||
* @return Le complexe nouvellement formé
|
||||
*/
|
||||
public static Complex ComplexFromExp(float mod, float arg){
|
||||
return new Complex( mod*(float)Math.cos(arg), mod*(float)Math.sin(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le conjugué d'un complexe.
|
||||
* @return Le conjugué du complexe actuel (this)
|
||||
*/
|
||||
public Complex conj(){
|
||||
return new Complex(this.realPart, (-1f*this.imagPart) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Somme deux complexes.
|
||||
* @param c2 Le complexe auquel ajouter le complexe actuel (this)
|
||||
* @return La somme des deux complexes
|
||||
*/
|
||||
public Complex sum(Complex c2){
|
||||
return new Complex(realPart+c2.getRealPart(), imagPart+c2.getImagPart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Soustrait c2 au complexe actuel (this).
|
||||
* @param c2 Le nombre à ôter
|
||||
* @return Le complexe résultant de la soustraction
|
||||
*/
|
||||
public Complex sub(Complex c2){
|
||||
return new Complex(realPart-c2.getRealPart(), imagPart-c2.getImagPart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplie deux complexes entre eux.
|
||||
* @param c2 Le complexe par lequel multiplier le complexe actuel (this)
|
||||
* @return Le produit des deux complexes
|
||||
*/
|
||||
public Complex mult(Complex c2){
|
||||
return new Complex( ((realPart * c2.getRealPart()) - (imagPart * c2.getImagPart())), ((realPart*c2.getImagPart()) + (c2.getRealPart()*imagPart)) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prend le carré de ce complex;
|
||||
* @return Le produit des deux complexes
|
||||
*/
|
||||
public Complex square(){
|
||||
return new Complex((realPart-imagPart)*(realPart+imagPart), 2*imagPart*realPart);
|
||||
}
|
||||
/** Renvoie la somme, en modifiant directement le complexe appelé */
|
||||
public Complex simpleSum(Complex c2){
|
||||
realPart += c2.getRealPart();
|
||||
imagPart += c2.getImagPart();
|
||||
return this;
|
||||
}
|
||||
/** Renvoie la multiplication, en modifiant directement le complexe appelé */
|
||||
public Complex simpleMult(Complex c2){
|
||||
float tmp = realPart;
|
||||
realPart = ((realPart * c2.getRealPart()) - (imagPart * c2.getImagPart()));
|
||||
imagPart = ((tmp*c2.getImagPart()) + (c2.getRealPart()*imagPart));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Permet de faire une multiplication de l'objet par lui-meme sans recreer une instance de Complexe */
|
||||
public void simpleSquare(){
|
||||
float tmp = realPart;
|
||||
realPart = (realPart-imagPart)*(realPart+imagPart);
|
||||
imagPart = 2*imagPart*tmp;
|
||||
}
|
||||
|
||||
|
||||
public float getRealPart(){
|
||||
return this.realPart;
|
||||
}
|
||||
public float getImagPart(){
|
||||
return this.imagPart;
|
||||
}
|
||||
|
||||
/** Definit ce complexe egal a celui donne en paramètre */
|
||||
public void setToEquals(Complex c){
|
||||
this.realPart = c.getRealPart();
|
||||
this.imagPart = c.getImagPart();
|
||||
}
|
||||
|
||||
/** Renvoie le module du complexe */
|
||||
public float getMod(){
|
||||
return (float)Math.sqrt((this.realPart*this.realPart)+(this.imagPart*this.imagPart));
|
||||
}
|
||||
/** Renvoie l'argument du complexe */
|
||||
public float getArg(){
|
||||
return (float)Math.atan(this.imagPart/this.realPart);
|
||||
}
|
||||
|
||||
}
|
245
src/generictools/Instant.java
Normal file
245
src/generictools/Instant.java
Normal file
|
@ -0,0 +1,245 @@
|
|||
package generictools;
|
||||
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 15/04/14.
|
||||
*
|
||||
* Instant
|
||||
*
|
||||
* Cette classe permet de repérer de manière absolue un instant dans le fichier traiter
|
||||
* Il permet ensuite de convertir cette instant en index de TemporalBuffer selon la fréquence d'échantillonage
|
||||
* et l'instant d'origine de ce dernier.
|
||||
*/
|
||||
public class Instant {
|
||||
private float timeMS=0;
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un temps dans le buffer de référence
|
||||
* @param time temps en ms.
|
||||
*/
|
||||
public Instant(float time){
|
||||
timeMS = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un temps dans le buffer de référence
|
||||
* @param ms temps en ms
|
||||
* @return Instant correspondant
|
||||
*/
|
||||
public static Instant fromTime(float ms){return new Instant(ms);}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un temps dans le buffer de référence
|
||||
* @param ms temps en ms
|
||||
* @param s temps en s
|
||||
* @param min temps en min
|
||||
* @param h temps en heure
|
||||
* @return Instant correpsondant
|
||||
*/
|
||||
public static Instant fromTime(float ms, int s, int min, int h){return new Instant(ms+1000*(s+60*(min+60*h)));}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un temps dans le buffer de référence
|
||||
* @param ms temps en ms
|
||||
* @param s temps en s
|
||||
* @param min temps en min
|
||||
* @return Instant correpsondant
|
||||
*/
|
||||
public static Instant fromTime(float ms, int s, int min){ return fromTime(ms,s,min,0);}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un temps dans le buffer de référence
|
||||
* @param ms temps en ms
|
||||
* @param s temps en s
|
||||
* @return Instant correpsondant
|
||||
*/
|
||||
public static Instant fromTime(float ms, int s){ return fromTime(ms, s, 0, 0);}
|
||||
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un index, un fréquence d'échantillonage,
|
||||
* l'instant d'origine est confondu avec celui du buffer de référence
|
||||
* @param index index de l'instant
|
||||
* @param sampleRate fréquence d'échantillonage
|
||||
* @return Instant correspondant
|
||||
*/
|
||||
public static Instant fromIndex(int index, int sampleRate){
|
||||
return new Instant(1000f*(float)index/(float)sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un index, un fréquence d'échantillonage et un instant d'origine
|
||||
* @param index index de l'instant
|
||||
* @param sampleRate fréquence d'échantillonage
|
||||
* @param originInstant Instant d'origine
|
||||
* @return Instant correspondant
|
||||
*/
|
||||
public static Instant fromIndex(int index, int sampleRate, Instant originInstant){
|
||||
return fromIndex(index,sampleRate).add(originInstant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un index, et un temporal buffer
|
||||
* @param index index de l'instant
|
||||
* @param temporalBuffer buffer temporel dans lequel on désire connaitre l'instant
|
||||
* @return Instant correspondant
|
||||
*/
|
||||
public static Instant fromIndex(int index, TemporalBuffer temporalBuffer){ return fromIndex(index, temporalBuffer.getSampleRate(), temporalBuffer.getOriginInstant());}
|
||||
|
||||
/**
|
||||
* Construit un instant en le définissant par un index, et un buffer qui est implicitement converti en buffer temporel,
|
||||
* si le buffer n'est pas un buffer temporel renvoit Instant(0)
|
||||
* @param index index de l'instant
|
||||
* @param buffer buffer temporel dans lequel on désire connaitre l'instant
|
||||
* @return Instant correspondant ou Instant(0)
|
||||
*/
|
||||
public static Instant fromIndex(int index, Buffer buffer){
|
||||
if(buffer instanceof TemporalBuffer)
|
||||
return fromIndex(index, (TemporalBuffer)buffer);
|
||||
return new Instant(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la durée en ms séparant ce buffer de celui passé en argument
|
||||
* @param t Instant à comparer
|
||||
* @return durée
|
||||
*/
|
||||
public float substract(Instant t){
|
||||
return timeMS - t.timeMS;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est plus grand que t
|
||||
*/
|
||||
public boolean isGreaterThan(Instant t){
|
||||
return timeMS>t.timeMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est plus petit que t
|
||||
*/
|
||||
public boolean isLowerThan(Instant t){
|
||||
return timeMS<t.timeMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est plus grand ou égal que t
|
||||
*/
|
||||
public boolean isGreaterOrEquThan(Instant t){
|
||||
return timeMS>=t.timeMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est plus petit ou égal que t
|
||||
*/
|
||||
public boolean isLowerOrEquThan(Instant t){
|
||||
return timeMS<=t.timeMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est égal à t
|
||||
*/
|
||||
public boolean equals(Object t){
|
||||
if(t instanceof Instant)
|
||||
return equals((Instant)t);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare cet instant avec celui passé en argument
|
||||
* @param t instant à comparer
|
||||
* @return Vrai si cet instant est égal à t
|
||||
*/
|
||||
public boolean equals(Instant t){
|
||||
return timeMS == t.timeMS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate cet instant d'un temps en ms /!\ Modifie cet instant
|
||||
* @param ms temps à translater
|
||||
* @return cet instant
|
||||
*/
|
||||
public Instant translate(float ms){
|
||||
timeMS += ms;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie un instant translate d'un temps en ms, sans modifier cet instant
|
||||
* @param ms temps à translater
|
||||
* @return l'instant translater
|
||||
*/
|
||||
public Instant translated(float ms){
|
||||
return new Instant(timeMS+ms);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return (int)Math.floor(timeMS/60000) + ":" +(int)Math.floor(timeMS%60000/1000)+ ":" +Math.round(timeMS%1000);
|
||||
}
|
||||
|
||||
|
||||
int mapToIndex(int sampleRate){
|
||||
return mapToIndex(sampleRate, new Instant(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'index correspondant à cet Instant en fonction de la fréquence d'échantillonage et de l'instant d'origine
|
||||
* @param sampleRate Fréquence d'échantillonage
|
||||
* @param originInstant Instant d'origine
|
||||
* @return index correspondant
|
||||
*/
|
||||
int mapToIndex(int sampleRate, Instant originInstant){ return Math.round((timeMS-originInstant.timeMS)*(float)sampleRate/1000f);}
|
||||
|
||||
/**
|
||||
* Retourne l'index correspondant à cet Instant dans un buffer temporel
|
||||
* @param temporalBuffer buffer temporel
|
||||
* @return index correspondant
|
||||
*/
|
||||
public int mapToIndex(TemporalBuffer temporalBuffer){
|
||||
return mapToIndex(temporalBuffer.getSampleRate(), temporalBuffer.getOriginInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le temps de l'instant dans le buffer de référence
|
||||
* @return temps en ms
|
||||
*/
|
||||
public float toMs(){
|
||||
return timeMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute à cet instant un autre instant /!\ Cet objet est modifié
|
||||
* @param i2 instant à ajouter
|
||||
* @return cet instant
|
||||
*/
|
||||
public Instant add(Instant i2){
|
||||
timeMS += i2.timeMS;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire de cet instant un autre instant /!\ Cet objet est modifié
|
||||
* @param i2 instant à ajouter
|
||||
* @return cet instant
|
||||
*/
|
||||
public Instant remove(Instant i2){
|
||||
timeMS -= i2.timeMS;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
56
src/generictools/Pair.java
Normal file
56
src/generictools/Pair.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
package generictools;
|
||||
|
||||
/**
|
||||
* Created by gaby on 21/03/14.
|
||||
*/
|
||||
public class Pair <T1,T2> {
|
||||
|
||||
private T1 v1;
|
||||
private T2 v2;
|
||||
|
||||
/**
|
||||
* Constuit en objet pair qui contient deux variable
|
||||
* @param first Première valeur
|
||||
* @param second Seconde Valeur
|
||||
*/
|
||||
public Pair(T1 first, T2 second) {
|
||||
v1 = first;
|
||||
v2 = second;
|
||||
}
|
||||
|
||||
public Pair() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la première valeur de la paire
|
||||
* @return La premiére valeur
|
||||
*/
|
||||
public T1 first() {
|
||||
return v1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la première valeur
|
||||
* @param first La nouvelle valeur
|
||||
*/
|
||||
public void setFirst(T1 first) {
|
||||
this.v1 = first;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie la seconde valeur de la paire
|
||||
* @return La seconde valeur
|
||||
*/
|
||||
public T2 second() {
|
||||
return v2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la seconde valeur
|
||||
* @param second La seconde valeur
|
||||
*/
|
||||
public void setSecond(T2 second) {
|
||||
this.v2 = second;
|
||||
}
|
||||
}
|
24
src/generictools/Strings.java
Normal file
24
src/generictools/Strings.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package generictools;
|
||||
|
||||
/** Created by Gabriel Augendre on 18/04/2014.
|
||||
* Renvoie la chaine de caractere la plus longue parmi celles donnees en paramètre */
|
||||
public class Strings {
|
||||
public static String longestString(String s1, String s2, String... sk) {
|
||||
if (sk.length == 0) {
|
||||
if (s1.length() > s2.length())
|
||||
return s1;
|
||||
return s2;
|
||||
} else {
|
||||
String[] array = new String[sk.length+2];
|
||||
int max = array[0].length();
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i].length() > max) {
|
||||
max = array[i].length();
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
return array[maxIndex];
|
||||
}
|
||||
}
|
||||
}
|
145
src/generictools/WindowFunction.java
Normal file
145
src/generictools/WindowFunction.java
Normal file
|
@ -0,0 +1,145 @@
|
|||
package generictools;
|
||||
|
||||
import processing.buffer.Buffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 08/04/2014.
|
||||
* Contient les coefficients de la fenetre de pondération (masque à appliquer au signal) utilisée par la FFT.
|
||||
* Générée une unique fois pour un traitement de FFT.
|
||||
*/
|
||||
public class WindowFunction {
|
||||
|
||||
private int startID;
|
||||
private final int width;
|
||||
private final Type windowType;
|
||||
private final Buffer<Float> temporalSignal;
|
||||
private ArrayList<Float> coef;
|
||||
private int divideFactor;
|
||||
|
||||
// Caractérise la forme de la fenetre
|
||||
public enum Type{
|
||||
RECTANGLE,
|
||||
HANN,
|
||||
HAMMING,
|
||||
TRIANGLE,
|
||||
BLACKMAN
|
||||
}
|
||||
|
||||
public WindowFunction(Type windowType, int width, Buffer<Float> temporalSignal){
|
||||
this.width = width;
|
||||
this.windowType = windowType;
|
||||
this.temporalSignal = temporalSignal;
|
||||
this.coef = new ArrayList<Float>(width);
|
||||
this.divideFactor = 1;
|
||||
calcCoef();
|
||||
}
|
||||
|
||||
/** Calcule l'ensemble des coefficients (masque à multiplier au signal) pour la largeur qui a été donnée
|
||||
* quand la fenetre a été construite */
|
||||
public void calcCoef() {
|
||||
final float coeffHamming = 0.53836f;
|
||||
final float coeffBlackman2 = 0.076849f;
|
||||
final float coeffBlackman0 = 0.42659f;
|
||||
final float coeffBlackman1 = 0.49656f;
|
||||
|
||||
switch (windowType){
|
||||
case RECTANGLE:
|
||||
for (int i = 0; i < width; i++) {
|
||||
coef.add(rectangle(width, i));
|
||||
}
|
||||
break;
|
||||
case HANN:
|
||||
for (int i = 0; i < width; i++) {
|
||||
coef.add(hann(width, i));
|
||||
}
|
||||
break;
|
||||
case HAMMING:
|
||||
for (int i = 0; i < width; i++) {
|
||||
coef.add(hamming(width, i, coeffHamming));
|
||||
}
|
||||
break;
|
||||
case TRIANGLE:
|
||||
for (int i = 0; i < width; i++) {
|
||||
coef.add(triangle(width, i));
|
||||
}
|
||||
break;
|
||||
case BLACKMAN:
|
||||
for (int i = 0; i < width; i++) {
|
||||
coef.add(blackman(width, i, coeffBlackman0, coeffBlackman1,coeffBlackman2));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Pondère la valeur du buffer avec celle de la fenetre */
|
||||
public float getValue(int actualID){
|
||||
Float valueBefore = temporalSignal.get(actualID);
|
||||
int id = actualID-startID;
|
||||
if(valueBefore == null || id < 0 || id >= width/divideFactor )
|
||||
return 0;
|
||||
|
||||
return coef.get(id*divideFactor)*valueBefore;
|
||||
}
|
||||
|
||||
/** Renvoie la valeur associée à la fenêtre rectangle au point actualID donné en paramètre */
|
||||
private static float rectangle(int width, int actualID) {
|
||||
if (actualID >= 0 && actualID <= width) {
|
||||
return 1f;
|
||||
} else {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie la valeur associée à la fenêtre Hann au point actualID donné en paramètre */
|
||||
private static float hann(int width, int actualID){
|
||||
if (actualID >= 0 && actualID <= width) {
|
||||
return (0.5f*( 1 - (float)Math.cos( (2*Math.PI*(actualID)) / (width-1)) ));
|
||||
} else {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie la valeur associée à la fenêtre Hamming au point actualID donné en paramètre */
|
||||
private static float hamming(int width, int actualID, float coeff){
|
||||
|
||||
if (actualID >= 0 && actualID <= width) {
|
||||
return (coeff - ((1f - coeff) * (float)Math.cos(2*Math.PI*(actualID) / (width-1))));
|
||||
} else {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie la valeur associée à la fenêtre triangle au point actualID donné en paramètre */
|
||||
private static float triangle(int width, int actualID){
|
||||
if (actualID >= 0 && actualID <= width) {
|
||||
return ( 1f - Math.abs(2 * (float) (actualID) / width) );
|
||||
} else {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie la valeur associée à la fenêtre Blackman au point actualID donné en paramètre */
|
||||
private static float blackman(int width, int actualID, float c0, float c1, float c2){
|
||||
if (actualID >= 0 && actualID <= width) {
|
||||
return (float)( c0 - c1*Math.cos(2*Math.PI*actualID / width) + c2*Math.cos(4* Math.PI*actualID / width));
|
||||
} else {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartID(int index){
|
||||
this.startID = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet la gestion d'un nombre de point sur lequel on calcule la FFT variable en fonction de la fréquence
|
||||
* @param divideFactor le facteur de division par lequel on divise le nombre de points maximums (puissance de 2)
|
||||
*/
|
||||
public void setDivideFactor(int divideFactor) {
|
||||
this.divideFactor = divideFactor;
|
||||
}
|
||||
}
|
143
src/gui/Box.java
Normal file
143
src/gui/Box.java
Normal file
|
@ -0,0 +1,143 @@
|
|||
package gui;
|
||||
|
||||
import generictools.Pair;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Classe générale permettant de gérer l'affichage des Process et des Inputs dans la fenêtre de contrôle.
|
||||
*/
|
||||
public class Box extends Group {
|
||||
|
||||
protected final static int NODE_WIDTH = ProcessSumUp.NODE_WIDTH;
|
||||
protected final static int NODE_HEIGHT = ProcessSumUp.NODE_HEIGHT;
|
||||
protected final static int TEXT_LENGTH = ProcessSumUp.TEXT_LENGTH;
|
||||
|
||||
private final Rectangle rectangle = new Rectangle(NODE_WIDTH, NODE_HEIGHT);
|
||||
protected final Text titre = new Text();
|
||||
|
||||
private final ArrayList<Node> clickableElements = new ArrayList<>();
|
||||
|
||||
private final Pair<Integer, Integer> coordinates = new Pair<>();
|
||||
private final ArrayList<ProcessBox> successorList = new ArrayList<>();
|
||||
|
||||
public Box() {
|
||||
this(Color.AQUA, new Text("Inconnu"));
|
||||
}
|
||||
|
||||
public Text getTitre() {
|
||||
return titre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les éléments dont on souhaite qu'ils affichent la fenêtre lors d'un clic.
|
||||
* Il s'agit typiquement du rectangle de fond ainsi que du titre.
|
||||
* @return L'arraylist des éléments
|
||||
*/
|
||||
public ArrayList<Node> getClickableElements() {
|
||||
return new ArrayList<>(clickableElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur
|
||||
* @param C La couleur de la boîte.
|
||||
* @param T Le texte à afficher.
|
||||
*/
|
||||
public Box(final Color C, final Text T) {
|
||||
super();
|
||||
|
||||
//Initialisation de la boîte
|
||||
rectangle.setFill(C);
|
||||
rectangle.setArcHeight(10);
|
||||
rectangle.setArcWidth(10);
|
||||
|
||||
DropShadow dS = new DropShadow();
|
||||
dS.setRadius(10);
|
||||
rectangle.setEffect(dS);
|
||||
|
||||
|
||||
//Initialisation du texte
|
||||
titre.setText(T.getText());
|
||||
titre.setFill(Color.BLACK);
|
||||
titre.setFont(Font.font("Arial", 15));
|
||||
titre.setX(20);
|
||||
titre.setY(24);
|
||||
|
||||
//Troncature du texte si trop long
|
||||
if (titre.getText().length() > TEXT_LENGTH+3) {
|
||||
titre.setText(titre.getText().substring(0,TEXT_LENGTH)+"...");
|
||||
}
|
||||
|
||||
this.getChildren().add(rectangle);
|
||||
this.getChildren().add(titre);
|
||||
|
||||
//On indique quels sont les éléments cliquables
|
||||
clickableElements.add(rectangle);
|
||||
clickableElements.add(titre);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un successeur à la liste des successeurs de cette box.
|
||||
* @param s Le successeur à ajouter.
|
||||
*/
|
||||
public void addSuccessor(final ProcessBox s) {
|
||||
this.successorList.add(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie les coordonnées de la boîte dans la grille.
|
||||
* @param m Abscisses (colonnes).
|
||||
* @param n Ordonnées (lignes).
|
||||
*/
|
||||
private void setCoordinates(int m, int n) {
|
||||
coordinates.setFirst(m);
|
||||
coordinates.setSecond(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère les coordonnées du/des successeurs.
|
||||
* @param c La coordonnée en abscisses de la boîte considérée.
|
||||
* @param l La coordonnée en ordonnées de la boîte considérée.
|
||||
* @param boxList La liste des successeurs.
|
||||
*/
|
||||
public void generateSuccCoord(int c, int l, ArrayList<Box> boxList) {
|
||||
this.setCoordinates(c, l);
|
||||
if (this instanceof InputBox) {
|
||||
InputBox iBox = (InputBox) this;
|
||||
boxList.add(iBox);
|
||||
}
|
||||
if (this instanceof ProcessBox) {
|
||||
ProcessBox pBox = (ProcessBox) this;
|
||||
if (!pBox.getProcess().isBoxDisplayed()) {
|
||||
boxList.add(pBox);
|
||||
pBox.getProcess().setBoxDisplayed(true);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < getSuccessorList().size(); i++) {
|
||||
successorList.get(i).generateSuccCoord(c+1, l+i, boxList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les coordonnées de la boîte dans la grille.
|
||||
* @return Une paire d'entiers représentant les coordonnées. En premier les abscisses, en second les ordonnées.
|
||||
*/
|
||||
public Pair<Integer, Integer> getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des successeurs.
|
||||
* @return Une ArrayList de ProcessBox représentant les successeurs de la boîte considérée.
|
||||
*/
|
||||
public ArrayList<ProcessBox> getSuccessorList() {
|
||||
return successorList;
|
||||
}
|
||||
}
|
5
src/gui/ClipView.java
Normal file
5
src/gui/ClipView.java
Normal file
|
@ -0,0 +1,5 @@
|
|||
package gui;
|
||||
|
||||
public interface ClipView {
|
||||
public void currentFrameChanged();
|
||||
}
|
24
src/gui/Connector.java
Normal file
24
src/gui/Connector.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package gui;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.shape.Line;
|
||||
|
||||
/**
|
||||
* Permet de connecter deux Box afin de voir les liens de successions dans les Process.
|
||||
*/
|
||||
public class Connector extends Line {
|
||||
private Connector(double startX, double startY, double endX, double endY) {
|
||||
super(startX, startY, endX, endY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une ligne entre deux noeuds de la fenêtre principale.
|
||||
* @param startBox La box à relier à la seconde.
|
||||
* @param endBox La seconde box à relier.
|
||||
*/
|
||||
public Connector(Box startBox, Box endBox) {
|
||||
final Point2D start = startBox.localToScene(Box.NODE_WIDTH,Box.NODE_HEIGHT/2);
|
||||
final Point2D end = endBox.localToScene(0.0,Box.NODE_HEIGHT/2);
|
||||
new Connector(start.getX(), start.getY(), end.getX(), end.getY());
|
||||
}
|
||||
}
|
29
src/gui/InputBox.java
Normal file
29
src/gui/InputBox.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
package gui;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import processing.AudioInputToBuffer;
|
||||
|
||||
/**
|
||||
* Définit la Box d'une Input, simple rectangle gris avec le nom de l'input.
|
||||
*/
|
||||
public class InputBox extends Box {
|
||||
private final AudioInputToBuffer input;
|
||||
|
||||
/**
|
||||
* Génère la box
|
||||
* @param input Le buffer d'input à associer.
|
||||
*/
|
||||
public InputBox(AudioInputToBuffer input) {
|
||||
super(Color.WHITESMOKE, new Text(input.getName()));
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de récupérer le buffer d'input associé à la box.
|
||||
* @return Le buffer d'input associé à la box.
|
||||
*/
|
||||
public AudioInputToBuffer getInput() {
|
||||
return input;
|
||||
}
|
||||
}
|
441
src/gui/MainWindow.java
Normal file
441
src/gui/MainWindow.java
Normal file
|
@ -0,0 +1,441 @@
|
|||
package gui;
|
||||
|
||||
|
||||
import actions.ExpertModeAction;
|
||||
import actions.ImportFileAction;
|
||||
import actions.QuitAction;
|
||||
import actions.ShowInternalFrameAction;
|
||||
import generictools.Pair;
|
||||
import gui.graphs.GraphicView;
|
||||
import processing.AudioInputToBuffer;
|
||||
import processing.ProcessControl;
|
||||
import processing.buffer.Buffer;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* La fenêtre principale du programme.
|
||||
* @author Gabriel Augendre <gabriel.augendre@insa-lyon.fr>
|
||||
*/
|
||||
class MainWindow {
|
||||
|
||||
// Initialize constants
|
||||
final private int INITIAL_WIDTH_NON_EXPERT = 700;
|
||||
final private int INITIAL_HEIGHT_NON_EXPERT = 120;
|
||||
final private int INITIAL_WIDTH_EXPERT = 1200;
|
||||
final private int INITIAL_HEIGHT_EXPERT = 500;
|
||||
final private boolean EXPERT_MODE_ENABLED_BY_DEFAULT = true;
|
||||
|
||||
private final ExpertModeAction expertModeAction = new ExpertModeAction(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
private final JPanel mainPanel = new JPanel(new BorderLayout(2,2));
|
||||
private final JFrame frame = new JFrame();
|
||||
private final JMenuBar menuBar = new JMenuBar();
|
||||
private final JMenu fileMenu = new JMenu("Fichier");
|
||||
private final JToolBar toolBar = new JToolBar();
|
||||
private final JMenu displayMenu = new JMenu("Affichage");
|
||||
private final JComboBox<String> sourceList = new JComboBox<>();
|
||||
private final JButton computeButton = new JButton("Compute");
|
||||
private final JButton stopRecordButton = new JButton("Stop recording");
|
||||
private final JButton stopComputeButton = new JButton("Stop computing");
|
||||
private final JButton resetButton = new JButton("Reset");
|
||||
private final JButton enableExpertMode = new JButton(expertModeAction);
|
||||
private final ImportFileAction importFileAction = new ImportFileAction(frame, sourceList);
|
||||
private final JButton targetFileButton = new JButton(importFileAction);
|
||||
private final JDesktopPane expertDisplay = new JDesktopPane();
|
||||
private final ArrayList<Pair<JInternalFrame, Integer>> iFrameList = new ArrayList<>();
|
||||
private final TimeSlider slider = new TimeSlider();
|
||||
private final QuitAction quitAction = new QuitAction(frame);
|
||||
private final JMenuItem importItem = new JMenuItem(importFileAction);
|
||||
private final JMenuItem quitItem = new JMenuItem(quitAction);
|
||||
|
||||
private final JMenuItem exportItem = new JMenuItem(new AbstractAction("Exporter") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ProcessControl.instance().launchPostProcess();
|
||||
}
|
||||
});
|
||||
|
||||
private final WindowListener exitListener = new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
super.windowClosing(e);
|
||||
int confirm = JOptionPane.showOptionDialog(null,
|
||||
"Êtes-vous certain de vouloir fermer la fenêtre ?\nToutes les modifications seront perdues.",
|
||||
"Confirmer la sortie", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null);
|
||||
if (confirm == 0) {
|
||||
ProcessControl.instance().kill();
|
||||
frame.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set Look and Feel (Mac OS X > Nimbus > System)
|
||||
static {
|
||||
try {
|
||||
boolean b = true;
|
||||
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
|
||||
UIManager.setLookAndFeel(info.getClassName());
|
||||
if ("Mac OS X".equals(info.getName())) {
|
||||
b = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (b) {
|
||||
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
|
||||
if ("Nimbus".equals(info.getName())) {
|
||||
UIManager.setLookAndFeel(info.getClassName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception d) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Exception f) {
|
||||
f.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur par défaut, le seul.
|
||||
*/
|
||||
private MainWindow() {
|
||||
|
||||
|
||||
createMainFrame();
|
||||
createExpertDisplay();
|
||||
createToolBar();
|
||||
createMenuBar();
|
||||
|
||||
createMagicWindow();
|
||||
|
||||
frame.setVisible(true);
|
||||
mainPanel.repaint();
|
||||
|
||||
|
||||
//ProcessControl.instance().clockTick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Création de la fenêtre principale.
|
||||
*/
|
||||
private void createMainFrame() {
|
||||
frame.setTitle("Reversound");
|
||||
|
||||
// The size depends on the expert mode state
|
||||
frame.setSize(
|
||||
EXPERT_MODE_ENABLED_BY_DEFAULT?INITIAL_WIDTH_EXPERT:INITIAL_WIDTH_NON_EXPERT,
|
||||
EXPERT_MODE_ENABLED_BY_DEFAULT?INITIAL_HEIGHT_EXPERT:INITIAL_HEIGHT_NON_EXPERT);
|
||||
if (EXPERT_MODE_ENABLED_BY_DEFAULT) frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
|
||||
// Content for this frame> <
|
||||
frame.setContentPane(mainPanel);
|
||||
|
||||
// Do nothing on close
|
||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
// A menu bar (because we need it) ;-)
|
||||
frame.setJMenuBar(menuBar);
|
||||
|
||||
// Resizable depends on the expert mode state
|
||||
frame.setResizable(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
|
||||
// Define what we do if expert mode is enabled or disabled
|
||||
expertModeAction.addPropertyChangeListener(evt -> {
|
||||
if(evt.getPropertyName().equals(ExpertModeAction.ACTIVE)){
|
||||
frame.setResizable((Boolean)evt.getNewValue());
|
||||
if (!(Boolean)evt.getNewValue()) {
|
||||
frame.setSize(INITIAL_WIDTH_NON_EXPERT, INITIAL_HEIGHT_NON_EXPERT);
|
||||
} else {
|
||||
frame.setSize(INITIAL_WIDTH_EXPERT, INITIAL_HEIGHT_EXPERT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Set close operation
|
||||
frame.addWindowListener(exitListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the toolbar and source list.
|
||||
*/
|
||||
private void createToolBar() {
|
||||
|
||||
// Put data into source list
|
||||
sourceList.addItem("Micro");
|
||||
sourceList.setPrototypeDisplayValue("Fichier");
|
||||
|
||||
// Transmettre au contrôleur la source choisie.
|
||||
sourceList.addItemListener(e -> {
|
||||
final int INDEX = sourceList.getSelectedIndex();
|
||||
System.out.println(sourceList.getItemAt(INDEX));
|
||||
|
||||
if(INDEX == 0)
|
||||
ProcessControl.instance().setInput(ProcessControl.instance().getMainInputID(), AudioInputToBuffer.fromMicro());
|
||||
else
|
||||
ProcessControl.instance().setInput(
|
||||
ProcessControl.instance().getMainInputID(),
|
||||
AudioInputToBuffer.fromFile(
|
||||
(File) (importFileAction.getValue(ImportFileAction.FILE))
|
||||
));
|
||||
});
|
||||
|
||||
//Modifie légèrement le look des boutons, surtout sur Mac OS X
|
||||
computeButton.putClientProperty("JButton.buttonType", "textured");
|
||||
stopRecordButton.putClientProperty("JButton.buttonType", "segmentedTextured");
|
||||
stopRecordButton.putClientProperty("JButton.segmentPosition", "first");
|
||||
stopComputeButton.putClientProperty("JButton.buttonType", "segmentedTextured");
|
||||
stopComputeButton.putClientProperty("JButton.segmentPosition", "last");
|
||||
targetFileButton.putClientProperty("JButton.buttonType", "textured");
|
||||
resetButton.putClientProperty("JButton.buttonType", "textured");
|
||||
|
||||
// Add elements to toolBar
|
||||
toolBar.add(computeButton);
|
||||
toolBar.add(stopRecordButton);
|
||||
toolBar.add(stopComputeButton);
|
||||
toolBar.add(resetButton);
|
||||
toolBar.add(sourceList);
|
||||
toolBar.add(targetFileButton);
|
||||
|
||||
// Configure le bouton reset
|
||||
resetButton.setToolTipText("Réinitialise le programme à son état d'origine.");
|
||||
resetButton.addActionListener(e -> reset());
|
||||
|
||||
// On n'affiche pas les boutons stop au début.
|
||||
setStopButtonsState(false);
|
||||
|
||||
// On relie les boutons au contrôle.
|
||||
computeButton.addActionListener(e -> {
|
||||
ProcessControl.instance().start();
|
||||
setStopButtonsState(true);
|
||||
stopRecordButton.setText("Stop recording");
|
||||
});
|
||||
stopRecordButton.addActionListener(e -> {
|
||||
if (stopRecordButton.getText().equals("Stop recording")) {
|
||||
stopRecordButton.setText("Record");
|
||||
ProcessControl.instance().stopRecord();
|
||||
} else {
|
||||
stopRecordButton.setText("Stop recording");
|
||||
ProcessControl.instance().start();
|
||||
}
|
||||
});
|
||||
stopComputeButton.addActionListener(e -> {
|
||||
ProcessControl.instance().stop();
|
||||
setStopButtonsState(false);
|
||||
});
|
||||
|
||||
// Add button to enable/disable the expert mode
|
||||
{
|
||||
setExpertButtonText(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
|
||||
expertModeAction.addPropertyChangeListener(evt -> {
|
||||
if(!evt.getPropertyName().equals(ExpertModeAction.ACTIVE))return;
|
||||
setExpertButtonText((boolean)evt.getNewValue());
|
||||
});
|
||||
|
||||
//enableExpertMode.setSelected(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
enableExpertMode.putClientProperty("JButton.buttonType", "textured");
|
||||
|
||||
toolBar.add(enableExpertMode);
|
||||
}
|
||||
|
||||
// Set not draggable
|
||||
toolBar.setFloatable(false);
|
||||
|
||||
// Add the toolbar to the main panel
|
||||
mainPanel.add(toolBar,BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the menus and addNote all elements.
|
||||
*/
|
||||
private void createMenuBar() {
|
||||
|
||||
// Setup the file menu
|
||||
{
|
||||
fileMenu.setMnemonic(KeyEvent.VK_F);
|
||||
|
||||
// Add items : Import, quit
|
||||
{
|
||||
//importItem.setText("Importer"); //Importer, sous-menu de Fichier
|
||||
importItem.setMnemonic(KeyEvent.VK_I);
|
||||
fileMenu.add(importItem);
|
||||
exportItem.setMnemonic(KeyEvent.VK_E);
|
||||
fileMenu.add(exportItem);
|
||||
quitItem.setMnemonic(KeyEvent.VK_Q);
|
||||
fileMenu.add(quitItem);
|
||||
}
|
||||
|
||||
menuBar.add(fileMenu);
|
||||
}
|
||||
|
||||
// Setup the display menu
|
||||
{
|
||||
displayMenu.setMnemonic(KeyEvent.VK_A);
|
||||
|
||||
// Add an item : Enable expert mode
|
||||
{
|
||||
final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(expertModeAction);
|
||||
menuItem.setSelected(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
displayMenu.add(menuItem);
|
||||
expertModeAction.addPropertyChangeListener(evt -> {
|
||||
if(!evt.getPropertyName().equals(ExpertModeAction.ACTIVE))return;
|
||||
menuItem.setSelected((boolean)evt.getNewValue());
|
||||
});
|
||||
}
|
||||
|
||||
menuBar.add(displayMenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the expert display.
|
||||
*/
|
||||
private void createExpertDisplay() {
|
||||
// Hide or show the pane if mode enabled by default.
|
||||
expertDisplay.setVisible(EXPERT_MODE_ENABLED_BY_DEFAULT);
|
||||
|
||||
// Add the pane to the main panel
|
||||
mainPanel.add(expertDisplay, BorderLayout.CENTER);
|
||||
|
||||
// When the expertMode is enabled, display the panel
|
||||
expertModeAction.addPropertyChangeListener(evt -> {
|
||||
if(evt.getPropertyName().equals(ExpertModeAction.ACTIVE)) {
|
||||
expertDisplay.setVisible((Boolean) evt.getNewValue());
|
||||
}
|
||||
});
|
||||
|
||||
mainPanel.add(slider, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a buffer in a JInternalFrame. If the frame was previously created, show it. Else, create a new one.
|
||||
* @param bufferID The ID of the buffer to be displayed.
|
||||
*/
|
||||
public void displayBuffer(int bufferID) {
|
||||
for (Pair<JInternalFrame, Integer> paire : iFrameList) {
|
||||
JInternalFrame iFr = paire.first();
|
||||
int bID = paire.second();
|
||||
|
||||
if (bID == bufferID) {
|
||||
if (!iFr.isVisible()) {
|
||||
expertDisplay.add(iFr);
|
||||
iFr.setVisible(true);
|
||||
}
|
||||
try {
|
||||
iFr.setSelected(true);
|
||||
} catch (PropertyVetoException e1) {
|
||||
e1.printStackTrace(System.err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
Buffer buffer = ProcessControl.instance().getBuffer(bufferID);
|
||||
final JInternalFrame iFrame = new JInternalFrame();
|
||||
|
||||
GraphicView spectreView = GraphicView.createGraphicView(buffer,true);
|
||||
addInternalFrame(iFrame, spectreView.getName(), bufferID, spectreView);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute la fenêtre de contrôle à l'affichage Expert.
|
||||
* Appelle addInternalFrame(final JInternalFrame IFRAME, final String TITLE, final GraphicView CONTENT).
|
||||
*/
|
||||
private void createMagicWindow() {
|
||||
JInternalFrame iFrame = new JInternalFrame();
|
||||
GraphicView v = new ProcessSumUp(this);
|
||||
addInternalFrame(iFrame, v.getName(), v);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute une fenêtre à l'affichage Expert et la configure selon les besoins de l'application.
|
||||
* @param IFRAME La fenêtre à ajouter.
|
||||
* @param TITLE Le titre de la fenêtre.
|
||||
* @param BUFFER_ID L'identifiant du buffer à ajouter à la fenêtre pour l'affichage.
|
||||
* @param CONTENT Le contenu de la fenêtre.
|
||||
*/
|
||||
private void addInternalFrame(final JInternalFrame IFRAME, final String TITLE, final int BUFFER_ID, final GraphicView CONTENT) {
|
||||
IFRAME.setTitle(TITLE);
|
||||
iFrameList.add(new Pair<>(IFRAME, BUFFER_ID));
|
||||
IFRAME.setVisible(true);
|
||||
expertDisplay.add(IFRAME);
|
||||
final int WINDOW_WIDTH = 350;
|
||||
IFRAME.setBounds((iFrameList.size() - 2) * 100 + 5, 310, WINDOW_WIDTH, 300);
|
||||
IFRAME.setResizable(true);
|
||||
IFRAME.setClosable(true);
|
||||
IFRAME.setMaximizable(true);
|
||||
IFRAME.setContentPane(CONTENT);
|
||||
displayMenu.add(new ShowInternalFrameAction(expertDisplay, IFRAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appelle addInternalFrame avec un BUFFER_ID de -1 (pour la fenêtre de contrôle uniquement.
|
||||
* @param IFRAME La fenêtre à ajouter.
|
||||
* @param TITLE Le titre de la fenêtre.
|
||||
* @param CONTENT Le contenu de la fenêtre.
|
||||
*/
|
||||
private void addInternalFrame(final JInternalFrame IFRAME, final String TITLE, final GraphicView CONTENT) {
|
||||
addInternalFrame(IFRAME, TITLE, -1, CONTENT);
|
||||
IFRAME.setClosable(false);
|
||||
final int WINDOW_WIDTH = (int)GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getWidth();
|
||||
IFRAME.setBounds(5,5,WINDOW_WIDTH-10,300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change l'état des boutons stop, compute et de la liste des sources de l'application.
|
||||
* @param state False si on souhaite cacher les boutons stop, true sinon.
|
||||
*/
|
||||
private void setStopButtonsState(final boolean state) {
|
||||
stopRecordButton.setVisible(state);
|
||||
stopRecordButton.setEnabled(state);
|
||||
stopComputeButton.setVisible(state);
|
||||
stopComputeButton.setEnabled(state);
|
||||
computeButton.setVisible(!state);
|
||||
computeButton.setEnabled(!state);
|
||||
sourceList.setVisible(!state);
|
||||
sourceList.setEnabled(!state);
|
||||
targetFileButton.setVisible(!state);
|
||||
targetFileButton.setEnabled(!state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le texte du bouton Expert en fonction d'un booléen.
|
||||
* @param b True si le mode expert est désactivé, false sinon.
|
||||
*/
|
||||
private void setExpertButtonText(boolean b) {
|
||||
enableExpertMode.setText(
|
||||
!b?
|
||||
"Activer le mode expert":
|
||||
"Désactiver le mode expert"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise l'application à son état d'origine.
|
||||
*/
|
||||
private void reset() {
|
||||
if (sourceList.getItemCount() > 1)
|
||||
sourceList.removeItemAt(1);
|
||||
sourceList.setPrototypeDisplayValue("Fichier");
|
||||
ProcessControl.instance().reset();
|
||||
setStopButtonsState(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter point to run this program.
|
||||
* @param args None
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
new MainWindow();
|
||||
}
|
||||
}
|
220
src/gui/PlayerControl.java
Normal file
220
src/gui/PlayerControl.java
Normal file
|
@ -0,0 +1,220 @@
|
|||
package gui;
|
||||
|
||||
import generictools.Instant;
|
||||
import processing.BufferPlayer;
|
||||
import processing.ProcessControl;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 12/03/14.
|
||||
* Controleur de lecture
|
||||
*
|
||||
* Il s'assure que toutes les vues soient synchronisées et est capable de démarrer, mettre en pause ou arrêter la lecture
|
||||
*/
|
||||
|
||||
public class PlayerControl{
|
||||
|
||||
public enum PlayingState{
|
||||
PLAYING,
|
||||
PAUSED,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
private PlayingState playingState;
|
||||
private Instant frame = new Instant(0);
|
||||
|
||||
private final ArrayList<Listener> views = new ArrayList<>();
|
||||
|
||||
private BufferPlayer player = new BufferPlayer(ProcessControl.instance().getMainInput().getBuffer());
|
||||
|
||||
static private PlayerControl pC = new PlayerControl();
|
||||
|
||||
/**
|
||||
* Retourne l'instance du singleton du PlayerControl
|
||||
* @return singleton du PlayerControl
|
||||
*/
|
||||
static public PlayerControl instance(){return pC;}
|
||||
|
||||
private PlayerControl() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Propage l'information "play" à travers toutes les vues controlées.
|
||||
*/
|
||||
public void play() {
|
||||
player.play();
|
||||
playingState = PlayingState.PLAYING;
|
||||
|
||||
emit(PlayerControlEvent.Type.PLAY_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Propage l'information "play" à travers toutes les vues controlées.
|
||||
* @param frame La frame depuis laquelle partir.
|
||||
*/
|
||||
public void play(Instant frame) {
|
||||
setFrame(frame);
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Propage l'information "pause" à travers toutes les vues controlées.
|
||||
*/
|
||||
public void pause() {
|
||||
player.pause();
|
||||
playingState = PlayingState.PAUSED;
|
||||
|
||||
emit(PlayerControlEvent.Type.PLAY_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Propage l'information "interrupt" à travers toutes les vues controlées.
|
||||
*/
|
||||
public void stop() {
|
||||
player.pause();
|
||||
setFrame(Instant.fromIndex(0,ProcessControl.instance().getMainInput().getBuffer()));
|
||||
playingState = PlayingState.STOPPED;
|
||||
emit(PlayerControlEvent.Type.PLAY_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'état du player (si il est en lecture, en pause ou arrêté
|
||||
* @return l'état du lecteur
|
||||
*/
|
||||
public PlayingState getPlayingState(){return playingState;}
|
||||
|
||||
|
||||
public void mute(){
|
||||
player.setMuted(true);
|
||||
}
|
||||
public void unMute(){
|
||||
player.setMuted(false);
|
||||
}
|
||||
public boolean isMute(){
|
||||
return player.isMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bascule l'état de mute: si muted unmute, sinon mute
|
||||
*/
|
||||
public void toggleMute(){
|
||||
if(isMute())
|
||||
unMute();
|
||||
else
|
||||
mute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie le volume du player
|
||||
* @param volume 0 = mute, 1 = fullVolume, 2 = saturation permanente...
|
||||
*/
|
||||
public void setVolume(float volume){
|
||||
player.setVolume(volume);
|
||||
}
|
||||
|
||||
public float getVolume(){
|
||||
return player.getVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force la position dans le buffer de référence, l'information est propagé à toutes les vues et lecteurs controlés
|
||||
* @param frame
|
||||
*/
|
||||
public void setFrame(Instant frame){
|
||||
player.setFrame(frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode appelée par le lecteur lorsque la position actuelle à changé
|
||||
* @param frame nouvelle poition
|
||||
*/
|
||||
public void frameChanged(Instant frame){
|
||||
this.frame = frame;
|
||||
emit(PlayerControlEvent.Type.FRAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la position actuelle dans le buffer de référence
|
||||
* @return position actuelle
|
||||
*/
|
||||
public Instant getFrame(){return frame;}
|
||||
|
||||
/**
|
||||
* Raccourci statique de getFrame()
|
||||
* @return position actuelle
|
||||
*/
|
||||
public static Instant frame(){return PlayerControl.instance().getFrame();}
|
||||
|
||||
public void setBuffer(TemporalBuffer<Float> buffer){
|
||||
player = new BufferPlayer(buffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajoute une vue au PlayerControl.
|
||||
*/
|
||||
public boolean addListener(Listener listener) {
|
||||
if(views.contains(listener))
|
||||
return false;
|
||||
|
||||
views.add(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une vue au PlayerControl
|
||||
* @param listener vue à supprimer
|
||||
* @return Faux si la vue n'était pas ajoutée
|
||||
*/
|
||||
public boolean removeListener(Listener listener){
|
||||
if(!views.contains(listener))
|
||||
return false;
|
||||
|
||||
views.remove(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void emit(PlayerControlEvent.Type type){
|
||||
PlayerControlEvent e = new PlayerControlEvent(type);
|
||||
for(Listener l: views)
|
||||
l.playerControlEvent(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface définissant les objets capable de recevoir les évenements de lecture
|
||||
*/
|
||||
public interface Listener{
|
||||
/**
|
||||
* Traite l'évenement de lecture
|
||||
* @param e évenement
|
||||
*/
|
||||
public default void playerControlEvent(PlayerControlEvent e) {
|
||||
switch (e.getType()){
|
||||
case FRAME:
|
||||
break;
|
||||
|
||||
case PLAY_STATE:
|
||||
switch (e.getPlayingState()){
|
||||
case PLAYING:
|
||||
break;
|
||||
case PAUSED:
|
||||
break;
|
||||
case STOPPED:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case VOLUME:
|
||||
break;
|
||||
|
||||
case MUTE_STATE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
src/gui/PlayerControlEvent.java
Normal file
57
src/gui/PlayerControlEvent.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package gui;
|
||||
|
||||
import generictools.Instant;
|
||||
|
||||
/**
|
||||
* Created by gaby on 08/05/14.
|
||||
*/
|
||||
public class PlayerControlEvent {
|
||||
public enum Type{
|
||||
FRAME,
|
||||
PLAY_STATE,
|
||||
VOLUME,
|
||||
MUTE_STATE
|
||||
}
|
||||
private final Type type;
|
||||
|
||||
private final Instant frame;
|
||||
private final PlayerControl.PlayingState playingState;
|
||||
private final float volume;
|
||||
private final boolean isMuted;
|
||||
|
||||
public PlayerControlEvent(Type type, Instant frame, PlayerControl.PlayingState playingState, float volume, boolean isMuted) {
|
||||
this.type = type;
|
||||
this.frame = frame;
|
||||
this.playingState = playingState;
|
||||
this.volume = volume;
|
||||
this.isMuted = isMuted;
|
||||
}
|
||||
public PlayerControlEvent(Type type){
|
||||
this.type = type;
|
||||
frame = PlayerControl.instance().getFrame();
|
||||
playingState = PlayerControl.instance().getPlayingState();
|
||||
volume = PlayerControl.instance().getVolume();
|
||||
isMuted = PlayerControl.instance().isMute();
|
||||
}
|
||||
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Instant getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
public PlayerControl.PlayingState getPlayingState() {
|
||||
return playingState;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return isMuted;
|
||||
}
|
||||
}
|
190
src/gui/ProcessBox.java
Normal file
190
src/gui/ProcessBox.java
Normal file
|
@ -0,0 +1,190 @@
|
|||
package gui;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import processing.processes.Process;
|
||||
import processing.processes.ProcessEvent;
|
||||
|
||||
/**
|
||||
* Définit la box d'un process qui aura des contrôles particuliers et différents d'une InputBox
|
||||
*/
|
||||
public class ProcessBox extends Box implements Process.Listener {
|
||||
private final Process<?> process;
|
||||
private final Button startStopButton = new Button();
|
||||
private final ProgressIndicator spinningWheel = new ProgressIndicator(0);
|
||||
private final ProgressBar progressBar = new ProgressBar(0);
|
||||
private final Button recomputeButton = new Button();
|
||||
|
||||
private final String playChar = "▶";
|
||||
private final String pauseChar = "||";
|
||||
private final String resetChar = "↻";
|
||||
|
||||
private boolean pauseState = false;
|
||||
private boolean waitingState = false;
|
||||
private boolean progressState = false;
|
||||
private boolean nameState = false;
|
||||
private boolean interruptState = false;
|
||||
|
||||
private GridPane gridPane = new GridPane();
|
||||
private ScrollPane scrollPane = new ScrollPane(gridPane);
|
||||
|
||||
/**
|
||||
* Constructeur.
|
||||
* @param process Le processus à associer à la box.
|
||||
*/
|
||||
public ProcessBox(Process process) {
|
||||
super(Color.LIGHTYELLOW, new Text(process.getName()));
|
||||
this.process = process;
|
||||
|
||||
super.titre.setX(70);
|
||||
|
||||
//On s'occupe d'abord du bouton de play/pause du process
|
||||
startStopButton.setText(playChar);
|
||||
this.getChildren().add(startStopButton);
|
||||
startStopButton.setTranslateX(5);
|
||||
startStopButton.setTranslateY(5);
|
||||
startStopButton.setPrefWidth(25);
|
||||
startStopButton.setMaxWidth(25);
|
||||
startStopButton.setOnAction(actionEvent -> {
|
||||
if (startStopButton.getText().equals(playChar)) {
|
||||
process.start();
|
||||
startStopButton.setText(pauseChar);
|
||||
}
|
||||
else {
|
||||
process.pause();
|
||||
startStopButton.setText(playChar);
|
||||
}
|
||||
});
|
||||
process.addListener(this);
|
||||
|
||||
recomputeButton.setText(resetChar);
|
||||
this.getChildren().add(recomputeButton);
|
||||
recomputeButton.setTranslateX(35);
|
||||
recomputeButton.setTranslateY(5);
|
||||
recomputeButton.setPrefWidth(25);
|
||||
recomputeButton.setMaxWidth(25);
|
||||
recomputeButton.setOnAction(actionEvent -> process.recomputeAll());
|
||||
|
||||
//Ensuite de la roue qui tourne
|
||||
this.getChildren().add(spinningWheel);
|
||||
spinningWheel.setTranslateY(7);
|
||||
spinningWheel.setTranslateX(Box.NODE_WIDTH - 50);
|
||||
spinningWheel.setMaxHeight(45);
|
||||
spinningWheel.getStylesheets().add(ProcessBox.class.getResource("ProgressIndicator.css").toExternalForm());
|
||||
|
||||
//Et enfin de la progress bar
|
||||
this.getChildren().add(progressBar);
|
||||
progressBar.setTranslateY(40);
|
||||
progressBar.setTranslateX(5);
|
||||
progressBar.setPrefWidth(Box.NODE_WIDTH - 10);
|
||||
progressBar.getStylesheets().add(ProcessBox.class.getResource("ProgressBar.css").toExternalForm());
|
||||
|
||||
this.getChildren().add(scrollPane);
|
||||
scrollPane.setTranslateX(5);
|
||||
scrollPane.setTranslateY(50);
|
||||
scrollPane.setPrefSize(Box.NODE_WIDTH-10, Box.NODE_HEIGHT-55);
|
||||
|
||||
gridPane.setPrefWidth(Box.NODE_WIDTH-40);
|
||||
gridPane.setTranslateX(5);
|
||||
gridPane.setTranslateY(5);
|
||||
gridPane.setVgap(2);
|
||||
gridPane.setHgap(5);
|
||||
|
||||
for (int i = 0; i < process.propertyManager().getNbProperties(); i++) {
|
||||
//On ajoute le nom de la propriété à la colonne 0 ligne i
|
||||
gridPane.add(new Text(process.propertyManager().getProperty(i).getName()), 0, i);
|
||||
//Et le node correspondant à la colonne 1 ligne i
|
||||
gridPane.add(process.propertyManager().getProperty(i).createEditNode(), 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie un objet Process.
|
||||
* @return Le process associé à la box.
|
||||
*/
|
||||
public Process<?> getProcess() {
|
||||
return process;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void processEvent(ProcessEvent event) {
|
||||
switch (event.getType()) {
|
||||
case PAUSED_STATE_CHANGED:
|
||||
pauseState = true;
|
||||
break;
|
||||
case WAITING_STATE:
|
||||
waitingState = true;
|
||||
break;
|
||||
case PROGRESS_CHANGED:
|
||||
progressState = true;
|
||||
break;
|
||||
case NAME_CHANGED:
|
||||
nameState = true;
|
||||
break;
|
||||
case INTERRUPT_AFTER:
|
||||
interruptState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change l'état de la roue d'indication de travail en cours.
|
||||
* @param b true si la roue doit tourner, false sinon
|
||||
*/
|
||||
private void setSpinState(boolean b) {
|
||||
if (b) {
|
||||
spinningWheel.setProgress(0);
|
||||
spinningWheel.setMaxHeight(45);
|
||||
}
|
||||
else {
|
||||
spinningWheel.setProgress(-1);
|
||||
spinningWheel.setMaxHeight(25);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProgress(double d) {
|
||||
progressBar.setProgress(d);
|
||||
}
|
||||
|
||||
private void setName(String s) {this.getTitre().setText(s);}
|
||||
|
||||
private void setButton(boolean b) {
|
||||
if (b) {
|
||||
startStopButton.setText(playChar);
|
||||
} else {
|
||||
startStopButton.setText(pauseChar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les différents états en fonction des événements qui ont déjà eu lieu.
|
||||
* Permet de différer la mise à jour et de ne pas la faire à chaque nouvel événement.
|
||||
*/
|
||||
public void update() {
|
||||
if (pauseState) {
|
||||
pauseState = false;
|
||||
setButton(process.isPaused() || !process.isRunning());
|
||||
}
|
||||
if (waitingState) {
|
||||
waitingState = false;
|
||||
setSpinState(process.isWaiting());
|
||||
}
|
||||
if (progressState) {
|
||||
progressState = false;
|
||||
setProgress(process.getProgress());
|
||||
}
|
||||
if (nameState) {
|
||||
nameState = false;
|
||||
setName(process.getName());
|
||||
}
|
||||
if (interruptState) {
|
||||
interruptState = false;
|
||||
}
|
||||
}
|
||||
}
|
167
src/gui/ProcessSumUp.java
Normal file
167
src/gui/ProcessSumUp.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package gui;
|
||||
|
||||
import gui.graphs.GraphicView;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import processing.AudioInputToBuffer;
|
||||
import processing.ProcessControl;
|
||||
import processing.processes.Process;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Stocke les inputs et les process pour les afficher dans une fenêtre et permettre leur contrôle.
|
||||
*/
|
||||
public class ProcessSumUp extends GraphicView {
|
||||
|
||||
//TODO: Contrôler les propriétés des traitements (right clic)
|
||||
|
||||
private final MainWindow mW;
|
||||
|
||||
protected static final int NODE_WIDTH = 300;
|
||||
protected static final int NODE_HEIGHT = 200;
|
||||
protected static final int TEXT_LENGTH = NODE_WIDTH/10-9;
|
||||
|
||||
private final StackPane group = new StackPane();
|
||||
private final ScrollPane scrollPane = new ScrollPane(group);
|
||||
private final GridPane grid = new GridPane();
|
||||
private final Text frameRateLabel = new Text(10,10,"");
|
||||
|
||||
private final ArrayList<Box> boxList = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Constructeur.
|
||||
* @param mainWindow L'instance de fenêtre principale du programme.
|
||||
*/
|
||||
public ProcessSumUp(MainWindow mainWindow) {
|
||||
super("Fenêtre de contrôle");
|
||||
initView();
|
||||
ProcessControl.instance().setProcessesView(this);
|
||||
mW = mainWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisation de la vue.
|
||||
*/
|
||||
public void initView() {
|
||||
Platform.runLater(() -> {
|
||||
setScene(new Scene(scrollPane, Color.WHITE));
|
||||
frameRateLabel.setFont(new Font("Candara", 5));
|
||||
frameRateLabel.setFill(Color.AZURE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mise à jour de la vue.
|
||||
*/
|
||||
public void updateView(){
|
||||
Platform.runLater(this::generateGraph);
|
||||
}
|
||||
|
||||
private void generateGraph() {
|
||||
grid.getChildren().clear();
|
||||
group.getChildren().clear();
|
||||
group.getChildren().add(grid);
|
||||
|
||||
|
||||
for (int i = 0; i < ProcessControl.instance().getInputNbr(); i++) {
|
||||
final AudioInputToBuffer input = ProcessControl.instance().getInput(i);
|
||||
final InputBox inputNode = createInputNode(input);
|
||||
|
||||
emptyBoxList();
|
||||
|
||||
setSuccessors(inputNode);
|
||||
inputNode.generateSuccCoord(0, i, boxList);
|
||||
for (int j = 0; j < boxList.size(); j++) {
|
||||
Box b = boxList.get(j);
|
||||
grid.add(b, b.getCoordinates().first(), b.getCoordinates().second());
|
||||
/*if (j < boxList.size()-1) {
|
||||
group.getChildren().addNote(new Connector(b, boxList.get(j+1)));
|
||||
group.getChildren().addNote(new Line(Box.NODE_WIDTH, Box.NODE_HEIGHT/2, Box.NODE_WIDTH+20, Box.NODE_WIDTH/2));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
group.getChildren().add(frameRateLabel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mise à jour du contenu des noeuds du panneau de contrôle.
|
||||
* @param frameRate Le taux de rafraîchissement.
|
||||
*/
|
||||
public void update(Float frameRate){
|
||||
Platform.runLater(() -> {
|
||||
frameRateLabel.setText(frameRate.toString() + " fps");
|
||||
boxList.stream().filter(b -> b instanceof ProcessBox).forEach(b -> {
|
||||
ProcessBox pb = (ProcessBox) b;
|
||||
pb.update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void emptyBoxList() {
|
||||
boxList.stream().filter(b -> b instanceof ProcessBox).forEach(b -> {
|
||||
ProcessBox pb = (ProcessBox) b;
|
||||
pb.getProcess().setBoxDisplayed(false);
|
||||
});
|
||||
boxList.clear();
|
||||
}
|
||||
|
||||
private void setSuccessors(InputBox box) {
|
||||
for (Process p : box.getInput().getBuffer().getUsedBy()) {
|
||||
setSuccessors(p, box);
|
||||
}
|
||||
}
|
||||
private void setSuccessors(ProcessBox box) {
|
||||
for (Process p : box.getProcess().getOutput().getUsedBy()) {
|
||||
setSuccessors(p, box);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSuccessors(Process p, Box box) {
|
||||
ProcessBox processBox = createProcessNode(p);
|
||||
box.addSuccessor(processBox);
|
||||
setSuccessors(processBox);
|
||||
}
|
||||
|
||||
private InputBox createInputNode(final AudioInputToBuffer input){
|
||||
InputBox g = new InputBox(input);
|
||||
|
||||
for (Node n : g.getClickableElements()) {
|
||||
n.addEventFilter(MouseEvent.MOUSE_CLICKED,
|
||||
e -> {
|
||||
mW.displayBuffer(ProcessControl.instance().getBufferIndex(input.getBuffer()));
|
||||
e.consume();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
private ProcessBox createProcessNode(final Process process){
|
||||
ProcessBox g = new ProcessBox(process);
|
||||
|
||||
for (Node n : g.getClickableElements()) {
|
||||
n.addEventFilter(MouseEvent.MOUSE_CLICKED,
|
||||
e -> {
|
||||
mW.displayBuffer(ProcessControl.instance().getBufferIndex(process.getOutput()));
|
||||
e.consume();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
}
|
1
src/gui/ProgressBar.css
Normal file
1
src/gui/ProgressBar.css
Normal file
|
@ -0,0 +1 @@
|
|||
.progress-bar .bar {-fx-padding:3px; -fx-background-insets:0;}
|
1
src/gui/ProgressIndicator.css
Normal file
1
src/gui/ProgressIndicator.css
Normal file
|
@ -0,0 +1 @@
|
|||
.progress-indicator .percentage {-fx-font-size: 0.01em;}
|
162
src/gui/TimeSlider.java
Normal file
162
src/gui/TimeSlider.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
package gui;
|
||||
|
||||
import generictools.Instant;
|
||||
import gui.viewer_state.BufferViewerState;
|
||||
import gui.viewer_state.Viewer;
|
||||
import processing.ProcessControl;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
/**
|
||||
* Un panel pour le contrôle du volum et du temps.
|
||||
*/
|
||||
public class TimeSlider extends JPanel implements PlayerControl.Listener, Viewer {
|
||||
|
||||
private boolean sliderUnlocked = false;
|
||||
private final PlayerControl control = PlayerControl.instance();
|
||||
|
||||
private final JSlider slider;
|
||||
private final JLabel posLabel;
|
||||
private final JButton playPause;
|
||||
private final JButton volUp = new JButton("+");
|
||||
private final JButton volDown = new JButton("-");
|
||||
private final JButton mute = new JButton("Mute");
|
||||
|
||||
/**
|
||||
* Génère le panel.
|
||||
*/
|
||||
public TimeSlider(){
|
||||
super();
|
||||
|
||||
BoxLayout layout = new BoxLayout(this, BoxLayout.LINE_AXIS);
|
||||
this.setLayout(layout);
|
||||
|
||||
playPause = new JButton("Play");
|
||||
add(playPause);
|
||||
|
||||
add(volDown);
|
||||
volDown.addActionListener(e -> PlayerControl.instance().setVolume(PlayerControl.instance().getVolume() - 0.1f));
|
||||
|
||||
add(volUp);
|
||||
volUp.addActionListener(e -> PlayerControl.instance().setVolume(PlayerControl.instance().getVolume() + 0.1f));
|
||||
|
||||
add(mute);
|
||||
mute.addActionListener(e -> {
|
||||
if (PlayerControl.instance().isMute()) {
|
||||
PlayerControl.instance().unMute();
|
||||
mute.setText("Mute");
|
||||
}
|
||||
else {
|
||||
PlayerControl.instance().mute();
|
||||
mute.setText("Unmute");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
slider = new JSlider(0,1,0);
|
||||
slider.setPreferredSize(new Dimension(300,25));
|
||||
add(slider);
|
||||
|
||||
posLabel = new JLabel();
|
||||
posLabel.setBorder(BorderFactory.createLoweredBevelBorder());
|
||||
posLabel.setPreferredSize(new Dimension(120,25));
|
||||
add(posLabel);
|
||||
|
||||
|
||||
ProcessControl p = ProcessControl.instance();
|
||||
BufferViewerState.sizeSensible(this, p.getMainInput().getBuffer()).setVisibility(true);
|
||||
|
||||
control.addListener(this);
|
||||
slider.addChangeListener(e -> sliderMoved());
|
||||
slider.addMouseListener(new MouseListener() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
sliderUnlocked = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
sliderUnlocked = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {}
|
||||
});
|
||||
|
||||
playPause.addActionListener(e -> {
|
||||
if (playPause.getText().equals("Pause"))
|
||||
control.pause();
|
||||
else
|
||||
control.play();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void sliderMoved() {
|
||||
if(sliderUnlocked) {
|
||||
control.setFrame(Instant.fromIndex(slider.getValue(), ProcessControl.instance().getMainInput().getBuffer()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void playerControlEvent(PlayerControlEvent e) {
|
||||
switch (e.getType()){
|
||||
case FRAME:
|
||||
TemporalBuffer<Float> b = ProcessControl.instance().getMainInput().getBuffer();
|
||||
if(!sliderUnlocked)
|
||||
slider.setValue(e.getFrame().mapToIndex(b));
|
||||
|
||||
posLabel.setText(e.getFrame() + " / "+ Instant.fromIndex(ProcessControl.instance().getMainInput().getBuffer().size(),44100));
|
||||
break;
|
||||
|
||||
case PLAY_STATE:
|
||||
switch (e.getPlayingState()){
|
||||
case PLAYING:
|
||||
playPause.setText("Pause");
|
||||
break;
|
||||
case PAUSED:
|
||||
playPause.setText("Play");
|
||||
break;
|
||||
case STOPPED:
|
||||
playPause.setText("Play");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case MUTE_STATE:
|
||||
if(e.isMuted())
|
||||
mute.setText("Unmute");
|
||||
else
|
||||
mute.setText("Mute");
|
||||
break;
|
||||
} }
|
||||
|
||||
@Override
|
||||
public void updateView() {
|
||||
slider.setMaximum(ProcessControl.instance().getMainInput().getBuffer().size());
|
||||
posLabel.setText(posLabel.getText().substring(0, posLabel.getText().indexOf("/")+1)+ Instant.fromIndex(ProcessControl.instance().getMainInput().getBuffer().size(), 44100));
|
||||
|
||||
if(!slider.isEnabled()) {
|
||||
slider.setEnabled(true);
|
||||
PlayerControl.instance().setFrame(Instant.fromTime(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanView() {
|
||||
slider.setMaximum(1);
|
||||
slider.setEnabled(false);
|
||||
posLabel.setText(" -- / --");
|
||||
}
|
||||
}
|
65
src/gui/graphs/AbstractBufferGraph.java
Normal file
65
src/gui/graphs/AbstractBufferGraph.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import gui.PlayerControl;
|
||||
import gui.viewer_state.BufferViewerState;
|
||||
import gui.viewer_state.Viewer;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 08/05/14.
|
||||
* Classe abstraite définissant les attributs et méthode communes au graph de buffer
|
||||
*/
|
||||
public abstract class AbstractBufferGraph <T extends Buffer> extends AbstractGraph implements Viewer, PlayerControl.Listener{
|
||||
private T buffer;
|
||||
private BufferViewerState state;
|
||||
|
||||
public AbstractBufferGraph(String name, Class type) {
|
||||
super(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractGraph setupGraph(Buffer b, GraphicView graphicView, boolean syncWithPlayer){
|
||||
AbstractBufferGraph g = getGenericGraph();
|
||||
g.setGraphicView(graphicView);
|
||||
g.setBuffer(b);
|
||||
|
||||
if(syncWithPlayer) {
|
||||
PlayerControl.instance().addListener(g);
|
||||
}
|
||||
|
||||
g.setupView();
|
||||
g.setBufferViewerState(BufferViewerState.continousWindow(g, b));
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractGraph setupGraph(ObservableObject o, GraphicView graphicView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le graph est valide (qu'il n'a pas été créé via un contructeur)
|
||||
* @return Vrai si valide
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return buffer!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visibilityChange(boolean isVisible) {
|
||||
state.setVisibility(isVisible);
|
||||
}
|
||||
|
||||
abstract protected AbstractBufferGraph getGenericGraph();
|
||||
|
||||
private void setBuffer(T buffer){this.buffer = buffer;}
|
||||
private void setBufferViewerState(BufferViewerState b){state = b;}
|
||||
|
||||
public T buffer(){return buffer;}
|
||||
public BufferViewerState state(){return state;}
|
||||
}
|
98
src/gui/graphs/AbstractGraph.java
Normal file
98
src/gui/graphs/AbstractGraph.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import processing.buffer.Buffer;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 07/05/14.
|
||||
* Classe abstraite définissant les attributs et méthode communes au graph
|
||||
*/
|
||||
public abstract class AbstractGraph {
|
||||
|
||||
protected final String name;
|
||||
private final Class type;
|
||||
private GraphicView graphicView;
|
||||
private boolean visibility = false;
|
||||
|
||||
protected AbstractGraph(String name, Class type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doit renvoyer le noeud JavaFX élémentaire du graph
|
||||
* @return Le noeud parent
|
||||
*/
|
||||
public abstract Parent getMainNode();
|
||||
|
||||
/**
|
||||
* Revoie le liste des éléments du menu contextuel
|
||||
* @return
|
||||
*/
|
||||
public void createContextMenu(JPopupMenu contextMenu){
|
||||
try {
|
||||
contextMenu.remove(1);
|
||||
}catch (IllegalArgumentException e){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le graph est valide (qu'il n'a pas été construit via un contructeur)
|
||||
* @return Vrai si valide
|
||||
*/
|
||||
public abstract boolean isValid();
|
||||
|
||||
public void setVisibility(boolean isVisible){
|
||||
visibilityChange(isVisible);
|
||||
visibility = isVisible;
|
||||
}
|
||||
|
||||
public boolean visibility(){
|
||||
return this.visibility;
|
||||
}
|
||||
|
||||
protected abstract void visibilityChange(boolean isVisible);
|
||||
|
||||
protected void setupView(){
|
||||
AbstractGraph thisGraph = this;
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initView();
|
||||
|
||||
// getMainNode().setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
|
||||
// @Override
|
||||
// public void handle(ContextMenuEvent e) {
|
||||
// graphicView.popupContextMenu(Math.round((float)e.getX()),Math.round((float)e.getY()),thisGraph);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
}
|
||||
protected abstract void initView();
|
||||
|
||||
abstract public AbstractGraph setupGraph(Buffer b, GraphicView graphicView, boolean syncWithPlayer);
|
||||
abstract public AbstractGraph setupGraph(ObservableObject o, GraphicView graphicView);
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public GraphicView getGraphicView() {
|
||||
return graphicView;
|
||||
}
|
||||
protected void setGraphicView(GraphicView graphicView){this.graphicView = graphicView;}
|
||||
}
|
136
src/gui/graphs/AbstractObservableGraph.java
Normal file
136
src/gui/graphs/AbstractObservableGraph.java
Normal file
|
@ -0,0 +1,136 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import gui.PlayerControl;
|
||||
import gui.PlayerControlEvent;
|
||||
import gui.viewer_state.BufferViewerState;
|
||||
import gui.viewer_state.ObservableViewerState;
|
||||
import gui.viewer_state.Viewer;
|
||||
import gui.viewer_state.ViewerState;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 09/05/14.
|
||||
* Classe abstraite définissant les attributs et méthode communes au graph d'objet observable
|
||||
*/
|
||||
public abstract class AbstractObservableGraph<T extends ObservableObject> extends AbstractGraph implements Viewer, PlayerControl.Listener {
|
||||
|
||||
private T toShow=null;
|
||||
private Buffer<T> buffer = null;
|
||||
private int currentID = -1;
|
||||
private ViewerState state;
|
||||
|
||||
protected AbstractObservableGraph(String name, Class type) {
|
||||
super(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractGraph setupGraph(Buffer buffer, GraphicView graphicView, boolean syncWithPlayer) {
|
||||
/*Class[] classes = b.getType().getClasses();
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
if(classes[i] == ObservableObject.class)
|
||||
break;
|
||||
else if(i == classes.length-1)
|
||||
return null;
|
||||
}*/
|
||||
|
||||
AbstractObservableGraph g = getGenericGraph();
|
||||
g.setGraphicView(graphicView);
|
||||
g.setBuffer(buffer);
|
||||
|
||||
|
||||
if(syncWithPlayer) {
|
||||
/*if(buffer instanceof TemporalBuffer)
|
||||
PlayerControl.instance().addListener(g);
|
||||
else
|
||||
return null;
|
||||
/*/
|
||||
PlayerControl.instance().addListener(g);
|
||||
//*/
|
||||
}
|
||||
|
||||
|
||||
g.setupView();
|
||||
|
||||
|
||||
g.setState(BufferViewerState.singleFrameWindow(g,buffer));
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractGraph setupGraph(ObservableObject o, GraphicView graphicView) {
|
||||
AbstractObservableGraph g = getGenericGraph();
|
||||
g.setGraphicView(graphicView);
|
||||
g.setState(new ObservableViewerState(g,o));
|
||||
g.setToShow(o);
|
||||
|
||||
g.setupView();
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return buffer!=null||toShow!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visibilityChange(boolean isVisible) {
|
||||
state.setVisibility(isVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerControlEvent(PlayerControlEvent e) {
|
||||
switch (e.getType()){
|
||||
case FRAME:
|
||||
int id = e.getFrame().mapToIndex((TemporalBuffer)buffer);
|
||||
currentID = id;
|
||||
((BufferViewerState)state).setSingleFrame(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateView() {
|
||||
if(buffer != null) {
|
||||
setToShow(buffer.get(currentID));
|
||||
}
|
||||
|
||||
if(toShow!=null)
|
||||
updateViewer();
|
||||
}
|
||||
|
||||
protected abstract AbstractObservableGraph getGenericGraph();
|
||||
|
||||
public T toShow() {
|
||||
return toShow;
|
||||
}
|
||||
|
||||
public void setToShow(T toShow) {
|
||||
this.toShow = toShow;
|
||||
|
||||
if(state instanceof ObservableViewerState)
|
||||
((ObservableViewerState)state).setObservableObject(toShow);
|
||||
}
|
||||
|
||||
public Buffer<T> buffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void setBuffer(Buffer<T> buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
|
||||
public ViewerState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
private void setState(ViewerState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
protected abstract void updateViewer();
|
||||
}
|
195
src/gui/graphs/AmpliFreqView.java
Normal file
195
src/gui/graphs/AmpliFreqView.java
Normal file
|
@ -0,0 +1,195 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.*;import javafx.collections.ObservableList;import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 01/06/14.
|
||||
*/
|
||||
public abstract class AmpliFreqView<T extends ObservableObject> extends AbstractObservableGraph<T>{
|
||||
|
||||
protected BarChart<String, Number> currentBarChart;
|
||||
private ArrayList<String> categories = new ArrayList<>();
|
||||
private Boolean ampliInDb = false;
|
||||
|
||||
public enum Type{
|
||||
REGULAR_FREQ,
|
||||
NOTES
|
||||
}
|
||||
|
||||
AmpliFreqView(String name, Class type) {
|
||||
super( name, type);
|
||||
}
|
||||
|
||||
private void createContent() {
|
||||
|
||||
// Définition des axes du graphe
|
||||
CategoryAxis xAxis = new CategoryAxis();
|
||||
NumberAxis yAxis = new NumberAxis(0, 1, 1);
|
||||
currentBarChart = new BarChart<>(xAxis,yAxis );
|
||||
|
||||
|
||||
createXAxis();
|
||||
|
||||
|
||||
// Définition de certaines propriétés graphiques du graphe
|
||||
currentBarChart.getStylesheets().add(SpectreView.class.getResource("AudioBarChart.css").toExternalForm());
|
||||
currentBarChart.setLegendVisible(false);
|
||||
currentBarChart.setAnimated(false);
|
||||
currentBarChart.setBarGap(0);
|
||||
currentBarChart.setCategoryGap(0);
|
||||
currentBarChart.setVerticalGridLinesVisible(false);
|
||||
|
||||
|
||||
// Initialisation du graphe
|
||||
xAxis.setTickLabelFont(new Font(10));
|
||||
yAxis.setLabel("Amplitude");
|
||||
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, null, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Doit renvoyer le noeud JavaFX élémentaire du graph
|
||||
*
|
||||
* @return Le noeud parent
|
||||
*/
|
||||
@Override
|
||||
public Parent getMainNode() {
|
||||
return currentBarChart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoie le liste des éléments du menu contextuel
|
||||
*
|
||||
* @param contextMenu
|
||||
*/
|
||||
@Override
|
||||
public void createContextMenu(JPopupMenu contextMenu) {
|
||||
JMenu choixDB = new JMenu("Axe amplitude");
|
||||
JRadioButtonMenuItem linAmpli = new JRadioButtonMenuItem("Amplitude linéaire",!ampliInDb);
|
||||
linAmpli.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if(e.getStateChange()==ItemEvent.SELECTED) {
|
||||
ampliInDb = false;
|
||||
state().invalidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
JRadioButtonMenuItem dBAmpli = new JRadioButtonMenuItem("Amplitude en déciBel",ampliInDb);
|
||||
dBAmpli.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if(e.getStateChange()==ItemEvent.SELECTED){
|
||||
ampliInDb = true;
|
||||
((NumberAxis)currentBarChart.getYAxis()).setUpperBound(1);
|
||||
state().invalidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
ButtonGroup b2 = new ButtonGroup();
|
||||
b2.add(linAmpli);
|
||||
choixDB.add(linAmpli);
|
||||
b2.add(dBAmpli);
|
||||
choixDB.add(dBAmpli);
|
||||
|
||||
contextMenu.add(choixDB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateViewer() {
|
||||
Platform.runLater(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
currentBarChart.setTitle("");
|
||||
if (toShow() == null) {
|
||||
cleanView();
|
||||
return;
|
||||
}
|
||||
//s = SpectreFilterFactory.hearingCorrection().setIntensity(0).filterSpectre(s);
|
||||
float yMax = 0f;
|
||||
|
||||
|
||||
for (int i = 0; i < categories.size(); i++) {
|
||||
|
||||
float ampli = getAmplitude(i);
|
||||
|
||||
if(ampliInDb)
|
||||
ampli = Spectre.ampliTodB(ampli);
|
||||
|
||||
currentBarChart.getData().get(0).getData().get(i).setYValue(ampli);
|
||||
if (ampli > yMax) {
|
||||
yMax = ampli;
|
||||
}
|
||||
}
|
||||
|
||||
if (yMax > Float.MAX_VALUE) {
|
||||
cleanView();
|
||||
currentBarChart.setTitle("Spectre incorrect");
|
||||
return;
|
||||
}
|
||||
|
||||
NumberAxis yAxis = ((NumberAxis) currentBarChart.getYAxis());
|
||||
// Changement du max d'axe Y si le nouveau max est plus grand que l'ancien.
|
||||
yMax = yMax > yAxis.getUpperBound() ? yMax * 1.01f : (float) yAxis.getUpperBound() * 0.995f;
|
||||
|
||||
yAxis.setTickUnit(Math.pow(10, (int) (Math.log10(yMax) - .2)));
|
||||
yAxis.setUpperBound(yMax);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cleanView() {
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
currentBarChart.setTitle("Spectre indisponible");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void setXAxisCategories(ArrayList<String> categories){
|
||||
this.categories = categories;
|
||||
Platform.runLater(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
createXAxis();
|
||||
state().invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createXAxis(){
|
||||
|
||||
if(currentBarChart.getData().isEmpty())
|
||||
currentBarChart.getData().add(new XYChart.Series<>());
|
||||
else
|
||||
currentBarChart.getData().get(0).getData().clear();
|
||||
|
||||
for (int i = 0; i < categories.size(); i++)
|
||||
currentBarChart.getData().get(0).getData().add(new XYChart.Data<>(categories.get(i), 0));
|
||||
}
|
||||
|
||||
protected abstract float getAmplitude(int idCategory);
|
||||
|
||||
|
||||
protected void initView() {
|
||||
createContent();
|
||||
}
|
||||
}
|
7
src/gui/graphs/AmpliView.css
Normal file
7
src/gui/graphs/AmpliView.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.thick-chart .chart-series-line {
|
||||
-fx-stroke-width: 1px;
|
||||
}
|
||||
.thick-chart .chart-series-line-fat {
|
||||
-fx-stroke-width: 2px;
|
||||
}
|
||||
.default-color0.chart-series-line { -fx-stroke: #0b1b64; }
|
604
src/gui/graphs/AmpliView.java
Normal file
604
src/gui/graphs/AmpliView.java
Normal file
|
@ -0,0 +1,604 @@
|
|||
package gui.graphs;
|
||||
|
||||
import generictools.Instant;
|
||||
import gui.PlayerControl;
|
||||
import gui.PlayerControlEvent;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Affiche un graphe en amplitudes du signal audio d'origine
|
||||
* Created by arthur on 08/04/14.
|
||||
*/
|
||||
public class AmpliView extends AbstractBufferGraph<TemporalBuffer<Float>>{
|
||||
|
||||
private BorderPane pane;
|
||||
private int frame;
|
||||
private LineChart<Number,Number> chart;
|
||||
private ScrollBar scrollBar;
|
||||
//max et min de l'axe y
|
||||
private float maxY = 70000;
|
||||
private float minY = -70000;
|
||||
final private ReadOnlyObjectWrapper autoMove;
|
||||
private CheckBox checkBoxCursorMove;
|
||||
private StackPane chartContainer;
|
||||
private int zoomLevel;
|
||||
private ArrayList<Integer> zoomArrayList;
|
||||
private Rectangle cursorRect;
|
||||
|
||||
/**
|
||||
* Le constructeur
|
||||
*/
|
||||
public AmpliView() {
|
||||
super("Amplimètre", Float.class);
|
||||
this.frame=0;
|
||||
autoMove = new ReadOnlyObjectWrapper();
|
||||
zoomLevel = 18;
|
||||
zoomArrayList = new ArrayList<Integer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée tous les élements pour l'affichage du graphe
|
||||
* @return BorderPane
|
||||
*/
|
||||
Parent createContent() {
|
||||
|
||||
//conteneur du graphe
|
||||
chartContainer = new StackPane();
|
||||
|
||||
//définition des axes
|
||||
final NumberAxis xAxis = new NumberAxis();
|
||||
final NumberAxis yAxis = new NumberAxis();
|
||||
xAxis.setLabel("Echantillons");
|
||||
xAxis.setLowerBound(0);
|
||||
// 262144 = 2^18 pour le niveau de zoom 18
|
||||
xAxis.setUpperBound(262144);
|
||||
xAxis.setTickUnit(262144/8);
|
||||
xAxis.setAutoRanging(false);
|
||||
yAxis.setAutoRanging(false);
|
||||
yAxis.setUpperBound(maxY);
|
||||
yAxis.setLowerBound(minY);
|
||||
|
||||
yAxis.setTickUnit((maxY - minY)/10);
|
||||
|
||||
//création du graphe
|
||||
chart = new LineChart<>(xAxis,yAxis);
|
||||
chart.setAnimated(false);
|
||||
chart.setCreateSymbols(false);
|
||||
chart.setLegendVisible(false);
|
||||
|
||||
//definition du style
|
||||
chart.getStylesheets().add(AmpliView.class.getResource("AmpliView.css").toExternalForm());
|
||||
chart.getStyleClass().add("thick-chart");
|
||||
|
||||
//ajoute le graphe dans le conteneur
|
||||
chartContainer.getChildren().add(chart);
|
||||
|
||||
//creation du cureseur
|
||||
cursorRect = new Rectangle();
|
||||
cursorRect.setManaged(false);
|
||||
cursorRect.setFill(Color.rgb(39, 93, 153));
|
||||
cursorRect.setWidth(1);
|
||||
//déplace le curseur à 0 (origine)
|
||||
moveCursor(true);
|
||||
chartContainer.getChildren().add(cursorRect);
|
||||
|
||||
//quand la hauteur de la fenêtre change, redimensionner le curseur
|
||||
chartContainer.heightProperty().addListener(new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> observableValue, Number oldHeight, Number newHeight) {
|
||||
resizeCursor();
|
||||
}
|
||||
});
|
||||
|
||||
//quand la largeur de la fenêtre change, redimensionner les contrôles
|
||||
chartContainer.widthProperty().addListener(new ChangeListener<Number>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> observableValue, Number oldWidth, Number newWidth) {
|
||||
resizeControls();
|
||||
moveCursor((Boolean) autoMove.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
//créer la checkbox pour suivre ou non le curseur
|
||||
checkBoxCursorMove = new CheckBox("Suivre curseur");
|
||||
checkBoxCursorMove.setSelected(true);
|
||||
autoMove.bind(checkBoxCursorMove.selectedProperty());
|
||||
|
||||
//création de la scrollbar pour se déplacer dans le graphe
|
||||
scrollBar = new ScrollBar();
|
||||
scrollBar.setMin(0);
|
||||
scrollBar.setMax(buffer().size());
|
||||
handleScrollBar();
|
||||
//quand la scrollbar est déplacée, se déplacer dans le graphe
|
||||
scrollBar.valueProperty().addListener((changeListener, oldVal, newVal) -> {
|
||||
moveChart(newVal);
|
||||
});
|
||||
|
||||
//création du curseur pour le zoom (en fait, agit plutôt comme un point)
|
||||
final Rectangle zoomRect = new Rectangle();
|
||||
|
||||
//crréation d'une serie vide pour le graphe au début
|
||||
chart.getData().add(new XYChart.Series());
|
||||
|
||||
//ctrl+scroll, zoome ou dézoome
|
||||
chart.setOnScroll(new EventHandler<ScrollEvent>() {
|
||||
@Override
|
||||
public void handle(ScrollEvent scrollEvent) {
|
||||
|
||||
if (scrollEvent.isControlDown()) {
|
||||
setUpZoom(zoomRect, scrollEvent);
|
||||
|
||||
//zoome
|
||||
if (scrollEvent.getDeltaY() > 0) {
|
||||
if (changeZoomLevel(true))
|
||||
zoomIn(zoomRect);
|
||||
//dézoome
|
||||
} else if (scrollEvent.getDeltaY() < 0) {
|
||||
if (changeZoomLevel(false))
|
||||
zoomOut(zoomRect);
|
||||
}
|
||||
|
||||
handleScrollBar();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//zoome sur l'axe y avec ctrl+scroll haut/bas
|
||||
chart.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
if(mouseEvent.isControlDown()) {
|
||||
if (mouseEvent.getButton() == MouseButton.PRIMARY) {
|
||||
zoomYAxis(true);
|
||||
} else {
|
||||
zoomYAxis(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//définit la frame quand on clique et drag le curseur
|
||||
chart.setOnMousePressed(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent event) {
|
||||
if (event.isPrimaryButtonDown() && !event.isControlDown()) {
|
||||
setFrame(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
chart.setOnMouseDragged(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent event) {
|
||||
if (event.isPrimaryButtonDown() && !event.isControlDown()) {
|
||||
setFrame(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//création du panel contenant la checkbox et la scrollbar
|
||||
final HBox controls = new HBox();
|
||||
controls.setPadding(new Insets(2));
|
||||
controls.getChildren().addAll(checkBoxCursorMove, scrollBar);
|
||||
|
||||
//création du panel global
|
||||
pane = new BorderPane();
|
||||
pane.setCenter(chartContainer);
|
||||
pane.setBottom(controls);
|
||||
|
||||
calculateZoomLevels();
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit une nouvelle frame
|
||||
* @param event L'événement de la souris
|
||||
*/
|
||||
private void setFrame(MouseEvent event){
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
|
||||
final double yAxisInScene = yAxis.localToScene(0, 0).getX() + yAxis.getWidth();
|
||||
final double mouseX = event.getX();
|
||||
final int timeToSet = xAxis.getValueForDisplay(mouseX - yAxisInScene).intValue();
|
||||
final int boundMax = xAxis.getUpperBound()>buffer().size() ? buffer().size() : (int)xAxis.getUpperBound();
|
||||
|
||||
if(timeToSet >= xAxis.getLowerBound() && timeToSet <= boundMax)
|
||||
PlayerControl.instance().setFrame(Instant.fromIndex(timeToSet, buffer()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redimensionne la scrollbar et la checkbox
|
||||
*/
|
||||
private void resizeControls(){
|
||||
Platform.runLater(() -> {
|
||||
scrollBar.setPrefWidth(chartContainer.getWidth() - checkBoxCursorMove.getWidth() - 5);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Redimensionne le curseur
|
||||
*/
|
||||
private void resizeCursor(){
|
||||
Platform.runLater(() -> {
|
||||
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
|
||||
cursorRect.setY(yAxis.localToScene(0, 0).getY());
|
||||
cursorRect.setHeight(yAxis.getHeight());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Redimensionne et repositionne la scrollbar en fonction du niveau de zoom
|
||||
*/
|
||||
private void handleScrollBar(){
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
final double interval = xAxis.getUpperBound()-xAxis.getLowerBound();
|
||||
|
||||
scrollBar.setMax(buffer().size());
|
||||
//redimensionne la barre
|
||||
scrollBar.setVisibleAmount(interval);
|
||||
//repositionne la barre
|
||||
scrollBar.setValue(xAxis.getLowerBound());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplacer la vue du graphe
|
||||
* @param frame The frame to display
|
||||
*/
|
||||
private void moveChart(final Number frame){
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
final int interval = (int)(xAxis.getUpperBound() - xAxis.getLowerBound());
|
||||
final int newLowerX = frame.intValue();
|
||||
final int newUpperX = frame.intValue() + interval;
|
||||
|
||||
xAxis.setLowerBound(newLowerX);
|
||||
xAxis.setUpperBound(newUpperX);
|
||||
|
||||
setBufferWindow();
|
||||
//repositionne le curseur à la bonne place
|
||||
moveCursor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace le curseur en fonction de la frame
|
||||
* @param locked Booleen indicant si le curseur doit être suivi
|
||||
*/
|
||||
private void moveCursor(final Boolean locked){
|
||||
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
|
||||
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
|
||||
final double yAxisInScene = yAxis.localToScene(0, 0).getX() + yAxis.getWidth();
|
||||
|
||||
if (locked) {
|
||||
final int interval = (int) (xAxis.getUpperBound() - xAxis.getLowerBound());
|
||||
//si la frame est plus grande que la limite sup, translater la
|
||||
//fenêtre vers la droite
|
||||
if (frame > xAxis.getUpperBound()) {
|
||||
xAxis.setLowerBound(xAxis.getUpperBound());
|
||||
xAxis.setUpperBound(xAxis.getLowerBound() + interval);
|
||||
setBufferWindow();
|
||||
}
|
||||
//si la frame est plus petite que la limite inf, translater la
|
||||
//fenêtre vers la gauche
|
||||
if (frame < 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
|
||||
//positionne le curseur
|
||||
public void run() {
|
||||
cursorRect.setX((frame - xAxis.getLowerBound()) * xAxis.getScale() + yAxisInScene);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Définit les coordonnées du rectangle en fonction de la position de la
|
||||
* souris
|
||||
* @param zoomRect Rectangle contenant les coordonnées de la souris pour
|
||||
* zoomer
|
||||
* @param scrollEvent Le scroll event
|
||||
*/
|
||||
private void setUpZoom(final Rectangle zoomRect, final ScrollEvent scrollEvent){
|
||||
zoomRect.setX(scrollEvent.getX());
|
||||
zoomRect.setY(scrollEvent.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoome le graphe en prenant en compte la position de la souris
|
||||
* @param zoomRect Rectangle contenant les coordonnées de la souris pour
|
||||
* zoomer
|
||||
*/
|
||||
private void zoomIn(final Rectangle zoomRect) {
|
||||
final NumberAxis yAxis = (NumberAxis)chart.getYAxis();
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
|
||||
final double yAxisInScene = yAxis.localToScene(0,0).getX() + yAxis.getWidth();
|
||||
double localPoint = (zoomRect.getX()-yAxisInScene)/xAxis.getScale()+xAxis.getLowerBound();
|
||||
|
||||
/* zoom in only if the mouse is in the chart (prevent the zoom when
|
||||
mouse on axis or beyond the size of the buffer*/
|
||||
if(localPoint >= xAxis.getLowerBound() && localPoint <= xAxis.getUpperBound()){ //&& localPoint <= buffer().size()){
|
||||
//define new upper and lower bounds, dividing by 2 in function of mouse position
|
||||
if(localPoint > buffer().size()){
|
||||
localPoint = buffer().size()/2;
|
||||
}
|
||||
double newLowerX = (localPoint + xAxis.getLowerBound()) / 2;
|
||||
double newUpperX = (localPoint + xAxis.getUpperBound()) / 2;
|
||||
|
||||
xAxis.setLowerBound(newLowerX);
|
||||
xAxis.setUpperBound(newUpperX);
|
||||
|
||||
//adapt scale
|
||||
if (xAxis.getUpperBound() - xAxis.getLowerBound() != xAxis.getTickUnit() * 8)
|
||||
xAxis.setTickUnit((xAxis.getUpperBound() - xAxis.getLowerBound())/8);
|
||||
|
||||
setBufferWindow();
|
||||
|
||||
moveCursor(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Zoom out the chart taking into account the position of the mouse
|
||||
* @param zoomRect Rectangle containing mouse coordinates to zoom
|
||||
*/
|
||||
private void zoomOut(final Rectangle zoomRect) {
|
||||
|
||||
final NumberAxis yAxis = (NumberAxis)chart.getYAxis();
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
|
||||
final double yAxisInScene = yAxis.localToScene(0,0).getX() + yAxis.getWidth();
|
||||
final double localPoint = (zoomRect.getX()-yAxisInScene)/xAxis.getScale()+xAxis.getLowerBound();
|
||||
|
||||
if(localPoint >= xAxis.getLowerBound() && localPoint <= xAxis.getUpperBound() ){
|
||||
double newLowerX = 2*xAxis.getLowerBound() - localPoint;
|
||||
double newUpperX = 2*xAxis.getUpperBound() - localPoint;
|
||||
|
||||
// 0 must always be at the origin of the chart
|
||||
if (newLowerX < 0) {
|
||||
newUpperX = newUpperX - newLowerX;
|
||||
newLowerX = 0;
|
||||
}
|
||||
xAxis.setLowerBound(newLowerX);
|
||||
xAxis.setUpperBound(newUpperX);
|
||||
|
||||
if (xAxis.getUpperBound() - xAxis.getLowerBound() != xAxis.getTickUnit() * 8)
|
||||
xAxis.setTickUnit((xAxis.getUpperBound() - xAxis.getLowerBound())/8);
|
||||
|
||||
setBufferWindow();
|
||||
|
||||
moveCursor(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom in or out y axis
|
||||
* @param in Zooming in or out on y axis
|
||||
*/
|
||||
private void zoomYAxis(boolean in){
|
||||
NumberAxis yAxis = (NumberAxis)chart.getYAxis();
|
||||
//prevent from zooming in when y axis upper bound is 4375
|
||||
if (in && yAxis.getUpperBound() != 4375) {
|
||||
yAxis.setUpperBound(yAxis.getUpperBound() / 2);
|
||||
yAxis.setLowerBound(yAxis.getLowerBound() / 2);
|
||||
//prevent from zooming out when y axis upper bound is 70000
|
||||
} else if (!in && yAxis.getUpperBound() != 70000){
|
||||
yAxis.setUpperBound(yAxis.getUpperBound() * 2);
|
||||
yAxis.setLowerBound(yAxis.getLowerBound() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a list of zoom levels which are power of 2
|
||||
*/
|
||||
private void calculateZoomLevels(){
|
||||
//30 min max
|
||||
final int MAX_SAMPLE_TIME = 44100*60*30;
|
||||
zoomArrayList = new ArrayList<>();
|
||||
int i=1;
|
||||
double reste = MAX_SAMPLE_TIME % Math.pow(2, i);
|
||||
zoomArrayList.add(0);
|
||||
|
||||
while(reste != MAX_SAMPLE_TIME){
|
||||
zoomArrayList.add(i);
|
||||
reste = (MAX_SAMPLE_TIME) % Math.pow(2,i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase or decrease the level of zoom depending if you are zooming in
|
||||
* or out
|
||||
* @param in Zooming in or out
|
||||
* @return
|
||||
*/
|
||||
private boolean changeZoomLevel(boolean in){
|
||||
boolean success = false;
|
||||
//can't go below zoom level 7 because chart is too extended that way,
|
||||
//not relevant
|
||||
if(in && zoomLevel != 7){
|
||||
zoomLevel--;
|
||||
success = true;
|
||||
//can't go further max zoom level
|
||||
}else if(!in && zoomLevel != zoomArrayList.size()-1) {
|
||||
zoomLevel++;
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setWindow() of BufferViewState
|
||||
*/
|
||||
private void setBufferWindow(){
|
||||
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
int upper = (int)xAxis.getUpperBound();
|
||||
state().setWindow((int)xAxis.getLowerBound(),upper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerControlEvent(PlayerControlEvent e) {
|
||||
switch (e.getType()){
|
||||
case FRAME:
|
||||
this.frame = e.getFrame().mapToIndex(buffer());
|
||||
moveCursor((Boolean)autoMove.getValue());
|
||||
break;
|
||||
|
||||
case PLAY_STATE:
|
||||
switch(e.getPlayingState()) {
|
||||
case STOPPED:
|
||||
//when player is stopped, move the window to the beginning
|
||||
//only if automove is checked
|
||||
if ((Boolean) autoMove.getValue())
|
||||
moveChart(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateView() {
|
||||
XYChart.Series s = new XYChart.Series();
|
||||
final int RESOLUTION = 1368;
|
||||
|
||||
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
|
||||
final int min = (int) xAxis.getLowerBound();
|
||||
int max = (int) xAxis.getUpperBound() > buffer().size() ? buffer().size() : (int) xAxis.getUpperBound();
|
||||
|
||||
/* If zoom level is below 14 meaning that the zoom is big (small
|
||||
portion of the signal drawn) we can directly draw amplitudes of
|
||||
the buffer at a specific step depending on the portion of the
|
||||
signal drawn.
|
||||
But if we are too zoomed out, the step method does not work
|
||||
anymore because we are "randomly" drawing amplitudes of the buffer
|
||||
which are thus not meaningful of the real signal and resulting in
|
||||
really odd drawings.
|
||||
To overcome this problem, we can divide the buffer in small "chunks"
|
||||
and draw the max amplitude of every chunk, which is more meaningful.
|
||||
The chunk length will depend on the zoom level.
|
||||
*/
|
||||
if(zoomLevel <= 14) {
|
||||
final int inc = (max - min) > RESOLUTION ? (max - min) / RESOLUTION : 1;
|
||||
//only the points that are visible
|
||||
for (int i = min; i < max; i += inc) {
|
||||
s.getData().add(new XYChart.Data(i, buffer().get(i)));
|
||||
if (buffer().get(i) > maxY)
|
||||
maxY = buffer().get(i);
|
||||
if (buffer().get(i) < minY)
|
||||
minY = buffer().get(i);
|
||||
}
|
||||
} else {
|
||||
float maxAmp = 0;
|
||||
//calculate the length of a single "chunk"
|
||||
int samplesPerPixel = calculateSamplesPerChunk();
|
||||
|
||||
|
||||
for(int i = min; i < max - samplesPerPixel; i+=samplesPerPixel) {
|
||||
//inside each chunk, search for the max amplitude
|
||||
for (int j = i; j < i + samplesPerPixel; j++) {
|
||||
if (buffer().get(j) > maxAmp) {
|
||||
maxAmp = buffer().get(j);
|
||||
}
|
||||
}
|
||||
//draw a vertical line between maxAmp and -maxAmp
|
||||
s.getData().add(new XYChart.Data(i, 0));
|
||||
s.getData().add(new XYChart.Data(i, -maxAmp));
|
||||
s.getData().add(new XYChart.Data(i, maxAmp));
|
||||
s.getData().add(new XYChart.Data(i, 0));
|
||||
maxY = maxAmp;
|
||||
maxAmp = 0;
|
||||
}
|
||||
}
|
||||
handleScrollBar();
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//set the series in the chart
|
||||
chart.getData().set(0, s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the size of a chunk to draw amplitudes
|
||||
* @return Number of samples represented by a chunk
|
||||
*/
|
||||
private int calculateSamplesPerChunk(){
|
||||
return (int)Math.round(Math.pow(2,zoomLevel) / 1368)*2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanView() {
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chart.getData().clear();
|
||||
chart.getData().add(new XYChart.Series());
|
||||
NumberAxis xAxis = (NumberAxis)chart.getXAxis();
|
||||
xAxis.setLowerBound(0);
|
||||
xAxis.setUpperBound(262144);
|
||||
xAxis.setTickUnit(262144/8);
|
||||
zoomLevel = 18;
|
||||
handleScrollBar();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractBufferGraph getGenericGraph() {
|
||||
return new AmpliView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getMainNode() {
|
||||
return pane;
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
createContent();
|
||||
state().setWindow(0, 262144); }
|
||||
}
|
||||
|
1
src/gui/graphs/AudioBarChart.css
Normal file
1
src/gui/graphs/AudioBarChart.css
Normal file
File diff suppressed because one or more lines are too long
95
src/gui/graphs/Bar.java
Normal file
95
src/gui/graphs/Bar.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
package gui.graphs;
|
||||
|
||||
/**
|
||||
* Adapted by arthur from Candle.java in javafx's Ensemble8 demo.
|
||||
*
|
||||
* Modified to change bar color more efficiently and to accept a color
|
||||
* in its attributes
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2008, 2013 Oracle and/or its affiliates.
|
||||
* All rights reserved. Use is subject to license terms.
|
||||
*
|
||||
* This file is available and licensed under the following license:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the name of Oracle Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
/** Bar node used for drawing a bar */
|
||||
public class Bar extends Group {
|
||||
private Region bar = new Region();
|
||||
private Color color;
|
||||
|
||||
/**
|
||||
* Constructeur
|
||||
* @param color La couleur de la barre
|
||||
*/
|
||||
Bar(Color color) {
|
||||
setAutoSizeChildren(false);
|
||||
getChildren().addAll(bar);
|
||||
this.color = color;
|
||||
updateStyleClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit une couleur
|
||||
* @param color La couleur de la barre
|
||||
*/
|
||||
public void setSeriesAndDataStyleClasses(Color color) {
|
||||
this.color = color;
|
||||
updateStyleClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la position et taille des barres
|
||||
* @param closeOffset La longueur de la barre
|
||||
* @param barHeight La hauteur de la barre
|
||||
* @param color La couleur de la barre
|
||||
*/
|
||||
public void update(double closeOffset, double barHeight, Color color) {
|
||||
updateStyleClasses();
|
||||
if (barHeight == -1) {
|
||||
barHeight = bar.prefHeight(-1);
|
||||
}
|
||||
bar.resizeRelocate(0, -barHeight / 2, closeOffset, barHeight);
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique la couleur pour l'affichage
|
||||
*/
|
||||
private void updateStyleClasses() {
|
||||
bar.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
}
|
||||
}
|
59
src/gui/graphs/BarExtraValues.java
Normal file
59
src/gui/graphs/BarExtraValues.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
package gui.graphs;
|
||||
|
||||
/**
|
||||
* Modified by arthur in javafx's Ensemble8 demo.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008, 2013 Oracle and/or its affiliates.
|
||||
* All rights reserved. Use is subject to license terms.
|
||||
*
|
||||
* This file is available and licensed under the following license:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the name of Oracle Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
/** Data extra values for storing close value and color. */
|
||||
public class BarExtraValues {
|
||||
private double close;
|
||||
private Color color;
|
||||
|
||||
public BarExtraValues(double close, Color color) {
|
||||
this.close = close;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public double getClose() {
|
||||
return close;
|
||||
}
|
||||
|
||||
public Color getColor(){
|
||||
return color;
|
||||
}
|
||||
}
|
491
src/gui/graphs/BargramView.java
Normal file
491
src/gui/graphs/BargramView.java
Normal file
|
@ -0,0 +1,491 @@
|
|||
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()));
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
74
src/gui/graphs/EventViewer.java
Normal file
74
src/gui/graphs/EventViewer.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.*;
|
||||
import generictools.Instant;
|
||||
import javafx.scene.paint.Color;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 31/05/14.
|
||||
*/
|
||||
public class EventViewer extends BargramView<TemporalBuffer<NoteEventList>> {
|
||||
OverlayGraph overlay;
|
||||
public EventViewer() {
|
||||
super("Evenorama", NoteEventList.class);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Color getColor(int idFreq, Instant time) {
|
||||
NoteEventList list = buffer().get(time);
|
||||
if(list == null)
|
||||
return null;
|
||||
|
||||
NoteEvent event = null;
|
||||
if(getGamme()== NoteGamme.gamme())
|
||||
event = list.getByNoteId(idFreq);
|
||||
|
||||
if(event==null)
|
||||
return null;
|
||||
|
||||
float saturation = (float)Math.min(.2f + event.getEventAmpli()/1000000f, .92);
|
||||
return Color.hsb(event.getEventType()==NoteEvent.Type.APPARITION?85:13, saturation, .73);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Runnable getOnMouseMoved(int idFreq, Instant time) {
|
||||
NoteEventList list = buffer().get(time);
|
||||
Runnable hide = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
overlay.setVisibility(false);
|
||||
}
|
||||
};
|
||||
if(list == null)
|
||||
return hide;
|
||||
|
||||
if(getGamme()!= NoteGamme.gamme())
|
||||
return hide;
|
||||
|
||||
final NoteEvent event = list.getByNoteId(idFreq);
|
||||
|
||||
if(event == null)
|
||||
return hide;
|
||||
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
((AbstractObservableGraph)overlay.getGraph()).setToShow(event.getNote().getProfil());
|
||||
overlay.setVisibility(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractBufferGraph getGenericGraph() {
|
||||
return new EventViewer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
super.initView();
|
||||
overlay = getGraphicView().createOverlay(getGraphicView().createGraphs(new Profil()).get(0), OverlayGraph.AutoPosition.MOUSE, false);
|
||||
}
|
||||
}
|
351
src/gui/graphs/GraphicView.java
Normal file
351
src/gui/graphs/GraphicView.java
Normal file
|
@ -0,0 +1,351 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import processing.buffer.Buffer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 12/03/14.
|
||||
* Classe gérant l'adaptation entre JavaFX et swing et chargée d'afficher les graphs et leurs overlays
|
||||
*/
|
||||
public class GraphicView extends JFXPanel{
|
||||
|
||||
static private ArrayList<Class> typeSupported = new ArrayList<>();
|
||||
static private ArrayList<ArrayList<AbstractGraph>> genericGraphes = new ArrayList<>();
|
||||
|
||||
static public void initGraph(){
|
||||
|
||||
registerGraph(new AmpliView());
|
||||
registerGraph(new Spectrogram());
|
||||
registerGraph(new SpectreView());
|
||||
registerGraph(new EventViewer());
|
||||
registerGraph(new ProfilView());
|
||||
registerGraph(new NoteViewer());
|
||||
}
|
||||
|
||||
static public void registerGraph(AbstractGraph graph){
|
||||
int idType = typeSupported.indexOf(graph.getType());;
|
||||
|
||||
if(idType==-1){
|
||||
typeSupported.add(graph.getType());
|
||||
genericGraphes.add(new ArrayList<>());
|
||||
idType = typeSupported.size()-1;
|
||||
}
|
||||
|
||||
genericGraphes.get(idType).add(graph);
|
||||
}
|
||||
|
||||
|
||||
private final String name;
|
||||
private JMenu switchMenu = null;
|
||||
private BorderPane layout = new BorderPane();
|
||||
private ArrayList<AbstractGraph> graphs = new ArrayList<>();
|
||||
private Pane overlayLayout = new Pane();
|
||||
private ArrayList<OverlayGraph> overlays = new ArrayList<>();
|
||||
private int idCurrentGraph=-1;
|
||||
private boolean visibility = false;
|
||||
|
||||
protected GraphicView( String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
|
||||
|
||||
this.addAncestorListener(new AncestorListener() {
|
||||
@Override
|
||||
public void ancestorAdded(AncestorEvent event) {
|
||||
visibilityChange(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent event) {
|
||||
visibilityChange(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void visibilityChange(boolean isVisible){
|
||||
if(visibility == isVisible)
|
||||
return;
|
||||
|
||||
visibility = isVisible;
|
||||
for (int i = 0; i < graphs.size(); i++) {
|
||||
graphs.get(i).visibilityChange(isVisible&&i==idCurrentGraph);
|
||||
}
|
||||
|
||||
for(OverlayGraph o: overlays)
|
||||
o.viewVisibiliyChange();
|
||||
|
||||
}
|
||||
|
||||
public boolean getVisibility(){
|
||||
return visibility;
|
||||
}
|
||||
|
||||
|
||||
private void setMainGraph(int idGraph){
|
||||
if(idGraph<0 || idGraph>graphs.size())
|
||||
return;
|
||||
|
||||
if(getGraph(idCurrentGraph)!=null)
|
||||
getGraph(idCurrentGraph).setVisibility(false);
|
||||
|
||||
for(OverlayGraph o: overlays)
|
||||
o.setVisibility(false);
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
layout.setCenter(graphs.get(idGraph).getMainNode());
|
||||
idCurrentGraph = idGraph;
|
||||
graphs.get(idGraph).setVisibility(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static GraphicView createGraphicView(Buffer buffer, boolean syncWithPlayer){
|
||||
|
||||
GraphicView g = new GraphicView(buffer.getName());
|
||||
ArrayList<AbstractGraph> graphs = createGraphs(buffer, g, syncWithPlayer);
|
||||
|
||||
if(graphs.size()==0)
|
||||
return null;
|
||||
|
||||
for(AbstractGraph graph: graphs)
|
||||
g.addGraph(graph);
|
||||
|
||||
|
||||
if(!finishSetup(g))
|
||||
return null;
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
public static GraphicView createGraphicView(ObservableObject o) {
|
||||
|
||||
GraphicView g = new GraphicView(o.toString());
|
||||
ArrayList<AbstractGraph> graphs = createGraphs(o, g);
|
||||
|
||||
if(graphs.size()==0)
|
||||
return null;
|
||||
|
||||
for(AbstractGraph graph: graphs)
|
||||
g.addGraph(graph);
|
||||
|
||||
if(!finishSetup(g))
|
||||
return null;
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
public static ArrayList<AbstractGraph> createGraphs(Buffer buffer, GraphicView graphicView, boolean syncWithPlayer){
|
||||
|
||||
if(typeSupported.isEmpty()) {
|
||||
initGraph();
|
||||
if(typeSupported.isEmpty())
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
int idType = typeSupported.indexOf(buffer.getType());
|
||||
if(idType==-1)
|
||||
return new ArrayList<>();
|
||||
|
||||
ArrayList<AbstractGraph> r = new ArrayList<>(genericGraphes.get(idType).size());
|
||||
|
||||
for (int i = 0; i < genericGraphes.get(idType).size(); i++)
|
||||
r.add(genericGraphes.get(idType).get(i).setupGraph(buffer, graphicView, syncWithPlayer));
|
||||
|
||||
return r;
|
||||
}
|
||||
public ArrayList<AbstractGraph> createGraphs(Buffer buffer, boolean syncWithPlayer){
|
||||
return createGraphs(buffer, this, syncWithPlayer);
|
||||
}
|
||||
|
||||
public static ArrayList<AbstractGraph> createGraphs(ObservableObject observableObject, GraphicView graphicView){
|
||||
if (typeSupported.isEmpty()) {
|
||||
initGraph();
|
||||
if (typeSupported.isEmpty())
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
int idType = typeSupported.indexOf(observableObject.getType());
|
||||
if(idType==-1)
|
||||
return new ArrayList<>();
|
||||
|
||||
ArrayList<AbstractGraph> r = new ArrayList<>(genericGraphes.get(idType).size());
|
||||
|
||||
for (int i = 0; i < genericGraphes.get(idType).size(); i++) {
|
||||
AbstractGraph abstractGraph = genericGraphes.get(idType).get(i).setupGraph(observableObject,graphicView);
|
||||
if(abstractGraph!=null)
|
||||
r.add(abstractGraph);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public ArrayList<AbstractGraph> createGraphs(ObservableObject observableObject){
|
||||
return createGraphs(observableObject, this);
|
||||
}
|
||||
|
||||
public OverlayGraph createOverlay(AbstractGraph graph, OverlayGraph.AutoPosition position, boolean showByDefault){
|
||||
OverlayGraph o = null;
|
||||
switch(position){
|
||||
case CUSTOM:
|
||||
o = new OverlayGraph(graph, this);
|
||||
break;
|
||||
case CENTERED:
|
||||
o = OverlayGraph.centeredOverlay(graph, 300,450);
|
||||
break;
|
||||
case PERFECT_CENTERED:
|
||||
o = OverlayGraph.perfectCenteredOverlay(graph);
|
||||
break;
|
||||
case MOUSE:
|
||||
o = OverlayGraph.followMouseGraph(graph, 200, 250);
|
||||
break;
|
||||
}
|
||||
|
||||
o.setVisibility(showByDefault);
|
||||
addOverlay(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
public void addOverlay(OverlayGraph overlayGraph){
|
||||
if(overlays.contains(overlayGraph))
|
||||
return;
|
||||
|
||||
overlays.add(overlayGraph);
|
||||
overlayLayout.getChildren().add(overlayGraph);
|
||||
}
|
||||
|
||||
public void removeOverlay(OverlayGraph overlayGraph){
|
||||
int id = overlays.indexOf(overlayGraph);
|
||||
if(id==-1)
|
||||
return;
|
||||
|
||||
overlays.remove(id);
|
||||
overlayLayout.getChildren().remove(id);
|
||||
}
|
||||
|
||||
public ArrayList<OverlayGraph> getOverlays() {
|
||||
return overlays;
|
||||
}
|
||||
|
||||
private static boolean finishSetup(GraphicView g){
|
||||
if(g.getNbrGraph()==0)
|
||||
return false;
|
||||
|
||||
if(g.getNbrGraph()>1) {
|
||||
JMenu menu = new JMenu("Type de graphe");
|
||||
ButtonGroup radioGroup = new ButtonGroup();
|
||||
for (int i = 0; i < g.getNbrGraph(); i++) {
|
||||
AbstractGraph graph = g.getGraph(i);
|
||||
JRadioButtonMenuItem graphItem = new JRadioButtonMenuItem(graph.getName(), i == 0);
|
||||
final int idGraph = i;
|
||||
graphItem.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED)
|
||||
g.setMainGraph(idGraph);
|
||||
}
|
||||
});
|
||||
|
||||
radioGroup.add(graphItem);
|
||||
menu.add(graphItem);
|
||||
}
|
||||
g.setSwitchMenu(menu);
|
||||
}
|
||||
|
||||
g.initGraphicView();
|
||||
g.setMainGraph(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initGraphicView(){
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setScene(new Scene(new StackPane(layout, overlayLayout)));
|
||||
layout.setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
|
||||
@Override
|
||||
public void handle(ContextMenuEvent contextMenuEvent) {
|
||||
|
||||
popupContextMenu((int)contextMenuEvent.getX(), (int)contextMenuEvent.getY());
|
||||
contextMenuEvent.consume();
|
||||
}
|
||||
});
|
||||
|
||||
overlayLayout.setMouseTransparent(true);
|
||||
for(OverlayGraph o: overlays)
|
||||
o.setGraphicView(GraphicView.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSwitchMenu(JMenu menu){switchMenu = menu;}
|
||||
|
||||
private int addGraph(AbstractGraph graph){graphs.add(graph); return graphs.size()-1;}
|
||||
|
||||
public int getNbrGraph(){return graphs.size();}
|
||||
|
||||
public AbstractGraph getGraph(int id){
|
||||
if(id<0|| id>graphs.size())
|
||||
return null;
|
||||
return graphs.get(id);
|
||||
}
|
||||
|
||||
public int getIdMainGraph(){return idCurrentGraph;}
|
||||
|
||||
public AbstractGraph getMainGraph(){return graphs.get(idCurrentGraph);}
|
||||
|
||||
|
||||
public void popupContextMenu(int x, int y){
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
if(switchMenu!=null) {
|
||||
contextMenu.add(switchMenu);
|
||||
contextMenu.addSeparator();
|
||||
}
|
||||
|
||||
getMainGraph().createContextMenu(contextMenu);
|
||||
contextMenu.show(this, x, y);
|
||||
}
|
||||
public void popupOverlayContextMenu(int x, int y, AbstractGraph graph){
|
||||
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
|
||||
graph.createContextMenu(contextMenu);
|
||||
contextMenu.show(this, x, y);
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private BorderPane getBorderPane(){return layout;}
|
||||
|
||||
}
|
32
src/gui/graphs/NoteViewer.java
Normal file
32
src/gui/graphs/NoteViewer.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.NoteList;
|
||||
import generictools.Instant;
|
||||
import javafx.scene.paint.Color;
|
||||
import processing.buffer.NoteBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 03/06/14.
|
||||
*/
|
||||
public class NoteViewer extends BargramView<NoteBuffer> {
|
||||
public NoteViewer() {
|
||||
super("Tablature", NoteList.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Color getColor(int idFreq, Instant time) {
|
||||
NoteList list = buffer().get(time);
|
||||
if(list == null)
|
||||
return null;
|
||||
|
||||
if(list.getNoteById(idFreq)!=null)
|
||||
return Color.rgb(51,108,212);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractBufferGraph getGenericGraph() {
|
||||
return new NoteViewer();
|
||||
}
|
||||
}
|
13
src/gui/graphs/Overlay.css
Normal file
13
src/gui/graphs/Overlay.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
.overlay{
|
||||
|
||||
-fx-border-radius: 10 20 10 20;
|
||||
-fx-background-radius: 25 10 25 10;
|
||||
|
||||
-fx-padding: 10;
|
||||
-fx-background-color: #ffffff;
|
||||
|
||||
/* top-left, top-right, bottom-right, and bottom-left corners, in that order. */
|
||||
}
|
||||
|
312
src/gui/graphs/OverlayGraph.java
Normal file
312
src/gui/graphs/OverlayGraph.java
Normal file
|
@ -0,0 +1,312 @@
|
|||
package gui.graphs;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
|
||||
/**
|
||||
* Created by gaby on 11/05/14.
|
||||
* Classe permettant d'afficher un overlay au dessus du graphs principal dans une GraphicView
|
||||
*/
|
||||
public class OverlayGraph extends BorderPane implements ChangeListener<Number>, EventHandler<MouseEvent> {
|
||||
private AbstractGraph graph;
|
||||
|
||||
private GraphicView graphicView;
|
||||
|
||||
private AutoPosition autoPosition = AutoPosition.CUSTOM;
|
||||
|
||||
private double xMousePos=0;
|
||||
private double yMousePos=0;
|
||||
private double xMouseOffset=0;
|
||||
private double yMouseOffset=0;
|
||||
|
||||
public OverlayGraph() {
|
||||
super();
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
getStyleClass().add("overlay");
|
||||
getStylesheets().add(AmpliView.class.getResource("Overlay.css").toExternalForm());
|
||||
|
||||
DropShadow dS = new DropShadow();
|
||||
dS.setRadius(10);
|
||||
dS.setOffsetY(2);
|
||||
dS.setOffsetX(2);
|
||||
setEffect(dS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public OverlayGraph(AbstractGraph graph){
|
||||
this();
|
||||
setGraph(graph);
|
||||
}
|
||||
public OverlayGraph(AbstractGraph graph, GraphicView graphicView){
|
||||
this(graph);
|
||||
setGraphicView(graphicView);
|
||||
}
|
||||
|
||||
public void setGraph(AbstractGraph graph){
|
||||
if(graph==null)
|
||||
return;
|
||||
|
||||
if(this.graph!=null)
|
||||
this.graph.setVisibility(false);
|
||||
graph.setVisibility(getVisibility());
|
||||
this.graph = graph;
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setCenter(graph.getMainNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static public OverlayGraph centeredOverlay(AbstractGraph graph, int height, int width){
|
||||
OverlayGraph r = new OverlayGraph(graph);
|
||||
r.setOverlayHeight(height);
|
||||
r.setOverlayWidth(width);
|
||||
r.setAutoPosition(AutoPosition.CENTERED);
|
||||
return r;
|
||||
}
|
||||
static public OverlayGraph perfectCenteredOverlay(AbstractGraph graph){
|
||||
OverlayGraph r = new OverlayGraph(graph);
|
||||
r.setAutoPosition(AutoPosition.PERFECT_CENTERED);
|
||||
return r;
|
||||
}
|
||||
static public OverlayGraph followMouseGraph(AbstractGraph graph, int height, int width){
|
||||
OverlayGraph r = new OverlayGraph(graph);
|
||||
r.setAutoPosition(AutoPosition.MOUSE);
|
||||
r.setOverlayHeight(height);
|
||||
r.setOverlayWidth(width);
|
||||
return r;
|
||||
}
|
||||
|
||||
public AbstractGraph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
public boolean getVisibility(){return isVisible();}
|
||||
|
||||
public void setVisibility(boolean visibility){
|
||||
setVisible(visibility);
|
||||
graph.setVisibility(visibility);
|
||||
|
||||
}
|
||||
|
||||
public void viewVisibiliyChange(){
|
||||
graph.setVisibility(getVisibility()||graphicView.getVisibility());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fait apparaitre le graph à la position indiqué
|
||||
* Si le positionnement automatique est en mode MOUSE la position de l'overlay est forcé
|
||||
* Si il est en mode CENTERED ou PERFECT_CENTERED, le passe automatiquement en mode CUSTOM
|
||||
* @param x Position horizontale absolue dans la GraphicView
|
||||
* @param y Position verticale absolue dans la GraphicView
|
||||
*/
|
||||
public void popupAt(double x, double y){
|
||||
if(autoPosition == AutoPosition.CENTERED || autoPosition==AutoPosition.PERFECT_CENTERED)
|
||||
setAutoPosition(AutoPosition.CUSTOM);
|
||||
|
||||
if(autoPosition==AutoPosition.CUSTOM) {
|
||||
setTranslateX(x);
|
||||
setTranslateY(y);
|
||||
}else{
|
||||
xMousePos = x;
|
||||
yMousePos = y;
|
||||
moveToMouse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la position horizontale de l'overlay.
|
||||
* Passe automatiquement le positionnement automatique en mode CUSTOM
|
||||
* @param x Position horizontale absolue dans la GraphicView
|
||||
*/
|
||||
public void setX(double x){
|
||||
if(autoPosition!=AutoPosition.CUSTOM)
|
||||
setAutoPosition(AutoPosition.CUSTOM);
|
||||
setTranslateX(x);
|
||||
}
|
||||
/**
|
||||
* Modifie la position verticale de l'overlay.
|
||||
* Passe automatiquement le positionnement automatique en mode CUSTOM
|
||||
* @param y Position verticale absolue dans la GraphicView
|
||||
*/
|
||||
public void setY(double y){
|
||||
if(autoPosition!=AutoPosition.CUSTOM)
|
||||
setAutoPosition(AutoPosition.CUSTOM);
|
||||
setTranslateX(y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la hauteur de l'overlay
|
||||
* Passe automatiquement le positionnement automatique en mose CENTERED si il était en mode PERFECT_CENTERED
|
||||
* @param height Hauteur de l'overlay
|
||||
*/
|
||||
public void setOverlayHeight(double height){
|
||||
if(autoPosition==AutoPosition.PERFECT_CENTERED)
|
||||
setAutoPosition(AutoPosition.CENTERED);
|
||||
super.setPrefHeight(height);
|
||||
}
|
||||
/**
|
||||
* Modifie la largeur de l'overlay
|
||||
* Passe automatiquement le positionnement automatique en mose CENTERED si il était en mode PERFECT_CENTERED
|
||||
* @param width Largeur de l'overlay
|
||||
*/
|
||||
public void setOverlayWidth(double width){
|
||||
if(autoPosition==AutoPosition.PERFECT_CENTERED)
|
||||
setAutoPosition(AutoPosition.CENTERED);
|
||||
super.setPrefWidth(width);
|
||||
}
|
||||
|
||||
|
||||
public double getxMouseOffset() {
|
||||
return xMouseOffset;
|
||||
}
|
||||
|
||||
public void setxMouseOffset(double xMouseOffset) {
|
||||
this.xMouseOffset = xMouseOffset;
|
||||
}
|
||||
|
||||
public double getyMouseOffset() {
|
||||
return yMouseOffset;
|
||||
}
|
||||
|
||||
public void setyMouseOffset(double yMouseOffset) {
|
||||
this.yMouseOffset = yMouseOffset;
|
||||
}
|
||||
|
||||
public void setGraphicView(GraphicView graphicView){
|
||||
if(graphicView==this.graphicView)
|
||||
return;
|
||||
|
||||
if(this.graphicView!=null) {
|
||||
if (autoPosition == AutoPosition.CENTERED || autoPosition == AutoPosition.PERFECT_CENTERED) {
|
||||
graphicView.getScene().widthProperty().removeListener(this);
|
||||
graphicView.getScene().heightProperty().removeListener(this);
|
||||
}else if (autoPosition == AutoPosition.MOUSE) {
|
||||
graphicView.getScene().removeEventHandler(MouseEvent.MOUSE_MOVED, this);
|
||||
}
|
||||
this.graphicView.removeOverlay(this);
|
||||
|
||||
}
|
||||
|
||||
setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
|
||||
@Override
|
||||
public void handle(ContextMenuEvent contextMenuEvent) {
|
||||
graphicView.popupOverlayContextMenu((int)contextMenuEvent.getSceneX(), (int)contextMenuEvent.getSceneY(), graph);
|
||||
contextMenuEvent.consume();
|
||||
}
|
||||
});
|
||||
|
||||
this.graphicView = graphicView;
|
||||
applyConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la propriété de placement automatique:
|
||||
* -CUSTOM: laisse le choix au programateur de placé ou il veut l'overlay
|
||||
* -CENTERED: centre la vue dans la fenetre (en tenant compte de la hauteur et largeur de l'overlay)
|
||||
* -MOUSE: positionne l'overlay sous la souris (en tenant compte du décalage mouseOffset)
|
||||
* @param autoPosition
|
||||
*/
|
||||
public void setAutoPosition(AutoPosition autoPosition){
|
||||
if(this.autoPosition == autoPosition)
|
||||
return;
|
||||
if(graphicView!=null) {
|
||||
if (this.autoPosition == AutoPosition.CENTERED || autoPosition == AutoPosition.PERFECT_CENTERED) {
|
||||
graphicView.getScene().widthProperty().removeListener(this);
|
||||
graphicView.getScene().heightProperty().removeListener(this);
|
||||
} else if (this.autoPosition == AutoPosition.MOUSE)
|
||||
graphicView.getScene().removeEventHandler(MouseEvent.MOUSE_MOVED, this);
|
||||
}
|
||||
this.autoPosition = autoPosition;
|
||||
applyConnection();
|
||||
}
|
||||
public AutoPosition getAutoPosition(){return autoPosition;}
|
||||
|
||||
private void applyConnection(){
|
||||
if(graphicView==null)
|
||||
return;
|
||||
|
||||
if(autoPosition == AutoPosition.CENTERED || autoPosition==AutoPosition.PERFECT_CENTERED) {
|
||||
graphicView.getScene().widthProperty().addListener(this);
|
||||
graphicView.getScene().heightProperty().addListener(this);
|
||||
centerGeometry();
|
||||
}
|
||||
|
||||
|
||||
if(autoPosition == AutoPosition.MOUSE) {
|
||||
graphicView.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, this);
|
||||
moveToMouse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void centerGeometry(){
|
||||
if(autoPosition==AutoPosition.PERFECT_CENTERED){
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
double h = graphicView.getParent().getHeight();
|
||||
double w = graphicView.getParent().getWidth();
|
||||
setTranslateX(w/20.0);
|
||||
setTranslateY(h/ 20.0);
|
||||
OverlayGraph.super.setPrefHeight(h*18.0/20.0);
|
||||
OverlayGraph.super.setPrefWidth(w*18.0/20.0);
|
||||
}
|
||||
});
|
||||
}else {
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
double h = graphicView.getParent().getHeight();
|
||||
double w = graphicView.getParent().getWidth();
|
||||
setTranslateX(w - getPrefWidth() / 2.0);
|
||||
setTranslateY(h - getPrefHeight() / 2.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
private void moveToMouse(){
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setTranslateX(xMousePos - xMouseOffset);
|
||||
setTranslateY(yMousePos - yMouseOffset);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public enum AutoPosition{
|
||||
CUSTOM,
|
||||
CENTERED,
|
||||
PERFECT_CENTERED,
|
||||
MOUSE
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) {
|
||||
centerGeometry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
xMousePos = mouseEvent.getX();
|
||||
yMousePos = mouseEvent.getY();
|
||||
moveToMouse();
|
||||
}
|
||||
|
||||
}
|
48
src/gui/graphs/ProfilView.java
Normal file
48
src/gui/graphs/ProfilView.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.*;import javafx.scene.chart.CategoryAxis;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 01/04/2014.
|
||||
*/
|
||||
public class ProfilView extends AmpliFreqView<Profil>{
|
||||
|
||||
|
||||
ProfilView() {
|
||||
super( "Visualisateur de profil", Profil.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractObservableGraph getGenericGraph() {
|
||||
return new ProfilView();
|
||||
}
|
||||
|
||||
protected void initView() {
|
||||
super.initView();
|
||||
currentBarChart.getXAxis().setTickLabelRotation(0);
|
||||
createCategory(8);
|
||||
}
|
||||
|
||||
private void createCategory(int nbCategory){
|
||||
ArrayList<String> categories = new ArrayList<>(nbCategory);
|
||||
|
||||
if(nbCategory>0)
|
||||
categories.add("Fonda");
|
||||
|
||||
for (int i = 1; i < nbCategory; i++)
|
||||
categories.add("Har " + String.valueOf(i));
|
||||
|
||||
|
||||
setXAxisCategories(categories);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getAmplitude(int idCategory) {
|
||||
return toShow().getAmplitude(idCategory);
|
||||
}}
|
||||
|
94
src/gui/graphs/SpectreView.java
Normal file
94
src/gui/graphs/SpectreView.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.Gamme;
|
||||
import conteneurs.NoteGamme;
|
||||
import conteneurs.RegularGamme;
|
||||
import conteneurs.Spectre;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 01/04/2014.
|
||||
*/
|
||||
public class SpectreView extends AmpliFreqView<Spectre>{
|
||||
|
||||
private Gamme xAxisGamme;
|
||||
|
||||
public enum Type{
|
||||
REGULAR_FREQ,
|
||||
NOTES
|
||||
}
|
||||
|
||||
SpectreView() {
|
||||
super( "Visualisateur de spectre", Spectre.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractObservableGraph getGenericGraph() {
|
||||
return new SpectreView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoie le liste des éléments du menu contextuel
|
||||
*
|
||||
* @param contextMenu
|
||||
*/
|
||||
@Override
|
||||
public void createContextMenu(JPopupMenu contextMenu) {
|
||||
super.createContextMenu(contextMenu);
|
||||
|
||||
JMenu choixAxesX = new JMenu("Axe fréquence");
|
||||
JRadioButtonMenuItem noteGamme = new JRadioButtonMenuItem("Gamme chromatique",xAxisGamme instanceof NoteGamme);
|
||||
noteGamme.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if(e.getStateChange()==ItemEvent.SELECTED)
|
||||
setXAxisGamme(NoteGamme.gamme());
|
||||
}
|
||||
});
|
||||
JRadioButtonMenuItem regularGamme = new JRadioButtonMenuItem("Gamme linéaire",xAxisGamme instanceof RegularGamme);
|
||||
regularGamme.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if(e.getStateChange()==ItemEvent.SELECTED)
|
||||
setXAxisGamme(new RegularGamme((float)Math.pow(10, (int)Math.log10(NoteGamme.getMinFreq())),(float)Math.pow(10, (int)Math.log10(NoteGamme.getMaxFreq())),1000));
|
||||
}
|
||||
});
|
||||
ButtonGroup b = new ButtonGroup();
|
||||
b.add(noteGamme);
|
||||
choixAxesX.add(noteGamme);
|
||||
b.add(regularGamme);
|
||||
choixAxesX.add(regularGamme);
|
||||
|
||||
contextMenu.add(choixAxesX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToShow(Spectre toShow) {
|
||||
if(toShow != null) {
|
||||
toShow = toShow.copy();
|
||||
if(toShow.getGamme() != xAxisGamme)
|
||||
toShow.castToGamme(xAxisGamme);
|
||||
}
|
||||
super.setToShow(toShow);
|
||||
}
|
||||
|
||||
public void setXAxisGamme(Gamme xAxisGamme){
|
||||
this.xAxisGamme = xAxisGamme;
|
||||
if(toShow()!=null)
|
||||
toShow().castToGamme(xAxisGamme);
|
||||
setXAxisCategories(xAxisGamme.toStringList());
|
||||
}
|
||||
|
||||
protected void initView() {
|
||||
super.initView();
|
||||
setXAxisGamme(NoteGamme.gamme());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getAmplitude(int idCategory) {
|
||||
return toShow().getAmplitude(idCategory);
|
||||
}}
|
91
src/gui/graphs/Spectrogram.java
Normal file
91
src/gui/graphs/Spectrogram.java
Normal file
|
@ -0,0 +1,91 @@
|
|||
package gui.graphs;
|
||||
|
||||
import conteneurs.Spectre;
|
||||
import generictools.Instant;
|
||||
import javafx.scene.paint.Color;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 31/05/14.
|
||||
*/
|
||||
public class Spectrogram extends BargramView<TemporalBuffer<Spectre>> {
|
||||
|
||||
Instant optiCastTime = new Instant(-1);
|
||||
Spectre optiSpectre = null;
|
||||
|
||||
private final float MAX = 4000000;
|
||||
|
||||
public Spectrogram() {
|
||||
super("Spectro", Spectre.class);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Color getColor(int idFreq, Instant time) {
|
||||
if(!time.equals(optiCastTime)) {
|
||||
optiCastTime = time;
|
||||
optiSpectre = buffer().get(time);
|
||||
if(optiSpectre!=null) {
|
||||
optiSpectre = optiSpectre.copy();
|
||||
if (optiSpectre.getGamme() != getGamme())
|
||||
optiSpectre.castToGamme(getGamme());
|
||||
}
|
||||
}
|
||||
|
||||
if(optiSpectre == null)
|
||||
return null;
|
||||
|
||||
return colorRamp(optiSpectre.getAmplitude(idFreq), MAX);
|
||||
}
|
||||
|
||||
public Color colorRamp(float pos, float maxPos){
|
||||
|
||||
if(pos == 0 || maxPos == 0)
|
||||
return Color.hsb(0,0,1);
|
||||
|
||||
float h1 = 360;
|
||||
float h2 = 330;
|
||||
float h3 = 310;
|
||||
if(pos<0){
|
||||
h1 = 190;
|
||||
h2 = 200;
|
||||
h3 = 222;
|
||||
pos = -pos;
|
||||
}
|
||||
|
||||
pos = Math.min(pos, maxPos-1);
|
||||
|
||||
float x = pos/maxPos;
|
||||
x = x*x;
|
||||
|
||||
if(x <= .08) {
|
||||
x/=.08;
|
||||
return Color.hsb(h1, x*.65f, 1);
|
||||
}
|
||||
|
||||
x-=.08;
|
||||
if(x <= .52) {
|
||||
x/=.52;
|
||||
return Color.hsb(((1f-x) * h1) + (x * h2), ((1f-x) * .65f) + (x * .8f), (1f-x) + (x * .75f));
|
||||
}
|
||||
x-=.52;
|
||||
x/=.4;
|
||||
|
||||
if(((1f-x) * h2) + (x * h3)>222 && ((1f-x) * h2) + (x * h3)<310)
|
||||
System.out.println("x:"+x+" h2:"+h2+"/ h3:"+h3);
|
||||
|
||||
try {
|
||||
return Color.hsb(((1f-x) * h2) + (x * h3), ((1f-x) * .8f) + (x * 1f), ((1f-x) * .75f) + (x * .53f));
|
||||
}catch (Exception e){
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Color.hsb(h3, 1f, .53f);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractBufferGraph getGenericGraph() {
|
||||
return new Spectrogram();
|
||||
}
|
||||
}
|
460
src/gui/graphs/SpectrogramView.java
Normal file
460
src/gui/graphs/SpectrogramView.java
Normal file
|
@ -0,0 +1,460 @@
|
|||
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));
|
||||
}
|
||||
}
|
227
src/gui/graphs/TraceChart.java
Normal file
227
src/gui/graphs/TraceChart.java
Normal file
|
@ -0,0 +1,227 @@
|
|||
package gui.graphs;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008, 2013 Oracle and/or its affiliates.
|
||||
* All rights reserved. Use is subject to license terms.
|
||||
*
|
||||
* This file is available and licensed under the following license:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the name of Oracle Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.shape.Path;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Un graphe XY customisé pour afficher des barres de longueur varriable et de
|
||||
* coordonnées variables.
|
||||
*
|
||||
* Adapté de CandleStickChart.java dans la démo Ensemble8 de javafx.
|
||||
* Originellement définit comme : "A CandleChart is a style of candle-chart used primarily
|
||||
* to describe price movements of a security, derivative,
|
||||
* or currency over time.
|
||||
* The Data Y value is used for the opening price and then the close, high and
|
||||
* low values are stored in the Data's extra value property using a
|
||||
* CandleExtraValues object."
|
||||
*
|
||||
* Modifié pour dessiner des barres horizontales plutôt que verticales et pour
|
||||
* accepter un String comme donnée en Y
|
||||
*/
|
||||
public class TraceChart<X, String> extends XYChart<Number, String> {
|
||||
|
||||
// -------------- CONSTRUCTORS ----------------------------------------------
|
||||
/**
|
||||
* Construct a new CandleStickChart with the given axis.
|
||||
*/
|
||||
public TraceChart(Axis<Number> xAxis, Axis<String> yAxis) {
|
||||
super(xAxis, yAxis);
|
||||
setAnimated(false);
|
||||
xAxis.setAnimated(false);
|
||||
yAxis.setAnimated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new TraceChart with the given axis and data.
|
||||
*/
|
||||
public TraceChart(Axis<Number> xAxis, Axis<String> yAxis, ObservableList<Series<Number, String>> data) {
|
||||
this(xAxis, yAxis);
|
||||
setData(data);
|
||||
}
|
||||
|
||||
// -------------- METHODS ------------------------------------------------------------------------------------------
|
||||
/** Called to update and layout the content for the plot */
|
||||
@Override protected void layoutPlotChildren() {
|
||||
// we have nothing to layout if no data is present
|
||||
if (getData() == null) {
|
||||
return;
|
||||
}
|
||||
// update candle positions
|
||||
for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
|
||||
XYChart.Series<Number, String> series = getData().get(seriesIndex);
|
||||
Iterator<Data<Number, String>> iter = getDisplayedDataIterator(series);
|
||||
Path seriesPath = null;
|
||||
if (series.getNode() instanceof Path) {
|
||||
seriesPath = (Path) series.getNode();
|
||||
seriesPath.getElements().clear();
|
||||
}
|
||||
while (iter.hasNext()) {
|
||||
XYChart.Data<Number, String> item = iter.next();
|
||||
double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
|
||||
double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
|
||||
Node itemNode = item.getNode();
|
||||
BarExtraValues extra = (BarExtraValues) item.getExtraValue();
|
||||
if (itemNode instanceof Bar && extra != null) {
|
||||
Bar bar = (Bar) itemNode;
|
||||
|
||||
double close = getXAxis().getDisplayPosition((extra.getClose()));
|
||||
// calculate candle width
|
||||
double barHeight = -1;
|
||||
if (getYAxis() instanceof CategoryAxis) {
|
||||
CategoryAxis yAxis = (CategoryAxis) getYAxis();
|
||||
barHeight = yAxis.getHeight()/yAxis.getCategories().size(); // no gap between ticks*/
|
||||
}
|
||||
// update candle
|
||||
bar.update(close - x, barHeight, extra.getColor());
|
||||
|
||||
// position the candle
|
||||
bar.setLayoutX(x);
|
||||
bar.setLayoutY(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void dataItemChanged(XYChart.Data<Number, String> item) {
|
||||
}
|
||||
|
||||
@Override protected void dataItemAdded(XYChart.Series<Number, String> series, int itemIndex, XYChart.Data<Number, String> item) {
|
||||
Node bar = createBar(item);
|
||||
getPlotChildren().add(bar);
|
||||
// always draw average line on top
|
||||
if (series.getNode() != null) {
|
||||
series.getNode().toFront();
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void dataItemRemoved(XYChart.Data<Number, String> item, XYChart.Series<Number, String> series) {
|
||||
final Node bar = item.getNode();
|
||||
getPlotChildren().remove(bar);
|
||||
}
|
||||
|
||||
@Override protected void seriesAdded(XYChart.Series<Number, String> series, int seriesIndex) {
|
||||
// handle any data already in series
|
||||
for (int j = 0; j < series.getData().size(); j++) {
|
||||
XYChart.Data item = series.getData().get(j);
|
||||
Node bar = createBar(item);
|
||||
getPlotChildren().add(bar);
|
||||
}
|
||||
// create series path
|
||||
Path seriesPath = new Path();
|
||||
//useless :)
|
||||
//seriesPath.getStyleClass().setAll("candlestick-average-line", "series" + seriesIndex);
|
||||
series.setNode(seriesPath);
|
||||
getPlotChildren().add(seriesPath);
|
||||
}
|
||||
|
||||
@Override protected void seriesRemoved(XYChart.Series<Number, String> series) {
|
||||
// remove all candle nodes
|
||||
for (XYChart.Data<Number, String> d : series.getData()) {
|
||||
final Node bar = d.getNode();
|
||||
getPlotChildren().remove(bar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Bar node to represent a single data item
|
||||
*/
|
||||
private Node createBar(final XYChart.Data item) {
|
||||
Node bar = item.getNode();
|
||||
BarExtraValues extra = (BarExtraValues) item.getExtraValue();
|
||||
// check if bar has already been created
|
||||
if (bar instanceof Bar) {
|
||||
((Bar) bar).setSeriesAndDataStyleClasses(extra.getColor());
|
||||
} else {
|
||||
bar = new Bar(extra.getColor());
|
||||
item.setNode(bar);
|
||||
}
|
||||
return bar;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is called when the range has been invalidated and we need to update it. If the axis are auto
|
||||
* ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
|
||||
* axis passing it that data.
|
||||
*/
|
||||
@Override
|
||||
protected void updateAxisRange() {
|
||||
// For candle stick chart we need to override this method as we need to let the axis know that they need to be able
|
||||
// to cover the whole area occupied by the high to low range not just its center data value
|
||||
final Axis<Number> xAxis = getXAxis();
|
||||
final Axis<String> yAxis = getYAxis();
|
||||
List<Number> xData = null;
|
||||
List<String> yData = null;
|
||||
if (xAxis.isAutoRanging()) {
|
||||
xData = new ArrayList<Number>();
|
||||
}
|
||||
if (yAxis.isAutoRanging()) {
|
||||
yData = new ArrayList<String>();
|
||||
}
|
||||
if (xData != null || yData != null) {
|
||||
for (XYChart.Series<Number, String> series : getData()) {
|
||||
for (XYChart.Data<Number, String> data : series.getData()) {
|
||||
if (yData != null) {
|
||||
yData.add(data.getYValue());
|
||||
}
|
||||
if (xData != null) {
|
||||
BarExtraValues extras = (BarExtraValues) data.getExtraValue();
|
||||
if (extras != null) {
|
||||
//yData.addNote(extras.getHigh());
|
||||
//yData.addNote(extras.getLow());
|
||||
} else {
|
||||
xData.add(data.getXValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xData != null) {
|
||||
xAxis.invalidateRange(xData);
|
||||
}
|
||||
if (yData != null) {
|
||||
yAxis.invalidateRange(yData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
152
src/gui/viewer_state/BufferViewerState.java
Normal file
152
src/gui/viewer_state/BufferViewerState.java
Normal file
|
@ -0,0 +1,152 @@
|
|||
package gui.viewer_state;
|
||||
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.BufferEvent;
|
||||
import processing.buffer.BufferEventListener;
|
||||
|
||||
/**
|
||||
* Created by gaby on 09/04/14.
|
||||
*
|
||||
* Classe controlleur s'interposant entre les buffers et leur visualisateur,
|
||||
* il gère les mises à jour différée dans le temps et les modifications de fenêtres du visualisateur
|
||||
*/
|
||||
public class BufferViewerState extends ViewerState implements BufferEventListener{
|
||||
|
||||
public enum Type{
|
||||
SINGLE_FRAME,
|
||||
CONTINUOUS_WINDOW,
|
||||
ALL_DATA,
|
||||
NO_DATA
|
||||
}
|
||||
|
||||
final private Type type;
|
||||
final private boolean bufferSizeSensible;
|
||||
|
||||
private int idMin=0, idMax=0;
|
||||
private Buffer buffer;
|
||||
|
||||
|
||||
private BufferViewerState(Buffer buffer, Viewer viewer, Type type, boolean bufferSizeSensible){
|
||||
super(viewer);
|
||||
this.type = type;
|
||||
this.bufferSizeSensible = bufferSizeSensible;
|
||||
this.buffer = buffer;
|
||||
buffer.addBufferListener(this);
|
||||
init();
|
||||
}
|
||||
|
||||
static public BufferViewerState singleFrameWindow( Viewer viewer, Buffer buffer){
|
||||
return new BufferViewerState(buffer, viewer, Type.SINGLE_FRAME, false);
|
||||
}
|
||||
static public BufferViewerState continousWindow(Viewer viewer, Buffer buffer){
|
||||
return new BufferViewerState(buffer, viewer, Type.CONTINUOUS_WINDOW, false);
|
||||
}
|
||||
static public BufferViewerState allDataWindow(Viewer viewer, Buffer buffer){
|
||||
return new BufferViewerState(buffer, viewer, Type.ALL_DATA, true);
|
||||
}
|
||||
static public BufferViewerState sizeSensible(Viewer viewer, Buffer buffer){
|
||||
return new BufferViewerState(buffer, viewer, Type.NO_DATA, true);
|
||||
}
|
||||
|
||||
public void setWindow(int idMin, int idMax){
|
||||
this.idMax = idMax;
|
||||
this.idMin = idMin;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
public void setSingleFrame(int id){
|
||||
idMax = id;
|
||||
idMin = id;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Type getType(){
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getMaximumIndex(){
|
||||
if(type==Type.CONTINUOUS_WINDOW)
|
||||
return idMax;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getMinimumIndex(){
|
||||
if(type==Type.CONTINUOUS_WINDOW)
|
||||
return idMin;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getSingleIndex(){
|
||||
if(type == Type.SINGLE_FRAME)
|
||||
return idMin;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean windowContains(int sampleID){
|
||||
switch (type){
|
||||
case NO_DATA:
|
||||
return false;
|
||||
case ALL_DATA:
|
||||
return true;
|
||||
case CONTINUOUS_WINDOW:
|
||||
return sampleID >= idMin && sampleID <= idMax;
|
||||
case SINGLE_FRAME:
|
||||
return sampleID == idMin;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bufferEvent(BufferEvent e) {
|
||||
if (e.getType() == BufferEvent.Type.SAMPLE_ADDED || e.getType() == BufferEvent.Type.SAMPLE_CHANGED){
|
||||
if (windowContains(e.getSampleID()))
|
||||
setState(State.UPDATE_NEEDED, slowingFactorOnDataChange());
|
||||
}else if(e.getType()== BufferEvent.Type.SAMPLE_DELETED){
|
||||
if(type==Type.SINGLE_FRAME)
|
||||
if(e.getSampleID()==idMin)
|
||||
|
||||
setState(State.CLEAN_NEEDED, slowingFactorOnDataChange());
|
||||
else if(windowContains(e.getSampleID()))
|
||||
setState(State.UPDATE_NEEDED, slowingFactorOnDataChange());
|
||||
|
||||
}else if(e.getType()== BufferEvent.Type.INVALIDATE)
|
||||
invalidate();
|
||||
|
||||
if(bufferSizeSensible&&(e.getType()== BufferEvent.Type.SAMPLE_ADDED||e.getType()== BufferEvent.Type.SAMPLE_DELETED))
|
||||
setState(State.UPDATE_NEEDED, slowingFactorOnDataChange());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDraw() {
|
||||
switch(type){
|
||||
case CONTINUOUS_WINDOW:
|
||||
return idMin< buffer.size();
|
||||
case SINGLE_FRAME:
|
||||
return buffer.get(idMin) != null;
|
||||
}
|
||||
return !buffer.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visibilityChanged() {
|
||||
if(visibility)
|
||||
buffer.addBufferListener(this);
|
||||
else
|
||||
buffer.removeBufferListener(this);
|
||||
}
|
||||
|
||||
public Buffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public boolean isSizeSensitive(){
|
||||
return bufferSizeSensible;
|
||||
}
|
||||
}
|
57
src/gui/viewer_state/ObservableViewerState.java
Normal file
57
src/gui/viewer_state/ObservableViewerState.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package gui.viewer_state;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import conteneurs.ObservableObjectEvent;
|
||||
|
||||
/**
|
||||
* Created by gaby on 07/05/14.
|
||||
*
|
||||
* Classe controlleur s'interposant entre les objets observables et leur visualisateur,
|
||||
* il gère les mises à jour différée dans le temps
|
||||
*/
|
||||
public class ObservableViewerState extends ViewerState implements ObservableObject.Listener {
|
||||
ObservableObject observableObject;
|
||||
|
||||
public ObservableViewerState(Viewer viewer, ObservableObject observableObject) {
|
||||
super(viewer);
|
||||
setObservableObject(observableObject);
|
||||
init();
|
||||
}
|
||||
|
||||
public ObservableViewerState(Viewer viewer){
|
||||
this(viewer, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
setState(State.UPDATE_NEEDED, slowingFactorOnDataChange());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDraw() {
|
||||
return observableObject!=null;
|
||||
}
|
||||
|
||||
public void setObservableObject(ObservableObject observableObject){
|
||||
if(this.observableObject!=null)
|
||||
this.observableObject.removeListener(this);
|
||||
|
||||
this.observableObject = observableObject;
|
||||
if(this.observableObject!=null)
|
||||
this.observableObject.addListener(this);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public ObservableObject getObservableObject() {
|
||||
return observableObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visibilityChanged() {
|
||||
if(isVisible()){
|
||||
observableObject.addListener(this);
|
||||
}else
|
||||
observableObject.removeListener(this);
|
||||
}
|
||||
}
|
10
src/gui/viewer_state/Viewer.java
Normal file
10
src/gui/viewer_state/Viewer.java
Normal file
|
@ -0,0 +1,10 @@
|
|||
package gui.viewer_state;
|
||||
|
||||
/**
|
||||
* Created by gaby on 09/04/14.
|
||||
* Interface définissant les visualisateurs
|
||||
*/
|
||||
public interface Viewer {
|
||||
public void updateView();
|
||||
public void cleanView();
|
||||
}
|
113
src/gui/viewer_state/ViewerState.java
Normal file
113
src/gui/viewer_state/ViewerState.java
Normal file
|
@ -0,0 +1,113 @@
|
|||
package gui.viewer_state;
|
||||
|
||||
import processing.ProcessControl;
|
||||
|
||||
/**
|
||||
* Created by gaby on 06/05/14.
|
||||
* Classe abstraite définissant les attributs communs au viewerState: relation avec les Graphics View
|
||||
*/
|
||||
public abstract class ViewerState {
|
||||
|
||||
public enum State{
|
||||
UPDATE_NEEDED,
|
||||
UPDATED,
|
||||
CLEAN_NEEDED
|
||||
}
|
||||
private State state = State.CLEAN_NEEDED;
|
||||
|
||||
final protected Viewer viewer;
|
||||
protected boolean visibility;
|
||||
private int slowingUpdate = 0;
|
||||
private int slowingFactorOnDataChange = 0;
|
||||
|
||||
protected ViewerState(Viewer viewer) {
|
||||
this.viewer = viewer;
|
||||
visibility = false;
|
||||
}
|
||||
|
||||
protected void init(){
|
||||
ProcessControl.instance().registerViewerState(this);
|
||||
setVisibility(false);
|
||||
}
|
||||
|
||||
public void invalidate(){
|
||||
if(!canDraw())
|
||||
state = State.CLEAN_NEEDED;
|
||||
else
|
||||
state = State.UPDATE_NEEDED;
|
||||
}
|
||||
|
||||
public void applyState(){
|
||||
if(slowingUpdate>0) {
|
||||
slowingUpdate--;
|
||||
return;
|
||||
}else if(slowingUpdate == -1)
|
||||
slowingUpdate = 0;
|
||||
|
||||
if(state == State.UPDATED)
|
||||
return;
|
||||
State sCopy = state;
|
||||
state = State.UPDATED;
|
||||
if(sCopy == State.UPDATE_NEEDED)
|
||||
viewer.updateView();
|
||||
else if(sCopy== State.CLEAN_NEEDED)
|
||||
viewer.cleanView();
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(boolean visibility) {
|
||||
if(visibility==this.visibility)
|
||||
return;
|
||||
|
||||
this.visibility = visibility;
|
||||
visibilityChanged();
|
||||
}
|
||||
|
||||
public Viewer getViewer() {
|
||||
return viewer;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
final public boolean setState(State state){
|
||||
return setState(state, 0);
|
||||
}
|
||||
|
||||
final public boolean setState(State state, int slowFactor){
|
||||
if(slowFactor!=0){
|
||||
if(slowFactor==-1)
|
||||
slowingUpdate = -1;
|
||||
else if(slowingUpdate==0)
|
||||
slowingUpdate = slowFactor;
|
||||
}
|
||||
if(state == State.UPDATED)
|
||||
return this.state == State.UPDATED;
|
||||
|
||||
this.state = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void forceState(State state){
|
||||
this.state = state;
|
||||
slowingUpdate = -1;
|
||||
}
|
||||
|
||||
abstract protected boolean canDraw();
|
||||
|
||||
protected void visibilityChanged(){
|
||||
|
||||
}
|
||||
|
||||
public int slowingFactorOnDataChange() {
|
||||
return slowingFactorOnDataChange;
|
||||
}
|
||||
|
||||
public void setSlowingFactorOnDataChange(int slowingFactorOnDataChange) {
|
||||
this.slowingFactorOnDataChange = slowingFactorOnDataChange;
|
||||
}
|
||||
}
|
404
src/processing/AudioInputToBuffer.java
Normal file
404
src/processing/AudioInputToBuffer.java
Normal file
|
@ -0,0 +1,404 @@
|
|||
package processing;
|
||||
|
||||
import processing.buffer.Storage;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/03/14.
|
||||
*/
|
||||
public class AudioInputToBuffer implements Runnable{
|
||||
private TemporalBuffer<Float> buffer;
|
||||
|
||||
private AudioInputStream input;
|
||||
private Integer channel;
|
||||
|
||||
private TargetDataLine line = null;
|
||||
private File file = null;
|
||||
|
||||
private String name = "Input";
|
||||
|
||||
private Thread thread;
|
||||
private boolean paused = false;
|
||||
private boolean running = false;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ce constructeur créé autant de buffer que de channel dans input.
|
||||
* Les buffers auront une fréquence d'échantillonage par défaut de 44100 Hz.
|
||||
* @param input flux audio d'entrée
|
||||
*/
|
||||
private AudioInputToBuffer(AudioInputStream input) {
|
||||
channel = 0;
|
||||
|
||||
this.input = input;
|
||||
|
||||
init(44100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ce constructeur créé un buffer alimenté par la chaine du flux correspondant à l'index en arguments
|
||||
* Les buffers auront une fréquence d'échantillonage par défaut de 44100 Hz.
|
||||
* @param input flux audio d'entrée
|
||||
* @param channel index de la chaine
|
||||
*/
|
||||
private AudioInputToBuffer(AudioInputStream input, int channel) {
|
||||
this(input, channel, 44100);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ce constructeur créé autant de buffer que d'index valide dans la liste des chaines du flux
|
||||
* De plus on force la fréquence d'échantillonage des buffers créés pour qu'elle soit égales à sampleRate
|
||||
* @param input flux audio d'entrée
|
||||
*/
|
||||
private AudioInputToBuffer(AudioInputStream input, int channel, int sampleRate) {
|
||||
this.channel = channel;
|
||||
if(input.getFormat().getChannels()<=channel)
|
||||
this.channel=0;
|
||||
|
||||
this.input = input;
|
||||
init(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génére un AudioInputToBuffer à partir d'une entrée audio du systeme
|
||||
* (normalement le micro... mais honnêtement je ne peux rien certifier)
|
||||
* @return l'AudioInputToBuffer
|
||||
*/
|
||||
static public AudioInputToBuffer fromMicro(){
|
||||
Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo();
|
||||
for (Mixer.Info aMixersInfo : mixersInfo) {
|
||||
Mixer mixer = AudioSystem.getMixer(aMixersInfo);
|
||||
Line.Info[] linesInfo = mixer.getTargetLineInfo();
|
||||
for (Line.Info aLinesInfo : linesInfo) {
|
||||
TargetDataLine l = null;
|
||||
try {
|
||||
l = (TargetDataLine) mixer.getLine(aLinesInfo);
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
AudioInputToBuffer r = new AudioInputToBuffer(new AudioInputStream(l));
|
||||
r.line = l;
|
||||
r.setName("Micro");
|
||||
|
||||
System.out.println(r.line.getFormat());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("AudioInputStream.fromMicro(): Aucun port d'entrée trouvée");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génére un AudioInputToBuffer à partir d'une entrée audio du systeme
|
||||
* (normalement le micro... mais honnêtement je ne peux rien certifier)
|
||||
* @param channel Spécifie les chaines de l'entrée à utiliser
|
||||
* @param sampleRate Spécifie la fréquence d'échantillonage à utiliser
|
||||
* @return l'AudioInputToBuffer
|
||||
*/
|
||||
static public AudioInputToBuffer fromMicro(Integer channel, int sampleRate){
|
||||
Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo();
|
||||
for (Mixer.Info aMixersInfo : mixersInfo) {
|
||||
Mixer mixer = AudioSystem.getMixer(aMixersInfo);
|
||||
Line.Info[] linesInfo = mixer.getTargetLineInfo();
|
||||
for (Line.Info aLinesInfo : linesInfo) {
|
||||
if (mixer.isLineSupported(aLinesInfo)) {
|
||||
TargetDataLine l = null;
|
||||
try {
|
||||
l = (TargetDataLine) mixer.getLine(aLinesInfo);
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
AudioInputToBuffer r = new AudioInputToBuffer(new AudioInputStream(l), channel, sampleRate);
|
||||
r.line = l;
|
||||
r.setName("Micro");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("AudioInputStream.fromMicro(): Aucun port d'entrée trouvée");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Génére un AudioInputToBuffer à partir d'un fichier audio
|
||||
* (si bien sur le format est correct, fin en fait que si c'est du wav pour l'instant)
|
||||
* @return l'AudioInputToBuffer
|
||||
*/
|
||||
static public AudioInputToBuffer fromFile(File file){
|
||||
try {
|
||||
if(!file.canRead()){
|
||||
System.out.println("AudioInputToBuffer.fromFile(): Erreur fichier illisible");
|
||||
return null;
|
||||
}
|
||||
|
||||
AudioInputToBuffer a = new AudioInputToBuffer(AudioSystem.getAudioInputStream(file), 0);
|
||||
a.setName(file.getName());
|
||||
a.file = file;
|
||||
return a;
|
||||
} catch (UnsupportedAudioFileException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génére un AudioInputToBuffer à partir d'un fichier audio
|
||||
* (si bien sur le format est correct, fin en fait que si c'est du wav pour l'instant)
|
||||
* @param channel Spécifie les chaines de l'entrée à utiliser
|
||||
* @param sampleRate Spécifie la fréquence d'échantillonage à utiliser
|
||||
* @return l'AudioInputToBuffer
|
||||
*/
|
||||
static public AudioInputToBuffer fromFile(File file, Integer channel, int sampleRate){
|
||||
try {
|
||||
AudioInputToBuffer a = new AudioInputToBuffer(AudioSystem.getAudioInputStream(file), channel, sampleRate);
|
||||
a.setName(file.getName());
|
||||
a.file = file;
|
||||
return a;
|
||||
} catch (UnsupportedAudioFileException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void init(int sampleRate){
|
||||
//On reformate le flux audio pour qu'il soit en PCM_SIGNED, little-endian et avec un nombre de channel = channel
|
||||
int sampleSizeBytes = input.getFormat().getSampleSizeInBits()/8;
|
||||
|
||||
int channel = input.getFormat().getChannels();
|
||||
|
||||
this.input = AudioSystem.getAudioInputStream(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, sampleSizeBytes * 8, channel, sampleSizeBytes * channel, sampleRate * channel, false), input);
|
||||
|
||||
buffer = new TemporalBuffer<>(Storage.Type.ArrayList, sampleRate);
|
||||
buffer.setName(name);
|
||||
buffer.setType(Float.class);
|
||||
buffer.setProcessingOrder(processing.processes.Process.ProcessingOrder.ASCENDING_ORDER);
|
||||
|
||||
thread = new Thread(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Démarre l'écriture dans le buffer
|
||||
* @return vrai si elle n'était pas déjà lancée
|
||||
*/
|
||||
public boolean start(){
|
||||
|
||||
if(paused) {
|
||||
paused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(thread.isAlive())
|
||||
return false;
|
||||
|
||||
thread = new Thread(this);
|
||||
|
||||
if(line != null){
|
||||
line.flush();
|
||||
try {
|
||||
line.open();
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
line.start();
|
||||
}
|
||||
|
||||
getBuffer().tryEnsureCapacity((int)input.getFrameLength());
|
||||
|
||||
paused = false;
|
||||
running = true;
|
||||
thread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mets l'écriture en pause, si la lecture est en temps réel les données sont perdues
|
||||
* @return vrai si l'écriture était lancée
|
||||
*/
|
||||
public boolean pause(){
|
||||
if(!thread.isAlive()||paused)
|
||||
return false;
|
||||
|
||||
if(line != null)
|
||||
line.stop();
|
||||
|
||||
paused = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppe l'écriture dans le buffer, ferme le port d'entrée si la lecture était en temps réel.
|
||||
* @return
|
||||
*/
|
||||
public boolean interrupt(){
|
||||
if(!thread.isAlive())
|
||||
return false;
|
||||
|
||||
thread.interrupt();
|
||||
running = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void clean(){
|
||||
if(line != null){
|
||||
line.stop();
|
||||
line.close();
|
||||
}
|
||||
|
||||
buffer.lastSampleAdded();
|
||||
System.out.println("Fin de l'enregistrement, à vous les studios!!!");
|
||||
}
|
||||
|
||||
public boolean isAlive(){
|
||||
return (!thread.isAlive()) || paused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final int NBR_CHANNEL = input.getFormat().getChannels();
|
||||
final int NBR_BYTES = input.getFormat().getSampleSizeInBits()/8;
|
||||
|
||||
while(running){
|
||||
|
||||
try {
|
||||
|
||||
|
||||
byte[] data = new byte[NBR_CHANNEL*NBR_BYTES];
|
||||
|
||||
//On récupère dans data une frame du signal tant que l'on peut, ensuite,
|
||||
// pour chaque buffer de sortie on recompose le float pour chaque buffer de la channel correspondante
|
||||
while(input.read(data,0, NBR_CHANNEL*NBR_BYTES)!=-1 && data!=null && !paused && running){
|
||||
|
||||
buffer.add(floatFromByteArray(data, channel*NBR_BYTES,NBR_BYTES));
|
||||
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(input.getFrameLength()!= AudioSystem.NOT_SPECIFIED && input.getFrameLength() == buffer.size()) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
do{
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
}while(paused&&!running);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur du flaot correspondant à une séquence d'octet
|
||||
* @param b tableau d'octets
|
||||
* @param offset indice du premier octet à traiter
|
||||
* @param nbByte nombre d'octet formant le float
|
||||
* @return la valeur en float de la séquence d'octet
|
||||
*/
|
||||
private static float floatFromByteArray(byte[] b, int offset, int nbByte){
|
||||
|
||||
if(nbByte>4||nbByte<1||nbByte>offset+b.length){
|
||||
System.out.println("Erreur de décodage byte à byte");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(nbByte){
|
||||
case 1:
|
||||
return (b[offset] & 0xFF);
|
||||
case 2:
|
||||
return (b[offset] & 0xFF) | ((b[1+offset]) << 8);
|
||||
case 3:
|
||||
return (b[offset] & 0xFF) | ((b[1+offset] & 0xFF) << 8) | ((b[2+offset]) << 16);
|
||||
case 4:
|
||||
return (b[offset] & 0xFF) | ((b[1+offset] & 0xFF) << 8) | ((b[2+offset] & 0xFF) << 16) | (b[3+offset] << 24);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie le buffer alimenté
|
||||
* @return Le buffer
|
||||
*/
|
||||
public TemporalBuffer<Float> getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setBuffer(TemporalBuffer<Float> buffer){
|
||||
this.buffer = buffer;
|
||||
buffer.setName(name);
|
||||
buffer.setType(Float.class);
|
||||
buffer.invalidateProcessedSamples();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le flux d'entrée
|
||||
* @return AudioInputStream qui alimente les buffers
|
||||
*/
|
||||
public AudioInputStream getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le numéro de la chaîne du flux d'entrée utilisée pour alimenté le buffer
|
||||
* @return Numéro de la chaîne (première chaîne = 0)
|
||||
*/
|
||||
public int getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'état du processus
|
||||
* @return Vrai si le processus est lancé
|
||||
*/
|
||||
public boolean isRunning(){
|
||||
return thread.isAlive();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
201
src/processing/BufferPlayer.java
Normal file
201
src/processing/BufferPlayer.java
Normal file
|
@ -0,0 +1,201 @@
|
|||
package processing;
|
||||
|
||||
import generictools.Instant;
|
||||
import gui.PlayerControl;
|
||||
import processing.buffer.StorageIterator;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
|
||||
/**
|
||||
* Created by gaby on 16/04/14.
|
||||
*/
|
||||
public class BufferPlayer implements Runnable{
|
||||
TemporalBuffer<Float> buffer;
|
||||
SourceDataLine line;
|
||||
int currentFrame = 0;
|
||||
|
||||
StorageIterator<Float> currentSample;
|
||||
|
||||
boolean muted = false;
|
||||
float volume = 1;
|
||||
|
||||
int iTest = 0;
|
||||
|
||||
Thread thread = new Thread(this);
|
||||
boolean running = false;
|
||||
final float CLOCK_PERIOD = .05f;
|
||||
|
||||
public BufferPlayer(TemporalBuffer<Float> buffer) {
|
||||
this.buffer = buffer;
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, buffer.getSampleRate(),16,1,2,44100, false), (int)(CLOCK_PERIOD*44100*2));
|
||||
Mixer m = AudioSystem.getMixer(AudioSystem.getMixerInfo()[0]);
|
||||
|
||||
for(Line.Info i : m.getSourceLineInfo()){
|
||||
System.out.println(i);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
line = (SourceDataLine)AudioSystem.getLine(info);
|
||||
line.open();
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
currentSample = buffer.getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarre le lecteur de buffer
|
||||
* @return Renvoie vrai si il n'était pas lancé
|
||||
*/
|
||||
public boolean play(){
|
||||
|
||||
if(thread.isAlive())
|
||||
return false;
|
||||
|
||||
line.flush();
|
||||
if(!line.isActive())
|
||||
line.start();
|
||||
|
||||
thread = new Thread(this, "BufferPlayer");
|
||||
thread.setPriority(Thread.MAX_PRIORITY);
|
||||
running = true;
|
||||
thread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mets en pause le lecteur
|
||||
* @return
|
||||
*/
|
||||
public boolean pause(){
|
||||
running = false;
|
||||
thread.interrupt();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la frame actuelle
|
||||
* @param frame
|
||||
*/
|
||||
public void setFrame(Instant frame){
|
||||
currentFrame = frame.mapToIndex(buffer);
|
||||
currentSample = buffer.getIterator(frame.mapToIndex(buffer));
|
||||
PlayerControl.instance().frameChanged(frame);
|
||||
}
|
||||
|
||||
private void clean(){
|
||||
running = false;
|
||||
line.stop();
|
||||
}
|
||||
|
||||
public static byte [] float2ByteArray (float value)
|
||||
{
|
||||
|
||||
int v = Math.round(value);
|
||||
return new byte[]{
|
||||
(byte) (v & 0xFF),
|
||||
(byte) ((v >>> 8) & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
public Instant getCurrentFrame(){
|
||||
return Instant.fromIndex(currentFrame, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'état muet du lecteur
|
||||
* @return Renvoie vrai si muet
|
||||
*/
|
||||
public boolean isMuted() {
|
||||
return muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie l'état muet du lecteur
|
||||
* @param muted
|
||||
*/
|
||||
public void setMuted(boolean muted) {
|
||||
this.muted = muted;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(float volume) {
|
||||
if(volume==0)
|
||||
setMuted(true);
|
||||
else
|
||||
setMuted(false);
|
||||
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* When an object implementing interface <code>Runnable</code> is used
|
||||
* to create a thread, starting the thread causes the object's
|
||||
* <code>run</code> method to be called in that separately executing
|
||||
* thread.
|
||||
* <p>
|
||||
* The general contract of the method <code>run</code> is that it may
|
||||
* take any action whatsoever.
|
||||
*
|
||||
* @see Thread#run()
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
boolean catchBack = false;
|
||||
while(!thread.interrupted()&&running) {
|
||||
long time = System.currentTimeMillis();
|
||||
if (currentFrame >= ProcessControl.instance().getMainInput().getBuffer().size()) {
|
||||
PlayerControl.instance().stop();
|
||||
return;
|
||||
}
|
||||
int lastI = (int) (buffer.getSampleRate() * CLOCK_PERIOD);
|
||||
|
||||
|
||||
float k = 1;
|
||||
if (!muted && currentSample.hasNext()) {
|
||||
if (catchBack) {
|
||||
k = 1.5f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastI*k; i++) {
|
||||
if (currentSample.hasNext())
|
||||
line.write(float2ByteArray(currentSample.next() * volume), 0, 2);
|
||||
else {
|
||||
byte[] nullBytes = new byte[(int)(lastI*k- i) * 2];
|
||||
line.write(nullBytes, 0, nullBytes.length);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
currentFrame += lastI*k;
|
||||
|
||||
|
||||
PlayerControl.instance().frameChanged(Instant.fromIndex(currentFrame, buffer));
|
||||
|
||||
time-=System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
if((int)(CLOCK_PERIOD*1000)+time>0) {
|
||||
thread.sleep((int) (CLOCK_PERIOD * 1000) + time);
|
||||
catchBack = false;
|
||||
}
|
||||
else {
|
||||
thread.sleep(0);
|
||||
catchBack = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
}
|
||||
clean();
|
||||
}
|
||||
}
|
218
src/processing/PostProcessScript.java
Normal file
218
src/processing/PostProcessScript.java
Normal file
|
@ -0,0 +1,218 @@
|
|||
package processing;
|
||||
|
||||
import conteneurs.Note;
|
||||
import generictools.Instant;
|
||||
import generictools.Strings;
|
||||
import processing.buffer.NoteBuffer;
|
||||
import processing.properties.FloatProperty;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**Created by gaby on 03/06/14.
|
||||
* Script qui sert à l'exportation vers Lilypond. Regroupe tous les algo de post-traitement nécéssaires à cette exportation. */
|
||||
public class PostProcessScript implements Runnable{
|
||||
|
||||
private NoteBuffer buffer;
|
||||
private float tolerance = 0.08f;
|
||||
private float progress = 0;
|
||||
Runnable callBack;
|
||||
|
||||
Thread thread = null;
|
||||
private static PostProcessScript script = null;
|
||||
|
||||
private PostProcessScript(NoteBuffer buffer, Runnable callBack){
|
||||
this.buffer = buffer;
|
||||
this.callBack = callBack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance le script
|
||||
* @param buffer
|
||||
* @param callBack
|
||||
*/
|
||||
public static void launchScript(NoteBuffer buffer, Runnable callBack){
|
||||
interrupt();
|
||||
PostProcessScript script = new PostProcessScript(buffer, callBack);
|
||||
script.thread = new Thread(script);
|
||||
script.thread.start();
|
||||
script = null;
|
||||
}
|
||||
|
||||
public static void interrupt(){
|
||||
if(script!=null && script.thread!=null && script.thread.isAlive())
|
||||
script.thread.interrupt();
|
||||
script = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
unifyTempo();
|
||||
|
||||
// System.out.println("NOTES:");
|
||||
//
|
||||
// for (int i = 0; i < buffer.getNotesNbr(); i++) {
|
||||
// System.out.println("\t" + buffer.getNote(i).getNoteName() + " (" + buffer.getNote(i).getUnifiedDuration() + ")");
|
||||
// }
|
||||
|
||||
JFileChooser jfc = new JFileChooser();
|
||||
jfc.setDialogTitle("Choix du dossier de sortie");
|
||||
jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
int ret = jfc.showOpenDialog(null);
|
||||
if(ret != JFileChooser.APPROVE_OPTION)
|
||||
return;
|
||||
|
||||
File file = new File(jfc.getSelectedFile().getPath()+"/exportLilypond.txt");
|
||||
System.out.println(jfc.getSelectedFile().getPath()+"/exportLilypond.txt");
|
||||
try {
|
||||
if(!file.exists())
|
||||
file.createNewFile();
|
||||
FileWriter fileWriter = new FileWriter(file);
|
||||
fileWriter.write(toLilyPond());
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if(callBack!=null)
|
||||
try {
|
||||
callBack.run();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String toLilyPond(){
|
||||
String r = "<< {";
|
||||
for (int i = 0; i < buffer.getNotesNbr(); i++) {
|
||||
r+=buffer.getNote(i).getEnglishNoteName() + buffer.getNote(i).getUnifiedDuration() + " ";
|
||||
}
|
||||
r+="} >>";
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Cherche le tempo du morceau en se basant sur les écarts entre les notes.
|
||||
* A partir de la, il donne a chaque note une duree (ronde, blanche noire, croche, etc) et permet de trouver le rythme */
|
||||
private void unifyTempo(){
|
||||
ArrayList<Float> listEcarts = calculeEcarts(bufferSortOut(buffer));
|
||||
ArrayList<Float> EcartsReference = new ArrayList<>();
|
||||
ArrayList<Integer> nEcartParType = new ArrayList<>();
|
||||
|
||||
|
||||
//Cr<EFBFBD>ation des cat<EFBFBD>gories de r<EFBFBD>f<EFBFBD>rence, tri<EFBFBD>es par ordre croissant
|
||||
|
||||
EcartsReference.add(listEcarts.get(0));
|
||||
nEcartParType.add(1);
|
||||
for (int i = 1; i < listEcarts.size(); i++) {
|
||||
float currentEcart = listEcarts.get(i);
|
||||
boolean newCategoryNeeded = true;
|
||||
for (int j = 0; j < EcartsReference.size(); j++) {
|
||||
//Si on est dans la marge de tolerance d<EFBFBD>finie en param<EFBFBD>tre, on red<EFBFBD>finit la r<EFBFBD>f<EFBFBD>rence gr<EFBFBD>ce <EFBFBD> une moyenne pond<EFBFBD>r<EFBFBD>e
|
||||
if(currentEcart>((1-tolerance)*EcartsReference.get(j)) && currentEcart<((1+tolerance)*EcartsReference.get(j))){
|
||||
EcartsReference.set(j, ( (nEcartParType.get(j)*EcartsReference.get(j))+ currentEcart) / (nEcartParType.get(j)+1) );
|
||||
nEcartParType.set(j, nEcartParType.get(j)+1);
|
||||
newCategoryNeeded = false;
|
||||
}
|
||||
}
|
||||
//Sinon cr<EFBFBD>ation d'une nouvelle cat<EFBFBD>gorie si aucune existante ne convient
|
||||
if(newCategoryNeeded){
|
||||
int j = 0;
|
||||
while(j<EcartsReference.size() && EcartsReference.get(j)<= currentEcart){
|
||||
j++;
|
||||
}
|
||||
EcartsReference.add(j,currentEcart);
|
||||
nEcartParType.add(j,1);
|
||||
}
|
||||
}
|
||||
|
||||
//Definition des ecarts unifies : l'ecart le plus utilise est la noire par défaut sauf si dans ce cas, le plus grand est > à une ronde
|
||||
ArrayList<String> UnifiedEcarts = new ArrayList<>(EcartsReference.size());
|
||||
int indexEcartMostUsed = 0;
|
||||
int maxUsed = 0;
|
||||
for (int i = 0; i < nEcartParType.size(); i++) {
|
||||
if( nEcartParType.get(i)> maxUsed){
|
||||
maxUsed = nEcartParType.get(i);
|
||||
indexEcartMostUsed = i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nEcartParType.size(); i++) {
|
||||
UnifiedEcarts.add("0");
|
||||
}
|
||||
|
||||
//Cas où on se retrouve avec le max plus grand qu'une ronde
|
||||
while(indexEcartMostUsed < EcartsReference.size()-1 && EcartsReference.get(indexEcartMostUsed)*(4+tolerance)>EcartsReference.get(EcartsReference.size()-1)){
|
||||
indexEcartMostUsed++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//Donne à chaque note une durée unified
|
||||
for (int i = 0; i < buffer.getNotesNbr(); i++) {
|
||||
double fac = Math.log(EcartsReference.get(indexEcartMostUsed)*4/ buffer.getNote(i).getDuration())/Math.log(2);
|
||||
buffer.getNote(i).setUnifiedDuration(String.valueOf(Math.round(Math.pow(2, (int) fac))) + (fac % 1 < 0.5 ? "." : ""));
|
||||
}
|
||||
|
||||
|
||||
System.out.println("ECARTS:");
|
||||
|
||||
for (int i = 0; i < UnifiedEcarts.size(); i++) {
|
||||
System.out.println("\t" + EcartsReference.get(i) + " (" + UnifiedEcarts.get(i) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/** Trie les instants de d<EFBFBD>part des notes dans l'ordre croissant
|
||||
* @param buffer le buffer dont les instants sont <EFBFBD> trier */
|
||||
private ArrayList<Instant> bufferSortOut(NoteBuffer buffer){
|
||||
ArrayList<Instant> tmp = new ArrayList<>();
|
||||
ArrayList<Instant> res = new ArrayList<>();
|
||||
// On r<EFBFBD>cup<EFBFBD>re tous les instans de d<EFBFBD>part
|
||||
for (int i = 0; i < buffer.getNotesNbr(); i++) {
|
||||
tmp.add(buffer.getNote(i).getStart());
|
||||
}
|
||||
|
||||
//Tri par ordre croissant
|
||||
res.add(tmp.get(0));
|
||||
for (int i = 1; i < tmp.size(); i++) {
|
||||
int j = 0;
|
||||
while(j<res.size() && res.get(j).isLowerOrEquThan(tmp.get(i))){
|
||||
j++;
|
||||
}
|
||||
res.add(j, tmp.get(i));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/** Calcule les <EFBFBD>carts entre les instants de d<EFBFBD>part successifs
|
||||
* @param instantList la liste des instants tri<EFBFBD>s par ordre croissant */
|
||||
private ArrayList<Float> calculeEcarts(ArrayList<Instant> instantList){
|
||||
ArrayList<Float> res = new ArrayList<>();
|
||||
for (int i = 1; i < instantList.size(); i++) {
|
||||
res.add(instantList.get(i).substract(instantList.get(i - 1)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean updateProgress(float progress){
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException e) {
|
||||
return true;
|
||||
}
|
||||
this.progress = progress;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static float getProgress(){
|
||||
if(script == null)
|
||||
return -1;
|
||||
return script.progress;
|
||||
}
|
||||
}
|
429
src/processing/ProcessControl.java
Normal file
429
src/processing/ProcessControl.java
Normal file
|
@ -0,0 +1,429 @@
|
|||
package processing;
|
||||
|
||||
|
||||
import conteneurs.NoteEventList;
|
||||
import conteneurs.Spectre;
|
||||
import generictools.Instant;
|
||||
import generictools.Pair;
|
||||
import gui.PlayerControl;
|
||||
import gui.ProcessSumUp;
|
||||
import gui.viewer_state.ViewerState;
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.NoteBuffer;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
import processing.processes.*;
|
||||
import processing.processes.FreqEdgeDetector;
|
||||
import processing.processes.Process;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Classe centrale pour le traitement et l'affichage:
|
||||
*
|
||||
* Elle est chargé non seulement de lancer, controler et stopper le traitement dans sa globalité,
|
||||
* mais elle contient aussi le thread d'affichage qui déclenche, au maximum 10 fois par secondes
|
||||
* la mise à jour des graphs quand leur buffer a été modifié.
|
||||
*/
|
||||
public class ProcessControl implements Runnable {
|
||||
|
||||
private final ArrayList<Pair<processing.processes.Process, Integer>> processes = new ArrayList<>();
|
||||
private final ArrayList<Pair<AudioInputToBuffer, Integer>> inputs= new ArrayList<>();
|
||||
private int mainInput=-1;
|
||||
private final ArrayList<Buffer> buffers = new ArrayList<>();
|
||||
|
||||
private final ArrayList<ViewerState> viewerStates = new ArrayList<>();
|
||||
|
||||
private ProcessSumUp processSumUp;
|
||||
private long timeMs=0;
|
||||
|
||||
private final Thread thread = new Thread(this, "GUI Thread");
|
||||
|
||||
|
||||
static private final ProcessControl p = new ProcessControl();
|
||||
|
||||
/**
|
||||
* Retourne l'instance du singleton de ProcessControl
|
||||
* @return Singleton de ProcessControl
|
||||
*/
|
||||
static public ProcessControl instance(){return p;}
|
||||
|
||||
private ProcessControl(){
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialise les processus
|
||||
*/
|
||||
public void init(){
|
||||
mainInput = addInput(AudioInputToBuffer.fromMicro());
|
||||
addProcess(new FFT(getMainInput().getBuffer(), 50));
|
||||
addProcess(new FreqEdgeDetector((TemporalBuffer<Spectre>)processes.get(0).first().output(),15,12));
|
||||
addProcess(new FreqEdgeToEvent((TemporalBuffer<Spectre>)processes.get(1).first().output(), 7, .97f));
|
||||
addProcess(new EventToNote((TemporalBuffer<NoteEventList>)processes.get(2).first().output()));
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarre le controleur: lance le thread d'interface et ceux des différents process et input...
|
||||
*/
|
||||
public void start(){
|
||||
for(Pair<Process,Integer> p: processes)
|
||||
p.first().start();
|
||||
|
||||
record();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance l'enregistrement seul: l'état des process n'est pas modifié, seul les inputs sont démarrés
|
||||
*/
|
||||
public void record(){
|
||||
for(Pair<AudioInputToBuffer,Integer> b: inputs)
|
||||
b.first().start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mets en pause les process et les inputs
|
||||
*/
|
||||
public void pause(){
|
||||
for(Pair<Process,Integer> p: processes)
|
||||
p.first().pause();
|
||||
for(Pair<AudioInputToBuffer,Integer> b: inputs)
|
||||
b.first().pause();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppe l'enregistrement seul: l'état des process n'est pas modifié, seul les inputs sont mis en pause
|
||||
*/
|
||||
public void stopRecord(){
|
||||
for(Pair<AudioInputToBuffer,Integer> b: inputs)
|
||||
b.first().interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppe tout les les process et les inputs
|
||||
*/
|
||||
public void stop(){
|
||||
stopRecord();
|
||||
for(Pair<Process,Integer> p: processes)
|
||||
p.first().interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tue tous les process, les inputs et le thread d'interface graphique
|
||||
*/
|
||||
public void kill(){
|
||||
|
||||
stop();
|
||||
PostProcessScript.interrupt();
|
||||
PlayerControl.instance().stop();
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise le controleur à sont état initial
|
||||
*/
|
||||
public void reset(){
|
||||
|
||||
stop();
|
||||
|
||||
|
||||
/*
|
||||
inputs.clear();
|
||||
processes.clear();
|
||||
buffers.clear();
|
||||
|
||||
viewerStates.clear();
|
||||
|
||||
init();
|
||||
|
||||
PlayerControl.instance().setBuffer(getMainInput().getBuffer());
|
||||
*/
|
||||
|
||||
for (Buffer b: buffers)
|
||||
b.clear();
|
||||
|
||||
PlayerControl.instance().setFrame(Instant.fromIndex(0,44100));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarre le post-processing (l'exportation vers lilypond
|
||||
*/
|
||||
public void launchPostProcess(){
|
||||
System.out.println("POST PROCESS!!!!!");
|
||||
stop();
|
||||
|
||||
PostProcessScript.launchScript((NoteBuffer) processes.get(3).first().output(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("PostProcess finished");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Vrai si il existe au moins un process ou input qui est toujours en traitement
|
||||
*/
|
||||
public boolean isRunning(){
|
||||
for(Pair<Process,Integer> p: processes)
|
||||
if(!p.first().isRunning())
|
||||
return false;
|
||||
for(Pair<AudioInputToBuffer,Integer> b: inputs)
|
||||
if(!b.first().isRunning())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Vrai si tout les process sont en attente: les inputs bloquent le traitement
|
||||
*/
|
||||
public boolean isWaiting(){
|
||||
for(Pair<Process,Integer> p: processes)
|
||||
if(!p.first().isWaiting())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie un process utilisé
|
||||
* @param id identifiant du process à récuperer
|
||||
* @return le process si il a été trouvé, null sinon.
|
||||
*/
|
||||
public Process<?> getProcess(int id){
|
||||
if(id>=processes.size()||id<0)
|
||||
return null;
|
||||
return processes.get(id).first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le process portant le nom en argument
|
||||
* @param name nom du process à récuperer
|
||||
* @return le process si il a été trouvé, null sinon.
|
||||
*/
|
||||
public Process getProcess(String name){
|
||||
for(Pair<Process,Integer> p:processes){
|
||||
if(p.first().name().equals(name))
|
||||
return p.first();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le nombre de processus
|
||||
* @return
|
||||
*/
|
||||
public int getProcessNbr(){
|
||||
return processes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'index du buffer associé à un process
|
||||
* @param processID Index correspondant au process
|
||||
* @return L"index du buffer
|
||||
*/
|
||||
public Integer getProcessBufferID(int processID) {
|
||||
if(processID>=processes.size()||processID<0)
|
||||
return null;
|
||||
return processes.get(processID).second();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie un input
|
||||
* @param id identifiant de l'input à récuperer
|
||||
* @return l'input si il a été trouvé, null sinon.
|
||||
*/
|
||||
public AudioInputToBuffer getInput(int id){
|
||||
if(id>=inputs.size()||id<0)
|
||||
return null;
|
||||
return inputs.get(id).first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le nombre d'inputs
|
||||
* @return
|
||||
*/
|
||||
public int getInputNbr() {
|
||||
return inputs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'index du buffer associé à un input
|
||||
* @param inputID Index correspondant à l'input
|
||||
* @return L"index du buffer
|
||||
*/
|
||||
public Integer getInputBufferID(int inputID) {
|
||||
if(inputID>=inputs.size()||inputID<0)
|
||||
return null;
|
||||
return inputs.get(inputID).second();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'input principal
|
||||
* @return
|
||||
*/
|
||||
public AudioInputToBuffer getMainInput() {
|
||||
return inputs.get(mainInput).first();
|
||||
}
|
||||
|
||||
public int getMainInputID(){
|
||||
return mainInput;
|
||||
}
|
||||
|
||||
|
||||
public int getBufferNbr(){
|
||||
return buffers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie un buffer
|
||||
* @param id identifiant du buffer à récuperer
|
||||
* @return le buffer si il a été trouvé, null sinon.
|
||||
*/
|
||||
public Buffer getBuffer(int id){
|
||||
if(id>=buffers.size()||id<0)
|
||||
return null;
|
||||
return buffers.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'index d'un buffer
|
||||
* @param b buffer à chercher
|
||||
* @return index de la première occurence ( et normalement la seule) du buffer dans la liste
|
||||
*/
|
||||
public int getBufferIndex(Buffer b){
|
||||
return buffers.indexOf(b);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ajout d'un process à la liste des processus, son buffer de sortie est enregistrer et ajouté à la liste des buffers
|
||||
* @param p Le process à ajouter
|
||||
* @return l'index sur lequel il a été ajouté
|
||||
*/
|
||||
int addProcess(Process p){
|
||||
for(Pair<Process,Integer> i:processes)
|
||||
if(i.first()==p)
|
||||
return -1;
|
||||
|
||||
processes.add(new Pair<>(p, addBuffer(p.output())));
|
||||
|
||||
updateProcessesView();
|
||||
|
||||
return processes.size()-1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajout d'un input à la liste des processus, son buffer de sortie est enregistrer et ajouté à la liste des buffers
|
||||
* @param p L'input à ajouter
|
||||
* @return l'index sur lequel il a été ajouté
|
||||
*/
|
||||
int addInput(AudioInputToBuffer p){
|
||||
for(Pair<AudioInputToBuffer,Integer> i:inputs)
|
||||
if(i.first()==p)
|
||||
return -1;
|
||||
|
||||
|
||||
inputs.add(new Pair<>(p, addBuffer(p.getBuffer())) );
|
||||
|
||||
updateProcessesView();
|
||||
|
||||
return inputs.size()-1;
|
||||
}
|
||||
|
||||
private int addBuffer(Buffer b){
|
||||
buffers.add(b);
|
||||
return buffers.size()-1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie une input, en reconnectant les vues et les process qui utilisaient la précédante
|
||||
* @param inputID index de l'input à remplacer
|
||||
* @param newInput nouvelle input
|
||||
* @return Vrai si l'opération c'est bien déroulée
|
||||
*/
|
||||
public boolean setInput(int inputID, AudioInputToBuffer newInput){
|
||||
if(newInput==null)
|
||||
return false;
|
||||
for(Pair<AudioInputToBuffer,Integer> i:inputs)
|
||||
if(i.first()==newInput)
|
||||
return false;
|
||||
|
||||
if(inputID>= inputs.size())
|
||||
return false;
|
||||
|
||||
boolean inputStarted = inputs.get(inputID).first().isAlive();
|
||||
inputs.get(inputID).first().interrupt();
|
||||
|
||||
newInput.setBuffer(inputs.get(inputID).first().getBuffer());
|
||||
|
||||
|
||||
|
||||
inputs.get(inputID).setFirst(newInput);
|
||||
|
||||
|
||||
updateProcessesView();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre la fenetre récapitulative des traitements pour pouvoir déclancher sa mise à jour
|
||||
* @param pV fenêtre récapitulative
|
||||
*/
|
||||
public void setProcessesView(ProcessSumUp pV) {
|
||||
this.processSumUp = pV;
|
||||
processSumUp.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclanche la mise à jour de la fenêtre récapitulative des traitements
|
||||
*/
|
||||
private void updateProcessesView(){
|
||||
if(processSumUp !=null)
|
||||
processSumUp.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un viewerState pour qu'il soit pris en compte dans le thread d'interface
|
||||
* @param viewerState ViewerState à enregistrer
|
||||
* @return Vrai si il n'était pas déjà ajouté
|
||||
*/
|
||||
public boolean registerViewerState(ViewerState viewerState){
|
||||
if(viewerStates.contains(viewerState))
|
||||
return false;
|
||||
|
||||
viewerStates.add(viewerState);
|
||||
viewerState.invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
for (ViewerState b : viewerStates)
|
||||
if (b.isVisible())
|
||||
b.applyState();
|
||||
|
||||
if (processSumUp != null) {
|
||||
timeMs = System.currentTimeMillis() - timeMs;
|
||||
processSumUp.update(1f / timeMs * 1000f);
|
||||
timeMs = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
102
src/processing/SpectreFilter.java
Normal file
102
src/processing/SpectreFilter.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
package processing;
|
||||
|
||||
import conteneurs.Gamme;
|
||||
import conteneurs.Spectre;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/04/14.
|
||||
*/
|
||||
public abstract class SpectreFilter {
|
||||
|
||||
protected SpectreFilter previousFilter = null;
|
||||
protected float filterIntensity = 1;
|
||||
protected boolean autoIntensity = true;
|
||||
|
||||
public SpectreFilter(){
|
||||
init();
|
||||
}
|
||||
|
||||
protected void init(){}
|
||||
|
||||
/**
|
||||
* Renvoie l'amplitude filtré en fonction de l'indice de la fréquence dans le spectre et du spectre
|
||||
* Cette méthode peut être hérité pour définir un filtre propre à chaque fréquence
|
||||
* @param idFreq l'index associé à la fréquence que l'on veut filtrer
|
||||
* @param spectre spectre d'entrée
|
||||
* @return amplitude filtrée
|
||||
*/
|
||||
protected abstract float filter(int idFreq, Spectre spectre);
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie l'amplitude filtré en fonction de l'indice de la fréquence dans le spectre et du spectre
|
||||
* @param idFreq l'index associé à la fréquence que l'on veut filtrer
|
||||
* @param spectre spectre du signal
|
||||
* @return l'amplitude de la fréquence filtré à travers la chaine de filtre
|
||||
*/
|
||||
public float filterAmpli(int idFreq, Spectre spectre){
|
||||
if(previousFilter==null)
|
||||
return autoIntensity?filterIntensity*filter(idFreq,spectre)+(1-filterIntensity)*spectre.getAmplitude(idFreq)/2:filter(idFreq,spectre);
|
||||
|
||||
return filter(idFreq, previousFilter.filterSpectre(spectre));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie le spectre filtré à travers la chaine de filtre du premier maillon jusqu'à celui-ci
|
||||
* @param spectre spectre d'entrée
|
||||
* @return spectre filtré à travers la chaine
|
||||
*/
|
||||
public Spectre filterSpectre(Spectre spectre){
|
||||
Spectre r = new Spectre(spectre.getGamme());
|
||||
|
||||
Gamme gamme = spectre.getGamme();
|
||||
if(previousFilter!=null)
|
||||
spectre = previousFilter.filterSpectre(spectre);
|
||||
|
||||
for (int i = 0; i < gamme.gammeSize(); i++)
|
||||
r.setAmplitudeAt(i, autoIntensity?filterIntensity*filter(i,spectre)+(1-filterIntensity)*spectre.getAmplitude(i)/2:filter(i,spectre));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chaine un filtre à celui-ci, le filtre chainer sera executé après ce filtre
|
||||
* @param filter le filtre à ajouter à la chaine de traitement
|
||||
* @return la chaine de filtre
|
||||
*/
|
||||
public SpectreFilter chainFilter(SpectreFilter filter){
|
||||
filter.previousFilter = this;
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coupe la chaine à ce filtre il est maintenant le premier filtre à être executé lors du calcul de la chaine
|
||||
* @return Vrai si il n'était pas déjà le premier maillon
|
||||
*/
|
||||
public boolean unchainFilter(){
|
||||
if(previousFilter==null)
|
||||
return false;
|
||||
|
||||
previousFilter = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le maillon précédant dans la chaine
|
||||
* @return
|
||||
*/
|
||||
public SpectreFilter getPreviousFilter(){
|
||||
return previousFilter;
|
||||
}
|
||||
|
||||
public float getFilterIntensity() {
|
||||
return filterIntensity;
|
||||
}
|
||||
|
||||
public SpectreFilter setIntensity(float filterIntensity) {
|
||||
this.filterIntensity = filterIntensity;
|
||||
return this;
|
||||
}
|
||||
}
|
25
src/processing/SpectreFilterFactory.java
Normal file
25
src/processing/SpectreFilterFactory.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package processing;
|
||||
|
||||
import conteneurs.Spectre;
|
||||
|
||||
/**
|
||||
* Created by gaby on 29/04/14.
|
||||
*/
|
||||
public class SpectreFilterFactory {
|
||||
static public SpectreFilter hearingCorrection(){
|
||||
return new SpectreFilter() {
|
||||
@Override
|
||||
protected void init() {
|
||||
autoIntensity = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float filter(int idFreq, Spectre spectre) {
|
||||
float f = spectre.getGamme().getFreq(idFreq)/1000f;
|
||||
float e = f-3.3f;
|
||||
return Spectre.dBToAmpli(spectre.getdBAmplitude(idFreq)-filterIntensity*(3.64f*(float)Math.pow(f,-.08f)-6.5f*(float)Math.exp(-.06*e*e)+(float)Math.pow(10f,-3f*f*f*f*f)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
345
src/processing/buffer/Buffer.java
Normal file
345
src/processing/buffer/Buffer.java
Normal file
|
@ -0,0 +1,345 @@
|
|||
package processing.buffer;
|
||||
|
||||
import conteneurs.ObservableObject;
|
||||
import conteneurs.ObservableObjectEvent;
|
||||
import processing.processes.Process;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Buffer <T> {
|
||||
|
||||
private final ArrayList<BufferEventListener> bufferListeners;
|
||||
private final ArrayList<Process<?>> usedBy;
|
||||
final private Storage.Type arrayType;
|
||||
private String name = "";
|
||||
private Class type = null;
|
||||
private boolean itemObservable = false;
|
||||
private Process.ProcessingOrder processingOrder = Process.ProcessingOrder.NO_ORDER;
|
||||
|
||||
private Storage<T> samples;
|
||||
private final StorageArrayList<Integer> updateIDs;
|
||||
|
||||
/**
|
||||
* Construit une instance de buffer selon un modéle de stockage
|
||||
* @param arrayType Modèle de stockage
|
||||
*/
|
||||
public Buffer(Storage.Type arrayType) {
|
||||
switch(arrayType){
|
||||
case ArrayList:
|
||||
samples = new StorageArrayList<>();
|
||||
break;
|
||||
}
|
||||
|
||||
updateIDs = new StorageArrayList<>();
|
||||
|
||||
bufferListeners = new ArrayList<>();
|
||||
usedBy = new ArrayList<>();
|
||||
this.arrayType = arrayType;
|
||||
}
|
||||
|
||||
private void emitBufferEvent(BufferEvent event) {
|
||||
|
||||
for(Process p: usedBy)
|
||||
p.addBufferEvent(event);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
for(BufferEventListener p: bufferListeners)
|
||||
p.bufferEvent(event);
|
||||
});
|
||||
|
||||
}
|
||||
private void emitBufferEvent(BufferEvent.Type type, int sampleID) {
|
||||
emitBufferEvent(new BufferEvent(type, sampleID, this));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connecte ce buffer à un listener qu'il fournit, de manière à propager les BufferEvent jusqu'à cet objet
|
||||
* @param p objet à connecter
|
||||
* @return Vrai si l'opération à fontionner (si le process n'était pas déjà ajouté)
|
||||
*/
|
||||
public boolean addBufferListener(BufferEventListener p){
|
||||
if(bufferListeners.contains(p))
|
||||
return false;
|
||||
|
||||
bufferListeners.add(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte ce buffer d'un listener
|
||||
* @param p listener à déconnecter
|
||||
* @return Vrai si l'opération à fontionner (si le listener était dans la liste)
|
||||
*/
|
||||
public boolean removeBufferListener(BufferEventListener p){
|
||||
int id = bufferListeners.indexOf(p);
|
||||
if(id == -1)
|
||||
return false;
|
||||
|
||||
bufferListeners.remove(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connecte ce buffer à une entrée d'un algo
|
||||
* @param p Algo à connecte
|
||||
* @return Vrai si l'opération c'est bien passée (si il n'avait pas déjà été appelée)
|
||||
*/
|
||||
public boolean connectProcess(Process p){
|
||||
if(usedBy.contains(p))
|
||||
return false;
|
||||
|
||||
usedBy.add(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean disconnectProcess(Process p){
|
||||
if(!usedBy.contains(p))
|
||||
return false;
|
||||
|
||||
usedBy.remove(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le type de stockage de ce buffer
|
||||
* @return type de stockage
|
||||
*/
|
||||
public Storage.Type getArrayType(){
|
||||
return arrayType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'indice de version associé à un échantillon.
|
||||
* @param id index de l'échantillon
|
||||
* @return indice de version
|
||||
*/
|
||||
public Integer getUpdateID(int id) {
|
||||
return updateIDs.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Surcharge de set(int,T,int) mais avec un indice de version = 1
|
||||
* @param index indice de l'échantillon à modifier ou ajouter
|
||||
* @param value valeur de l'échantillon
|
||||
* @return la valeur de l'échantillon précédant /!\ Peut retourner null si aucun n'échantillon ne correspondait à l'index /!\
|
||||
*/
|
||||
public T set(int index, T value){ return set(index, value, null);}
|
||||
|
||||
|
||||
/**
|
||||
* Modifie un échantillon ou l'ajoute à au stockage si index ne correspond à aucun élément, complète alors avec des échantillons = null.
|
||||
* De plus, updateId est associé à l'échantillon en tant qu'indice de version
|
||||
* @param index indice de l'échantillon à modifier ou ajouter
|
||||
* @param value valeur de l'échantillon
|
||||
* @param updateId indice de version à associer
|
||||
* @return la valeur de l'échantillon précédant /!\ Peut retourner null si aucun n'échantillon ne correspondait à l'index /!\
|
||||
*/
|
||||
public T set(int index, T value, Integer updateId){
|
||||
|
||||
T previous = samples.set( index, value);
|
||||
updateIDs.set(index, updateId);
|
||||
|
||||
if(itemObservable)
|
||||
((ObservableObject)value).addListener(
|
||||
new ObservableObject.SignedListener() {
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String signature() {
|
||||
return this.hashCode() + ":" + index;
|
||||
}
|
||||
});
|
||||
|
||||
if(previous==null) {
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_ADDED, index);
|
||||
return null;
|
||||
}else{
|
||||
if(value==null)
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_DELETED, index);
|
||||
else
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, index);
|
||||
|
||||
if(itemObservable)
|
||||
((ObservableObject)previous).removeSignedListener(this.hashCode()+":"+index);
|
||||
}
|
||||
|
||||
return previous;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Surcharge de addNote(T) mais avec un indice de version = 1
|
||||
* @param value valeur de l'échantillon à ajouter
|
||||
* @return vrai si l'opération c'est bien passé (mais peut-elle mal se passer?)...
|
||||
*/
|
||||
public boolean add(T value){
|
||||
return add(value,0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un élément après le dernier élément du stockage et lui associe un indice de version
|
||||
* @param value valeur de l'échantillon à ajouter
|
||||
* @param updateId indice de version à associer
|
||||
* @return vrai si l'opération c'est bien passé (mais peut-elle rater?)...
|
||||
*/
|
||||
boolean add(T value, int updateId){
|
||||
if(!samples.add(value))
|
||||
return false;
|
||||
|
||||
updateIDs.add(updateId);
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_ADDED, samples.size() - 1);
|
||||
|
||||
if(itemObservable)
|
||||
((ObservableObject)value).addListener(new ObservableObject.Listener() {
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, samples.size()-1);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur d'un échantillon
|
||||
* @param id index de l'échantillon
|
||||
* @return valeur de l'échantillon /!\ Peut revoyer null si aucun échantillon ne correspond à l'index /!\
|
||||
*/
|
||||
public T get(int id){
|
||||
return samples.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un échantillon du buffer
|
||||
* @param id index de l'échantillon à supprimer
|
||||
* @return la valeur de l'échantillon supprimer /!\ Peut renvoyer null si aucun échantillon ne correspondait à cet index /!\
|
||||
*/
|
||||
public T remove(int id){
|
||||
|
||||
T sampleDeleted = samples.remove(id);
|
||||
updateIDs.remove(id);
|
||||
|
||||
if(sampleDeleted!=null) {
|
||||
emitBufferEvent(BufferEvent.Type.SAMPLE_DELETED, id);
|
||||
if(itemObservable)
|
||||
((ObservableObject)sampleDeleted).removeSignedListener(this.hashCode() + ":" + id);
|
||||
}
|
||||
|
||||
return sampleDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nombre maximum d'élément de tableau (dernier index +1)
|
||||
* @return la taille de l'espace de stockage
|
||||
*/
|
||||
public int size(){
|
||||
return samples.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le buffer est vide où non
|
||||
* @return Vrai si il est vide
|
||||
*/
|
||||
public boolean isEmpty(){
|
||||
return size()==0;
|
||||
}
|
||||
|
||||
public boolean isCorrectIndex(int id){
|
||||
return get(id)!=null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste des indexs associés à des éléments dans le Storage: aucun de ces indexs ne retournera null par get(index)
|
||||
* * @return la liste des indexs de tout les éléments du Storage
|
||||
*/
|
||||
public ArrayList<Integer> getSamplesIndex(){
|
||||
return samples.getSamplesIndex();
|
||||
}
|
||||
|
||||
|
||||
public StorageIterator<T> getIterator(){
|
||||
return samples.getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un iterateur pointant initialement sur l'élément de la liste correspondant à l'index en argument
|
||||
* @param id index de l'élément sur lequel pointe initialement l'iterateur
|
||||
* @return Un iterateur sur ce stockage
|
||||
*/
|
||||
|
||||
public StorageIterator<T> getIterator(int id){
|
||||
return samples.getIterator(id);
|
||||
}
|
||||
|
||||
public void lastSampleAdded(){
|
||||
emitBufferEvent(BufferEvent.Type.INTERRUPT_PROCESS,-1);
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
switch(arrayType){
|
||||
case ArrayList:
|
||||
samples = new StorageArrayList<>();
|
||||
break;
|
||||
}
|
||||
|
||||
emitBufferEvent(BufferEvent.Type.INVALIDATE,-1);
|
||||
}
|
||||
|
||||
public void invalidateProcessedSamples(){
|
||||
emitBufferEvent(BufferEvent.Type.INVALIDATE,-1);
|
||||
}
|
||||
|
||||
public ArrayList<processing.processes.Process<?>> getUsedBy() {
|
||||
return usedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réserve directement un espace sans modifier la taille du tableau (si la daforme si prête)
|
||||
* @param capacity capacité maximale;
|
||||
*/
|
||||
public void tryEnsureCapacity(int capacity){
|
||||
samples.tryEnsureCapacity(capacity);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Class getType() {
|
||||
if(type==null&&!samples.isEmpty())
|
||||
return samples.get(getSamplesIndex().get(0)).getClass();
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Class type) {
|
||||
this.type = type;
|
||||
Class[] classes = type.getClasses();
|
||||
for (int i = 0; i < classes.length; i++)
|
||||
if (classes[i].equals(ObservableObject.class)) {
|
||||
itemObservable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
itemObservable = false;
|
||||
}
|
||||
|
||||
|
||||
public Process.ProcessingOrder getProcessingOrder() {
|
||||
return processingOrder;
|
||||
}
|
||||
|
||||
public void setProcessingOrder(Process.ProcessingOrder processingOrder) {
|
||||
this.processingOrder = processingOrder;
|
||||
}
|
||||
}
|
78
src/processing/buffer/BufferEvent.java
Normal file
78
src/processing/buffer/BufferEvent.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
package processing.buffer;
|
||||
|
||||
|
||||
/**
|
||||
* Caractérise l'évennement envoyé par le buffer
|
||||
*/
|
||||
|
||||
public class BufferEvent {
|
||||
private final int sampleID;
|
||||
private final Type type;
|
||||
private final Buffer bufferSource;
|
||||
|
||||
|
||||
|
||||
public BufferEvent (Type eventType, int sampleID, Buffer bufferSource) {
|
||||
this.sampleID = sampleID;
|
||||
type = eventType;
|
||||
this.bufferSource = bufferSource;
|
||||
}
|
||||
|
||||
/** Renvoie le type d'évenement */
|
||||
public Type getType () {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Renvoie l'identifiant associé a l'échantillon en attribut */
|
||||
public int getSampleID () {
|
||||
return sampleID;
|
||||
}
|
||||
|
||||
/** Renvoie le buffer source d'ou provient l'évenement */
|
||||
public Buffer getSource () {
|
||||
return bufferSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof BufferEvent && bufferSource == ((BufferEvent) obj).getSource();
|
||||
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
String r = "";
|
||||
|
||||
switch(type){
|
||||
case SAMPLE_ADDED:
|
||||
r+="Sample Added";
|
||||
break;
|
||||
case SAMPLE_CHANGED:
|
||||
r+="Sample Changed";
|
||||
break;
|
||||
case SAMPLE_DELETED:
|
||||
r+="Sample Deleted";
|
||||
break;
|
||||
case INVALIDATE:
|
||||
r+= "Invalidate processed samples";
|
||||
break;
|
||||
case INTERRUPT_PROCESS:
|
||||
r+="Interrupt process";
|
||||
break;
|
||||
}
|
||||
|
||||
r+= ": " + sampleID;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Définit les différents évemenements qui peuvent apparaitre dans un buffer */
|
||||
public enum Type {
|
||||
SAMPLE_ADDED,
|
||||
SAMPLE_CHANGED,
|
||||
SAMPLE_DELETED,
|
||||
INTERRUPT_PROCESS,
|
||||
INVALIDATE
|
||||
}
|
||||
|
||||
}
|
||||
|
8
src/processing/buffer/BufferEventListener.java
Normal file
8
src/processing/buffer/BufferEventListener.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package processing.buffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 02/04/14.
|
||||
*/
|
||||
public interface BufferEventListener {
|
||||
public void bufferEvent(BufferEvent e);
|
||||
}
|
107
src/processing/buffer/NoteBuffer.java
Normal file
107
src/processing/buffer/NoteBuffer.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
package processing.buffer;
|
||||
|
||||
import conteneurs.*;
|
||||
import generictools.Instant;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Created by gaby on 02/06/14.
|
||||
* Buffer qui sert à stocker des notes */
|
||||
public class NoteBuffer extends TemporalBuffer<NoteList> implements ObservableObject.Listener{
|
||||
|
||||
ArrayList<Note> notes = new ArrayList<>();
|
||||
|
||||
public NoteBuffer(Storage.Type arrayType, int sampleRate, Instant initialFrame) {
|
||||
super(arrayType, sampleRate, initialFrame);
|
||||
}
|
||||
|
||||
/** Permet d'ajouter une note au buffer de notes
|
||||
* @param note la note à ajouter
|
||||
* @return l'identifiant lié a l'ajout de la note */
|
||||
public int addNote(Note note){
|
||||
int id = notes.size();
|
||||
notes.add(note);
|
||||
|
||||
note.setBuffer(this);
|
||||
note.setBufferID(id);
|
||||
|
||||
note.addListener(this);
|
||||
NoteID noteID = new NoteID(note);
|
||||
|
||||
for (int i = note.getStart().mapToIndex(this); i <= note.getEnd().mapToIndex(this); i++)
|
||||
add(i, noteID);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Permet de retirer une note au buffer
|
||||
* @param bufferID l'identifiant de la note (pour le buffer)
|
||||
* @return true si ça a fonctionné, false sinon */
|
||||
public boolean removeNote(int bufferID){
|
||||
Note n = getNote(bufferID);
|
||||
if(n==null)
|
||||
return false;
|
||||
|
||||
for (int i = n.getStart().mapToIndex(this); i <= n.getEnd().mapToIndex(this); i++) {
|
||||
get(i).remove(get(i).getIdById(n.getNoteID()));
|
||||
}
|
||||
|
||||
n.removeListener(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Permet d'ajouter une note à un instant particulier, caractérisé par son temps
|
||||
* @param timeID le numero de l'instant
|
||||
* @param noteID l'identifiant de la note */
|
||||
public void add(int timeID, NoteID noteID){
|
||||
if(get(timeID)==null)
|
||||
set(timeID, new NoteList());
|
||||
get(timeID).add(noteID);
|
||||
}
|
||||
|
||||
public Note getNote(int noteID){
|
||||
if(noteID<0 || noteID>notes.size())
|
||||
return null;
|
||||
|
||||
return notes.get(noteID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void observableOjectEvent(ObservableObjectEvent e) {
|
||||
if(e.getEmmiter() instanceof Note){
|
||||
Note note = (Note)e.getEmmiter();
|
||||
|
||||
int id = note.getStart().mapToIndex(this);
|
||||
while(isCorrectIndex(id) && id<= note.getEnd().mapToIndex(this) && get(id).getNoteById(note.getNoteID())==null){
|
||||
get(id).add(note);
|
||||
id++;
|
||||
}
|
||||
|
||||
id = note.getStart().mapToIndex(this)-1;
|
||||
while(isCorrectIndex(id) && get(id).getNoteById(note.getNoteID())!=null){
|
||||
get(id).remove(note);
|
||||
id--;
|
||||
}
|
||||
|
||||
id = note.getEnd().mapToIndex(this);
|
||||
while(isCorrectIndex(id) && get(id).getNoteById(note.getNoteID())==null){
|
||||
get(id).add(note);
|
||||
id--;
|
||||
}
|
||||
|
||||
id = note.getEnd().mapToIndex(this)+1;
|
||||
while(isCorrectIndex(id) && get(id).getNoteById(note.getNoteID())!=null){
|
||||
get(id).remove(note);
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie le nombre de notes presentes dans le buffer */
|
||||
public int getNotesNbr(){
|
||||
return notes.size();
|
||||
}
|
||||
}
|
77
src/processing/buffer/Storage.java
Normal file
77
src/processing/buffer/Storage.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package processing.buffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public interface Storage<T> {
|
||||
|
||||
public enum Type{
|
||||
ArrayList
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément d'index id, null si aucun n'y est associé
|
||||
* @param id index de l'élément
|
||||
* @return l'élément /!\ Peut renvoyer null si aucun élément ne correspond à l'index /!\
|
||||
*/
|
||||
public T get(int id);
|
||||
|
||||
/**
|
||||
* Modifie un élément à un index donné ou l'ajoute si aucun élément ne correspond à l'index
|
||||
* @param id index de l'élément à modifier ou ajouter
|
||||
* @param value valeur de l'élément
|
||||
* @return la valeur de l'élément précédant /!\ Peut renvoyer null si aucun élément ne correspondait à cet index /!\
|
||||
*/
|
||||
public T set(int id, T value);
|
||||
|
||||
/**
|
||||
* Ajoute une élément en fin de liste
|
||||
* @param value valeur de l'élément à ajouter
|
||||
* @return Vrai si l'opération a réussie (mais peut-elle rater?)...
|
||||
*/
|
||||
public boolean add(T value);
|
||||
|
||||
/**
|
||||
* Supprime un élément de la liste
|
||||
* @param id index de l'élément à supprimer
|
||||
* @return la valeur de l'élément supprimer /!\ Peut renvoyer null si aucun élément ne correspondait à cet index /!\
|
||||
*/
|
||||
public T remove(int id);
|
||||
|
||||
/**
|
||||
* Retourne le nombre maximum d'élément de tableau (dernier index +1)
|
||||
* @return la taille de l'espace de stockage
|
||||
*/
|
||||
public int size();
|
||||
|
||||
/**
|
||||
* Vérifie si tout les échantillons du stockage sont null
|
||||
* @return Vrai si size() = 0 ou si aucun échantillon n'est stocké dans l'espace disponible
|
||||
*/
|
||||
public boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Retourne la liste des indexs associés à des éléments dans le Storage: aucun de ces indexs ne retournera null par get(index)
|
||||
* * @return la liste des indexs de tout les éléments du Storage
|
||||
*/
|
||||
public ArrayList<Integer> getSamplesIndex();
|
||||
|
||||
/**
|
||||
* Génère un iterateur pointant initialement sur le première élément de la liste
|
||||
* @return Un iterateur sur ce stockage
|
||||
*/
|
||||
public StorageIterator<T> getIterator();
|
||||
|
||||
/**
|
||||
* Génère un iterateur pointant initialement sur l'élément de la liste correspondant à l'index en argument
|
||||
* @param initialIndex index de l'élément sur lequel pointe initialement l'iterateur
|
||||
* @return Un iterateur sur ce stockage
|
||||
*/
|
||||
public StorageIterator<T> getIterator(int initialIndex);
|
||||
|
||||
/**
|
||||
* Réserve directement un espace sans modifier la taille du tableau (si la forme si prête)
|
||||
* @param capacity capacité maximale;
|
||||
*/
|
||||
public void tryEnsureCapacity(int capacity);
|
||||
}
|
169
src/processing/buffer/StorageArrayList.java
Normal file
169
src/processing/buffer/StorageArrayList.java
Normal file
|
@ -0,0 +1,169 @@
|
|||
package processing.buffer;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Classe "adaptateur" entre les classiques ArrayList et les instances de Storage utilisable par Buffer.
|
||||
* Certaines méthodes de ArrayList ont été redéfinie, nottament get renvoie null si il n'y as pas d'éléments demandé (il ne provoque pas d'exeption).
|
||||
* @param <T> Le type des éléments contenu dans ce StorageArrayList
|
||||
*/
|
||||
|
||||
public class StorageArrayList<T> extends ArrayList<T> implements Storage<T> {
|
||||
|
||||
private int lastIndex=-1;
|
||||
int idIterator=-1;
|
||||
|
||||
public StorageArrayList(){
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redéfinition de get(int) de ArrayList. La principale différence est de renvoyer null plutot que de lancer une exeption
|
||||
* @param index index de l'élément désiré
|
||||
* @param silent si vrai le rapport d'exeption ne sera pas affiché
|
||||
* @return l'élément correspondant à l'index, ou null si aucun n'y était associé
|
||||
*/
|
||||
public T get(int index, boolean silent){
|
||||
|
||||
if(index < 0 || index >=size())
|
||||
return null;
|
||||
|
||||
return super.get(index);
|
||||
|
||||
}
|
||||
|
||||
public T get(int index){
|
||||
if(index < 0 || index >=size())
|
||||
return null;
|
||||
|
||||
return super.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redéfintion de set(int,T) de ArrayList. La principale différence est que l'index donné n'est pas obligé d'être < size().
|
||||
* La méthode s'occupera, si c'est le cas de compléter par des éléments null les indexs entre size()-1 et index
|
||||
* @param index index de l'élément à modifier
|
||||
* @param value nouvelle valeur de cet élément
|
||||
* @return la valeur de l'élément précédant (null si aucun ne s'y trouvait).
|
||||
*/
|
||||
public T set(int index, T value){
|
||||
T previous = get(index);
|
||||
|
||||
if(value==null)
|
||||
return remove(index);
|
||||
|
||||
if(previous==null) {
|
||||
ensureSize(index + 1);
|
||||
lastIndex = index;
|
||||
}
|
||||
|
||||
super.set(index, value);
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redéfintion de addNote(T) de ArrayList. Ajoute un élément après le dernier: à dernier index +1;
|
||||
* @param value valeur de l'élément à ajouter
|
||||
* @return Vrai si l'opération c'est bien passé (mais peut-elle rater?)...
|
||||
*/
|
||||
public boolean add(T value){
|
||||
|
||||
set(lastIndex+1,value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assure que la taille minimum de la liste est newSize (en complétant avec des éléments null)
|
||||
* @param newSize la nouvelle taille minimum de la liste
|
||||
*/
|
||||
void ensureSize(int newSize){
|
||||
ensureCapacity(newSize);
|
||||
|
||||
while(super.size()<newSize)
|
||||
super.add(null);
|
||||
}
|
||||
|
||||
public T remove(int index){
|
||||
T r;
|
||||
try{
|
||||
r = super.remove(index);
|
||||
}catch(IndexOutOfBoundsException e){
|
||||
//e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
do {
|
||||
lastIndex--;
|
||||
}while(get(lastIndex)==null&&lastIndex>-1);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
public int size(){ return lastIndex+1;}
|
||||
|
||||
public boolean isEmpty(){ return lastIndex==-1; }
|
||||
|
||||
public ArrayList<Integer> getSamplesIndex(){
|
||||
|
||||
ArrayList<Integer> r = new ArrayList<>();
|
||||
r.ensureCapacity(lastIndex);
|
||||
|
||||
for (int i = 0; i < size(); i++)
|
||||
if(get(i)!=null)
|
||||
r.add(i);
|
||||
|
||||
r.trimToSize();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Génère un iterateur pointant initialement sur le première élément de la liste
|
||||
*
|
||||
* @return Un iterateur sur ce stockage
|
||||
*/
|
||||
@Override
|
||||
public StorageIterator<T> getIterator() {
|
||||
return getIterator(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un iterateur pointant initialement sur l'élément de la liste correspondant à l'index en argument
|
||||
*
|
||||
* @param initialIndex index de l'élément sur lequel pointe initialement l'iterateur
|
||||
* @return Un iterateur sur ce stockage
|
||||
*/
|
||||
@Override
|
||||
public StorageIterator<T> getIterator(final int initialIndex) {
|
||||
return new StorageIterator<T>() {
|
||||
private int currentID = initialIndex;
|
||||
@Override
|
||||
public T next() {
|
||||
if(currentID>=size())
|
||||
return null;
|
||||
|
||||
return get(currentID++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentID < size();
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Réserve directement un espace sans modifier la taille du tableau (si la forme si prête)
|
||||
*
|
||||
* @param capacity capacité maximale;
|
||||
*/
|
||||
@Override
|
||||
public void tryEnsureCapacity(int capacity) {
|
||||
ensureCapacity(capacity);
|
||||
}
|
||||
}
|
20
src/processing/buffer/StorageIterator.java
Normal file
20
src/processing/buffer/StorageIterator.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
package processing.buffer;
|
||||
|
||||
/**
|
||||
* Created by gaby on 31/03/14.
|
||||
*/
|
||||
public interface StorageIterator<T> {
|
||||
/**
|
||||
* Renvoie la valeur de l'élément suivant, l'iterator ce décale alors à cet élément
|
||||
* @return
|
||||
*/
|
||||
public T next();
|
||||
|
||||
/**
|
||||
* Teste si il reste un élément disponible après l'élément actuel
|
||||
* @return Vrai si on peut appeler next()...
|
||||
*/
|
||||
public boolean hasNext();
|
||||
|
||||
|
||||
}
|
64
src/processing/buffer/TemporalBuffer.java
Normal file
64
src/processing/buffer/TemporalBuffer.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package processing.buffer;
|
||||
|
||||
import generictools.Instant;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/03/14.
|
||||
*/
|
||||
public class TemporalBuffer <T> extends Buffer<T> {
|
||||
private int sampleRate = 0;
|
||||
private Instant originInstant =Instant.fromTime(0);
|
||||
|
||||
|
||||
/**
|
||||
* Constructeur du buffer temporel
|
||||
* @param arrayType type de stockage
|
||||
* @param sampleRate fréquence d'échantillonage en Hz
|
||||
*/
|
||||
public TemporalBuffer(Storage.Type arrayType, int sampleRate) {
|
||||
super(arrayType);
|
||||
this.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public TemporalBuffer(Storage.Type arrayType, int sampleRate, Instant originInstant) {
|
||||
this(arrayType, sampleRate);
|
||||
this.originInstant = originInstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la fréquence d'échantillonage
|
||||
* @return Fréquence d'échatillonage en Hz
|
||||
*/
|
||||
|
||||
public int getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur de l'instant pour l'index 0
|
||||
* @return Instant de l'origine des indexs
|
||||
*/
|
||||
public Instant getOriginInstant() {
|
||||
return originInstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur de l'échantillon à un instant donné
|
||||
* @param time Temps en ms
|
||||
* @return Valeur de l'échantillon
|
||||
*/
|
||||
public T get(float time){
|
||||
return get(Instant.fromTime(time).mapToIndex(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la valeur de l'échantillon à un instant donné
|
||||
* @param instant Instant
|
||||
* @return Valeur de l'échantillon
|
||||
*/
|
||||
public T get(Instant instant){
|
||||
return get(instant.mapToIndex(this));
|
||||
}
|
||||
|
||||
|
||||
}
|
141
src/processing/processes/EventToNote.java
Normal file
141
src/processing/processes/EventToNote.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
package processing.processes;
|
||||
|
||||
import conteneurs.Note;
|
||||
import conteneurs.NoteEvent;
|
||||
import conteneurs.NoteEventList;
|
||||
import conteneurs.NoteList;
|
||||
import generictools.Instant;
|
||||
import processing.buffer.NoteBuffer;
|
||||
import processing.buffer.Storage;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
import processing.properties.FloatProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 03/06/14.
|
||||
*/
|
||||
public class EventToNote extends IndexedProcess<NoteList>{
|
||||
|
||||
ArrayList<Note> stillAlive = new ArrayList<>();
|
||||
|
||||
FloatProperty minDuration;
|
||||
|
||||
public EventToNote( TemporalBuffer<NoteEventList> input) {
|
||||
super("Event to note", input, new NoteBuffer(Storage.Type.ArrayList, input.getSampleRate(), input.getOriginInstant()));
|
||||
minDuration = new FloatProperty("Durée minimum (ms)", 100, 0, 2000);
|
||||
propertyManager().addProperty(minDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Retourne le résultat du calcul à placer à l'index outputID
|
||||
*
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Résultat du calcul
|
||||
*/
|
||||
@Override
|
||||
protected NoteList compute(Instant outputID) {
|
||||
NoteEventList list = input().get(outputID);
|
||||
NoteList result = new NoteList();
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
NoteEvent e = list.get(i);
|
||||
|
||||
if(e.getEventType()== NoteEvent.Type.APPARITION){
|
||||
if(aliveNoteByFreq(e.getNote().getIdNote())==null) {
|
||||
//Si une apparition est détectée et la note n'est pas active
|
||||
Note n = new Note(e.getNote().getIdNote(), outputID, outputID.translated(1));
|
||||
output().addNote(n);
|
||||
stillAlive.add(n);
|
||||
System.out.print("");
|
||||
|
||||
}
|
||||
}else{
|
||||
|
||||
Note noteAlive = aliveNoteByFreq(e.getNote().getIdNote());
|
||||
if(noteAlive!=null) {
|
||||
//Si une disparition est détectée et la note est active
|
||||
if(noteAlive.getDuration()>minDuration.getValue())
|
||||
stillAlive.remove(aliveNoteIdByFreq(e.getNote().getIdNote()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Note n: stillAlive)
|
||||
n.setEnd(outputID.translated(1));
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Note aliveNoteByFreq(int idFreq){
|
||||
for(Note n:stillAlive)
|
||||
if(n.getNoteID() == idFreq)
|
||||
return n;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected int aliveNoteIdByFreq(int idFreq){
|
||||
for (int id = 0; id < stillAlive.size(); id++)
|
||||
if(stillAlive.get(id).getNoteID() == idFreq)
|
||||
return id;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie la liste des index dépendant de l'échantillon d'index inputID.
|
||||
*
|
||||
* @param inputBufferID index du buffer de l'échantillon
|
||||
* @param inputID index de l'échantillon qui est utilisé par ...
|
||||
* @return La liste des indexs de sortie qui utilise inputID pour leur calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant> idUsedBy(int inputBufferID, int inputID) {
|
||||
ArrayList<Instant> r = new ArrayList<>();
|
||||
r.add(Instant.fromIndex(inputID, this.input()));
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie, pour chaque buffer d'entrée la liste des index nécessaire au calcul de l'échantillon d'index outputID.
|
||||
*
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Un tableau de même taille que getInputs(), et qui contient les liste des échantillons de chaque buffer de getInputs() nécessaire au calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant>[] idNeededFor(int outputID) {
|
||||
ArrayList<Instant> r = new ArrayList<>();
|
||||
ArrayList<Instant>[] ret = new ArrayList[1];
|
||||
r.add(Instant.fromIndex(outputID, output()));
|
||||
ret[0]=r;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le type du buffer de sortie
|
||||
*
|
||||
* @return La class des échantillones du buffer de sortie
|
||||
*/
|
||||
@Override
|
||||
protected Class getType() {
|
||||
return NoteList.class;
|
||||
}
|
||||
protected ProcessingOrder getProcessingOrder(){
|
||||
return ProcessingOrder.ASCENDING_ORDER;
|
||||
}
|
||||
|
||||
public TemporalBuffer<NoteEventList> input(){
|
||||
return (TemporalBuffer<NoteEventList>)super.input();
|
||||
}
|
||||
|
||||
public NoteBuffer output(){
|
||||
return (NoteBuffer)super.output();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clean() {
|
||||
stillAlive.clear();
|
||||
super.clean();
|
||||
}
|
||||
}
|
230
src/processing/processes/FFT.java
Normal file
230
src/processing/processes/FFT.java
Normal file
|
@ -0,0 +1,230 @@
|
|||
package processing.processes;
|
||||
|
||||
import conteneurs.NoteGamme;
|
||||
import conteneurs.RegularGamme;
|
||||
import conteneurs.Spectre;
|
||||
import generictools.Complex;
|
||||
import generictools.Instant;
|
||||
import generictools.Pair;
|
||||
import generictools.WindowFunction;
|
||||
import gui.graphs.SpectreView;
|
||||
import processing.buffer.Storage;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
import processing.properties.ChoiceProperty;
|
||||
import processing.properties.IntProperty;
|
||||
import processing.properties.Property;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Algorithme de FFT: Calcule la transformée de Fourier d'un signal temporel et renvoie le spectre correspondant
|
||||
*/
|
||||
|
||||
public class FFT extends IndexedProcess<Spectre> {
|
||||
|
||||
private IntProperty nFFT_Max;
|
||||
private ChoiceProperty<WindowFunction> window;
|
||||
|
||||
private int nFFt;
|
||||
private final int freq_echant;
|
||||
|
||||
private SpectreView.Type grapheType;
|
||||
private Complex expOddFFT;
|
||||
private ArrayList<Integer> selectedIndex;
|
||||
private RegularGamme regGammeFFT;
|
||||
|
||||
|
||||
public FFT(TemporalBuffer<Float> temporalSignal, int sampleRate) {
|
||||
super("FFT",temporalSignal, new TemporalBuffer<Spectre>(Storage.Type.ArrayList, sampleRate));
|
||||
|
||||
nFFT_Max = new IntProperty("nFFT_Max", 8192*2*2);
|
||||
nFFT_Max.setMin(1);
|
||||
window = new ChoiceProperty<>("Window Type", createWindows(), Window.Type.class, 4);
|
||||
|
||||
|
||||
propertyManager().addProperty(nFFT_Max);
|
||||
propertyManager().addProperty(window);
|
||||
|
||||
freq_echant = temporalSignal.getSampleRate();
|
||||
regGammeFFT = new RegularGamme(0f, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue());
|
||||
setGrapheType(SpectreView.Type.NOTES);
|
||||
|
||||
nFFT_Max.addListener(new Property.Listener<Integer>() {
|
||||
@Override
|
||||
public void propertyChange(Property source, Integer previousValue) {
|
||||
window.forceChoices(createWindows());
|
||||
regGammeFFT = new RegularGamme(0f, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue());
|
||||
setGrapheType(SpectreView.Type.NOTES);
|
||||
}
|
||||
});
|
||||
|
||||
this.expOddFFT = new Complex(0,0);
|
||||
}
|
||||
|
||||
private ArrayList<Pair<WindowFunction, String>> createWindows(){
|
||||
ArrayList<Pair<WindowFunction, String>> choices = new ArrayList<>();
|
||||
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.RECTANGLE, nFFT_Max.getValue(), input()), "RECTANGLE"));
|
||||
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.HANN, nFFT_Max.getValue(), input()), "HANN"));
|
||||
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.HAMMING, nFFT_Max.getValue(), input()), "HAMMING"));
|
||||
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.TRIANGLE, nFFT_Max.getValue(), input()), "TRIANGLE"));
|
||||
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.BLACKMAN, nFFT_Max.getValue(), input()), "BLACKMAN"));
|
||||
return choices;
|
||||
}
|
||||
|
||||
/** Suivant le type d'affichage, on n'a pas besoin de calculer toutes les fréquences.
|
||||
* grapheType permet d'indiquer les fréquences utiles à calculer.
|
||||
* @param idOutput l'échantillon sur lequel on base la FFT
|
||||
* @return le spectre sur la gammeConsidérée
|
||||
*/
|
||||
protected Spectre compute (Instant idOutput){
|
||||
this.nFFt = nFFT_Max.getValue();
|
||||
//long time = System.currentTimeMillis();
|
||||
window.getValue().setStartID(idOutput.mapToIndex(input()));
|
||||
Spectre res = new Spectre(new RegularGamme(0, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue()));
|
||||
int divFactor, currentSelectId;
|
||||
float freq, ampliFreq;
|
||||
|
||||
for (int i = 0; i < selectedIndex.size(); i++) {
|
||||
currentSelectId = selectedIndex.get(i);
|
||||
freq = regGammeFFT.getFreq(currentSelectId);
|
||||
divFactor = divideFactor(freq);
|
||||
// Si changement de coefficient, on doit changer le nombre de points sur lequels on calcule la FFT
|
||||
nFFt = nFFT_Max.getValue() / divFactor;
|
||||
window.getValue().setDivideFactor(divFactor);
|
||||
|
||||
this.expOddFFT = Complex.ComplexFromExp(1,(float)(-2*Math.PI*currentSelectId)/nFFT_Max.getValue());
|
||||
//On calcule la FFT pour cette fréquence uniquement: sélection uniquement des fréquences correspondant à des notes
|
||||
ampliFreq = calculFFT(idOutput.mapToIndex(input()), 1).getMod();
|
||||
res.setAmplitudeAt(currentSelectId, ampliFreq);
|
||||
}
|
||||
|
||||
if(this.grapheType == SpectreView.Type.NOTES) {
|
||||
res.castToGamme(NoteGamme.gamme());
|
||||
}
|
||||
|
||||
//time = System.currentTimeMillis() - time;
|
||||
//System.out.println("FFT done in : "+ time + " ms");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Calcule la FFT d'un signal contenu dans un Buffer en entrée
|
||||
* @param startID: indice de départ dans le Buffer pour le calcul de la FFT
|
||||
* @param step: le "pas" actuel, qui va doubler à chaque récursion
|
||||
* @return le tableau de complexes qui correspondent aux fréquences */
|
||||
private Complex calculFFT(int startID, int step) {
|
||||
if (step != (this.nFFt)) {
|
||||
Complex exp = new Complex(expOddFFT);
|
||||
expOddFFT.simpleSquare();
|
||||
Complex oddFFT = calculFFT(startID + step, step * 2);
|
||||
|
||||
expOddFFT.setToEquals(exp);
|
||||
expOddFFT.simpleSquare();
|
||||
Complex evenFFT = calculFFT(startID, step * 2);
|
||||
return evenFFT.simpleSum(oddFFT.simpleMult(exp));
|
||||
} else {
|
||||
return new Complex(this.window.getValue().getValue(startID), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected ArrayList<Instant> idUsedBy(int inputID, int inputBufferID) {
|
||||
ArrayList<Instant> r = new ArrayList<>();
|
||||
if(inputBufferID!=0 || inputID<0){
|
||||
return r;
|
||||
}
|
||||
for (int i = 0; i < this.nFFt; i++) {
|
||||
r.add(Instant.fromIndex(inputID+i,input()));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
protected ArrayList<Instant>[] idNeededFor (int outputID) {
|
||||
ArrayList<Instant>[] r = new ArrayList[1];
|
||||
r[0] = new ArrayList<>();
|
||||
|
||||
r[0].add(Instant.fromIndex(outputID, output()));
|
||||
|
||||
return r;
|
||||
}
|
||||
public int setNfft(int nPoints){
|
||||
int nextPow = 1;
|
||||
while( nextPow < nPoints){
|
||||
nextPow = nextPow*2;
|
||||
}
|
||||
this.nFFt = nextPow;
|
||||
return this.nFFt;
|
||||
}
|
||||
public int getnFFt(){
|
||||
return this.nFFt;
|
||||
}
|
||||
|
||||
|
||||
/** Renvoie le type du buffer de sortie *
|
||||
* @return La class des échantillones du buffer de sortie */
|
||||
@Override
|
||||
public Class getType() {
|
||||
return Spectre.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcessingOrder getProcessingOrder() {
|
||||
return ProcessingOrder.ASCENDING_ORDER;
|
||||
}
|
||||
|
||||
/** Modifie le type de graphe et calcule les index correspondant à un certain nFFt. nFFt dépend de la fréquence.
|
||||
* @param grapheType le type de graphique */
|
||||
public void setGrapheType(SpectreView.Type grapheType) {
|
||||
this.grapheType = grapheType;
|
||||
|
||||
// Cas où le graphe est sous forme de NOTES
|
||||
if(this.grapheType == SpectreView.Type.NOTES) {
|
||||
int noteGammeSize = NoteGamme.gammeStatSize();
|
||||
float freq;
|
||||
|
||||
this.selectedIndex = new ArrayList<>(noteGammeSize);
|
||||
int index;
|
||||
for (int i = 0; i < noteGammeSize; i++) {
|
||||
freq = NoteGamme.getStatFreq(i);
|
||||
ArrayList<Number> neighboorIndex = regGammeFFT.getIndex(freq);
|
||||
|
||||
//Recherche de l'indice le plus proche
|
||||
index = (int)neighboorIndex.get(0);
|
||||
if (neighboorIndex.size()>1 && (float) neighboorIndex.get(2) > 0.5) {
|
||||
index = (int)neighboorIndex.get(1);
|
||||
}
|
||||
selectedIndex.add(index);
|
||||
}
|
||||
|
||||
// Cas où le graphe est sous forme de REGULAR_FREQ
|
||||
} else if (this.grapheType == SpectreView.Type.REGULAR_FREQ){
|
||||
this.selectedIndex = new ArrayList<Integer>(this.nFFt);
|
||||
for (int i = 0; i < this.nFFt; i++) {
|
||||
selectedIndex.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Renvoie le facteur de division de nFFT (optimisation de nFFt pour obtenir une bonne résolution dans les basses fréquences,
|
||||
* et une bonne rapidité de calcul dans les hautes fréquences
|
||||
* @param freq la fréquence considéré
|
||||
* @return le facteur par lequel on divise nFFt */
|
||||
private int divideFactor(float freq){
|
||||
if(freq < 200){
|
||||
return 1;
|
||||
} else if (freq < 1000){
|
||||
return 2;
|
||||
} else if (freq < 5000) {
|
||||
return 8;
|
||||
} else {
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSampleNbrPerIteration() {
|
||||
return nFFT_Max.getValue();
|
||||
}
|
||||
}
|
||||
|
227
src/processing/processes/FreqEdgeDetector.java
Normal file
227
src/processing/processes/FreqEdgeDetector.java
Normal file
|
@ -0,0 +1,227 @@
|
|||
package processing.processes;
|
||||
|
||||
import conteneurs.Spectre;
|
||||
import generictools.Instant;
|
||||
import processing.buffer.Storage;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
import processing.properties.FloatProperty;
|
||||
import processing.properties.IntProperty;
|
||||
import processing.properties.Property;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 20/05/2014.
|
||||
* Fait un calcul différentiel sur les spectres pour détecter les apparitions/disparitions de notes
|
||||
* Notre algo utilise une approximation du filtre de Canny (dérivée de Gaussienne)
|
||||
*/
|
||||
public class FreqEdgeDetector extends IndexedProcess {
|
||||
|
||||
private ArrayList<Float> coefs;
|
||||
|
||||
private FloatProperty sigma;
|
||||
private IntProperty nPoints;
|
||||
private IntProperty nPointsPasseBas;
|
||||
|
||||
public FreqEdgeDetector(TemporalBuffer<Spectre> input, int maxNombrePoints, int maxNombreDePointPasseBas) {
|
||||
super("Frequencies Edge Detector", input, new TemporalBuffer<Spectre>(Storage.Type.ArrayList, input.getSampleRate(), Instant.fromIndex(maxNombrePoints+maxNombreDePointPasseBas,input)));
|
||||
sigma = new FloatProperty("sigma", 100f);
|
||||
sigma.setMin(0.00001f);
|
||||
nPoints = new IntProperty("nPoints", maxNombrePoints);
|
||||
nPoints.setMin(1);
|
||||
nPoints.setMax(50);
|
||||
nPoints.addListener(new Property.Listener<Integer>() {
|
||||
@Override
|
||||
public void propertyChange(Property source, Integer previousValue) {
|
||||
coefs = FreqEdgeDetector.calcCoef(sigma.getValue(), nPoints.getValue());
|
||||
}
|
||||
});
|
||||
sigma.addListener(new Property.Listener<Float>() {
|
||||
@Override
|
||||
public void propertyChange(Property source, Float previousValue) {
|
||||
coefs = FreqEdgeDetector.calcCoef(sigma.getValue(), nPoints.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
nPointsPasseBas = new IntProperty("nPoints Passe Bas", maxNombreDePointPasseBas);
|
||||
nPointsPasseBas.setMin(1);
|
||||
nPointsPasseBas.setMax(50);
|
||||
coefs = FreqEdgeDetector.calcCoef(sigma.getValue(), nPoints.getValue());
|
||||
|
||||
propertyManager().addProperty(sigma);
|
||||
propertyManager().addProperty(nPoints);
|
||||
propertyManager().addProperty(nPointsPasseBas);
|
||||
}
|
||||
|
||||
/** Algorithme de calcul qui applique le masque définit dans le constructeur */
|
||||
@Override
|
||||
protected Spectre compute(Instant outputID) {
|
||||
int id = outputID.mapToIndex(output());
|
||||
|
||||
Spectre r = new Spectre(((Spectre)input().get(id)).getGamme());
|
||||
int iMax = r.getGamme().gammeSize();
|
||||
// for (int i = 0; i < iMax; i++) {
|
||||
// float ampl = coefs.get(0)* ((Spectre)input().get(id)).getAmplitude(i);
|
||||
// for(int k = 1; k < this.coefs.size(); k++){
|
||||
// Spectre sApres = (Spectre)input().get(id+k);
|
||||
// Spectre sAvant = (Spectre)input().get(id-k);
|
||||
// if(sApres == null){
|
||||
// sApres = new Spectre(r.getGamme());
|
||||
// }
|
||||
// if(sAvant == null){
|
||||
// sAvant = new Spectre(r.getGamme());
|
||||
// }
|
||||
// ampl += ( coefs.get(k)*sApres.getAmplitude(i) ) - ( coefs.get(k)*sAvant.getAmplitude(i) );
|
||||
// }
|
||||
// r.setAmplitudeAt(i, ampl);
|
||||
// }
|
||||
|
||||
for(int k = 1; k < this.coefs.size(); k++){
|
||||
Spectre sApres =passeBas(id+k);
|
||||
Spectre sAvant = passeBas(id-k);
|
||||
if(sApres == null){
|
||||
sApres = new Spectre(r.getGamme());
|
||||
}
|
||||
if(sAvant == null){
|
||||
sAvant = new Spectre(r.getGamme());
|
||||
}
|
||||
for (int i = 0; i < iMax; i++) {
|
||||
float ampl = ( coefs.get(k)*sApres.getAmplitude(i) ) - ( coefs.get(k)*sAvant.getAmplitude(i) );
|
||||
r.setAmplitudeAt(i, r.getAmplitude(i)+ampl);
|
||||
}
|
||||
}
|
||||
|
||||
return r.noiseFilter();
|
||||
}
|
||||
|
||||
|
||||
/** Réalise une moyenne sur des échantillons successifs -> filtre passe-bas pour lisser le bruit
|
||||
* @param inputID l'indice de l'échantillon de le buffer
|
||||
* @return le Spectre moyenné */
|
||||
private Spectre passeBas(int inputID){
|
||||
|
||||
if(input().get(inputID)==null)
|
||||
return null;
|
||||
|
||||
Spectre r = new Spectre( ((Spectre)input().get(inputID)).getGamme());
|
||||
int nPoint = 0;
|
||||
|
||||
for(int i=-nPointsPasseBas.getValue(); i<nPointsPasseBas.getValue()+1; i++){
|
||||
Spectre currentSpectre = (Spectre)input().get(inputID-i);
|
||||
if(currentSpectre!=null) {
|
||||
for (int j = 0; j < currentSpectre.getGamme().gammeSize(); j++)
|
||||
r.setAmplitudeAt(j, r.getAmplitude(j) + currentSpectre.getAmplitude(j));
|
||||
|
||||
nPoint++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < r.getGamme().gammeSize(); j++)
|
||||
r.setAmplitudeAt(j, r.getAmplitude(j)/nPoint);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Calcule les différents coefficients d'un masque à appliquer au signal
|
||||
* @param sigma la "largeur de la gaussienne". Définit un facteur d'échelle
|
||||
* @param nPoints le nombre de coefficients côté >0 que l'on veut. Au total il y en aura (nPoints-1)*2 + 1
|
||||
* @return la liste des coefficients */
|
||||
public static ArrayList<Float> calcCoef( double sigma, int nPoints){
|
||||
ArrayList<Float> coefs = new ArrayList<Float>();
|
||||
double step = 4.0*sigma/(double)nPoints;
|
||||
double newCoeff = 1;
|
||||
double sum = 0;
|
||||
for (int i = 0; i < nPoints ; i++) {
|
||||
newCoeff = (step*i)*Math.exp((-1*step*step*i*i)/(2*sigma*sigma));
|
||||
coefs.add((float)newCoeff);
|
||||
sum += newCoeff;
|
||||
}
|
||||
|
||||
for (int i = 0; i < coefs.size(); i++) {
|
||||
coefs.set(i, coefs.get(i)/(float)sum);
|
||||
}
|
||||
return coefs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Permet de changer les paramètres du masque à afficher et donc de changer les coefficients en conséquence
|
||||
* @param sigma le nouveau coefficient d'échelle
|
||||
* @param newNPoint le nouveau nombre de coefficients positifs */
|
||||
public void setCoefs(float sigma, int newNPoint) {
|
||||
this.coefs = FreqEdgeDetector.calcCoef(sigma, newNPoint);
|
||||
}
|
||||
|
||||
public void setSigma(float sigma) {
|
||||
this.sigma.setValue(sigma);
|
||||
setCoefs(this.sigma.getValue(), this.nPoints.getValue());
|
||||
}
|
||||
|
||||
public void setnPoints(int nPoints) {
|
||||
this.nPoints.setValue(nPoints);
|
||||
setCoefs(this.sigma.getValue(), this.nPoints.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie la liste des index dépendant de l'échantillon d'index inputID.
|
||||
* @param inputID index de l'échantillon qui est utilisé par ...
|
||||
* @param inputBufferID
|
||||
* @return La liste des indexs de sortie qui utilise inputID pour leur calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant> idUsedBy(int inputID, int inputBufferID) {
|
||||
if(inputID!=0)
|
||||
return new ArrayList<Instant>();
|
||||
|
||||
ArrayList<Instant> r = new ArrayList<>((nPoints.getValue()+nPointsPasseBas.getValue())*2+1);
|
||||
int outputID = Instant.fromIndex(inputID, input()).mapToIndex(output());
|
||||
|
||||
r.add(Instant.fromIndex(inputBufferID, input()));
|
||||
for(int k = 1; k < (nPoints.getValue()+nPointsPasseBas.getValue()); k++){
|
||||
r.add(0, Instant.fromIndex(outputID - k,output()));
|
||||
r.add(Instant.fromIndex(outputID + k, output()));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/** À redéfinir: Renvoie, pour chaque buffer d'entrée la liste des index nécessaire au calcul de l'échantillon d'index outputID.
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Un tableau de même taille que getInputs(), et qui contient les liste des échantillons de chaque buffer de getInputs() nécessaire au calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant>[] idNeededFor(int outputID) {
|
||||
ArrayList<Instant>[] t = new ArrayList[1];
|
||||
ArrayList<Instant> r = new ArrayList<>((nPoints.getMax()+nPointsPasseBas.getMax())*2+1);
|
||||
|
||||
t[0] = r;
|
||||
if(outputID<0)
|
||||
return t;
|
||||
|
||||
int inputID = Instant.fromIndex(outputID, output()).mapToIndex(input());
|
||||
r.add(Instant.fromIndex(inputID, input()));
|
||||
for(int k = 1; k < (nPoints.getValue()+nPointsPasseBas.getValue()); k++){
|
||||
r.add(0, Instant.fromIndex(inputID - k, input()));
|
||||
r.add(Instant.fromIndex(inputID + k, input()));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Renvoie le type du buffer de sortie
|
||||
* @return La class des échantillones du buffer de sortie */
|
||||
@Override
|
||||
protected Class getType() {
|
||||
return Spectre.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcessingOrder getProcessingOrder() {
|
||||
return ProcessingOrder.ASCENDING_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSampleNbrPerIteration() {
|
||||
return nPoints.getValue()*2 + nPointsPasseBas.getValue()*2 +1;
|
||||
}
|
||||
}
|
||||
|
163
src/processing/processes/FreqEdgeToEvent.java
Normal file
163
src/processing/processes/FreqEdgeToEvent.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
package processing.processes;
|
||||
|
||||
import conteneurs.*;
|
||||
import generictools.Instant;
|
||||
import javafx.collections.ObservableList;
|
||||
import processing.buffer.Storage;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
import processing.properties.FloatProperty;import processing.properties.IntProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Gabriel on 21/05/2014.
|
||||
* Permet de passer de la détection des contours aux evenements, donc apparition et disparition de notes */
|
||||
public class FreqEdgeToEvent extends IndexedProcess<NoteEventList> {
|
||||
|
||||
IntProperty nPointRegroup;
|
||||
FloatProperty cutCoef;
|
||||
|
||||
|
||||
public FreqEdgeToEvent(TemporalBuffer<Spectre> input, int nPointRegroupMax, float cutCoef) {
|
||||
super("FreqEdgeToEvent", input, new TemporalBuffer<NoteEventList>(Storage.Type.ArrayList, input.getSampleRate(), Instant.fromIndex(0, input)));
|
||||
nPointRegroup = new IntProperty("nPoint regroupement", nPointRegroupMax);
|
||||
nPointRegroup.setMax(50);
|
||||
nPointRegroup.setMin(0);
|
||||
|
||||
|
||||
this.cutCoef = new FloatProperty("cut Coef", cutCoef);
|
||||
this.cutCoef.setMax(1);
|
||||
this.cutCoef.setMin(0);
|
||||
|
||||
|
||||
propertyManager().addProperty(nPointRegroup);
|
||||
propertyManager().addProperty(this.cutCoef);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NoteEventList compute(Instant outputID) {
|
||||
|
||||
NoteEventList listeEvent = new NoteEventList();
|
||||
int id = outputID.mapToIndex(output());
|
||||
Spectre r = maxRegroup(id);
|
||||
if(r == null)
|
||||
return new NoteEventList();
|
||||
|
||||
|
||||
// Si jamais ce n'est pas un spectre de notes, on le transforme pour pouvoir travailler dessus
|
||||
if(!(r.getGamme() instanceof NoteGamme)){
|
||||
r.castToGamme(NoteGamme.gamme());
|
||||
}
|
||||
|
||||
ArrayList<Float> ampliEdges = r.getAmplitudes();
|
||||
Gamme frequencies = r.getGamme();
|
||||
float currentAmpli;
|
||||
for (int i = 1; i < frequencies.gammeSize(); i++) {
|
||||
currentAmpli = ampliEdges.get(i);
|
||||
//Cas d'une disparation brutale de note
|
||||
Profil profil = new Profil();
|
||||
profil.setAmplitude(Math.abs(currentAmpli), 0);
|
||||
|
||||
if (currentAmpli < -200000f) {
|
||||
listeEvent.add(NoteEvent.disparitionEvent(new NoteProfiled(i, profil), -currentAmpli));
|
||||
} else if (currentAmpli > 300000f) {
|
||||
listeEvent.add(NoteEvent.apparitionEvent(new NoteProfiled(i, profil), currentAmpli));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return listeEvent;
|
||||
}
|
||||
|
||||
/** Supprime les maximums qui sont trop proches suivant une certaine marge de tolérance définie par cutCoeff
|
||||
* @param inputID l'indice du buffer d'entrée */
|
||||
private Spectre maxRegroup(int inputID){
|
||||
if(input().get(inputID)==null)
|
||||
return null;
|
||||
|
||||
Spectre currentSpectre = ((Spectre)input().get(inputID)).copy();
|
||||
if(currentSpectre==null)
|
||||
return null;
|
||||
|
||||
|
||||
for(int i=0; i<=nPointRegroup.getValue(); i++){
|
||||
Spectre previousSpectre = input().get(inputID-i)!=null?(Spectre)input().get(inputID-i):new Spectre( ((Spectre)input().get(inputID)).getGamme());
|
||||
Spectre nextSpectre = input().get(inputID+i)!=null?(Spectre)input().get(inputID+i):new Spectre( ((Spectre)input().get(inputID)).getGamme());
|
||||
|
||||
for (int j = 1; j < currentSpectre.getGamme().gammeSize(); j++) {
|
||||
float currentAmpli = currentSpectre.getAmplitude(j);
|
||||
float previousAmpli = (previousSpectre.getAmplitude(j)*currentAmpli)>0?Math.abs(previousSpectre.getAmplitude(j)):0;
|
||||
float nextAmpli = (nextSpectre.getAmplitude(j)*currentAmpli)>0?Math.abs(nextSpectre.getAmplitude(j)):-1;
|
||||
|
||||
currentAmpli = Math.abs(currentAmpli);
|
||||
if(currentAmpli == 0)
|
||||
continue;
|
||||
|
||||
if(previousAmpli>currentAmpli/cutCoef.getValue() || nextAmpli>currentAmpli/cutCoef.getValue())
|
||||
currentSpectre.setAmplitudeAt(j, 0);
|
||||
}
|
||||
}
|
||||
return currentSpectre;
|
||||
}
|
||||
/** Renvoie le rapport entre 2 float */
|
||||
private float rapport(float f1, float f2){
|
||||
return Math.min(f1,f2)/Math.max(f1,f2);
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie la liste des index dépendant de l'échantillon d'index inputID.
|
||||
* @param inputID index de l'échantillon qui est utilisé par ...
|
||||
* @param inputBufferID
|
||||
* @return La liste des indexs de sortie qui utilise inputID pour leur calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant> idUsedBy(int inputBufferID, int inputID) {
|
||||
if(inputID!=0)
|
||||
return new ArrayList<Instant>();
|
||||
|
||||
ArrayList<Instant> r = new ArrayList<>((nPointRegroup.getValue())*2+1);
|
||||
int outputID = Instant.fromIndex(inputID, input()).mapToIndex(output());
|
||||
|
||||
r.add(Instant.fromIndex(inputBufferID, input()));
|
||||
for(int k = 1; k < (nPointRegroup.getValue()); k++){
|
||||
r.add(0, Instant.fromIndex(outputID - k,output()));
|
||||
r.add(Instant.fromIndex(outputID + k, output()));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/** À redéfinir: Renvoie, pour chaque buffer d'entrée la liste des index nécessaire au calcul de l'échantillon d'index outputID.
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Un tableau de même taille que getInputs(), et qui contient les liste des échantillons de chaque buffer de getInputs() nécessaire au calcul.
|
||||
*/
|
||||
@Override
|
||||
protected ArrayList<Instant>[] idNeededFor(int outputID) {
|
||||
ArrayList<Instant>[] t = new ArrayList[1];
|
||||
ArrayList<Instant> r = new ArrayList<>((nPointRegroup.getValue())*2+1);
|
||||
|
||||
t[0] = r;
|
||||
if(outputID<0)
|
||||
return t;
|
||||
|
||||
int inputID = Instant.fromIndex(outputID, output()).mapToIndex(input());
|
||||
r.add(Instant.fromIndex(inputID, input()));
|
||||
for(int k = 1; k < (nPointRegroup.getValue()); k++){
|
||||
r.add(0, Instant.fromIndex(inputID - k, input()));
|
||||
r.add(Instant.fromIndex(inputID + k, input()));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Renvoie le type du buffer de sortie
|
||||
* @return La class des échantillones du buffer de sortie */
|
||||
@Override
|
||||
protected Class getType() {
|
||||
return NoteEventList.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProcessingOrder getProcessingOrder() {
|
||||
return ProcessingOrder.ASCENDING_ORDER;
|
||||
}
|
||||
}
|
135
src/processing/processes/IndexedProcess.java
Normal file
135
src/processing/processes/IndexedProcess.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
package processing.processes;
|
||||
|
||||
import generictools.Instant;
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.BufferEvent;
|
||||
import processing.buffer.TemporalBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class IndexedProcess <T> extends Process<T> {
|
||||
|
||||
public IndexedProcess (String name, Buffer[] inputs, Buffer<T> output) {
|
||||
super(name, inputs, output);
|
||||
}
|
||||
public IndexedProcess(String name, Buffer input, Buffer<T> output) {
|
||||
super(name,input, output);
|
||||
}
|
||||
|
||||
protected BufferEventProcessor initBufferEventProcessor(){
|
||||
|
||||
switch (getProcessingOrder()){
|
||||
case ASCENDING_ORDER:
|
||||
return new BufferEventProcessor() {
|
||||
|
||||
int[] lastInputIndex = {};
|
||||
|
||||
|
||||
@Override
|
||||
public void processBufferEvent(BufferEvent e, int updateID) {
|
||||
//Test de la possibilité du calcul sur l'échantillon en sortie suivant
|
||||
|
||||
for (int i = 0; i < lastInputIndex.length; i++) {
|
||||
if(lastInputIndex[i]>inputs()[i].size()-1)
|
||||
return;
|
||||
}
|
||||
|
||||
output().add(compute(Instant.fromIndex(output().size(), output())));
|
||||
|
||||
|
||||
ArrayList<Instant>[] idNeeded= idNeededFor(output().size());
|
||||
lastInputIndex = new int[idNeeded.length];
|
||||
for (int i = 0; i < idNeeded.length; i++)
|
||||
lastInputIndex[i] = idNeeded[i].get(idNeeded[i].size()-1).mapToIndex(inputs()[i]);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new BufferEventProcessor() {
|
||||
@Override
|
||||
public void processBufferEvent(BufferEvent e, int eventUpdateID) {
|
||||
int bufferID = 0;
|
||||
for(int i =1; i<inputs.length;i++)
|
||||
if(inputs[i]==e.getSource()) {
|
||||
bufferID = i;
|
||||
break;
|
||||
}
|
||||
|
||||
//Des indices d'entrées on été modifiés, il faut mettre à jour les échantillons de sortie concernés
|
||||
|
||||
//SI AJOUT
|
||||
// SI SET remplacer isComputable par != null;
|
||||
|
||||
for (Instant i : idUsedBy(e.getSampleID(), bufferID)) {
|
||||
Integer outputUpdateID = output.getUpdateID(i.mapToIndex(output()));
|
||||
int iOutput = i.mapToIndex(output());
|
||||
if (iOutput>=0 && isComputable(iOutput) && (outputUpdateID==null || eventUpdateID > outputUpdateID || (eventUpdateID < 0 && outputUpdateID > 0))) {
|
||||
output().set(iOutput, compute(i), updateID);
|
||||
updateID++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isComputable(int outputID){
|
||||
|
||||
ArrayList<Instant>[] needed = idNeededFor(outputID);
|
||||
|
||||
if(needed.length==0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < needed.length; i++) {
|
||||
for (Instant j : needed[i])
|
||||
if (inputs[i].get(j.mapToIndex(inputs()[i])) == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* À redéfinir: Retourne le résultat du calcul à placer à l'index outputID
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Résultat du calcul
|
||||
*/
|
||||
protected abstract T compute (Instant outputID);
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie la liste des index dépendant de l'échantillon d'index inputID.
|
||||
* @param inputBufferID index du buffer de l'échantillon
|
||||
* @param inputID index de l'échantillon qui est utilisé par ...
|
||||
* @return La liste des indexs de sortie qui utilise inputID pour leur calcul.
|
||||
*/
|
||||
protected abstract ArrayList<Instant> idUsedBy (int inputBufferID, int inputID);
|
||||
|
||||
/**
|
||||
* À redéfinir: Renvoie, pour chaque buffer d'entrée la liste des index nécessaire au calcul de l'échantillon d'index outputID.
|
||||
* @param outputID index de l'échantillon que l'on veut calculer
|
||||
* @return Un tableau de même taille que getInputs(), et qui contient les liste des échantillons de chaque buffer de getInputs() nécessaire au calcul.
|
||||
*/
|
||||
protected abstract ArrayList<Instant>[] idNeededFor (int outputID);
|
||||
|
||||
|
||||
public TemporalBuffer input(){
|
||||
return (TemporalBuffer)super.input();
|
||||
}
|
||||
|
||||
|
||||
public TemporalBuffer[] inputs(){
|
||||
TemporalBuffer[] r = new TemporalBuffer[super.inputs().length];
|
||||
for (int i = 0; i < super.inputs().length; i++) {
|
||||
r[i] = (TemporalBuffer)super.inputs()[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public TemporalBuffer<T> output(){
|
||||
return (TemporalBuffer<T>)super.output();
|
||||
}
|
||||
|
||||
|
||||
}
|
494
src/processing/processes/Process.java
Normal file
494
src/processing/processes/Process.java
Normal file
|
@ -0,0 +1,494 @@
|
|||
package processing.processes;
|
||||
|
||||
|
||||
import generictools.Pair;
|
||||
import processing.buffer.Buffer;
|
||||
import processing.buffer.BufferEvent;
|
||||
import processing.properties.PropertiesManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class Process<T> implements Runnable {
|
||||
final Buffer[] inputs;
|
||||
private boolean[] inputsInterupted;
|
||||
final Buffer<T> output;
|
||||
|
||||
private boolean interuptAfter = false, paused = false, waiting = true;
|
||||
|
||||
private final String name;
|
||||
private PropertiesManager properties;
|
||||
private ArrayList<Listener> listeners = new ArrayList<>();
|
||||
|
||||
private LinkedList<Pair<BufferEvent,Integer> > bufferEvents;
|
||||
int updateID = 0;
|
||||
private Thread thread;
|
||||
|
||||
private BufferEventProcessor bProcessor;
|
||||
|
||||
private boolean boxDisplayed = false;
|
||||
|
||||
public enum ProcessingOrder {
|
||||
NO_ORDER,
|
||||
ASCENDING_ORDER
|
||||
}
|
||||
|
||||
public enum ProcessingState {
|
||||
PROCESSING,
|
||||
WAITING,
|
||||
PAUSED,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
public Process(String name, Buffer[] inputs, Buffer<T> output) {
|
||||
this.inputs = inputs;
|
||||
|
||||
this.output = output;
|
||||
this.name = name;
|
||||
|
||||
init();
|
||||
}
|
||||
public Process(String name, Buffer input, Buffer<T> output) {
|
||||
inputs = new Buffer[1];
|
||||
inputs[0] = input;
|
||||
|
||||
this.output = output;
|
||||
this.name = name;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void init(){
|
||||
bufferEvents = new LinkedList<>();
|
||||
thread = new Thread(this);
|
||||
|
||||
output.setName(name);
|
||||
output.setType(getType());
|
||||
|
||||
switch (getProcessingOrder()){
|
||||
case ASCENDING_ORDER:
|
||||
output.setProcessingOrder(ProcessingOrder.ASCENDING_ORDER);
|
||||
for (int i = 0; i < inputs.length; i++) {
|
||||
if(inputs[i].getProcessingOrder()!=ProcessingOrder.ASCENDING_ORDER) {
|
||||
output.setProcessingOrder(ProcessingOrder.NO_ORDER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
inputsInterupted = new boolean[inputs.length];
|
||||
|
||||
for(int i=0; i<inputs.length; i++) {
|
||||
inputs[i].connectProcess(this);
|
||||
inputsInterupted[i] = false;
|
||||
}
|
||||
|
||||
properties = new PropertiesManager();
|
||||
|
||||
bProcessor = initBufferEventProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les buffers d'entrées
|
||||
* @return Liste des buffers d'entrées
|
||||
*/
|
||||
public Buffer[] inputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le premier (possiblement le seul) buffers d'entrées
|
||||
* @return Le premier buffers de la liste des buffers d'entrées
|
||||
*/
|
||||
public Buffer input(){return inputs[0];}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie le buffer de sortie
|
||||
* @return renvoie le buffer de sortie
|
||||
*/
|
||||
public Buffer<T> output() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public String name(){
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setInput(int inputID, Buffer newInput){
|
||||
|
||||
inputs[inputID].disconnectProcess(this);
|
||||
newInput.connectProcess(this);
|
||||
inputs[inputID] = newInput;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void addBufferEvent(BufferEvent e){
|
||||
|
||||
if(e.getType()== BufferEvent.Type.SAMPLE_ADDED) {
|
||||
bufferEvents.addLast(new Pair<>(e, updateID));
|
||||
emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED);
|
||||
}else {
|
||||
if (e.getType() == BufferEvent.Type.INTERRUPT_PROCESS) {
|
||||
int bufferID = 0;
|
||||
for (int i = 1; i < inputs.length; i++) {
|
||||
if (inputs[i] == e.getSource())
|
||||
bufferID = i;
|
||||
}
|
||||
inputsInterupted[bufferID] = true;
|
||||
|
||||
if (isAllInputInterrupted()) {
|
||||
interuptAfter = true;
|
||||
emitProcessEvent(ProcessEvent.Type.INTERRUPT_AFTER);
|
||||
}
|
||||
} else if (e.getType() == BufferEvent.Type.INVALIDATE) {
|
||||
boolean b = pause();
|
||||
bufferEvents.clear();
|
||||
if (b)
|
||||
start();
|
||||
} else {
|
||||
|
||||
for (int i = 0; i < bufferEvents.size(); i++)
|
||||
if (bufferEvents.get(i).first().getSource() == e.getSource()) {
|
||||
bufferEvents.remove(i);
|
||||
break;
|
||||
}
|
||||
|
||||
bufferEvents.addFirst(new Pair<>(e, updateID));
|
||||
emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
propertyManager().lockProperty();
|
||||
while (!bufferEvents.isEmpty()&&!paused) {
|
||||
setWaiting(false);
|
||||
Pair<BufferEvent, Integer> event = bufferEvents.pollFirst();
|
||||
if(event==null)
|
||||
continue;
|
||||
bProcessor.processBufferEvent(event.first(), event.second());
|
||||
|
||||
|
||||
emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED);
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setWaiting(true);
|
||||
|
||||
if(interuptAfter){
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
propertyManager().unlockProperty();
|
||||
Thread.sleep(50);
|
||||
propertyManager().lockProperty();
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
|
||||
while(paused)
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
clean();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void clean(){
|
||||
properties.unlockProperty();
|
||||
pause();
|
||||
output.lastSampleAdded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarre le traitement
|
||||
* @return Renvoie vrai si le traitement n'était pas déjà démarré
|
||||
*/
|
||||
|
||||
public boolean start(){
|
||||
if(thread.isAlive()) {
|
||||
if(paused) {
|
||||
paused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
interuptAfter = false;
|
||||
paused = false;
|
||||
|
||||
thread = new Thread(this);
|
||||
thread.setName(name);
|
||||
thread.start();
|
||||
|
||||
emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interompt le traitement
|
||||
* @return Renvoie vrai si le traitement était lancé
|
||||
*/
|
||||
public boolean interrupt(){
|
||||
if(!thread.isAlive())
|
||||
return false;
|
||||
|
||||
thread.interrupt();
|
||||
emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interompt le traitement une fois que tout les événements ont été traités
|
||||
* @return Renvoie vrai si le traitement était lancé
|
||||
*/
|
||||
|
||||
|
||||
public boolean stopAfterEventsClean(){
|
||||
if(!thread.isAlive())
|
||||
return false;
|
||||
|
||||
emitProcessEvent(ProcessEvent.Type.INTERRUPT_AFTER);
|
||||
interuptAfter = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mais en pause le traitement sans supprimer le thread
|
||||
* @return Renvoie vrai si le traitement était lancé
|
||||
*/
|
||||
|
||||
|
||||
public boolean pause(){
|
||||
if(!thread.isAlive())
|
||||
return false;
|
||||
|
||||
paused = true;
|
||||
emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void recomputeAll(){
|
||||
output.clear();
|
||||
interrupt();
|
||||
properties.unlockProperty();
|
||||
bufferEvents.clear();
|
||||
bProcessor = initBufferEventProcessor();
|
||||
for(Buffer input: inputs()){
|
||||
ArrayList<Integer> indexes = input.getSamplesIndex();
|
||||
for(Integer index:indexes)
|
||||
addBufferEvent(new BufferEvent(BufferEvent.Type.SAMPLE_ADDED, index, input));
|
||||
}
|
||||
start();
|
||||
|
||||
for(Process p:output().getUsedBy())
|
||||
p.recomputeAll();
|
||||
}
|
||||
|
||||
public boolean isAllInputInterrupted(){
|
||||
for (boolean anInputsInterupted : inputsInterupted) {
|
||||
if (!anInputsInterupted)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setWaiting(boolean waiting){
|
||||
if(this.waiting == waiting)
|
||||
return;
|
||||
|
||||
this.waiting = waiting;
|
||||
emitProcessEvent(ProcessEvent.Type.WAITING_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le nom du processus
|
||||
* @return Le nom
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le buffer de sortie
|
||||
* @return buffer
|
||||
*/
|
||||
public Buffer<T> getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie un buffer d'entrée
|
||||
* @param index index du buffer d'entrée
|
||||
* @return Le buffer correspondant /!\ Peut renvoyer null si l'index n'est pas correct /!\
|
||||
*/
|
||||
public Buffer getInputs(int index) {
|
||||
if(index>=inputs.length || index<1)
|
||||
return null;
|
||||
|
||||
return inputs[index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie le nombre de buffer d'entrée
|
||||
* @return Nombre d'inputs
|
||||
*/
|
||||
public int getInputsNbr(){
|
||||
return inputs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des buffersEvent non traités
|
||||
* @return liste des buffersEvent
|
||||
*/
|
||||
public ArrayList<BufferEvent> getBufferEventsQueue() {
|
||||
ArrayList<BufferEvent> r = new ArrayList<>(bufferEvents.size());
|
||||
r.addAll(bufferEvents.stream().map(Pair<BufferEvent, Integer>::first).collect(Collectors.toList()));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les propriétés associées à cet algorythme
|
||||
* @return le propertiesManager du process
|
||||
*/
|
||||
public PropertiesManager propertyManager() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'état du processus
|
||||
* @return Vrai si tout l'algo est en attente d'événement sur les canaux d'entrées
|
||||
*/
|
||||
public boolean isWaiting() {
|
||||
return waiting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'état du processus
|
||||
* @return Vrai si l'algo est en pause
|
||||
*/
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie l'état du processus
|
||||
* @return Vrai si le processus est lancé
|
||||
*/
|
||||
public boolean isRunning(){
|
||||
return thread.isAlive();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie la valeur de la progression du process
|
||||
* @return la progression: 0 -> aucun échantillon traité; 1 -> tous les échantillons ont été traités
|
||||
*/
|
||||
public float getProgress(){
|
||||
if(bufferEvents.size() == 0)
|
||||
return 0;
|
||||
double n = getSampleNbrPerIteration();
|
||||
return 1-(float)Math.log(n)/(float)Math.log(bufferEvents.size()+n-1);
|
||||
}
|
||||
|
||||
protected float getSampleNbrPerIteration(){return 10;}
|
||||
|
||||
/**
|
||||
* Renvoie le type du buffer de sortie
|
||||
* @return La class des échantillones du buffer de sortie
|
||||
*/
|
||||
protected abstract Class getType();
|
||||
|
||||
protected abstract ProcessingOrder getProcessingOrder();
|
||||
|
||||
|
||||
protected interface BufferEventProcessor {
|
||||
void processBufferEvent(BufferEvent e, int updateID);
|
||||
}
|
||||
|
||||
protected abstract BufferEventProcessor initBufferEventProcessor();
|
||||
|
||||
public interface Listener{
|
||||
public void processEvent(ProcessEvent event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connecte ce process à un listener qu'il fournit, de manière à propager les BufferEvent jusqu'à cet objet
|
||||
* @param p objet à connecter
|
||||
* @return Vrai si l'opération à fontionner (si le listener n'était pas déjà ajouté)
|
||||
*/
|
||||
public boolean addListener(Listener p){
|
||||
if(listeners.contains(p))
|
||||
return false;
|
||||
|
||||
listeners.add(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte ce process d'un listener
|
||||
* @param p listener à déconnecter
|
||||
* @return Vrai si l'opération à fontionner (si le listener était dans la liste)
|
||||
*/
|
||||
public boolean removeListener(Listener p){
|
||||
int id = listeners.indexOf(p);
|
||||
if(id == -1)
|
||||
return false;
|
||||
|
||||
listeners.remove(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void emitProcessEvent(ProcessEvent event) {
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
for (Listener p : listeners)
|
||||
p.processEvent(event);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void emitProcessEvent(ProcessEvent.Type type) {
|
||||
emitProcessEvent(ProcessEvent.eventFrom(type, this));
|
||||
|
||||
}
|
||||
/**
|
||||
* Indique si la box du buffer est affichée dans le ProcessSumUp ou non.
|
||||
* @return true si elle est affichée, false sinon.
|
||||
*/
|
||||
public boolean isBoxDisplayed() {
|
||||
return boxDisplayed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change l'état d'affichage de la box du Buffer dans le ProcessSumUp. N'appeler que lorsque le système crée la Box.
|
||||
* @param boxDisplayed L'état : true si on l'affiche, false si on l'enlève.
|
||||
*/
|
||||
public void setBoxDisplayed(boolean boxDisplayed) {
|
||||
this.boxDisplayed = boxDisplayed;
|
||||
}
|
||||
}
|
48
src/processing/processes/ProcessEvent.java
Normal file
48
src/processing/processes/ProcessEvent.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package processing.processes;
|
||||
|
||||
/**
|
||||
* Created by gaby on 25/05/14.
|
||||
* Caractérise les différents evenements qui peuvent intervenir sur les processus qui tournent */
|
||||
public class ProcessEvent {
|
||||
|
||||
//Les différents types d'évènements
|
||||
public enum Type{
|
||||
PAUSED_STATE_CHANGED,
|
||||
WAITING_STATE,
|
||||
PROGRESS_CHANGED,
|
||||
NAME_CHANGED,
|
||||
INTERRUPT_AFTER
|
||||
}
|
||||
private Type type;
|
||||
private String name;
|
||||
private boolean isPaused;
|
||||
private boolean isWaiting;
|
||||
|
||||
public ProcessEvent(Type type, String name, boolean isPaused, boolean isWaiting) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.isPaused = isPaused;
|
||||
this.isWaiting = isWaiting;
|
||||
}
|
||||
|
||||
/** Renvoie l'évenement lié au process donné en paramètre, en fonction du type donné en paramètre */
|
||||
public static ProcessEvent eventFrom(Type type, Process process){
|
||||
return new ProcessEvent(type, process.getName(), process.isPaused(), process.isWaiting());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return isPaused;
|
||||
}
|
||||
|
||||
public boolean isWaiting() {
|
||||
return isWaiting;
|
||||
}
|
||||
}
|
49
src/processing/properties/BooleanProperty.java
Normal file
49
src/processing/properties/BooleanProperty.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package processing.properties;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Control;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/05/14.
|
||||
*/
|
||||
public class BooleanProperty extends Property<Boolean> {
|
||||
ArrayList<CheckBox> nodes = new ArrayList<>();
|
||||
|
||||
public BooleanProperty(String name, boolean value) {
|
||||
super(name, Boolean.class, value);
|
||||
}
|
||||
|
||||
public BooleanProperty(String name){
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
*
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
@Override
|
||||
public Control createEditNode() {
|
||||
CheckBox node = new CheckBox();
|
||||
node.setOnAction(actionEvent -> {
|
||||
if(node.isSelected()!=getValue())
|
||||
nodeValueModified(node.isSelected());
|
||||
});
|
||||
node.setSelected(getValue());
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
@Override
|
||||
protected void syncNodesValue() {
|
||||
for(CheckBox c: nodes)
|
||||
c.setSelected(getApparentValue());
|
||||
}
|
||||
}
|
131
src/processing/properties/ChoiceProperty.java
Normal file
131
src/processing/properties/ChoiceProperty.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package processing.properties;
|
||||
|
||||
import generictools.Pair;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Control;
|
||||
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/05/14.
|
||||
*/
|
||||
public class ChoiceProperty<T> extends Property<T>{
|
||||
ArrayList<Pair<T, String>> choices;
|
||||
private int currentID = 0;
|
||||
ArrayList<ComboBox<String>> comboBoxs = new ArrayList<>();
|
||||
|
||||
public ChoiceProperty(String name, ArrayList<Pair<T,String>> choices, Class type, int defaultIndex) {
|
||||
super(name, type, choices.get(defaultIndex).first());
|
||||
currentID = defaultIndex;
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
public ChoiceProperty(String name, ArrayList<Pair<T, String>> choices, Class type) {
|
||||
this(name, choices, type, 0);
|
||||
}
|
||||
|
||||
/** Modifie la valeur associée à chaque choix possible
|
||||
* @param choices l'ensemble des choix possibles sous forme d'ArrayList de Pair */
|
||||
public void forceChoices(ArrayList<Pair<T,String>> choices){
|
||||
if(choices.size()==0)
|
||||
return;
|
||||
|
||||
if(getSelectedItemIndex()>=choices.size())
|
||||
setSelectedItem(0);
|
||||
|
||||
this.choices = choices;
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for(ComboBox<String> c: comboBoxs){
|
||||
c.getItems().clear();
|
||||
for(Pair<T, String> choice : choices)
|
||||
c.getItems().add(choice.second());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setSelectedItem(getSelectedItemIndex());
|
||||
}
|
||||
|
||||
/** Renvoie l'indice de l'élément qui est selectionné en ce moment */
|
||||
public int getSelectedItemIndex(){
|
||||
return currentID;
|
||||
}
|
||||
|
||||
/** Renvoie le nom de l'élément sélectionné en ce moment */
|
||||
public String getSelectedItemName(){
|
||||
return choices.get(currentID).second();
|
||||
}
|
||||
|
||||
/** Redéfinit la valeur actuelle
|
||||
* @param item le nouvel élément
|
||||
* @return vrai si ça a fonctionné, faux sinon */
|
||||
public boolean setValue(T item){
|
||||
if(item == getValue())
|
||||
return false;
|
||||
|
||||
int id = 0;
|
||||
for(id=0; id<choices.size(); id++){
|
||||
if(choices.get(id).first()==item)
|
||||
break;
|
||||
}
|
||||
|
||||
if(id==choices.size())
|
||||
choices.add(new Pair<T,String>(item,item.toString()));
|
||||
|
||||
return setSelectedItem(id);
|
||||
}
|
||||
|
||||
/** Rétrocontrole sur l'interface
|
||||
* @param index l'indice de l'item sélectionné
|
||||
* @return vrai si ça a fonctionné, faux sinon */
|
||||
public boolean setSelectedItem(int index){
|
||||
if(index<0 || index >= choices.size())
|
||||
return false;
|
||||
|
||||
currentID = index;
|
||||
return super.setValue(choices.get(index).first());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
*
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
@Override
|
||||
public Control createEditNode() {
|
||||
|
||||
ComboBox<String> comboBox = new ComboBox<>();
|
||||
|
||||
for(Pair<T, String> c : choices)
|
||||
comboBox.getItems().add(c.second());
|
||||
|
||||
comboBox.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
if(comboBox.getSelectionModel().getSelectedIndex() != getSelectedItemIndex())
|
||||
setSelectedItem(comboBox.getSelectionModel().getSelectedIndex());
|
||||
}
|
||||
});
|
||||
comboBox.getSelectionModel().select(getSelectedItemIndex());
|
||||
comboBoxs.add(comboBox);
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
@Override
|
||||
protected void syncNodesValue() {
|
||||
for(ComboBox<String> c: comboBoxs)
|
||||
c.getSelectionModel().select(getSelectedItemIndex());
|
||||
}
|
||||
}
|
126
src/processing/properties/FloatProperty.java
Normal file
126
src/processing/properties/FloatProperty.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
package processing.properties;
|
||||
|
||||
import com.sun.org.apache.bcel.internal.generic.FLOAD;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/05/14.
|
||||
*/
|
||||
public class FloatProperty extends Property<Float> {
|
||||
|
||||
ArrayList<TextField> textFields = new ArrayList<>();
|
||||
float min = Float.MIN_VALUE;
|
||||
float max = Float.MAX_VALUE;
|
||||
|
||||
public FloatProperty(String name, float value){
|
||||
super(name, Float.class, value);
|
||||
}
|
||||
|
||||
|
||||
public FloatProperty(String name) {
|
||||
this(name, 0);
|
||||
}
|
||||
|
||||
|
||||
public FloatProperty(String name, float value, float min, float max){
|
||||
this(name, value);
|
||||
setMin(min);
|
||||
setMax(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur de la propriété en s'assurant qu'elle respecte les conditions de minimum et de maximum
|
||||
* @param value la nouvelle valeur
|
||||
* @return Renvoie vrai si l'opération s'est bien déroulé (si la valeur est dans l'intervale autorisé)
|
||||
*/
|
||||
public boolean setValue(Float value){
|
||||
if(value>=min && value<=max)
|
||||
return super.setValue(value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur maximale de l'intervale autorisé et modifie la valeur min si l'intervale est incorrect
|
||||
* @param max nouvelle valeur max
|
||||
*/
|
||||
public void setMax(float max){
|
||||
this.max = max;
|
||||
if(min>max)
|
||||
min = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur minimale de l'intervale autorisé et modifie la valeur max si l'intervale est incorrect
|
||||
* @param min nouvelle valeur min
|
||||
*/
|
||||
public void setMin(float min){
|
||||
this.min = min;
|
||||
if(min>max)
|
||||
max = min;
|
||||
}
|
||||
|
||||
public float getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public float getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
*
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
@Override
|
||||
public Control createEditNode() {
|
||||
TextField textField = new TextField();
|
||||
textField.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
float v = 0;
|
||||
try{
|
||||
v = Float.parseFloat(textField.getText());
|
||||
}catch (NumberFormatException e){
|
||||
textField.setText(getValue().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if(v==getValue())
|
||||
return;
|
||||
|
||||
if(v<min){
|
||||
textField.setText(String.valueOf(min));
|
||||
nodeValueModified(min);
|
||||
return;
|
||||
}
|
||||
if(v>max){
|
||||
textField.setText(String.valueOf(max));
|
||||
nodeValueModified(max);
|
||||
return;
|
||||
}
|
||||
|
||||
nodeValueModified(v);
|
||||
}
|
||||
});
|
||||
textFields.add(textField);
|
||||
textField.setText(getValue().toString());
|
||||
return textField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
@Override
|
||||
protected void syncNodesValue() {
|
||||
for(TextField t: textFields)
|
||||
t.setText(getApparentValue().toString());
|
||||
}
|
||||
}
|
129
src/processing/properties/IntProperty.java
Normal file
129
src/processing/properties/IntProperty.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package processing.properties;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/05/14.
|
||||
*/
|
||||
public class IntProperty extends Property<Integer> {
|
||||
ArrayList<TextField> textFields = new ArrayList<>();
|
||||
int min = Integer.MIN_VALUE;
|
||||
int max = Integer.MAX_VALUE;
|
||||
|
||||
|
||||
public IntProperty(String name, int value){
|
||||
super(name, Float.class, value);
|
||||
}
|
||||
|
||||
public IntProperty(String name) {
|
||||
this(name, 0);
|
||||
}
|
||||
|
||||
|
||||
public IntProperty(String name, int value, int min, int max){
|
||||
this(name);
|
||||
setMin(min);
|
||||
setMax(max);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur de la propriété en s'assurant qu'elle respecte les conditions de minimum et de maximum
|
||||
* @param value la nouvelle valeur
|
||||
* @return Renvoie vrai si l'opération s'est bien déroulé (si la valeur est dans l'intervale autorisé)
|
||||
*/
|
||||
public boolean setValue(Integer value){
|
||||
if(value>=min && value<=max)
|
||||
return super.setValue(value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur maximale de l'intervale autorisé et modifie la valeur min si l'intervale est incorrect
|
||||
* @param max nouvelle valeur max
|
||||
*/
|
||||
public void setMax(int max){
|
||||
this.max = max;
|
||||
if(min>max)
|
||||
min = max;
|
||||
if(getValue()>max)
|
||||
setValue(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur minimale de l'intervale autorisé et modifie la valeur max si l'intervale est incorrect
|
||||
* @param min nouvelle valeur min
|
||||
*/
|
||||
public void setMin(int min){
|
||||
this.min = min;
|
||||
if(min>max)
|
||||
max = min;
|
||||
if(getValue()<min)
|
||||
setValue(min);
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
*
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
@Override
|
||||
public Control createEditNode() {
|
||||
TextField textField = new TextField();
|
||||
textField.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
int v = 0;
|
||||
try{
|
||||
v = Integer.parseInt(textField.getText());
|
||||
}catch (NumberFormatException e){
|
||||
textField.setText(getValue().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if(v==getValue())
|
||||
return;
|
||||
|
||||
if(v<min){
|
||||
textField.setText(String.valueOf(min));
|
||||
nodeValueModified(min);
|
||||
return;
|
||||
}
|
||||
if(v>max){
|
||||
textField.setText(String.valueOf(max));
|
||||
nodeValueModified(max);
|
||||
return;
|
||||
}
|
||||
|
||||
nodeValueModified(v);
|
||||
}
|
||||
});
|
||||
textFields.add(textField);
|
||||
textField.setText(getValue().toString());
|
||||
return textField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
@Override
|
||||
protected void syncNodesValue() {
|
||||
for(TextField t: textFields)
|
||||
t.setText(getApparentValue().toString());
|
||||
}
|
||||
}
|
81
src/processing/properties/PropertiesManager.java
Normal file
81
src/processing/properties/PropertiesManager.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
package processing.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 03/04/14.
|
||||
* Gestionnaire des propriétés. Permet de controler toutes les propriétés associées à chaque process.
|
||||
*/
|
||||
public class PropertiesManager {
|
||||
|
||||
private final ArrayList<Property<?>> properties;
|
||||
|
||||
public PropertiesManager(){
|
||||
properties = new ArrayList<>();
|
||||
}
|
||||
|
||||
/** Permet d'ajouter une propriété */
|
||||
public int addProperty(Property<?> property){
|
||||
for (Property property1 : properties) {
|
||||
if (property.getName() == property1.getName())
|
||||
return -1;
|
||||
}
|
||||
properties.add(property);
|
||||
|
||||
return properties.size()-1;
|
||||
}
|
||||
|
||||
|
||||
/** Récupère une propriété grâce à son identifiant */
|
||||
public Property<?> getProperty(int id){
|
||||
if(id >= properties.size() || id < 0)
|
||||
return null;
|
||||
|
||||
return properties.get(id);
|
||||
}
|
||||
|
||||
/** Renvoie la propriété donnée identifiée par son noim
|
||||
* @param name le nom de la propriété
|
||||
* @return la propriété */
|
||||
public Property getProperty(String name){
|
||||
return getProperty(getPropertyIndex(name));
|
||||
}
|
||||
|
||||
public int getPropertyIndex(Property property){
|
||||
return properties.indexOf(property);
|
||||
}
|
||||
|
||||
/** Renvoie l'indice de la propriété grâce à son nom
|
||||
* @param name le nom de la propriété
|
||||
* @return l'indice de la propriété */
|
||||
public int getPropertyIndex(String name){
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
if(name.equals(properties.get(i).getName()))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Renvoie le nombre de propriétés ajoutées */
|
||||
public int getNbProperties() {
|
||||
return properties.size();
|
||||
}
|
||||
|
||||
/** Permet de bloquer une propriété: la rend non modifiable pendant qu'un process l'utilise */
|
||||
public void lockProperty(){
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
properties.get(i).lock();
|
||||
}
|
||||
}
|
||||
|
||||
/** Permet de débloquer une propriété : la rend modifiable dès qu'elle n'est plus utilisée */
|
||||
public boolean unlockProperty(){
|
||||
boolean r = false;
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
if(properties.get(i).unlock())
|
||||
r = true;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
}
|
168
src/processing/properties/Property.java
Normal file
168
src/processing/properties/Property.java
Normal file
|
@ -0,0 +1,168 @@
|
|||
package processing.properties;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Control;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 03/04/14.
|
||||
*/
|
||||
public abstract class Property <T> {
|
||||
private final String name;
|
||||
private T value;
|
||||
|
||||
private boolean locked = false;
|
||||
private T temporaryValue;
|
||||
|
||||
private final Class type;
|
||||
|
||||
private final ArrayList<Listener<T>> listeners;
|
||||
|
||||
public Property(String name, Class type, T value) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
|
||||
listeners = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la valeur de la propriété
|
||||
* @return le valeur de la propriété
|
||||
*/
|
||||
public final T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final T getApparentValue(){
|
||||
if(locked)
|
||||
return temporaryValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifie la valeur de la propriété
|
||||
* @param value la nouvelle valeur
|
||||
* @return Renvoie vrai si l'opération s'est bien déroulé (mais peut-elle mal se passer?)
|
||||
*/
|
||||
public boolean setValue(final T value) {
|
||||
nodeValueModified(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode à appeler pour gerer la modification de la valeur de la propriété en interne
|
||||
* @param value la nouvelle valeur
|
||||
*/
|
||||
protected final void nodeValueModified(final T value){
|
||||
if(locked)
|
||||
temporaryValue = value;
|
||||
else
|
||||
changeRealValue(value);
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
syncNodesValue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final void changeRealValue(final T value){
|
||||
T previousValue = this.value;
|
||||
this.value = value;
|
||||
emitChangeEvent(previousValue);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------- Manage Event Listener -----------------------
|
||||
|
||||
private void emitChangeEvent(T previousValue){
|
||||
for(Listener<T> p: listeners)
|
||||
p.propertyChange(this,previousValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un listener à cette propriété, lorsquelle sera modifié, le listener sera prévenu
|
||||
* @param listener le listener à ajouter
|
||||
* @return Vrai si l'opération d'ajout s'est bien déroulé (si le listener n'avais pasété déjà ajouté)
|
||||
*/
|
||||
public final boolean addListener(Listener<T> listener){
|
||||
if(listeners.contains(listener))
|
||||
return false;
|
||||
|
||||
listeners.add(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime le listener de la liste des listeners liés à cette propriété
|
||||
* @param listener le listener à supprimer
|
||||
* @return Renvoie vrai si le listener existait dans la liste et à bien été supprimer
|
||||
*/
|
||||
public final boolean removeListener(Listener<T> listener){
|
||||
if(!listeners.contains(listener))
|
||||
return false;
|
||||
|
||||
listeners.remove(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public interface Listener<T> {
|
||||
public void propertyChange(Property source, T previousValue);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------- Manage Type -------------------------------
|
||||
|
||||
/**
|
||||
* Retourne le type de la propriété (equivalent T)
|
||||
* @return Le type de la proprriété
|
||||
*/
|
||||
public final Class getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return le nom de la propriété
|
||||
*/
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
public abstract Control createEditNode();
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
protected abstract void syncNodesValue();
|
||||
|
||||
|
||||
public void lock(){
|
||||
if(locked)
|
||||
unlock();
|
||||
temporaryValue = value;
|
||||
locked = true;
|
||||
}
|
||||
public boolean unlock(){
|
||||
if(temporaryValue==value)
|
||||
return false;
|
||||
|
||||
changeRealValue(temporaryValue);
|
||||
temporaryValue = null;
|
||||
locked = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
}
|
56
src/processing/properties/StringProperty.java
Normal file
56
src/processing/properties/StringProperty.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
package processing.properties;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by gaby on 27/05/14.
|
||||
*/
|
||||
public class StringProperty extends Property<String> {
|
||||
ArrayList<TextField> textFields = new ArrayList<>();
|
||||
|
||||
public StringProperty(String name, String value) {
|
||||
super(name, String.class, value);
|
||||
}
|
||||
|
||||
public StringProperty(String name){
|
||||
this(name, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'élément javaFX de modification de la propriété
|
||||
*
|
||||
* @return L'élément graphique lié à la propriété
|
||||
*/
|
||||
@Override
|
||||
public Control createEditNode() {
|
||||
TextField textField = new TextField(getValue());
|
||||
textField.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
if(getValue()!=textField.getText())
|
||||
nodeValueModified(textField.getText());
|
||||
|
||||
}
|
||||
});
|
||||
textFields.add(textField);
|
||||
return textField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appeler lorsque la proriété est modifier par autre chose que le l'élément graphique,
|
||||
* il faut donc mettre à jour se dernier
|
||||
*/
|
||||
@Override
|
||||
protected void syncNodesValue() {
|
||||
for(TextField t:textFields)
|
||||
t.setText(getApparentValue());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue