view game/SoundManager.java @ 138:9eb791e2fa17

Optimize board updating logic, so that the old placed tiles need not to be redrawn from scratch on each screen update, as they do not change usually.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 25 Nov 2011 11:04:09 +0200
parents 4c0dec72e2f0
children d6d92845d6a2
line wrap: on
line source

/*
 * Ristipolku Game Engine
 * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
 */
package game;

import java.util.*;
import java.io.*;
import game.*;
import javax.sound.sampled.*;


public class SoundManager extends ThreadGroup
{
    private boolean alive;
    private LinkedList queue;
    private int id;
    private static int poolID;

    private AudioFormat playbackFormat;
    private ThreadLocal localLine;
    private ThreadLocal localBuffer;
    private Object pausedLock;
    private boolean paused;


    public SoundManager(AudioFormat format)
    {
        this(format, getMaxSimultaneousSounds(format));
    }


    public SoundManager(AudioFormat format, int maxSounds)
    {
        super("SoundManagerPool-" + (poolID++));

        int numThreads = Math.min(maxSounds, getMaxSimultaneousSounds(playbackFormat));

        System.out.print("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n");

        setDaemon(true);
        alive = true;

        playbackFormat = format;
        localLine = new ThreadLocal();
        localBuffer = new ThreadLocal();
        pausedLock = new Object();

        queue = new LinkedList();
        for (int i = 0; i < numThreads; i++)
            new PooledThread().start();

        synchronized (this)
        {
            notifyAll();
        }
    }


    public static int getMaxSimultaneousSounds(AudioFormat playbackFormat)
    {
        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);

        Mixer.Info[] info = AudioSystem.getMixerInfo();
        System.out.print("SMGR.getMaxSimultaneousSounds() mixer information:\n");
        Mixer.Info select = null;
        for (Mixer.Info i : info)
        {
            System.out.print("  - '"+i.getName()+"'\n");
            if (i.getName().equals("Java Sound Audio Engine"))
                select = i;
        }

        Mixer mixer = AudioSystem.getMixer(select);

        Mixer.Info i = mixer.getMixerInfo();
        System.out.print("  * selected='"+i.getName()+"'\n");

        int maxLines = mixer.getMaxLines(lineInfo);
        if (maxLines == AudioSystem.NOT_SPECIFIED)
            maxLines = 8;

        System.out.print("  * maxLines="+maxLines+"\n");

