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 Runnable
is used
+ * to create a thread, starting the thread causes the object's
+ * run
method to be called in that separately executing
+ * thread.
+ *
+ * The general contract of the method run
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();
+ }
+}
diff --git a/src/processing/PostProcessScript.java b/src/processing/PostProcessScript.java
new file mode 100644
index 0000000..361121e
--- /dev/null
+++ b/src/processing/PostProcessScript.java
@@ -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 listEcarts = calculeEcarts(bufferSortOut(buffer));
+ ArrayList EcartsReference = new ArrayList<>();
+ ArrayList nEcartParType = new ArrayList<>();
+
+
+ //Cr�ation des cat�gories de r�f�rence, tri�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�finie en param�tre, on red�finit la r�f�rence gr�ce � une moyenne pond�r�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�ation d'une nouvelle cat�gorie si aucune existante ne convient
+ if(newCategoryNeeded){
+ int j = 0;
+ while(j à une ronde
+ ArrayList UnifiedEcarts = new ArrayList<>(EcartsReference.size());
+ int indexEcartMostUsed = 0;
+ int maxUsed = 0;
+ for (int i = 0; i < nEcartParType.size(); i++) {
+ if( nEcartParType.get(i)> maxUsed){
+ maxUsed = nEcartParType.get(i);
+ indexEcartMostUsed = i;
+ }
+ }
+ for (int i = 0; i < nEcartParType.size(); i++) {
+ UnifiedEcarts.add("0");
+ }
+
+ //Cas où on se retrouve avec le max plus grand qu'une ronde
+ while(indexEcartMostUsed < EcartsReference.size()-1 && EcartsReference.get(indexEcartMostUsed)*(4+tolerance)>EcartsReference.get(EcartsReference.size()-1)){
+ indexEcartMostUsed++;
+ }
+
+
+
+
+
+
+ //Donne à chaque note une durée unified
+ for (int i = 0; i < buffer.getNotesNbr(); i++) {
+ double fac = Math.log(EcartsReference.get(indexEcartMostUsed)*4/ buffer.getNote(i).getDuration())/Math.log(2);
+ buffer.getNote(i).setUnifiedDuration(String.valueOf(Math.round(Math.pow(2, (int) fac))) + (fac % 1 < 0.5 ? "." : ""));
+ }
+
+
+ System.out.println("ECARTS:");
+
+ for (int i = 0; i < UnifiedEcarts.size(); i++) {
+ System.out.println("\t" + EcartsReference.get(i) + " (" + UnifiedEcarts.get(i) + ")");
+ }
+ }
+
+ /** Trie les instants de d�part des notes dans l'ordre croissant
+ * @param buffer le buffer dont les instants sont � trier */
+ private ArrayList bufferSortOut(NoteBuffer buffer){
+ ArrayList tmp = new ArrayList<>();
+ ArrayList res = new ArrayList<>();
+ // On r�cup�re tous les instans de d�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 calculeEcarts(ArrayList instantList){
+ ArrayList 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;
+ }
+}
diff --git a/src/processing/ProcessControl.java b/src/processing/ProcessControl.java
new file mode 100644
index 0000000..311eaf4
--- /dev/null
+++ b/src/processing/ProcessControl.java
@@ -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> processes = new ArrayList<>();
+ private final ArrayList> inputs= new ArrayList<>();
+ private int mainInput=-1;
+ private final ArrayList buffers = new ArrayList<>();
+
+ private final ArrayList 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)processes.get(0).first().output(),15,12));
+ addProcess(new FreqEdgeToEvent((TemporalBuffer)processes.get(1).first().output(), 7, .97f));
+ addProcess(new EventToNote((TemporalBuffer)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 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 b: inputs)
+ b.first().start();
+ }
+
+ /**
+ * Mets en pause les process et les inputs
+ */
+ public void pause(){
+ for(Pair p: processes)
+ p.first().pause();
+ for(Pair 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 b: inputs)
+ b.first().interrupt();
+ }
+
+ /**
+ * Stoppe tout les les process et les inputs
+ */
+ public void stop(){
+ stopRecord();
+ for(Pair 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 p: processes)
+ if(!p.first().isRunning())
+ return false;
+ for(Pair 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 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 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 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 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 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;
+ }
+
+ }
+ }
+}
diff --git a/src/processing/SpectreFilter.java b/src/processing/SpectreFilter.java
new file mode 100644
index 0000000..591de0f
--- /dev/null
+++ b/src/processing/SpectreFilter.java
@@ -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;
+ }
+}
diff --git a/src/processing/SpectreFilterFactory.java b/src/processing/SpectreFilterFactory.java
new file mode 100644
index 0000000..9665ddf
--- /dev/null
+++ b/src/processing/SpectreFilterFactory.java
@@ -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)));
+ }
+ };
+ }
+
+}
diff --git a/src/processing/buffer/Buffer.java b/src/processing/buffer/Buffer.java
new file mode 100644
index 0000000..574cba9
--- /dev/null
+++ b/src/processing/buffer/Buffer.java
@@ -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 {
+
+ private final ArrayList bufferListeners;
+ private final ArrayList> 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 samples;
+ private final StorageArrayList updateIDs;
+
+ /**
+ * Construit une instance de buffer selon un modéle de stockage
+ * @param arrayType Modèle de stockage
+ */
+ public Buffer(Storage.Type arrayType) {
+ switch(arrayType){
+ case ArrayList:
+ samples = new StorageArrayList<>();
+ break;
+ }
+
+ updateIDs = new StorageArrayList<>();
+
+ bufferListeners = new ArrayList<>();
+ usedBy = new ArrayList<>();
+ this.arrayType = arrayType;
+ }
+
+ private void emitBufferEvent(BufferEvent event) {
+
+ for(Process p: usedBy)
+ p.addBufferEvent(event);
+
+ SwingUtilities.invokeLater(() -> {
+ for(BufferEventListener p: bufferListeners)
+ p.bufferEvent(event);
+ });
+
+ }
+ private void emitBufferEvent(BufferEvent.Type type, int sampleID) {
+ emitBufferEvent(new BufferEvent(type, sampleID, this));
+
+ }
+
+
+ /**
+ * Connecte ce buffer à un listener qu'il fournit, de manière à propager les BufferEvent jusqu'à cet objet
+ * @param p objet à connecter
+ * @return Vrai si l'opération à fontionner (si le process n'était pas déjà ajouté)
+ */
+ public boolean addBufferListener(BufferEventListener p){
+ if(bufferListeners.contains(p))
+ return false;
+
+ bufferListeners.add(p);
+ return true;
+ }
+
+ /**
+ * Déconnecte ce buffer d'un listener
+ * @param p listener à déconnecter
+ * @return Vrai si l'opération à fontionner (si le listener était dans la liste)
+ */
+ public boolean removeBufferListener(BufferEventListener p){
+ int id = bufferListeners.indexOf(p);
+ if(id == -1)
+ return false;
+
+ bufferListeners.remove(id);
+ return true;
+ }
+
+ /**
+ * Connecte ce buffer à une entrée d'un algo
+ * @param p Algo à connecte
+ * @return Vrai si l'opération c'est bien passée (si il n'avait pas déjà été appelée)
+ */
+ public boolean connectProcess(Process p){
+ if(usedBy.contains(p))
+ return false;
+
+ usedBy.add(p);
+ return true;
+ }
+
+ public boolean disconnectProcess(Process p){
+ if(!usedBy.contains(p))
+ return false;
+
+ usedBy.remove(p);
+ return true;
+ }
+
+ /**
+ * Retourne le type de stockage de ce buffer
+ * @return type de stockage
+ */
+ public Storage.Type getArrayType(){
+ return arrayType;
+ }
+
+ /**
+ * Retourne l'indice de version associé à un échantillon.
+ * @param id index de l'échantillon
+ * @return indice de version
+ */
+ public Integer getUpdateID(int id) {
+ return updateIDs.get(id);
+ }
+
+ /**
+ * Surcharge de set(int,T,int) mais avec un indice de version = 1
+ * @param index indice de l'échantillon à modifier ou ajouter
+ * @param value valeur de l'échantillon
+ * @return la valeur de l'échantillon précédant /!\ Peut retourner null si aucun n'échantillon ne correspondait à l'index /!\
+ */
+ public T set(int index, T value){ return set(index, value, null);}
+
+
+ /**
+ * Modifie un échantillon ou l'ajoute à au stockage si index ne correspond à aucun élément, complète alors avec des échantillons = null.
+ * De plus, updateId est associé à l'échantillon en tant qu'indice de version
+ * @param index indice de l'échantillon à modifier ou ajouter
+ * @param value valeur de l'échantillon
+ * @param updateId indice de version à associer
+ * @return la valeur de l'échantillon précédant /!\ Peut retourner null si aucun n'échantillon ne correspondait à l'index /!\
+ */
+ public T set(int index, T value, Integer updateId){
+
+ T previous = samples.set( index, value);
+ updateIDs.set(index, updateId);
+
+ if(itemObservable)
+ ((ObservableObject)value).addListener(
+ new ObservableObject.SignedListener() {
+ @Override
+ public void observableOjectEvent(ObservableObjectEvent e) {
+ emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, index);
+ }
+
+ @Override
+ public String signature() {
+ return this.hashCode() + ":" + index;
+ }
+ });
+
+ if(previous==null) {
+ emitBufferEvent(BufferEvent.Type.SAMPLE_ADDED, index);
+ return null;
+ }else{
+ if(value==null)
+ emitBufferEvent(BufferEvent.Type.SAMPLE_DELETED, index);
+ else
+ emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, index);
+
+ if(itemObservable)
+ ((ObservableObject)previous).removeSignedListener(this.hashCode()+":"+index);
+ }
+
+ return previous;
+
+ }
+
+ /**
+ * Surcharge de addNote(T) mais avec un indice de version = 1
+ * @param value valeur de l'échantillon à ajouter
+ * @return vrai si l'opération c'est bien passé (mais peut-elle mal se passer?)...
+ */
+ public boolean add(T value){
+ return add(value,0);
+ }
+
+ /**
+ * Ajoute un élément après le dernier élément du stockage et lui associe un indice de version
+ * @param value valeur de l'échantillon à ajouter
+ * @param updateId indice de version à associer
+ * @return vrai si l'opération c'est bien passé (mais peut-elle rater?)...
+ */
+ boolean add(T value, int updateId){
+ if(!samples.add(value))
+ return false;
+
+ updateIDs.add(updateId);
+ emitBufferEvent(BufferEvent.Type.SAMPLE_ADDED, samples.size() - 1);
+
+ if(itemObservable)
+ ((ObservableObject)value).addListener(new ObservableObject.Listener() {
+ @Override
+ public void observableOjectEvent(ObservableObjectEvent e) {
+ emitBufferEvent(BufferEvent.Type.SAMPLE_CHANGED, samples.size()-1);
+ }
+ });
+
+ return true;
+ }
+
+ /**
+ * Renvoie la valeur d'un échantillon
+ * @param id index de l'échantillon
+ * @return valeur de l'échantillon /!\ Peut revoyer null si aucun échantillon ne correspond à l'index /!\
+ */
+ public T get(int id){
+ return samples.get(id);
+ }
+
+ /**
+ * Supprime un échantillon du buffer
+ * @param id index de l'échantillon à supprimer
+ * @return la valeur de l'échantillon supprimer /!\ Peut renvoyer null si aucun échantillon ne correspondait à cet index /!\
+ */
+ public T remove(int id){
+
+ T sampleDeleted = samples.remove(id);
+ updateIDs.remove(id);
+
+ if(sampleDeleted!=null) {
+ emitBufferEvent(BufferEvent.Type.SAMPLE_DELETED, id);
+ if(itemObservable)
+ ((ObservableObject)sampleDeleted).removeSignedListener(this.hashCode() + ":" + id);
+ }
+
+ return sampleDeleted;
+ }
+
+ /**
+ * Retourne le nombre maximum d'élément de tableau (dernier index +1)
+ * @return la taille de l'espace de stockage
+ */
+ public int size(){
+ return samples.size();
+ }
+
+ /**
+ * Vérifie si le buffer est vide où non
+ * @return Vrai si il est vide
+ */
+ public boolean isEmpty(){
+ return size()==0;
+ }
+
+ public boolean isCorrectIndex(int id){
+ return get(id)!=null;
+ }
+
+ /**
+ * Retourne la liste des indexs associés à des éléments dans le Storage: aucun de ces indexs ne retournera null par get(index)
+ * * @return la liste des indexs de tout les éléments du Storage
+ */
+ public ArrayList getSamplesIndex(){
+ return samples.getSamplesIndex();
+ }
+
+
+ public StorageIterator 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 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> 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;
+ }
+}
\ No newline at end of file
diff --git a/src/processing/buffer/BufferEvent.java b/src/processing/buffer/BufferEvent.java
new file mode 100644
index 0000000..42f9224
--- /dev/null
+++ b/src/processing/buffer/BufferEvent.java
@@ -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
+ }
+
+}
+
diff --git a/src/processing/buffer/BufferEventListener.java b/src/processing/buffer/BufferEventListener.java
new file mode 100644
index 0000000..7ffe462
--- /dev/null
+++ b/src/processing/buffer/BufferEventListener.java
@@ -0,0 +1,8 @@
+package processing.buffer;
+
+/**
+ * Created by gaby on 02/04/14.
+ */
+public interface BufferEventListener {
+ public void bufferEvent(BufferEvent e);
+}
diff --git a/src/processing/buffer/NoteBuffer.java b/src/processing/buffer/NoteBuffer.java
new file mode 100644
index 0000000..66a2756
--- /dev/null
+++ b/src/processing/buffer/NoteBuffer.java
@@ -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 implements ObservableObject.Listener{
+
+ ArrayList 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();
+ }
+}
\ No newline at end of file
diff --git a/src/processing/buffer/Storage.java b/src/processing/buffer/Storage.java
new file mode 100644
index 0000000..c7dd833
--- /dev/null
+++ b/src/processing/buffer/Storage.java
@@ -0,0 +1,77 @@
+package processing.buffer;
+
+import java.util.ArrayList;
+
+
+public interface Storage {
+
+ 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 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 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 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);
+}
diff --git a/src/processing/buffer/StorageArrayList.java b/src/processing/buffer/StorageArrayList.java
new file mode 100644
index 0000000..d637737
--- /dev/null
+++ b/src/processing/buffer/StorageArrayList.java
@@ -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 Le type des éléments contenu dans ce StorageArrayList
+ */
+
+public class StorageArrayList extends ArrayList implements Storage {
+
+ private int lastIndex=-1;
+ int idIterator=-1;
+
+ public StorageArrayList(){
+ super();
+ }
+
+ /**
+ * Redéfinition de get(int) de ArrayList. La principale différence est de renvoyer null plutot que de lancer une exeption
+ * @param index index de l'élément désiré
+ * @param silent si vrai le rapport d'exeption ne sera pas affiché
+ * @return l'élément correspondant à l'index, ou null si aucun n'y était associé
+ */
+ public T get(int index, boolean silent){
+
+ if(index < 0 || index >=size())
+ return null;
+
+ return super.get(index);
+
+ }
+
+ public T get(int index){
+ if(index < 0 || index >=size())
+ return null;
+
+ return super.get(index);
+ }
+
+ /**
+ * Redéfintion de set(int,T) de ArrayList. La principale différence est que l'index donné n'est pas obligé d'être < size().
+ * La méthode s'occupera, si c'est le cas de compléter par des éléments null les indexs entre size()-1 et index
+ * @param index index de l'élément à modifier
+ * @param value nouvelle valeur de cet élément
+ * @return la valeur de l'élément précédant (null si aucun ne s'y trouvait).
+ */
+ public T set(int index, T value){
+ T previous = get(index);
+
+ if(value==null)
+ return remove(index);
+
+ if(previous==null) {
+ ensureSize(index + 1);
+ lastIndex = index;
+ }
+
+ super.set(index, value);
+
+ return previous;
+ }
+
+ /**
+ * Redéfintion de addNote(T) de ArrayList. Ajoute un élément après le dernier: à dernier index +1;
+ * @param value valeur de l'élément à ajouter
+ * @return Vrai si l'opération c'est bien passé (mais peut-elle rater?)...
+ */
+ public boolean add(T value){
+
+ set(lastIndex+1,value);
+
+ return true;
+ }
+
+ /**
+ * Assure que la taille minimum de la liste est newSize (en complétant avec des éléments null)
+ * @param newSize la nouvelle taille minimum de la liste
+ */
+ void ensureSize(int newSize){
+ ensureCapacity(newSize);
+
+ while(super.size()-1);
+ return r;
+ }
+
+
+ public int size(){ return lastIndex+1;}
+
+ public boolean isEmpty(){ return lastIndex==-1; }
+
+ public ArrayList getSamplesIndex(){
+
+ ArrayList 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 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 getIterator(final int initialIndex) {
+ return new StorageIterator() {
+ 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);
+ }
+}
diff --git a/src/processing/buffer/StorageIterator.java b/src/processing/buffer/StorageIterator.java
new file mode 100644
index 0000000..f4018f5
--- /dev/null
+++ b/src/processing/buffer/StorageIterator.java
@@ -0,0 +1,20 @@
+package processing.buffer;
+
+/**
+ * Created by gaby on 31/03/14.
+ */
+public interface StorageIterator {
+ /**
+ * 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();
+
+
+}
diff --git a/src/processing/buffer/TemporalBuffer.java b/src/processing/buffer/TemporalBuffer.java
new file mode 100644
index 0000000..de20102
--- /dev/null
+++ b/src/processing/buffer/TemporalBuffer.java
@@ -0,0 +1,64 @@
+package processing.buffer;
+
+import generictools.Instant;
+
+/**
+ * Created by gaby on 27/03/14.
+ */
+public class TemporalBuffer extends Buffer {
+ 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));
+ }
+
+
+}
diff --git a/src/processing/processes/EventToNote.java b/src/processing/processes/EventToNote.java
new file mode 100644
index 0000000..f8254a2
--- /dev/null
+++ b/src/processing/processes/EventToNote.java
@@ -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{
+
+ ArrayList stillAlive = new ArrayList<>();
+
+ FloatProperty minDuration;
+
+ public EventToNote( TemporalBuffer 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 idUsedBy(int inputBufferID, int inputID) {
+ ArrayList 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[] idNeededFor(int outputID) {
+ ArrayList r = new ArrayList<>();
+ ArrayList[] 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 input(){
+ return (TemporalBuffer)super.input();
+ }
+
+ public NoteBuffer output(){
+ return (NoteBuffer)super.output();
+ }
+
+ @Override
+ protected void clean() {
+ stillAlive.clear();
+ super.clean();
+ }
+}
\ No newline at end of file
diff --git a/src/processing/processes/FFT.java b/src/processing/processes/FFT.java
new file mode 100644
index 0000000..692cc30
--- /dev/null
+++ b/src/processing/processes/FFT.java
@@ -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 {
+
+ private IntProperty nFFT_Max;
+ private ChoiceProperty window;
+
+ private int nFFt;
+ private final int freq_echant;
+
+ private SpectreView.Type grapheType;
+ private Complex expOddFFT;
+ private ArrayList selectedIndex;
+ private RegularGamme regGammeFFT;
+
+
+ public FFT(TemporalBuffer temporalSignal, int sampleRate) {
+ super("FFT",temporalSignal, new TemporalBuffer(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() {
+ @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> createWindows(){
+ ArrayList> 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 idUsedBy(int inputID, int inputBufferID) {
+ ArrayList 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[] idNeededFor (int outputID) {
+ ArrayList[] r = new ArrayList[1];
+ r[0] = new ArrayList<>();
+
+ r[0].add(Instant.fromIndex(outputID, output()));
+
+ return r;
+ }
+ public int setNfft(int nPoints){
+ int nextPow = 1;
+ while( nextPow < nPoints){
+ nextPow = nextPow*2;
+ }
+ this.nFFt = nextPow;
+ return this.nFFt;
+ }
+ public int getnFFt(){
+ return this.nFFt;
+ }
+
+
+ /** Renvoie le type du buffer de sortie *
+ * @return La class des échantillones du buffer de sortie */
+ @Override
+ public Class getType() {
+ return Spectre.class;
+ }
+
+ @Override
+ protected ProcessingOrder getProcessingOrder() {
+ return ProcessingOrder.ASCENDING_ORDER;
+ }
+
+ /** Modifie le type de graphe et calcule les index correspondant à un certain nFFt. nFFt dépend de la fréquence.
+ * @param grapheType le type de graphique */
+ public void setGrapheType(SpectreView.Type grapheType) {
+ this.grapheType = grapheType;
+
+ // Cas où le graphe est sous forme de NOTES
+ if(this.grapheType == SpectreView.Type.NOTES) {
+ int noteGammeSize = NoteGamme.gammeStatSize();
+ float freq;
+
+ this.selectedIndex = new ArrayList<>(noteGammeSize);
+ int index;
+ for (int i = 0; i < noteGammeSize; i++) {
+ freq = NoteGamme.getStatFreq(i);
+ ArrayList neighboorIndex = regGammeFFT.getIndex(freq);
+
+ //Recherche de l'indice le plus proche
+ index = (int)neighboorIndex.get(0);
+ if (neighboorIndex.size()>1 && (float) neighboorIndex.get(2) > 0.5) {
+ index = (int)neighboorIndex.get(1);
+ }
+ selectedIndex.add(index);
+ }
+
+ // Cas où le graphe est sous forme de REGULAR_FREQ
+ } else if (this.grapheType == SpectreView.Type.REGULAR_FREQ){
+ this.selectedIndex = new ArrayList(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();
+ }
+ }
+
diff --git a/src/processing/processes/FreqEdgeDetector.java b/src/processing/processes/FreqEdgeDetector.java
new file mode 100644
index 0000000..695068e
--- /dev/null
+++ b/src/processing/processes/FreqEdgeDetector.java
@@ -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 coefs;
+
+ private FloatProperty sigma;
+ private IntProperty nPoints;
+ private IntProperty nPointsPasseBas;
+
+ public FreqEdgeDetector(TemporalBuffer input, int maxNombrePoints, int maxNombreDePointPasseBas) {
+ super("Frequencies Edge Detector", input, new TemporalBuffer(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() {
+ @Override
+ public void propertyChange(Property source, Integer previousValue) {
+ coefs = FreqEdgeDetector.calcCoef(sigma.getValue(), nPoints.getValue());
+ }
+ });
+ sigma.addListener(new Property.Listener() {
+ @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(); i0 que l'on veut. Au total il y en aura (nPoints-1)*2 + 1
+ * @return la liste des coefficients */
+ public static ArrayList