uk.co.modularaudio.mads.base.soundfile_player.ui.rollpainter.SoundfileDisplaySampleFactory.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.modularaudio.mads.base.soundfile_player.ui.rollpainter.SoundfileDisplaySampleFactory.java

Source

/**
 *
 * Copyright (C) 2015 - Daniel Hams, Modular Audio Limited
 *                      daniel.hams@gmail.com
 *
 * Mad 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.
 *
 * Mad 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 Mad.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package uk.co.modularaudio.mads.base.soundfile_player.ui.rollpainter;

import java.awt.Graphics2D;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.co.modularaudio.mads.base.soundfile_player.ui.SoundfilePlayerColorDefines;
import uk.co.modularaudio.mads.base.soundfile_player.ui.SoundfilePlayerMadUiInstance;
import uk.co.modularaudio.mads.base.soundfile_player.ui.SoundfilePlayerZoomToggleGroupUiJComponent.ZoomLevel;
import uk.co.modularaudio.service.samplecaching.SampleCacheClient;
import uk.co.modularaudio.service.samplecaching.SampleCachingService;
import uk.co.modularaudio.util.audio.gui.mad.rollpainter.RollPaintDefaultUpdateStructure;
import uk.co.modularaudio.util.audio.gui.mad.rollpainter.RollPaintDirection;
import uk.co.modularaudio.util.audio.gui.mad.rollpainter.RollPaintUpdate;
import uk.co.modularaudio.util.audio.gui.mad.rollpainter.RollPaintUpdateType;
import uk.co.modularaudio.util.audio.gui.mad.rollpainter.RollPainterSampleFactory;
import uk.co.modularaudio.util.audio.timing.AudioTimingUtils;
import uk.co.modularaudio.util.bufferedimage.AllocationMatch;
import uk.co.modularaudio.util.bufferedimage.BufferedImageAllocator;
import uk.co.modularaudio.util.exception.DatastoreException;
import uk.co.modularaudio.util.thread.RealtimeMethodReturnCodeEnum;

public class SoundfileDisplaySampleFactory
        implements RollPainterSampleFactory<SoundfileDisplayBuffer, SoundfileDisplayBufferClearer> {
    private static Log log = LogFactory.getLog(SoundfileDisplaySampleFactory.class.getName());

    private final SoundfileDisplayBufferClearer bufferClearer;

    private final SampleCachingService sampleCachingService;
    private final BufferedImageAllocator bia;
    private final int displayWidth;
    private final int displayWidthMinusOneOverTwo;
    private final int displayHeight;
    //   private final RPSoundfilePlayerMadUiInstance uiInstance;

    private final RollPaintDefaultUpdateStructure updateStructure = new RollPaintDefaultUpdateStructure();

    private final AllocationMatch allocationMatch = new AllocationMatch();

    private long receivedBufferPos = 0;

    private long lastBufferPos;
    private float captureLengthMillis = ZoomLevel.ZOOMED_DEFAULT.getMillisForLevel();
    private int numSamplesPerPixel;

    private float displayMultiplier;

    private boolean needsFullUpdate;

    private float minValue;
    private float maxValue;
    private float previousMinValue;
    private float previousMaxValue;

    private SampleCacheClient scc;

    private final MinMaxSampleAcceptor minMaxSampleAcceptor = new MinMaxSampleAcceptor();

    private final static int DEFAULT_SAMPLES_PER_PIXEL = 371;

    public SoundfileDisplaySampleFactory(final SampleCachingService sampleCachingService,
            final BufferedImageAllocator bia, final int displayWidth, final int displayHeight,
            final SoundfilePlayerMadUiInstance uiInstance) {
        this.sampleCachingService = sampleCachingService;
        this.bia = bia;
        this.displayWidth = displayWidth;
        this.displayWidthMinusOneOverTwo = (int) ((displayWidth - 1.0f) / 2.0f);
        this.displayHeight = displayHeight;
        //      this.uiInstance = uiInstance;

        lastBufferPos = 0;
        numSamplesPerPixel = DEFAULT_SAMPLES_PER_PIXEL;
        needsFullUpdate = true;
        maxValue = -Float.MAX_VALUE;
        minValue = -maxValue;
        previousMaxValue = 0.0f;
        previousMinValue = 0.0f;

        bufferClearer = new SoundfileDisplayBufferClearer(displayWidth, displayHeight);
    }

    private int getNumSamplesAvailable() {
        final long numReadable = getNumReadable();
        long numPixelsCanOutput = numReadable / numSamplesPerPixel;
        numPixelsCanOutput = (numPixelsCanOutput > displayWidth ? displayWidth : numPixelsCanOutput);
        final int numPixelsCanOutputInt = (int) numPixelsCanOutput;
        //      if( numPixelsCanOutputInt != 0 )
        //      {
        //         log.debug("Returning " + numPixelsCanOutputInt + " pixels available for output");
        //      }
        return numPixelsCanOutputInt;
    }

    @Override
    public void fullFillSamples(final RollPaintUpdate update, final SoundfileDisplayBuffer buffer) {
        if (scc == null) {
            return;
        }
        final Graphics2D g = buffer.g;

        g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_WAVE_FG_COLOR);

        final long bufferIndex = receivedBufferPos;
        int bufferIndexRemainder = -(numSamplesPerPixel * (displayWidthMinusOneOverTwo));

        //      int numSamplesForDisplay = (numSamplesPerPixel * displayWidth);
        //      long startBufferPos = bufferIndex;
        //      long endBufferPos = bufferIndex + numSamplesForDisplay;
        //      log.debug("Full fill from index(" + startBufferPos + ") to (" + endBufferPos +")");

        int numPixelsDone = 0;

        // Set up min and max
        final long preIndex = bufferIndex + bufferIndexRemainder - numSamplesPerPixel;
        calcMinMaxForSamples(preIndex);
        previousMinValue = minValue;
        previousMaxValue = maxValue;
        minValue = 0.0f;
        maxValue = 0.0f;

        for (; numPixelsDone < displayWidth; bufferIndexRemainder += numSamplesPerPixel, ++numPixelsDone) {
            final long indexInt = bufferIndex + bufferIndexRemainder;
            //         log.debug("FullFill pulling pixel " + numPixelsDone + " from index " + indexInt + " due to pos(" + bufferIndex + ")-(" +
            //               bufferIndexRemainder + ")" );

            calcMinMaxForSamples(indexInt);
            extendMinMaxWithPrevious();

            fillInMinMaxLine(g, numPixelsDone, minValue, maxValue);

            previousMinValue = minValue;
            previousMaxValue = maxValue;
        }

        lastBufferPos = receivedBufferPos;
        //      log.debug("Full fill setting last buffer pos to (" + lastBufferPos +") with zero remainder");

        //      log.debug("Full fill");
    }

    @Override
    public void deltaFillSamples(final RollPaintUpdate update, final int displayOffset,
            final SoundfileDisplayBuffer buffer, final int bufferSampleOffset, final int numSamples,
            final SoundfileDisplayBuffer otherBuffer) {
        //      log.debug("DeltaFill asked to paint(" + numToPaint + ") at displayOffset(" + displayOffset + ") paintOffset(" + paintOffset + ")");

        final Graphics2D g = buffer.g;

        g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_WAVE_FG_COLOR);

        int numPixelsDone = 0;
        switch (update.getDirection()) {
        case BACKWARDS: {
            //            g.setColor(Color.RED );
            final int offsetToBackwardEdge = numSamplesPerPixel * (displayWidthMinusOneOverTwo + numSamples);

            final long bufferIndex = lastBufferPos;
            int bufferRemainder = -offsetToBackwardEdge;

            //            log.debug("Delta backwards starting from bufferIndex(" + bufferIndex + ")");

            final long preIndexInt = bufferIndex + bufferRemainder - numSamplesPerPixel;
            //            log.debug("So previous bottom value was as index " + preIndexInt + " due to pos(" + bufferIndex + ")-(" +
            //                  (bufferRemainder + numSamplesPerPixel ) + ")");
            calcMinMaxForSamples(preIndexInt);
            previousMinValue = minValue;
            previousMaxValue = maxValue;

            for (; numPixelsDone < numSamples; bufferRemainder += numSamplesPerPixel, ++numPixelsDone) {
                final long indexInt = bufferIndex + bufferRemainder;
                //               log.debug("Backwards paint pixel (" + numPixelsDone + ") from index(" + indexInt +")");

                calcMinMaxForSamples(indexInt);
                extendMinMaxWithPrevious();

                fillInMinMaxLine(g, bufferSampleOffset + numPixelsDone, minValue, maxValue);

                previousMinValue = minValue;
                previousMaxValue = maxValue;
            }
            final int numSamplesToMove = numPixelsDone * numSamplesPerPixel;
            //            log.debug("Moving position (" + lastBufferPos + ") backward " + numSamplesToMove + " samples");
            lastBufferPos -= numSamplesToMove;
            break;
        }
        default: {
            //            g.setColor(Color.GREEN );

            final int offsetToForwardEdge = numSamplesPerPixel * (displayWidthMinusOneOverTwo + 1);

            final long bufferIndex = lastBufferPos;
            int bufferRemainder = offsetToForwardEdge;

            //            log.debug("Delta forwards starting from bufferIndex(" + bufferIndex + ")");

            // Fill in previous min max from pixel before (rough is good enough)
            final long preIndexInt = bufferIndex + bufferRemainder - numSamplesPerPixel;
            //            log.debug("So previous top value was as index " + preIndexInt + " due to pos(" + bufferIndex + ")-(" +
            //                  (bufferRemainder - numSamplesPerPixel ) + ")");
            calcMinMaxForSamples(preIndexInt);
            previousMinValue = minValue;
            previousMaxValue = maxValue;

            for (; numPixelsDone < numSamples; bufferRemainder += numSamplesPerPixel, ++numPixelsDone) {
                final long indexInt = bufferIndex + bufferRemainder;
                //               log.debug("Forwards paint pixel (" + numPixelsDone + ") from index(" + indexInt +")");

                calcMinMaxForSamples(indexInt);
                extendMinMaxWithPrevious();

                final int xOffset = bufferSampleOffset + numPixelsDone;
                fillInMinMaxLine(g, xOffset, minValue, maxValue);

                previousMinValue = minValue;
                previousMaxValue = maxValue;
            }

            final int numSamplesToMove = numPixelsDone * numSamplesPerPixel;

            //            log.debug("Moving position (" + lastBufferPos + ") forward " + numSamplesToMove + " samples");

            lastBufferPos += numSamplesToMove;

            break;
        }
        }

        //      log.debug("Delta fill did " + numPixelsDone );
    }

    @Override
    public RollPaintUpdate getPaintUpdate() {
        final int numPixelsAvailableInt = getNumSamplesAvailable();
        updateStructure.setDirection(
                (numPixelsAvailableInt > 0 ? RollPaintDirection.FORWARDS : RollPaintDirection.BACKWARDS));
        final int absNpa = Math.abs(numPixelsAvailableInt);
        updateStructure.setNumSamplesInUpdate(numPixelsAvailableInt);

        if (absNpa == 0 && !needsFullUpdate) {
            updateStructure.setUpdateType(RollPaintUpdateType.NONE);
        } else if (absNpa > displayWidth || absNpa > 0 && numSamplesPerPixel < 4.0f) {
            resetForFullRepaint();
        }

        if (needsFullUpdate) {
            needsFullUpdate = false;
            updateStructure.setUpdateType(RollPaintUpdateType.FULL);
        } else {
            if (absNpa > 0) {
                updateStructure.setUpdateType(RollPaintUpdateType.DELTA);
            } else {
                updateStructure.setUpdateType(RollPaintUpdateType.NONE);
            }
        }

        return updateStructure;
    }

    @Override
    public SoundfileDisplayBufferClearer getBufferClearer() {
        return bufferClearer;
    }

    @Override
    public SoundfileDisplayBuffer createBuffer(final int bufNum) throws DatastoreException {
        return new SoundfileDisplayBuffer(bufNum, bia, displayWidth, displayHeight, allocationMatch);
    }

    @Override
    public void freeBuffer(final SoundfileDisplayBuffer bufferToFree) throws DatastoreException {
        bia.freeBufferedImage(bufferToFree.tbi);
    }

    public void setSampleCacheClient(final SampleCacheClient scc) {
        this.scc = scc;
        receivedBufferPos = scc.getCurrentFramePosition();
        computeSamplesPerPixel();
        resetForFullRepaint();
    }

    private void computeSamplesPerPixel() {
        if (scc == null) {
            numSamplesPerPixel = DEFAULT_SAMPLES_PER_PIXEL;
        } else {
            final int sccSampleRate = scc.getSampleRate();
            final int numTotalSamples = AudioTimingUtils.getNumSamplesForMillisAtSampleRate(sccSampleRate,
                    captureLengthMillis);
            this.numSamplesPerPixel = (int) (numTotalSamples / (float) displayWidth);
        }
        //      log.debug("Reset num samples per pixel to " + numSamplesPerPixel );
    }

    public void setCaptureLengthMillis(final float captureLengthMillis) {
        this.captureLengthMillis = captureLengthMillis;
        computeSamplesPerPixel();
        resetForFullRepaint();
    }

    public void resetForFullRepaint() {
        lastBufferPos = receivedBufferPos;
        needsFullUpdate = true;
        //      log.debug("Resetting for full repaint");
    }

    public void setCurrentPosition(final long newPosition) {
        receivedBufferPos = newPosition;
    }

    private long getNumReadable() {
        // Need the sign, too as that indicates direction
        return receivedBufferPos - lastBufferPos;
    }

    private void calcMinMaxForSamples(final long sampleStartIndex) {
        minMaxSampleAcceptor.reset();
        long endIndex = sampleStartIndex + numSamplesPerPixel;

        if (numSamplesPerPixel < 1.0f) {
            endIndex = sampleStartIndex + 1;
        }

        final RealtimeMethodReturnCodeEnum retCode = sampleCachingService.readSamplesInBlocksForCacheClient(scc,
                sampleStartIndex, (int) (endIndex - sampleStartIndex), minMaxSampleAcceptor);

        if (retCode != RealtimeMethodReturnCodeEnum.SUCCESS) {
            log.error("Failed during min max fetch of sample blocks using acceptor");
        }

        minValue = minMaxSampleAcceptor.minMaxValues[0];
        maxValue = minMaxSampleAcceptor.minMaxValues[1];
    }

    private void extendMinMaxWithPrevious() {
        if (previousMaxValue < minValue) {
            minValue = previousMaxValue;
        }
        if (previousMinValue > maxValue) {
            maxValue = previousMinValue;
        }
    }

    private void fillInMinMaxLine(final Graphics2D g, final int pixelX, final float minValue,
            final float maxValue) {
        final float multiplier = displayHeight / 2.0f;

        final int yMinVal = (int) (-(minValue * displayMultiplier) * multiplier + multiplier);
        final int yMaxVal = (int) (-(maxValue * displayMultiplier) * multiplier + multiplier);
        g.drawLine(pixelX, yMinVal, pixelX, yMaxVal);
    }

    public void setDisplayMultiplier(final float displayMultiplier) {
        this.displayMultiplier = displayMultiplier;
    }
}