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