OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Blame | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.utils;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

public class SoundGenerator {
    private int sampleRate = 44100; // 16 * 1024 or ~16KHz
    public static final double A4 = 440.;

    public static final byte FADE_NONE = 0;
    public static final byte FADE_LINEAR = 1;
    public static final byte FADE_QUADRATIC = 2;

    public static final byte WAVE_SIN = 0;
    public static final byte WAVE_SQUARE = 1;
    public static final byte WAVE_TRIANGLE = 2;
    public static final byte WAVE_SAWTOOTH = 3;

    private static final double BYTE_OSCI = 127.0; // max value for byte

    private static double linearFade(double i, int max) {
        return (max - (float) i) / max;
    }

    private static double quadraticFade(double i, int max) {
        // (-1/ max^2) * i^2 + 1
        return (-1 / Math.pow(max, 2)) * Math.pow(i, 2) + 1;
    }

    private static double sinWave(double i, double period) {
        return Math.sin(2.0 * Math.PI * i / period);
    }

    private static double squareWave(double i, double period) {
        return (i % period) / period < .5 ? 1 : -1;
    }

    private static double triangleWave(double i, double period) {
        return Math.asin(Math.sin((2 * Math.PI) * (i / period)));
    }

    private static double sawtoothWave(double i, double period) {
        return -1 + 2 * ((i % period) / period);
    }

    public void playGradient(double fstart, double fend, double duration, double volume, byte fadeend, byte wave) {
        byte[] freqdata = new byte[(int) (duration * sampleRate)];

        // Generate the sound
        for (int i = 0; i < freqdata.length; i++) {
            freqdata[i] = (byte) generateValue(i, duration, fstart + (fend - fstart) * (i / (double) freqdata.length), volume, fadeend, wave);
        }

        // Play it
        try {
            final AudioFormat af = new AudioFormat(sampleRate, 8, 1, true, true);
            SourceDataLine line = AudioSystem.getSourceDataLine(af);
            line.open(af, sampleRate);
            line.start();
            line.write(freqdata, 0, freqdata.length);
            line.drain();
            line.close();
        } catch (LineUnavailableException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Same as playSound but plays several frequences at the same time<br/>
     * <code>
     * double[] freqs = {440.0,440*1.5}; <br/>
     * SoundGenerator.playSound(freqs,1.0,0.5,SoundGenerator.FADE_LINEAR,SoundGenerator.WAVE_SIN);
     * </code>
     * 
     */
    public void playSounds(double[] freqs, double duration, double volume, byte fade, byte wave) {
        if (freqs.length == 0) {
            throw new IllegalArgumentException("no frequences");
        }
        volume = volume / freqs.length; // ease volume to avoid overflow

        double[][] soundData = new double[freqs.length][];

        for (int i = 0; i < soundData.length; i++) {
            soundData[i] = generateSoundData(freqs[i], duration, volume, fade, wave);
        }
        byte[] freqdata = new byte[soundData[0].length];

        for (int i = 0; i < soundData[0].length; i++) {
            for (int j = 0; j < soundData.length; j++) {
                freqdata[i] += (byte) (soundData[j][i]);
            }
        }

        // Play it
        try {
            final AudioFormat af = new AudioFormat(sampleRate, 8, 1, true, true);
            SourceDataLine line = AudioSystem.getSourceDataLine(af);
            line.open(af, sampleRate);
            line.start();
            line.write(freqdata, 0, freqdata.length);
            line.drain();
            line.close();
        } catch (LineUnavailableException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Play a sound at a given frequency freq during duration (in seconds) with volume as strenght
     * <br/>
     * <br/>
     * <code>SoundGenerator.playSound(440.0,1.0,0.5,SoundGenerator.FADE_LINEAR,SoundGenerator.WAVE_SIN);</code><br/>
     * Available fades : FADE_NONE, FADE_LINEAR, FADE_QUADRATIC<br/>
     * Available waves : WAVE_SIN, WAVE_SQUARE, WAVE_TRIANGLE, WAVE_SAWTOOTH<br/>
     */
    public void playSound(double freq, double duration, double volume, byte fade, byte wave) {

        double[] soundData = generateSoundData(freq, duration, volume, fade, wave);
        byte[] freqdata = new byte[soundData.length];

        for (int i = 0; i < soundData.length; i++) {
            freqdata[i] = (byte) soundData[i];
        }

        // Play it
        try {
            final AudioFormat af = new AudioFormat(sampleRate, 8, 1, true, true);
            SourceDataLine line = AudioSystem.getSourceDataLine(af);
            line.open(af, sampleRate);
            line.start();
            line.write(freqdata, 0, freqdata.length);
            line.drain();
            line.close();
        } catch (LineUnavailableException e) {
            throw new IllegalStateException(e);
        }
    }

    public double generateValue(double i, double duration, double freq, double volume, byte fade, byte wave) {
        double period = (double) sampleRate / freq;
        double fadeData = 0;
        double waveData = 0;
        int length = (int) (duration * sampleRate);

        switch (fade) {
        case FADE_NONE:
            fadeData = 1.0D;
            break;
        case FADE_LINEAR:
            fadeData = linearFade(i, length);
            break;
        case FADE_QUADRATIC:
            fadeData = quadraticFade(i, length);
            break;
        default:
            fadeData = 1.0D;
        }
        switch (wave) {
        case WAVE_SIN:
            waveData = sinWave(i, period);
            break;
        case WAVE_SQUARE:
            waveData = squareWave(i, period);
            break;
        case WAVE_TRIANGLE:
            waveData = triangleWave(i, period);
            break;
        case WAVE_SAWTOOTH:
            waveData = sawtoothWave(i, period);
            break;
        default:
            waveData = sinWave(i, period);
        }
        return (waveData * BYTE_OSCI * fadeData * volume);
    }

    public double[] generateSoundData(double freq, double duration, double volume, byte fade, byte wave) {
        double[] freqdata = new double[(int) (duration * sampleRate)];

        // Generate the sound
        for (int i = 0; i < freqdata.length; i++) {
            freqdata[i] = generateValue(i, duration, freq, volume, fade, wave);
        }
        return freqdata;
    }

    public void sampleRate(int sr) {
        sampleRate = sr;
    }

    public int sampleRate() {
        return sampleRate;
    }
}