adding code

Switching from INSA svn to GitHub
This commit is contained in:
gaugendre 2014-06-11 15:35:06 +02:00
parent 19d75499bb
commit 6b4ec42c01
90 changed files with 11908 additions and 0 deletions

21
src/JNLP/Reversound.jnlp Normal file
View 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
View file

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: gui.MainWindow
Permissions: all-permissions

View 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);
}*/
}

View 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);
}
}
}

View 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));
}
}

View 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);
}
}
}
}

View 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 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
View 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
View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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 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;
}
}

View 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 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();
}
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}

View 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();
}

View 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
}
}

View 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;
}
}

View 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
View 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-
* @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;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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];
}
}
}

View 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
View 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
View file

@ -0,0 +1,5 @@
package gui;
public interface ClipView {
public void currentFrameChanged();
}

24
src/gui/Connector.java Normal file
View 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
View 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
View 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 &lt;gabriel.augendre@insa-lyon.fr&gt;
*/
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
View 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;
}
}
}
}

View 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
View 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
View 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
View file

@ -0,0 +1 @@
.progress-bar .bar {-fx-padding:3px; -fx-background-insets:0;}

View file

@ -0,0 +1 @@
.progress-indicator .percentage {-fx-font-size: 0.01em;}

162
src/gui/TimeSlider.java Normal file
View 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(" -- / --");
}
}

View 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;}
}

View 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;}
}

View 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();
}

View 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();
}
}

View 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; }

View 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); }
}

File diff suppressed because one or more lines are too long

95
src/gui/graphs/Bar.java Normal file
View 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)));
}
}

View 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;
}
}

View 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()));
}
});
}
}

View 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);
}
}

View 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;}
}

View 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();
}
}

View 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. */
}

View 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();
}
}

View 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);
}}

View 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);
}}

View 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();
}
}

View 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));
}
}

View 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);
}
}
}
}

View 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;
}
}

View 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);
}
}

View 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();
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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 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;
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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)));
}
};
}
}

View 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 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;
}
}

View 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
}
}

View file

@ -0,0 +1,8 @@
package processing.buffer;
/**
* Created by gaby on 02/04/14.
*/
public interface BufferEventListener {
public void bufferEvent(BufferEvent e);
}

View 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();
}
}

View 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);
}

View 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 &lt; 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);
}
}

View 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();
}

View 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));
}
}

View 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();
}
}

View 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 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 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();
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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());
}
}

View 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());
}
}

View 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());
}
}

View 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());
}
}

View 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;
}
}

View 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;
}
}

View 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());
}
}