reversound/src/processing/processes/FFT.java
2014-06-16 16:48:34 +02:00

251 lines
8.4 KiB
Java

/*
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 <http://www.gnu.org/licenses/>.
*/
package processing.processes;
import conteneurs.NoteGamme;
import conteneurs.RegularGamme;
import conteneurs.Spectre;
import generictools.Complex;
import generictools.Instant;
import generictools.Pair;
import generictools.WindowFunction;
import gui.graphs.SpectreView;
import processing.buffer.Storage;
import processing.buffer.TemporalBuffer;
import processing.properties.ChoiceProperty;
import processing.properties.IntProperty;
import processing.properties.Property;
import java.awt.*;
import java.util.ArrayList;
/**
* Algorithme de FFT: Calcule la transformée de Fourier d'un signal temporel et renvoie le spectre correspondant
*/
public class FFT extends IndexedProcess<Spectre> {
private IntProperty nFFT_Max;
private ChoiceProperty<WindowFunction> window;
private int nFFt;
private final int freq_echant;
private SpectreView.Type grapheType;
private Complex expOddFFT;
private ArrayList<Integer> selectedIndex;
private RegularGamme regGammeFFT;
public FFT(TemporalBuffer<Float> temporalSignal, int sampleRate) {
super("FFT",temporalSignal, new TemporalBuffer<Spectre>(Storage.Type.ArrayList, sampleRate));
nFFT_Max = new IntProperty("nFFT_Max", 8192*2*2);
nFFT_Max.setMin(1);
window = new ChoiceProperty<>("Window Type", createWindows(), Window.Type.class, 4);
propertyManager().addProperty(nFFT_Max);
propertyManager().addProperty(window);
freq_echant = temporalSignal.getSampleRate();
regGammeFFT = new RegularGamme(0f, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue());
setGrapheType(SpectreView.Type.NOTES);
nFFT_Max.addListener(new Property.Listener<Integer>() {
@Override
public void propertyChange(Property source, Integer previousValue) {
window.forceChoices(createWindows());
regGammeFFT = new RegularGamme(0f, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue());
setGrapheType(SpectreView.Type.NOTES);
}
});
this.expOddFFT = new Complex(0,0);
}
private ArrayList<Pair<WindowFunction, String>> createWindows(){
ArrayList<Pair<WindowFunction, String>> choices = new ArrayList<>();
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.RECTANGLE, nFFT_Max.getValue(), input()), "RECTANGLE"));
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.HANN, nFFT_Max.getValue(), input()), "HANN"));
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.HAMMING, nFFT_Max.getValue(), input()), "HAMMING"));
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.TRIANGLE, nFFT_Max.getValue(), input()), "TRIANGLE"));
choices.add(new Pair<>(new WindowFunction(WindowFunction.Type.BLACKMAN, nFFT_Max.getValue(), input()), "BLACKMAN"));
return choices;
}
/** Suivant le type d'affichage, on n'a pas besoin de calculer toutes les fréquences.
* grapheType permet d'indiquer les fréquences utiles à calculer.
* @param idOutput l'échantillon sur lequel on base la FFT
* @return le spectre sur la gammeConsidérée
*/
protected Spectre compute (Instant idOutput){
this.nFFt = nFFT_Max.getValue();
//long time = System.currentTimeMillis();
window.getValue().setStartID(idOutput.mapToIndex(input()));
Spectre res = new Spectre(new RegularGamme(0, nFFT_Max.getValue(), (float)freq_echant/nFFT_Max.getValue()));
int divFactor, currentSelectId;
float freq, ampliFreq;
for (int i = 0; i < selectedIndex.size(); i++) {
currentSelectId = selectedIndex.get(i);
freq = regGammeFFT.getFreq(currentSelectId);
divFactor = divideFactor(freq);
// Si changement de coefficient, on doit changer le nombre de points sur lequels on calcule la FFT
nFFt = nFFT_Max.getValue() / divFactor;
window.getValue().setDivideFactor(divFactor);
this.expOddFFT = Complex.ComplexFromExp(1,(float)(-2*Math.PI*currentSelectId)/nFFT_Max.getValue());
//On calcule la FFT pour cette fréquence uniquement: sélection uniquement des fréquences correspondant à des notes
ampliFreq = calculFFT(idOutput.mapToIndex(input()), 1).getMod();
res.setAmplitudeAt(currentSelectId, ampliFreq);
}
if(this.grapheType == SpectreView.Type.NOTES) {
res.castToGamme(NoteGamme.gamme());
}
//time = System.currentTimeMillis() - time;
//System.out.println("FFT done in : "+ time + " ms");
return res;
}
/** Calcule la FFT d'un signal contenu dans un Buffer en entrée
* @param startID: indice de départ dans le Buffer pour le calcul de la FFT
* @param step: le "pas" actuel, qui va doubler à chaque récursion
* @return le tableau de complexes qui correspondent aux fréquences */
private Complex calculFFT(int startID, int step) {
if (step != (this.nFFt)) {
Complex exp = new Complex(expOddFFT);
expOddFFT.simpleSquare();
Complex oddFFT = calculFFT(startID + step, step * 2);
expOddFFT.setToEquals(exp);
expOddFFT.simpleSquare();
Complex evenFFT = calculFFT(startID, step * 2);
return evenFFT.simpleSum(oddFFT.simpleMult(exp));
} else {
return new Complex(this.window.getValue().getValue(startID), 0);
}
}
protected ArrayList<Instant> idUsedBy(int inputID, int inputBufferID) {
ArrayList<Instant> r = new ArrayList<>();
if(inputBufferID!=0 || inputID<0){
return r;
}
for (int i = 0; i < this.nFFt; i++) {
r.add(Instant.fromIndex(inputID+i,input()));
}
return r;
}
protected ArrayList<Instant>[] idNeededFor (int outputID) {
ArrayList<Instant>[] r = new ArrayList[1];
r[0] = new ArrayList<>();
r[0].add(Instant.fromIndex(outputID, output()));
return r;
}
public int setNfft(int nPoints){
int nextPow = 1;
while( nextPow < nPoints){
nextPow = nextPow*2;
}
this.nFFt = nextPow;
return this.nFFt;
}
public int getnFFt(){
return this.nFFt;
}
/** Renvoie le type du buffer de sortie *
* @return La class des échantillones du buffer de sortie */
@Override
public Class getType() {
return Spectre.class;
}
@Override
protected ProcessingOrder getProcessingOrder() {
return ProcessingOrder.ASCENDING_ORDER;
}
/** Modifie le type de graphe et calcule les index correspondant à un certain nFFt. nFFt dépend de la fréquence.
* @param grapheType le type de graphique */
public void setGrapheType(SpectreView.Type grapheType) {
this.grapheType = grapheType;
// Cas où le graphe est sous forme de NOTES
if(this.grapheType == SpectreView.Type.NOTES) {
int noteGammeSize = NoteGamme.gammeStatSize();
float freq;
this.selectedIndex = new ArrayList<>(noteGammeSize);
int index;
for (int i = 0; i < noteGammeSize; i++) {
freq = NoteGamme.getStatFreq(i);
ArrayList<Number> neighboorIndex = regGammeFFT.getIndex(freq);
//Recherche de l'indice le plus proche
index = (int)neighboorIndex.get(0);
if (neighboorIndex.size()>1 && (float) neighboorIndex.get(2) > 0.5) {
index = (int)neighboorIndex.get(1);
}
selectedIndex.add(index);
}
// Cas où le graphe est sous forme de REGULAR_FREQ
} else if (this.grapheType == SpectreView.Type.REGULAR_FREQ){
this.selectedIndex = new ArrayList<Integer>(this.nFFt);
for (int i = 0; i < this.nFFt; i++) {
selectedIndex.add(i);
}
}
}
/** Renvoie le facteur de division de nFFT (optimisation de nFFt pour obtenir une bonne résolution dans les basses fréquences,
* et une bonne rapidité de calcul dans les hautes fréquences
* @param freq la fréquence considéré
* @return le facteur par lequel on divise nFFt */
private int divideFactor(float freq){
if(freq < 200){
return 1;
} else if (freq < 1000){
return 2;
} else if (freq < 5000) {
return 8;
} else {
return 16;
}
}
@Override
protected float getSampleNbrPerIteration() {
return nFFT_Max.getValue();
}
}