        return maxLines;
    }


    protected void cleanUp()
    {
        System.out.print("SMGR.cleanUp()\n");
        // signal to unpause
        setPaused(false);

        System.out.print("SMGR.cleanUp(): closing mixer\n");

        // close the mixer (stops any running sounds)
        Mixer mixer = AudioSystem.getMixer(null);
        if (mixer.isOpen())
            mixer.close();

        System.out.print("SMGR.cleanUp(): leaving\n");
    }


    public void setPaused(boolean paused)
    {
        if (this.paused != paused)
        {
            synchronized (pausedLock)
            {
                this.paused = paused;
                if (!paused)
                {
                    // restart sounds
                    pausedLock.notifyAll();
                }
            }
        }
    }


    public boolean isPaused()
    {
        return paused;
    }


    public Sound getSound(String filename)
    {
        return getSound(getAudioInputStream(filename));
    }


    public Sound getSound(InputStream is)
    {
        return getSound(getAudioInputStream(is));
    }


    public Sound getSound(AudioInputStream audioStream)
    {
        if (audioStream == null)
            return null;

        // get the number of bytes to read
        int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());

        // read the entire stream
        byte[] samples = new byte[length];
        DataInputStream is = new DataInputStream(audioStream);
        try {
            is.readFully(samples);
            is.close();
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }

        // return the samples
        return new Sound(samples);
    }


    public AudioInputStream getAudioInputStream(String filename)
    {
        ResourceLoader res = new ResourceLoader(filename);
        if (res == null || res.getStream() == null)
        {
            System.out.print("Could not load audio resource '"+ filename +"'.\n");
            return null;
        }
        try {
            return getAudioInputStream(res.getStream());
        }
        catch (Exception ex)
        {
            System.out.print("Could not get AudioInputStream for '"+ filename +"'\n");
            return null;
        }
    }


    public AudioInputStream getAudioInputStream(InputStream is)
    {
        try {
            if (!is.markSupported())
                is = new BufferedInputStream(is);

            // open the source stream
            AudioInputStream source = AudioSystem.getAudioInputStream(is);

            // convert to playback format
            return AudioSystem.getAudioInputStream(playbackFormat, source);
        }

        catch (UnsupportedAudioFileException ex) {
            ex.printStackTrace();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
        catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }

        return null;
    }


    public InputStream play(Sound sound)
    {
        InputStream is;
        if (sound != null)
        {
            is = new ByteArrayInputStream(sound.getSamples());
            return play(is);
        }
        return null;
    }


    public InputStream play(InputStream is)
    {
        System.out.print("SMGR.play(is="+is+")\n");
        if (is != null)
        {
            runTask(new SoundPlayer(is));
        }
        return is;
    }


    protected void threadStarted()
    {
        synchronized (this)
        {
            try {
                wait();
            }
            catch (InterruptedException ex) { }
        }


        // use a short, 100ms (1/10th sec) buffer for filters that
        // change in real-time
        int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);

        // create, open, and start the line
        SourceDataLine line;
        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);

        System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n");

        try {
            line = (SourceDataLine) AudioSystem.getLine(lineInfo);
            line.open(playbackFormat, bufferSize);
        }
        catch (LineUnavailableException ex)
        {
            System.out.print("SMGR.threadStarted() line unavailable!\n");
            // the line is unavailable - signal to end this thread
            Thread.currentThread().interrupt();
            return;
        }

        // Change volume
        Control.Type ct = FloatControl.Type.MASTER_GAIN;
        if (line.isControlSupported(ct))
        {
            FloatControl c = (FloatControl) line.getControl(ct);
            c.setValue(-20f);
        }

        line.start();

        // create the buffer
        byte[] buffer = new byte[bufferSize];

        // set this thread's locals
        localLine.set(line);
        localBuffer.set(buffer);
    }


    protected void threadStopped()
    {
        System.out.print("SMGR.threadStopped()\n");
        SourceDataLine line = (SourceDataLine) localLine.get();
        if (line != null)
        {
            line.drain();
            line.close();
        }
    }


    protected class SoundPlayer implements Runnable
    {
        private InputStream source;

        public SoundPlayer(InputStream source)
        {
            this.source = source;
        }

        public void run()
        {
            // get line and buffer from ThreadLocals
            SourceDataLine line = (SourceDataLine) localLine.get();
            byte[] buffer = (byte[])localBuffer.get();

            if (line == null || buffer == null)
                return;

            // copy data to the line
            try {
                boolean playing = true;
                while (playing) {
                    // if paused, wait until unpaused
                    synchronized (pausedLock)
                    {
                        if (paused) {
                            try {
                                pausedLock.wait();
                            }
                            catch (InterruptedException ex) {
                                return;
                            }
                        }
                    }

                    // copy data
                    int bufPos = 0;
                    while (bufPos < buffer.length && playing)
                    {
                        int res = source.read(buffer, bufPos, buffer.length - bufPos);
                        if (res != -1)
                            bufPos += res;
                        else
                            playing = false;
                    }
                    if (playing)
                        line.write(buffer, 0, bufPos);
                }
            }

            catch (IOException ex) {
                ex.printStackTrace();
            }

        }
    }

    public synchronized void runTask(Runnable task)
    {
        if (!alive)
        {
            throw new IllegalStateException();
        }
        if (task != null)
        {
            queue.add(task);
            notify();
        }

    }

    protected synchronized Runnable getTask() throws InterruptedException
    {
        while (queue.size() == 0)
        {
            if (!alive)
                return null;

            wait();
        }
        return (Runnable) queue.removeFirst();
    }


    public synchronized void close()
    {
        System.out.print("SMGR.close()\n");

        if (alive)
        {
            System.out.print("SMGR.close(): alive queue clear\n");
            // Clear queue
            alive = false;
            queue.clear();
            interrupt();
        }

        cleanUp();
        System.out.print("SMGR.close(): leaving\n");
    }


    public void join()
    {
        System.out.print("SMGR.join()\n");
        cleanUp();

        synchronized (this)
        {
            alive = false;
            notifyAll();
        }

        Thread[] threads = new Thread[activeCount()];
        int count = enumerate(threads);
        for (int i = 0; i < count; i++)
        {
            try {
                threads[i].join();
            }
            catch (InterruptedException ex)
            {
            }
        }
    }


    private class PooledThread extends Thread
    {
        public PooledThread()
        {
            super(SoundManager.this, "SoundManagerPool-" + (id++));
        }

        public void run()
        {
            threadStarted();

            while (!isInterrupted()) {
                Runnable task = null;
                try {
                    task = getTask();
                }
                catch (InterruptedException ex)
                {
                }

                if (task == null)
                    break;

                try {
                    task.run();
                }
                catch (Throwable t) {
                    uncaughtException(this, t);
                }
            }
            threadStopped();
        }
    }
}