/* RoundBufferMultichannelSampleSource.java * */ package org.signalml.domain.signal.samplesource; import java.util.Iterator; import java.util.List; import java.util.concurrent.Semaphore; import java.util.logging.Level; import javax.swing.event.EventListenerList; import org.signalml.plugin.export.SignalMLException; import org.signalml.plugin.export.change.events.PluginSignalChangeEvent; import org.signalml.plugin.export.change.listeners.PluginSignalChangeListener; import org.signalml.plugin.export.signal.ExportedSignalDocument; import org.signalml.plugin.export.view.DocumentView; import org.signalml.plugin.impl.change.events.PluginSignalChangeEventImpl; public class RoundBufferMultichannelSampleSource extends DoubleArraySampleSource implements OriginalMultichannelSampleSource, ChangeableMultichannelSampleSource { /** * The list containing objects listening for change in this sample source. */ private EventListenerList listenerList = new EventListenerList(); protected int nextInsertPos; protected boolean full; protected DocumentView documentView; protected float samplingFrequency; protected Object[] labels; /** * The calibration gain for the signal - the value by which each sample * value is multiplied. */ private float[] calibrationGain; /** * The calibration offset for the signal - the value which is added * to each sample value. */ private float[] calibrationOffset; /** * Semaphore preventing simultaneous read/write/newSamplesCount operations. */ private Semaphore semaphore; /** * Stores the number of samples added to this sample source. */ private long addedSamplesCount = 0; public RoundBufferMultichannelSampleSource(int channelCount, int sampleCount) { super(null, channelCount, sampleCount); this.samples = new double[channelCount][sampleCount]; nextInsertPos = 0; full = false; semaphore = new Semaphore(1); } public DocumentView getDocumentView() { return documentView; } public void setDocumentView(DocumentView documentView) { this.documentView = documentView; } synchronized int getNextInsertPos() { return nextInsertPos; } synchronized void setNextInsertPos(int nextInsertPos) { this.nextInsertPos = nextInsertPos; } synchronized boolean isFull() { return full; } synchronized void setFull(boolean full) { this.full = full; } synchronized double[][] getSamples() { return this.samples; } synchronized void setSamples(double[][] samples) { this.samples = samples; } protected synchronized void incrNextInsertPos() { nextInsertPos++; if (nextInsertPos == sampleCount) { full = true; nextInsertPos = 0; } } @Override public synchronized void addSampleChunk(float[] newSamples) { for (int i = 0; i < channelCount; i++) { samples[i][nextInsertPos] = newSamples[i]; } incrNextInsertPos(); addedSamplesCount++; fireNewSamplesAddedEvent(); } @Override public synchronized void addSamples(float[] newSamples) { addSampleChunk(newSamples); fireNewSamplesAddedEvent(); } @Override public synchronized void addSamples(List<float[]> newSamples) { for (Iterator< float[]> i = newSamples.iterator(); i.hasNext();) addSampleChunk(i.next()); fireNewSamplesAddedEvent(); } // przy zwykłych źródłach sygnału sampleCount jest znany z góry, a tu nie; // tutaj sampleCount oznacza maksymalną ilość próbek w stanie gdy cały bufor już jest wypełniony // offset powinien być w ramach przedziału od zera do sampleCount-1 - count // jeśli bufor jest pusty to wszystkie próbki od zera do sampleCount - 1 są równe zero // jeśli bufor jest częściowo wypełniony, to próbki od zera do sampleCount - 1 - n są równe zero // a pozostałe nie, gdzie n jest liczbą próbek zaczytanych do bufora; jesli cały bufor jest wypełiony // to offset trzeba przesunąć odpowiednio względem bieżącego punktu wstawiania @Override public synchronized void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) { getSamples(channel, target, signalOffset, count, arrayOffset, true); } public synchronized void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset, boolean calibrate) { double[] tmp = new double[sampleCount]; if (full) { for (int i = 0; i < sampleCount; i++) { tmp[i] = samples[channel][(nextInsertPos + i) % sampleCount]; } } else { if (nextInsertPos == 0) { for (int i = 0; i < sampleCount; i++) { tmp[i] = 0.0; } } else { int n = sampleCount - nextInsertPos; for (int i = 0; i < n; i++) tmp[i] = 0.0; for (int i = n; i < sampleCount; i++) { tmp[i] = samples[channel][i - n]; } } } //calibration if (calibrate) { for (int i = 0; i < count; i++) target[arrayOffset+i] = calibrateSample(tmp[signalOffset + i], channel); } else { for (int i = 0; i < count; i++) target[arrayOffset+i] = tmp[signalOffset + i]; } } protected double calibrateSample(double inputSampleValue, int channelIndex) { if (calibrationGain != null && calibrationOffset != null) return calibrationGain[channelIndex] * inputSampleValue + calibrationOffset[channelIndex]; return inputSampleValue; } @Override public OriginalMultichannelSampleSource duplicate() throws SignalMLException { return null; } @Override public void setCalibrationGain(float calibration) { } @Override public void setChannelCount(int channelCount) { } @Override public String getLabel(int channel) { if (labels != null) return labels[channel].toString(); else return super.getLabel(channel); } public Object[] getLabels() { return labels; } public void setLabels(Object[] labels) { this.labels = labels; } @Override public void lock() { try { semaphore.acquire(); } catch (InterruptedException ex) { java.util.logging.Logger.getLogger(RoundBufferMultichannelSampleSource.class.getName()).log(Level.SEVERE, null, ex); } } @Override public void unlock() { semaphore.release(); } /* * Gets the number of received samples. * * @return number of samples received from the start */ public int getReceivedSampleCount() { if (full) { return sampleCount; } else { return nextInsertPos; } } @Override public boolean isCalibrationCapable() { return false; } @Override public float[] getCalibrationGain() { return calibrationGain; } @Override public boolean areIndividualChannelsCalibrationCapable() { return false; } @Override public void setCalibrationGain(float[] calibration) { this.calibrationGain = calibration; } @Override public float getSingleCalibrationGain() { return 1F; } /** * Returns the number of samples per second * @return the number of samples per second = 128 */ @Override public float getSamplingFrequency() { return this.samplingFrequency; } /** * Sets the number of samples per second * @param sampling - sampling frequency * @return Null */ @Override public void setSamplingFrequency(float sampling) { this.samplingFrequency = sampling; } /** * Getting and setting calibration offset for RoundBufferMultichannelSampleSource * is not supported yet. * TODO: a clean-up is needed, and calibration gain/offset calculations * should be performed in this class. * @return (not supported) */ @Override public float[] getCalibrationOffset() { return calibrationOffset; } @Override public void setCalibrationOffset(float[] calibrationOffset) { this.calibrationOffset = calibrationOffset; } @Override public void setCalibrationOffset(float calibrationOffset) { throw new UnsupportedOperationException("Not supported yet."); } @Override public float getSingleCalibrationOffset() { throw new UnsupportedOperationException("Not supported yet."); } @Override public void addSignalChangeListener(PluginSignalChangeListener listener) { listenerList.add(PluginSignalChangeListener.class, listener); } protected void fireNewSamplesAddedEvent() { Object[] listeners = listenerList.getListenerList(); PluginSignalChangeEvent e = null; for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==PluginSignalChangeListener.class) { if (e == null) { ExportedSignalDocument document = null; if (getDocumentView() != null) document = (ExportedSignalDocument) getDocumentView().getDocument(); e = new PluginSignalChangeEventImpl(document); } ((PluginSignalChangeListener)listeners[i+1]).newSamplesAdded(e); } } } @Override public long getAddedSamplesCount() { return addedSamplesCount; } }