This is an automated email from the git hooks/post-receive script. New commit to branch feature/7063 in repository tutti. See http://git.codelutin.com/tutti.git commit e3a6167ae93a2450f9612353eaa40e8406908137 Author: Tony CHEMIT <chemit@codelutin.com> Date: Wed Jan 20 09:51:47 2016 +0100 Utilisation d'un vrai objet Java pour jouer les son (SoundEngine) et revue du threading (See #7063) --- .../fr/ifremer/tutti/ui/swing/TuttiUIContext.java | 9 ++ .../frequency/SpeciesFrequencyUIHandler.java | 11 +- .../actions/ApplySpeciesFrequencyRafaleAction.java | 21 +-- .../ifremer/tutti/ui/swing/util/SoundEngine.java | 126 ++++++++++++++++ .../fr/ifremer/tutti/ui/swing/util/SoundUtil.java | 159 ++++++++++++++------- .../fr/ifremer/tutti/ui/swing/SoundUtilTest.java | 33 ++--- 6 files changed, 272 insertions(+), 87 deletions(-) diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/TuttiUIContext.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/TuttiUIContext.java index 4d84c0c..70b7f3c 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/TuttiUIContext.java +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/TuttiUIContext.java @@ -57,6 +57,7 @@ import fr.ifremer.tutti.service.referential.TuttiReferentialSynchronizeService; import fr.ifremer.tutti.service.report.ReportGenerationService; import fr.ifremer.tutti.ui.swing.content.MainUI; import fr.ifremer.tutti.ui.swing.updater.DeleteHelper; +import fr.ifremer.tutti.ui.swing.util.SoundEngine; import fr.ifremer.tutti.ui.swing.util.TuttiUIUtil; import fr.ifremer.tutti.ui.swing.util.UIMessageNotifier; import fr.ifremer.tutti.ui.swing.util.auth.AuthenticationInfo; @@ -300,6 +301,8 @@ public class TuttiUIContext extends AbstractBean implements Closeable, UIMessage */ private boolean closed; + private final SoundEngine soundEngine; + public static TuttiUIContext newContext(TuttiConfiguration config) { Preconditions.checkNotNull(config); Preconditions.checkState(applicationContext == null, @@ -386,6 +389,7 @@ public class TuttiUIContext extends AbstractBean implements Closeable, UIMessage tuttiActionFactory = new TuttiActionFactory(); tuttiActionEngine = new ApplicationActionEngine(tuttiActionFactory); this.updateAuthenticationStore = Maps.newTreeMap(); + this.soundEngine = new SoundEngine(config); } @Override @@ -612,6 +616,7 @@ public class TuttiUIContext extends AbstractBean implements Closeable, UIMessage IOUtils.closeQuietly(serviceContext); IOUtils.closeQuietly(ichtyometerReader); + IOUtils.closeQuietly(soundEngine); // remove listeners PropertyChangeListener[] listeners = getPropertyChangeListeners(); @@ -760,6 +765,10 @@ public class TuttiUIContext extends AbstractBean implements Closeable, UIMessage return serviceContext.getService(ReportGenerationService.class); } + public SoundEngine getSoundEngine() { + return soundEngine; + } + public boolean useRealPersistenceService() { return isDbExist() && isDbLoaded(); } diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/SpeciesFrequencyUIHandler.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/SpeciesFrequencyUIHandler.java index 8d2a5ab..b4643fc 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/SpeciesFrequencyUIHandler.java +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/SpeciesFrequencyUIHandler.java @@ -40,7 +40,7 @@ import fr.ifremer.tutti.ui.swing.content.operation.catches.FrequencyConfiguratio import fr.ifremer.tutti.ui.swing.content.operation.catches.species.SpeciesBatchRowModel; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyCellComponent.FrequencyCellEditor; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.actions.ApplySpeciesFrequencyRafaleAction; -import fr.ifremer.tutti.ui.swing.util.SoundUtil; +import fr.ifremer.tutti.ui.swing.util.SoundEngine; import fr.ifremer.tutti.ui.swing.util.TuttiBeanMonitor; import fr.ifremer.tutti.ui.swing.util.TuttiNumberTickUnitSource; import fr.ifremer.tutti.ui.swing.util.TuttiUI; @@ -218,8 +218,6 @@ public class SpeciesFrequencyUIHandler extends AbstractTuttiTableUIHandler<Speci @Override public void beforeInit(SpeciesFrequencyUI ui) { - this.applySpeciesFrequencyRafaleAction = new ApplySpeciesFrequencyRafaleAction(ui); - super.beforeInit(ui); this.weightUnit = getConfig().getSpeciesWeightUnit(); @@ -254,6 +252,8 @@ public class SpeciesFrequencyUIHandler extends AbstractTuttiTableUIHandler<Speci @Override public void afterInit(SpeciesFrequencyUI ui) { + applySpeciesFrequencyRafaleAction = new ApplySpeciesFrequencyRafaleAction(ui); + initUI(this.ui); List<Caracteristic> lengthStepCaracterics = @@ -673,9 +673,8 @@ public class SpeciesFrequencyUIHandler extends AbstractTuttiTableUIHandler<Speci } else { - if (getConfig().isIchtyometerErrorReceptionBeepEnabled()) { - SoundUtil.beep(getConfig().getIchtyometerErrorReceptionBeepFrequency(), 3); - } + SoundEngine soundEngine = getContext().getSoundEngine(); + soundEngine.beepOnIchtyometerErrorReception(); throw new ApplicationBusinessException( t("tutti.editSpeciesFrequencies.error.itchyometer.bad.record", record.getRecord())); diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/actions/ApplySpeciesFrequencyRafaleAction.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/actions/ApplySpeciesFrequencyRafaleAction.java index 2c880ec..d026a23 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/actions/ApplySpeciesFrequencyRafaleAction.java +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/operation/catches/species/frequency/actions/ApplySpeciesFrequencyRafaleAction.java @@ -25,7 +25,6 @@ package fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.ac */ import com.google.common.collect.Lists; -import fr.ifremer.tutti.TuttiConfiguration; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyLogRowModel; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyLogsTableModel; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyRowModel; @@ -33,7 +32,7 @@ import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.Spe import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyUI; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyUIHandler; import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyUIModel; -import fr.ifremer.tutti.ui.swing.util.SoundUtil; +import fr.ifremer.tutti.ui.swing.util.SoundEngine; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.swingx.JXTable; @@ -56,10 +55,12 @@ public class ApplySpeciesFrequencyRafaleAction { /** Logger. */ private static final Log log = LogFactory.getLog(ApplySpeciesFrequencyRafaleAction.class); - private final SpeciesFrequencyUI ui; + protected final SpeciesFrequencyUI ui; + protected final SoundEngine soundEngine; public ApplySpeciesFrequencyRafaleAction(SpeciesFrequencyUI ui) { this.ui = ui; + this.soundEngine = ui.getHandler().getContext().getSoundEngine(); } public void applyRafaleStep(Float step, boolean fromIchtyometer) { @@ -113,18 +114,18 @@ public class ApplySpeciesFrequencyRafaleAction { ui.getTable().scrollRowToVisible(rowIndex); if (fromIchtyometer) { + String unit = model.getLengthStepCaracteristicUnit(); handler.showInformationMessage(t("tutti.editSpeciesFrequencies.addMeasure", step, aroundLengthStep, unit)); - TuttiConfiguration config = handler.getConfig(); - if (config.isIchtyometerDataReceptionBeepEnabled()) { - SoundUtil.beep(config.getIchtyometerDataReceptionBeepFrequency()); - } - if (config.isIchtyometerVoiceEnabled()) { - SoundUtil.readNumber(aroundLengthStep, unit); - } + soundEngine.beepOnIchtyometerDataReception(unit, aroundLengthStep); + } + //FIXME Remove this after tests + String unit = model.getLengthStepCaracteristicUnit(); + soundEngine.beepOnIchtyometerDataReception(unit, aroundLengthStep); + JXTable logsTable = ui.getLogsTable(); SpeciesFrequencyLogsTableModel logsTableModel = (SpeciesFrequencyLogsTableModel) logsTable.getModel(); SpeciesFrequencyLogRowModel newRow = logsTableModel.createNewRow(); diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundEngine.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundEngine.java new file mode 100644 index 0000000..1d1a756 --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundEngine.java @@ -0,0 +1,126 @@ +package fr.ifremer.tutti.ui.swing.util; + +import com.google.common.base.MoreObjects; +import fr.ifremer.tutti.TuttiConfiguration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.jaxx.application.ApplicationTechnicalException; + +import java.io.Closeable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Created on 20/01/16. + * + * @author Tony Chemit - chemit@codelutin.com + * @since 4.4 + */ +public class SoundEngine implements Closeable { + + /** Logger. */ + private static final Log log = LogFactory.getLog(SoundEngine.class); + + protected final TuttiConfiguration configuration; + protected final Thread thread; + protected final LinkedBlockingQueue<Measure> measuresToSay; + protected boolean stop; + + public SoundEngine(TuttiConfiguration configuration) { + this.configuration = configuration; + this.thread = new Thread(new SoundEngineRunnable(), toString()); + this.measuresToSay = new LinkedBlockingQueue<>(); + this.thread.start(); + if (log.isInfoEnabled()) { + log.info("Starting sound engine thread: " + thread); + } + } + + public synchronized void beepOnIchtyometerDataReception(String unit, float aroundLengthStep) { + + if (configuration.isIchtyometerDataReceptionBeepEnabled()) { + SoundUtil.beep(configuration.getIchtyometerDataReceptionBeepFrequency()); + } + + if (configuration.isIchtyometerVoiceEnabled()) { + measuresToSay.add(new Measure(unit, aroundLengthStep)); + } + + } + + public synchronized void beepOnIchtyometerErrorReception() { + + if (configuration.isIchtyometerErrorReceptionBeepEnabled()) { + SoundUtil.beep(configuration.getIchtyometerErrorReceptionBeepFrequency(), 3); + } + + } + + @Override + public synchronized void close() { + + if (log.isInfoEnabled()) { + log.info("Stopping sound engine thread: " + thread); + } + stop = true; + thread.interrupt(); + } + + protected class Measure { + + protected final String unit; + protected final float aroundLengthStep; + + protected Measure(String unit, float aroundLengthStep) { + this.unit = unit; + this.aroundLengthStep = aroundLengthStep; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("unit", unit) + .add("aroundLengthStep", aroundLengthStep) + .toString(); + } + } + + protected class SoundEngineRunnable implements Runnable { + + @Override + public void run() { + + while (true) { + + try { + Measure measure = measuresToSay.poll(1, TimeUnit.SECONDS); + + if (measure != null) { + + if (log.isInfoEnabled()) { + log.info("New Measure to say: " + measure); + } + + SoundUtil.readNumber(measure.aroundLengthStep, measure.unit); + + } + + if (stop) { + break; + } + + } catch (InterruptedException e) { + + if (!stop) { + throw new ApplicationTechnicalException("Could not get measure to say", e); + } + + } + + + } + + } + } + +} diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundUtil.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundUtil.java index f69c4c2..e9d1bff 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundUtil.java +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/util/SoundUtil.java @@ -3,8 +3,6 @@ package fr.ifremer.tutti.ui.swing.util; /* * #%L * Tutti :: UI - * $Id:$ - * $HeadURL:$ * %% * Copyright (C) 2012 - 2016 Ifremer * %% @@ -77,79 +75,134 @@ public class SoundUtil { 1, // channels true, // signed false); // bigEndian - SourceDataLine sdl = AudioSystem.getSourceDataLine(af); - sdl.open(af); - sdl.start(); - for (int i = 0, end = msecs * 8 ; i < end ; i++) { - double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; - buf[0] = (byte)(Math.sin(angle) * 127.0 * vol); - sdl.write(buf,0,1); + try (SourceDataLine sdl = AudioSystem.getSourceDataLine(af)) { + sdl.open(af); + sdl.start(); + for (int i = 0, end = msecs * 8; i < end; i++) { + double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; + buf[0] = (byte) (Math.sin(angle) * 127.0 * vol); + sdl.write(buf, 0, 1); + } + sdl.drain(); + sdl.stop(); } - sdl.drain(); - sdl.stop(); - sdl.close(); } - public static synchronized void readNumber(final double number, final String unit) { - - new Thread(new Runnable() { + public static void readNumber(double number, String unit) { - @Override - public void run() { - - try { + try { - int thousands = (int) (number / 1000); - int hundreds = (int) (number % 1000) / 100; - int tensAndUnits = (int) number % 100; - int decimal = (int) (number * 10) % 10; + int thousands = (int) (number / 1000); + int hundreds = (int) (number % 1000) / 100; + int tensAndUnits = (int) number % 100; + int decimal = (int) (number * 10) % 10; - List<AudioInputStream> audioInputStreams = new ArrayList<>(); + List<AudioInputStream> audioInputStreams = new ArrayList<>(); - addSound(audioInputStreams, thousands, 1000); - addSound(audioInputStreams, hundreds, 100); - if (tensAndUnits != 0 || thousands == 0 && hundreds == 0) { - addSound(audioInputStreams, tensAndUnits); - } - if (decimal > 0) { - addSound(audioInputStreams, ","); - addSound(audioInputStreams, decimal); - } - addSound(audioInputStreams, unit); + addSound(audioInputStreams, thousands, 1000); + addSound(audioInputStreams, hundreds, 100); + if (tensAndUnits != 0 || thousands == 0 && hundreds == 0) { + addSound(audioInputStreams, tensAndUnits); + } + if (decimal > 0) { + addSound(audioInputStreams, ","); + addSound(audioInputStreams, decimal); + } + addSound(audioInputStreams, unit); - if (!audioInputStreams.isEmpty()) { + if (!audioInputStreams.isEmpty()) { - AudioFormat format = audioInputStreams.get(0).getFormat(); - DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); - SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info); - audioLine.open(format); - audioLine.start(); + AudioFormat format = audioInputStreams.get(0).getFormat(); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + try (SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info)) { + audioLine.open(format); + audioLine.start(); - byte[] bytesBuffer = new byte[4096]; - int bytesRead; + byte[] bytesBuffer = new byte[4096]; - while (!audioInputStreams.isEmpty()) { - AudioInputStream audioInputStream = audioInputStreams.remove(0); + while (!audioInputStreams.isEmpty()) { + try (AudioInputStream audioInputStream = audioInputStreams.remove(0)) { + int bytesRead; while ((bytesRead = audioInputStream.read(bytesBuffer)) != -1) { audioLine.write(bytesBuffer, 0, bytesRead); } - audioInputStream.close(); } - - audioLine.drain(); - audioLine.close(); } - } catch (Exception e) { - if (log.isErrorEnabled()) { - log.error("Error while reading " + number + " " + unit, e); - } + audioLine.drain(); + } } - }).start(); + } catch (Exception e) { + if (log.isErrorEnabled()) { + log.error("Error while reading " + number + " " + unit, e); + } + } + } +// public static synchronized void readNumber(final double number, final String unit) { +// +// new Thread(new Runnable() { +// +// @Override +// public void run() { +// +// try { +// +// int thousands = (int) (number / 1000); +// int hundreds = (int) (number % 1000) / 100; +// int tensAndUnits = (int) number % 100; +// int decimal = (int) (number * 10) % 10; +// +// List<AudioInputStream> audioInputStreams = new ArrayList<>(); +// +// addSound(audioInputStreams, thousands, 1000); +// addSound(audioInputStreams, hundreds, 100); +// if (tensAndUnits != 0 || thousands == 0 && hundreds == 0) { +// addSound(audioInputStreams, tensAndUnits); +// } +// if (decimal > 0) { +// addSound(audioInputStreams, ","); +// addSound(audioInputStreams, decimal); +// } +// addSound(audioInputStreams, unit); +// +// if (!audioInputStreams.isEmpty()) { +// +// AudioFormat format = audioInputStreams.get(0).getFormat(); +// DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); +// try (SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info)) { +// audioLine.open(format); +// audioLine.start(); +// +// byte[] bytesBuffer = new byte[4096]; +// +// while (!audioInputStreams.isEmpty()) { +// try (AudioInputStream audioInputStream = audioInputStreams.remove(0)) { +// int bytesRead; +// while ((bytesRead = audioInputStream.read(bytesBuffer)) != -1) { +// audioLine.write(bytesBuffer, 0, bytesRead); +// } +// } +// } +// +// audioLine.drain(); +// +// } +// } +// +// } catch (Exception e) { +// if (log.isErrorEnabled()) { +// log.error("Error while reading " + number + " " + unit, e); +// } +// } +// } +// +// }).start(); +// } + protected static long addSound(List<AudioInputStream> inputStreams, int number) throws IOException, UnsupportedAudioFileException { return addSound(inputStreams, number, 1); } diff --git a/tutti-ui-swing/src/test/java/fr/ifremer/tutti/ui/swing/SoundUtilTest.java b/tutti-ui-swing/src/test/java/fr/ifremer/tutti/ui/swing/SoundUtilTest.java index de2daa0..1054ae3 100644 --- a/tutti-ui-swing/src/test/java/fr/ifremer/tutti/ui/swing/SoundUtilTest.java +++ b/tutti-ui-swing/src/test/java/fr/ifremer/tutti/ui/swing/SoundUtilTest.java @@ -3,8 +3,6 @@ package fr.ifremer.tutti.ui.swing; /* * #%L * Tutti :: UI - * $Id:$ - * $HeadURL:$ * %% * Copyright (C) 2012 - 2016 Ifremer * %% @@ -29,27 +27,26 @@ import org.junit.Test; /** * @author Kevin Morin (Code Lutin) - * @since x.x + * @since 4.4 */ public class SoundUtilTest { @Test public void testSound() throws InterruptedException { - SoundUtil.readNumber(1, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(35, "mm"); - Thread.sleep(3000); - SoundUtil.readNumber(2001, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(2300, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(2000.5, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(2000, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(200, "cm"); - Thread.sleep(3000); - SoundUtil.readNumber(201, "cm"); + testReadNumber(101.5f, "cm"); + testReadNumber(1, "cm"); + testReadNumber(35, "mm"); + testReadNumber(2001, "cm"); + testReadNumber(2300, "cm"); + testReadNumber(2000.5f, "cm"); + testReadNumber(2000, "cm"); + testReadNumber(200, "cm"); + testReadNumber(201, "cm"); + } + + protected void testReadNumber(float measure, String unit) throws InterruptedException { + + SoundUtil.readNumber(measure, unit); Thread.sleep(3000); } } -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.