425 lines
11 KiB
Java
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;
|
|
}
|
|
}
|