250 lines
8.4 KiB
Java
250 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();
|
|
}
|
|
}
|
|
|