/* 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 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 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 getBuffer() { return buffer; } public void setBuffer(TemporalBuffer 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; } }