/* 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.processes; import generictools.Pair; import processing.buffer.Buffer; import processing.buffer.BufferEvent; import processing.properties.PropertiesManager; import javax.swing.*; import java.util.ArrayList; import java.util.LinkedList; import java.util.stream.Collectors; public abstract class Process implements Runnable { final Buffer[] inputs; private boolean[] inputsInterupted; final Buffer output; private boolean interuptAfter = false, paused = false, waiting = true; private final String name; private PropertiesManager properties; private ArrayList listeners = new ArrayList<>(); private LinkedList > bufferEvents; int updateID = 0; private Thread thread; private BufferEventProcessor bProcessor; private boolean boxDisplayed = false; public enum ProcessingOrder { NO_ORDER, ASCENDING_ORDER } public enum ProcessingState { PROCESSING, WAITING, PAUSED, STOPPED } public Process(String name, Buffer[] inputs, Buffer output) { this.inputs = inputs; this.output = output; this.name = name; init(); } public Process(String name, Buffer input, Buffer output) { inputs = new Buffer[1]; inputs[0] = input; this.output = output; this.name = name; init(); } public void init(){ bufferEvents = new LinkedList<>(); thread = new Thread(this); output.setName(name); output.setType(getType()); switch (getProcessingOrder()){ case ASCENDING_ORDER: output.setProcessingOrder(ProcessingOrder.ASCENDING_ORDER); for (int i = 0; i < inputs.length; i++) { if(inputs[i].getProcessingOrder()!=ProcessingOrder.ASCENDING_ORDER) { output.setProcessingOrder(ProcessingOrder.NO_ORDER); break; } } break; } inputsInterupted = new boolean[inputs.length]; for(int i=0; i output() { return output; } public String name(){ return name; } public void setInput(int inputID, Buffer newInput){ inputs[inputID].disconnectProcess(this); newInput.connectProcess(this); inputs[inputID] = newInput; } public void addBufferEvent(BufferEvent e){ if(e.getType()== BufferEvent.Type.SAMPLE_ADDED) { bufferEvents.addLast(new Pair<>(e, updateID)); emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED); }else { if (e.getType() == BufferEvent.Type.INTERRUPT_PROCESS) { int bufferID = 0; for (int i = 1; i < inputs.length; i++) { if (inputs[i] == e.getSource()) bufferID = i; } inputsInterupted[bufferID] = true; if (isAllInputInterrupted()) { interuptAfter = true; emitProcessEvent(ProcessEvent.Type.INTERRUPT_AFTER); } } else if (e.getType() == BufferEvent.Type.INVALIDATE) { boolean b = pause(); bufferEvents.clear(); if (b) start(); } else { for (int i = 0; i < bufferEvents.size(); i++) if (bufferEvents.get(i).first().getSource() == e.getSource()) { bufferEvents.remove(i); break; } bufferEvents.addFirst(new Pair<>(e, updateID)); emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED); } } } public void run() { while (true) { propertyManager().lockProperty(); while (!bufferEvents.isEmpty()&&!paused) { setWaiting(false); Pair event = bufferEvents.pollFirst(); if(event==null) continue; bProcessor.processBufferEvent(event.first(), event.second()); emitProcessEvent(ProcessEvent.Type.PROGRESS_CHANGED); try { Thread.sleep(0); } catch (InterruptedException e) { clean(); return; } } setWaiting(true); if(interuptAfter){ clean(); return; } try { propertyManager().unlockProperty(); Thread.sleep(50); propertyManager().lockProperty(); } catch (InterruptedException e) { clean(); return; } while(paused) try { Thread.sleep(500); } catch (InterruptedException e) { clean(); return; } } } protected void clean(){ properties.unlockProperty(); pause(); output.lastSampleAdded(); } /** * Démarre le traitement * @return Renvoie vrai si le traitement n'était pas déjà démarré */ public boolean start(){ if(thread.isAlive()) { if(paused) { paused = false; return true; } return false; } interuptAfter = false; paused = false; thread = new Thread(this); thread.setName(name); thread.start(); emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED); return true; } /** * Interompt le traitement * @return Renvoie vrai si le traitement était lancé */ public boolean interrupt(){ if(!thread.isAlive()) return false; thread.interrupt(); emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED); return true; } /** * Interompt le traitement une fois que tout les événements ont été traités * @return Renvoie vrai si le traitement était lancé */ public boolean stopAfterEventsClean(){ if(!thread.isAlive()) return false; emitProcessEvent(ProcessEvent.Type.INTERRUPT_AFTER); interuptAfter = true; return true; } /** * Mais en pause le traitement sans supprimer le thread * @return Renvoie vrai si le traitement était lancé */ public boolean pause(){ if(!thread.isAlive()) return false; paused = true; emitProcessEvent(ProcessEvent.Type.PAUSED_STATE_CHANGED); return true; } public void recomputeAll(){ output.clear(); interrupt(); properties.unlockProperty(); bufferEvents.clear(); bProcessor = initBufferEventProcessor(); for(Buffer input: inputs()){ ArrayList indexes = input.getSamplesIndex(); for(Integer index:indexes) addBufferEvent(new BufferEvent(BufferEvent.Type.SAMPLE_ADDED, index, input)); } start(); for(Process p:output().getUsedBy()) p.recomputeAll(); } public boolean isAllInputInterrupted(){ for (boolean anInputsInterupted : inputsInterupted) { if (!anInputsInterupted) return false; } return true; } private void setWaiting(boolean waiting){ if(this.waiting == waiting) return; this.waiting = waiting; emitProcessEvent(ProcessEvent.Type.WAITING_STATE); } /** * Renvoie le nom du processus * @return Le nom */ public String getName() { return name; } /** * Renvoie le buffer de sortie * @return buffer */ public Buffer getOutput() { return output; } /** * Renvoie un buffer d'entrée * @param index index du buffer d'entrée * @return Le buffer correspondant /!\ Peut renvoyer null si l'index n'est pas correct /!\ */ public Buffer getInputs(int index) { if(index>=inputs.length || index<1) return null; return inputs[index]; } /** * Renvoie le nombre de buffer d'entrée * @return Nombre d'inputs */ public int getInputsNbr(){ return inputs.length; } /** * Renvoie la liste des buffersEvent non traités * @return liste des buffersEvent */ public ArrayList getBufferEventsQueue() { ArrayList r = new ArrayList<>(bufferEvents.size()); r.addAll(bufferEvents.stream().map(Pair::first).collect(Collectors.toList())); return r; } /** * Renvoie les propriétés associées à cet algorythme * @return le propertiesManager du process */ public PropertiesManager propertyManager() { return properties; } /** * Renvoie l'état du processus * @return Vrai si tout l'algo est en attente d'événement sur les canaux d'entrées */ public boolean isWaiting() { return waiting; } /** * Renvoie l'état du processus * @return Vrai si l'algo est en pause */ public boolean isPaused() { return paused; } /** * Renvoie l'état du processus * @return Vrai si le processus est lancé */ public boolean isRunning(){ return thread.isAlive(); } /** * Renvoie la valeur de la progression du process * @return la progression: 0 -> aucun échantillon traité; 1 -> tous les échantillons ont été traités */ public float getProgress(){ if(bufferEvents.size() == 0) return 0; double n = getSampleNbrPerIteration(); return 1-(float)Math.log(n)/(float)Math.log(bufferEvents.size()+n-1); } protected float getSampleNbrPerIteration(){return 10;} /** * Renvoie le type du buffer de sortie * @return La class des échantillones du buffer de sortie */ protected abstract Class getType(); protected abstract ProcessingOrder getProcessingOrder(); protected interface BufferEventProcessor { void processBufferEvent(BufferEvent e, int updateID); } protected abstract BufferEventProcessor initBufferEventProcessor(); public interface Listener{ public void processEvent(ProcessEvent event); } /** * Connecte ce process à un listener qu'il fournit, de manière à propager les BufferEvent jusqu'à cet objet * @param p objet à connecter * @return Vrai si l'opération à fontionner (si le listener n'était pas déjà ajouté) */ public boolean addListener(Listener p){ if(listeners.contains(p)) return false; listeners.add(p); return true; } /** * Déconnecte ce process d'un listener * @param p listener à déconnecter * @return Vrai si l'opération à fontionner (si le listener était dans la liste) */ public boolean removeListener(Listener p){ int id = listeners.indexOf(p); if(id == -1) return false; listeners.remove(id); return true; } private void emitProcessEvent(ProcessEvent event) { SwingUtilities.invokeLater(() -> { for (Listener p : listeners) p.processEvent(event); }); } private void emitProcessEvent(ProcessEvent.Type type) { emitProcessEvent(ProcessEvent.eventFrom(type, this)); } /** * Indique si la box du buffer est affichée dans le ProcessSumUp ou non. * @return true si elle est affichée, false sinon. */ public boolean isBoxDisplayed() { return boxDisplayed; } /** * Change l'état d'affichage de la box du Buffer dans le ProcessSumUp. N'appeler que lorsque le système crée la Box. * @param boxDisplayed L'état : true si on l'affiche, false si on l'enlève. */ public void setBoxDisplayed(boolean boxDisplayed) { this.boxDisplayed = boxDisplayed; } }