reversound/src/conteneurs/Spectre.java
2014-06-16 16:48:34 +02:00

366 lines
14 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 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<Float> 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<Float> newAmplitudes){
if(newAmplitudes.size() == this.gamme.gammeSize()) {
this.amplitudes = newAmplitudes;
} else if(newAmplitudes.size() > this.gamme.gammeSize()) {
this.amplitudes = (ArrayList<Float>)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<this.amplitudes.size()) {
this.amplitudes.set(id, newAmplitude);
emit(ObservableObjectEvent.Type.DATA_CHANGED);
}
}
/** Change l'amplitude en dB à 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 dBAmplitude la valeur de l'amplitude de la fréquence à modifier en dB
*/
public void setdBAmplitudeAt (int id, float dBAmplitude) {
setAmplitudeAt(id, dBToAmpli(dBAmplitude));
}
/** Change l'amplitude à d'une fréquence donnée.
* Pour cela, on cherche l'indice correspondant à cette fréquence
* @param freq la fréquence dont l'amplitude est à modifier/définir
* @param newAmplitude la valeur de l'amplitude de la fréquence à modifier
*/
public void setAmplitudeAt (float freq, float newAmplitude) {
ArrayList<Number> 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<Float, Float> getAmplitude(float freq, InterpolationType iType) {
ArrayList<Number> 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<Float, Float> 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)<gamme.gammeSize()? this.gamme.getFreq((Integer) listRes.get(INDEX_NEXT) + 1):0;
res = new Pair<>(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<Float, Float> getdBAmplitude(float freq) { Pair<Float,Float> 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<Float> 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<Float,Float> 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<Float> 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<Float> newAmpli = new ArrayList<Float>(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<Float> 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;
}
}