366 lines
14 KiB
Java
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;
|
|
}
|
|
}
|