/* 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 conteneurs; import generictools.Pair; import java.util.ArrayList; /** * Spectre fait le lien entre les fréquences et leur amplitude. Les deux sont stockés dans des tableaux, par ordre de fréquences croissantes. * L'association fréquences - index est faite dans la gamme * L'association amplitude - index est faite dans le spectre * Les index sont communs, donc on a le lien directement entre les 2. */ public class Spectre extends ObservableObject{ private Gamme gamme; private ArrayList amplitudes; private float moyennePos; private float moyenneNeg; private float eTypePos; private float eTypeNeg; private static final int INDEX_PREV = 0; private static final int INDEX_NEXT = 1; private static final int INDEX_COEFF = 2; private static final int INDEX_UNIQUE = 0; public enum InterpolationType{ DEFAULT, SQUARRED, CUBIC, MOYENNE } public Spectre(Gamme gamme) { this.gamme = gamme; this.amplitudes = new ArrayList<>(gamme.gammeSize()); for (int i = 0; i < gamme.gammeSize(); i++) { this.amplitudes.add(0f); } } /** * Définit toutes les amplitudes d'un coup * @param newAmplitudes amplitudes à charger */ public void setAllAmplitudes(ArrayList newAmplitudes){ if(newAmplitudes.size() == this.gamme.gammeSize()) { this.amplitudes = newAmplitudes; } else if(newAmplitudes.size() > this.gamme.gammeSize()) { this.amplitudes = (ArrayList)newAmplitudes.subList(0,this.gamme.gammeSize()); } else { this.amplitudes = newAmplitudes; while(this.amplitudes.size() != this.gamme.gammeSize()){ this.amplitudes.add(0f); } } emit(ObservableObjectEvent.Type.DATA_CHANGED); } /** Change l'amplitude à un index donné * @param id l'indice de la fréquence dont on veut définir l'amplitude (rappel: elles sont classées dans l'ordre croissant * @param newAmplitude la valeur de l'amplitude de la fréquence à modifier */ public void setAmplitudeAt (int id, float newAmplitude) { if(id>=0 && id listRes = this.gamme.getIndex(freq); if(listRes.size() == 1){ this.setAmplitudeAt((Integer)listRes.get(INDEX_UNIQUE), newAmplitude); // TEST emit(ObservableObjectEvent.Type.DATA_CHANGED); } } /** Change l'amplitude à d'une fréquence donnée en dB. * Pour cela, on cherche l'indice correspondant à cette fréquence * @param freq la fréquence dont l'amplitude est à modifier/définir * @param dBAmplitude la valeur de l'amplitude de la fréquence à modifier en dB */ public void setdBAmplitudeAt (float freq, float dBAmplitude) { setAmplitudeAt(freq, dBToAmpli(dBAmplitude));} /** Renvoie l'amplitude d'une fréquence * @param id l'indice de la fréquence dont on cherche l'amplitude * @return l'amplitude de la fréquence. */ public float getAmplitude (int id) { return this.amplitudes.get(id); } /** Renvoie l'amplitude d'une fréquence en dB * @param id l'indice de la fréquence dont on cherche l'amplitude * @return l'amplitude de la fréquence en dB. */ public float getdBAmplitude (int id) { return ampliTodB(this.amplitudes.get(id)); } /** Renvoie l'amplitude d'une fréquence donnée en paramètre, -1 si la fréquence n'existe pas * @param freq la fréquence donnée dont on cherche l'amplitude * @return l'amplitude de la fréquence. */ public Pair getAmplitude(float freq, InterpolationType iType) { ArrayList listRes = this.gamme.getIndex(freq); if(listRes.size() == 1 && ((Integer)listRes.get(0)!=-1)){ return new Pair<>(this.amplitudes.get((Integer)listRes.get(INDEX_UNIQUE)), 1f); } else if((Integer)listRes.get(0) == -1) { return new Pair<>(0f, 0f); } else { float amplitudePrev = this.amplitudes.get((Integer)listRes.get(INDEX_PREV)); float amplitudeNext = this.amplitudes.get((Integer)listRes.get(INDEX_NEXT)); float coeff = (Float)listRes.get(INDEX_COEFF); // Pondère en fonction du coefficient donné pour situer la fréquence entre les 2 les plus proches Pair res = null; switch(iType){ case DEFAULT: res = new Pair<> ( ( amplitudePrev * (1-coeff) ) + (amplitudeNext * coeff), coeff); break; case SQUARRED: res = new Pair<> ( (float)Math.sqrt((amplitudePrev*amplitudePrev*(1-coeff)*(1-coeff) ) + (amplitudeNext*amplitudeNext*coeff*coeff)), coeff); break; case MOYENNE: res = new Pair<> ((amplitudeNext+amplitudePrev)/2, coeff); break; case CUBIC: float y1 = (Integer)listRes.get(INDEX_PREV)>1? this.amplitudes.get((Integer) listRes.get(INDEX_PREV) - 1):0; float y2 = amplitudePrev; float y3 = amplitudeNext; float y4 = this.amplitudes.get((Integer) listRes.get(INDEX_NEXT) + 1); float x1 = this.gamme.getFreq((Integer) listRes.get(INDEX_PREV)-1); float x2 = this.gamme.getFreq((Integer) listRes.get(INDEX_PREV)); float x3 = this.gamme.getFreq((Integer) listRes.get(INDEX_NEXT)); float x4 = (Integer)listRes.get(INDEX_NEXT)(lagrangeInterp(x1, x2, x3, x4, y1, y2, y3, y4, freq), coeff); break; } return res; } } /** Calcule la valeur en un point grâce à une interpolation par un polynome de degré 3, * qui doit passer par les 4 points dont les coordonnées sont données en paramètre */ private float lagrangeInterp(float x1, float x2, float x3, float x4, float y1, float y2, float y3, float y4, float freq){ float ampli = y1 * ( ((freq-x2)*(freq-x3)*(freq-x4))/((x1-x2)*(x1-x3)*(x1-x4)) ); ampli += y2 * ( ((freq-x1)*(freq-x3)*(freq-x4))/((x2-x1)*(x2-x3)*(x2-x4)) ); ampli += y3 * ( ((freq-x1)*(freq-x2)*(freq-x4))/((x3-x1)*(x3-x2)*(x3-x4)) ); ampli += y4 * ( ((freq-x1)*(freq-x2)*(freq-x3))/((x4-x1)*(x4-x2)*(x4-x3)) ); return ampli; } /** Renvoie l'amplitude d'une fréquence donnée en paramètre, -1 si la fréquence n'existe pas * @param freq la fréquence donnée dont on cherche l'amplitude * @return l'amplitude de la fréquence en dB. */ public Pair getdBAmplitude(float freq) { Pair r = getAmplitude(freq, InterpolationType.DEFAULT); r.setFirst(ampliTodB(r.first())); return r;} /** Change la gamme sur laquelle ce spectre travaille: on ne sélectionne que certaines fréquences, ou on ne prend pas les mêmes, ou bien de manière pas linéaire * La gamme donnée en paramètre contient certaines fréquences, pour trouver les amplitudes soit on les a directement, soit on les interpole suivant si on les avait déjà ou non. * Renvoie un indice sur la qualité de l'interpolation réalisée sur les fréquences. * @param newGamme la nouvelle Gamme contenant les nouvelles fréquences dont on doit calculer les amplitudes * @return l'indice de qualité de l'interpolation: 0 au plus mauvais (on tombe toujours pile au milieu entre les 2 fréquences), 1 si on tombe toujours sur une fréquence pré existante */ public float castToGamme (Gamme newGamme) { ArrayList newAmplitudes = new ArrayList<>(); float sumIndicesQ = 0; float indiceQualite = 1; //System.out.println("newGamme size: "+newGamme.gammeSize()); newAmplitudes.add(0f); for(int i = 1; i < newGamme.gammeSize()-1; i++){ Pair coupleFreqCoef = this.getAmplitude(newGamme.getFreq(i), InterpolationType.CUBIC); newAmplitudes.add(coupleFreqCoef.first()); sumIndicesQ += Math.abs((coupleFreqCoef.second()-0.5)*2); } newAmplitudes.add(0f); float moyenneIndiceQ = sumIndicesQ / newGamme.gammeSize(); //Calcul l'indice de qualité de l'ensemble du traitement //System.out.println("Moyenne indices qualités : "+moyenneIndiceQ); indiceQualite = ((moyenneIndiceQ * newGamme.gammeSize()) + (newAmplitudes.size() - newGamme.gammeSize())) / newAmplitudes.size(); indiceQualite = indiceQualite*indiceQualite; this.gamme = newGamme; this.amplitudes = newAmplitudes; emit(ObservableObjectEvent.Type.DATA_CHANGED); return indiceQualite; } /** Crée une RegularGamme à partir de la gamme actuelle, sans aucune extrapolation. * Selectionne la fréquence la plus proche pour caler celle de la gamme existante */ //public void castToRegularGamme(RegularGamme newGamme){ public void castToRegularGamme(){ ArrayList newAmplitudes = new ArrayList<>(); RegularGamme newGammeTest = new RegularGamme(0f, Math.round(this.gamme.getFreq(this.getGamme().gammeSize()-1)), 1f); for(int i = 0; i < newGammeTest.gammeSize(); i++){ newAmplitudes.add(0f); } for (int i = 0; i < this.gamme.gammeSize()-1; i++) { //System.out.println(this.gamme.getFreq(i)); newAmplitudes.set(Math.round(this.gamme.getFreq(i)), this.amplitudes.get(i)); } this.gamme = newGammeTest; this.amplitudes = newAmplitudes; } /** Permet de copier un spectre. Utile si jamais on ne veut pas modifier directement un buffer partagé par plusieurs algo, * et si on veut faire un traitement sur celui-là * @return la copie de ce spectre, avec une nouvelle adresse mémoire du coup */ public Spectre copy(){ Spectre newSpectre = new Spectre(this.gamme); ArrayList newAmpli = new ArrayList(this.amplitudes.size()); for (int i = 0; i < this.amplitudes.size(); i++) { newAmpli.add(this.amplitudes.get(i)); } newSpectre.setAllAmplitudes(newAmpli); return newSpectre; } /** Renvoie la gamme liée à cette instance de spectre */ public Gamme getGamme() { return this.gamme; } public ArrayList getAmplitudes() { return amplitudes; } /** Calcule les moyennes des amplitudes positives et négatives ainsi que leurs écarts-types. * Sert à faire un filtrage du bruit sur les spectres */ public void calculStats(){ int gammeSize = this.getGamme().gammeSize(); int nPos = 0; int nNeg = 0; // Calcul de la moyenne for (int i = 0; i < gammeSize; i++) { if(this.getAmplitude(i)>=0) { moyennePos += this.getAmplitude(i); nPos++; } else { moyenneNeg += this.getAmplitude(i); nNeg++; } } moyennePos = moyennePos / nPos; moyenneNeg = moyenneNeg / nNeg; // Calcul de l'écart-type for (int i = 0; i < gammeSize; i++) { if(this.getAmplitude(i)>=0) eTypePos += (this.getAmplitude(i) - moyennePos)*(this.getAmplitude(i) - moyennePos ); else eTypeNeg += (this.getAmplitude(i) - moyenneNeg)*(this.getAmplitude(i) - moyenneNeg ); } eTypePos = (float)Math.sqrt(eTypePos/nPos); eTypeNeg = (float)Math.sqrt(eTypeNeg/nNeg); } /** Filtre le bruit sur le principe de moyenne + ou - écart-type, que l'on doit calculer * VALEUR_SEUIL : Moyenne + (2*écart-type) --> 95,4% des valeurs à l'intérieur. * @return le spectre sans le bruit */ public Spectre noiseFilter(){ this.calculStats(); if(this == null){ return null; } Spectre res = this.copy(); //final float VALEUR_SEUIL_POS = 0.7f*(float)(this.eTypePos)+this.moyennePos; //final float VALEUR_SEUIL_NEG = -0.7f*(float)(this.eTypeNeg)+this.moyenneNeg; final float VALEUR_SEUIL_POS = this.moyennePos; final float VALEUR_SEUIL_NEG = this.moyenneNeg; for (int i = 1; i < getGamme().gammeSize(); i++) { if(res.getAmplitude(i) < VALEUR_SEUIL_POS && res.getAmplitude(i)>VALEUR_SEUIL_NEG){ res.setAmplitudeAt(i, 0); } } res.setAmplitudeAt(0, VALEUR_SEUIL_POS); return res; } /** * Renvoie la valeur en dB d'une intensité en pression * @param ampli valeur en pression * @return valeur en dB */ static public float ampliTodB(float ampli){return 10f*(float)Math.log10(ampli);} /** * Renvoie la valeur en pression d'une intensité en dB * @param dB valeur en dB * @return valeur en pression */ static public float dBToAmpli(float dB){return (float)Math.pow(dB/10f, 10);} public float getMoyennePos() { return moyennePos; } public float getMoyenneNeg() { return moyenneNeg; } public float geteTypePos() { return eTypePos; } public float geteTypeNeg() { return eTypeNeg; } @Override public String toString() { return "Spectre"; } @Override public Class getType() { return Spectre.class; } }