/* Reversound is used to get the music sheet of a piece from a music file. Copyright (C) 2014 Gabriel AUGENDRE Copyright (C) 2014 Gabriel DIENY Copyright (C) 2014 Arthur GAUCHER Copyright (C) 2014 Gabriel LEPETIT-AIMON This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ 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 calcCoef( double sigma, int nPoints){ ArrayList coefs = new ArrayList(); 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 idUsedBy(int inputID, int inputBufferID) { if(inputID!=0) return new ArrayList(); ArrayList 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[] idNeededFor(int outputID) { ArrayList[] t = new ArrayList[1]; ArrayList 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; } }