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

425 lines
11 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;
import processing.buffer.Storage;
import processing.buffer.TemporalBuffer;
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
/**
* Created by gaby on 27/03/14.
*/
public class AudioInputToBuffer implements Runnable{
private TemporalBuffer<Float> buffer;
private AudioInputStream input;
private Integer channel;
private TargetDataLine line = null;
private File file = null;
private String name = "Input";
private Thread thread;
private boolean paused = false;
private boolean running = false;
/**
* Ce constructeur créé autant de buffer que de channel dans input.
* Les buffers auront une fréquence d'échantillonage par défaut de 44100 Hz.
* @param input flux audio d'entrée
*/
private AudioInputToBuffer(AudioInputStream input) {
channel = 0;
this.input = input;
init(44100);
}
/**
* Ce constructeur créé un buffer alimenté par la chaine du flux correspondant à l'index en arguments
* Les buffers auront une fréquence d'échantillonage par défaut de 44100 Hz.
* @param input flux audio d'entrée
* @param channel index de la chaine
*/
private AudioInputToBuffer(AudioInputStream input, int channel) {
this(input, channel, 44100);
}
/**
* Ce constructeur créé autant de buffer que d'index valide dans la liste des chaines du flux
* De plus on force la fréquence d'échantillonage des buffers créés pour qu'elle soit égales à sampleRate
* @param input flux audio d'entrée
*/
private AudioInputToBuffer(AudioInputStream input, int channel, int sampleRate) {
this.channel = channel;
if(input.getFormat().getChannels()<=channel)
this.channel=0;
this.input = input;
init(sampleRate);
}
/**
* Génére un AudioInputToBuffer à partir d'une entrée audio du systeme
* (normalement le micro... mais honnêtement je ne peux rien certifier)
* @return l'AudioInputToBuffer
*/
static public AudioInputToBuffer fromMicro(){
Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo();
for (Mixer.Info aMixersInfo : mixersInfo) {
Mixer mixer = AudioSystem.getMixer(aMixersInfo);
Line.Info[] linesInfo = mixer.getTargetLineInfo();
for (Line.Info aLinesInfo : linesInfo) {
TargetDataLine l = null;
try {
l = (TargetDataLine) mixer.getLine(aLinesInfo);
} catch (LineUnavailableException e) {
e.printStackTrace();
return null;
}
AudioInputToBuffer r = new AudioInputToBuffer(new AudioInputStream(l));
r.line = l;
r.setName("Micro");
System.out.println(r.line.getFormat());
return r;
}
}
System.out.println("AudioInputStream.fromMicro(): Aucun port d'entrée trouvée");
return null;
}
/**
* Génére un AudioInputToBuffer à partir d'une entrée audio du systeme
* (normalement le micro... mais honnêtement je ne peux rien certifier)
* @param channel Spécifie les chaines de l'entrée à utiliser
* @param sampleRate Spécifie la fréquence d'échantillonage à utiliser
* @return l'AudioInputToBuffer
*/
static public AudioInputToBuffer fromMicro(Integer channel, int sampleRate){
Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo();
for (Mixer.Info aMixersInfo : mixersInfo) {
Mixer mixer = AudioSystem.getMixer(aMixersInfo);
Line.Info[] linesInfo = mixer.getTargetLineInfo();
for (Line.Info aLinesInfo : linesInfo) {
if (mixer.isLineSupported(aLinesInfo)) {
TargetDataLine l = null;
try {
l = (TargetDataLine) mixer.getLine(aLinesInfo);
} catch (LineUnavailableException e) {
e.printStackTrace();
return null;
}
AudioInputToBuffer r = new AudioInputToBuffer(new AudioInputStream(l), channel, sampleRate);
r.line = l;
r.setName("Micro");
return r;
}
}
}
System.out.println("AudioInputStream.fromMicro(): Aucun port d'entrée trouvée");
return null;
}
/**
* Génére un AudioInputToBuffer à partir d'un fichier audio
* (si bien sur le format est correct, fin en fait que si c'est du wav pour l'instant)
* @return l'AudioInputToBuffer
*/
static public AudioInputToBuffer fromFile(File file){
try {
if(!file.canRead()){
System.out.println("AudioInputToBuffer.fromFile(): Erreur fichier illisible");
return null;
}
AudioInputToBuffer a = new AudioInputToBuffer(AudioSystem.getAudioInputStream(file), 0);
a.setName(file.getName());
a.file = file;
return a;
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Génére un AudioInputToBuffer à partir d'un fichier audio
* (si bien sur le format est correct, fin en fait que si c'est du wav pour l'instant)
* @param channel Spécifie les chaines de l'entrée à utiliser
* @param sampleRate Spécifie la fréquence d'échantillonage à utiliser
* @return l'AudioInputToBuffer
*/
static public AudioInputToBuffer fromFile(File file, Integer channel, int sampleRate){
try {
AudioInputToBuffer a = new AudioInputToBuffer(AudioSystem.getAudioInputStream(file), channel, sampleRate);
a.setName(file.getName());
a.file = file;
return a;
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private void init(int sampleRate){
//On reformate le flux audio pour qu'il soit en PCM_SIGNED, little-endian et avec un nombre de channel = channel
int sampleSizeBytes = input.getFormat().getSampleSizeInBits()/8;
int channel = input.getFormat().getChannels();
this.input = AudioSystem.getAudioInputStream(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, sampleSizeBytes * 8, channel, sampleSizeBytes * channel, sampleRate * channel, false), input);
buffer = new TemporalBuffer<>(Storage.Type.ArrayList, sampleRate);
buffer.setName(name);
buffer.setType(Float.class);
buffer.setProcessingOrder(processing.processes.Process.ProcessingOrder.ASCENDING_ORDER);
thread = new Thread(this);
}
/**
* Démarre l'écriture dans le buffer
* @return vrai si elle n'était pas déjà lancée
*/
public boolean start(){
if(paused) {
paused = false;
return true;
}
if(thread.isAlive())
return false;
thread = new Thread(this);
if(line != null){
line.flush();
try {
line.open();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
line.start();
}
getBuffer().tryEnsureCapacity((int)input.getFrameLength());
paused = false;
running = true;
thread.start();
return true;
}
/**
* Mets l'écriture en pause, si la lecture est en temps réel les données sont perdues
* @return vrai si l'écriture était lancée
*/
public boolean pause(){
if(!thread.isAlive()||paused)
return false;
if(line != null)
line.stop();
paused = true;
return true;
}
/**
* Stoppe l'écriture dans le buffer, ferme le port d'entrée si la lecture était en temps réel.
* @return
*/
public boolean interrupt(){
if(!thread.isAlive())
return false;
thread.interrupt();
running = false;
return true;
}
private void clean(){
if(line != null){
line.stop();
line.close();
}
buffer.lastSampleAdded();
System.out.println("Fin de l'enregistrement, à vous les studios!!!");
}
public boolean isAlive(){
return (!thread.isAlive()) || paused;
}
@Override
public void run() {
final int NBR_CHANNEL = input.getFormat().getChannels();
final int NBR_BYTES = input.getFormat().getSampleSizeInBits()/8;
while(running){
try {
byte[] data = new byte[NBR_CHANNEL*NBR_BYTES];
//On récupère dans data une frame du signal tant que l'on peut, ensuite,
// pour chaque buffer de sortie on recompose le float pour chaque buffer de la channel correspondante
while(input.read(data,0, NBR_CHANNEL*NBR_BYTES)!=-1 && data!=null && !paused && running){
buffer.add(floatFromByteArray(data, channel*NBR_BYTES,NBR_BYTES));
try {
Thread.sleep(0);
} catch (InterruptedException e) {
clean();
return;
}
}
if(input.getFrameLength()!= AudioSystem.NOT_SPECIFIED && input.getFrameLength() == buffer.size()) {
clean();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
do{
try {
Thread.sleep(300);
} catch (InterruptedException e) {
clean();
return;
}
}while(paused&&!running);
}
}
/**
* Renvoie la valeur du flaot correspondant à une séquence d'octet
* @param b tableau d'octets
* @param offset indice du premier octet à traiter
* @param nbByte nombre d'octet formant le float
* @return la valeur en float de la séquence d'octet
*/
private static float floatFromByteArray(byte[] b, int offset, int nbByte){
if(nbByte>4||nbByte<1||nbByte>offset+b.length){
System.out.println("Erreur de décodage byte à byte");
return 0;
}
switch(nbByte){
case 1:
return (b[offset] & 0xFF);
case 2:
return (b[offset] & 0xFF) | ((b[1+offset]) << 8);
case 3:
return (b[offset] & 0xFF) | ((b[1+offset] & 0xFF) << 8) | ((b[2+offset]) << 16);
case 4:
return (b[offset] & 0xFF) | ((b[1+offset] & 0xFF) << 8) | ((b[2+offset] & 0xFF) << 16) | (b[3+offset] << 24);
}
return 0;
}
/**
* Renvoie le buffer alimenté
* @return Le buffer
*/
public TemporalBuffer<Float> getBuffer() {
return buffer;
}
public void setBuffer(TemporalBuffer<Float> buffer){
this.buffer = buffer;
buffer.setName(name);
buffer.setType(Float.class);
buffer.invalidateProcessedSamples();
}
/**
* Renvoie le flux d'entrée
* @return AudioInputStream qui alimente les buffers
*/
public AudioInputStream getInput() {
return input;
}
/**
* Renvoie le numéro de la chaîne du flux d'entrée utilisée pour alimenté le buffer
* @return Numéro de la chaîne (première chaîne = 0)
*/
public int getChannel() {
return channel;
}
/**
* Renvoie l'état du processus
* @return Vrai si le processus est lancé
*/
public boolean isRunning(){
return thread.isAlive();
}
public String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
}