Есть ли в Java встроенные библиотеки для аудио _synthesis_? - PullRequest
17 голосов
/ 14 января 2010

Примечание. Я НЕ хочу «читать аудио файл foo.bar и воспроизводить его».

Я хочу программно генерировать аудио файлы на лету и воспроизводить их.

Поддерживает ли Javaвстроили библиотеки для этого, или это относится к системно-зависимым библиотекам?

Спасибо!

Ответы [ 7 ]

41 голосов
/ 14 января 2010

Используя подход Эндрю , вот пример, который играет равную темперированную шкалу .

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

public class Tone {

    public static void main(String[] args) throws LineUnavailableException {
        final AudioFormat af =
            new AudioFormat(Note.SAMPLE_RATE, 8, 1, true, true);
        SourceDataLine line = AudioSystem.getSourceDataLine(af);
        line.open(af, Note.SAMPLE_RATE);
        line.start();
        for  (Note n : Note.values()) {
            play(line, n, 500);
            play(line, Note.REST, 10);
        }
        line.drain();
        line.close();
    }

    private static void play(SourceDataLine line, Note note, int ms) {
        ms = Math.min(ms, Note.SECONDS * 1000);
        int length = Note.SAMPLE_RATE * ms / 1000;
        int count = line.write(note.data(), 0, length);
    }
}

enum Note {

    REST, A4, A4$, B4, C4, C4$, D4, D4$, E4, F4, F4$, G4, G4$, A5;
    public static final int SAMPLE_RATE = 16 * 1024; // ~16KHz
    public static final int SECONDS = 2;
    private byte[] sin = new byte[SECONDS * SAMPLE_RATE];

    Note() {
        int n = this.ordinal();
        if (n > 0) {
            double exp = ((double) n - 1) / 12d;
            double f = 440d * Math.pow(2d, exp);
            for (int i = 0; i < sin.length; i++) {
                double period = (double)SAMPLE_RATE / f;
                double angle = 2.0 * Math.PI * i / period;
                sin[i] = (byte)(Math.sin(angle) * 127f);
            }
        }
    }

    public byte[] data() {
        return sin;
    }
}

Этот низкоуровневый подход может подойти для старых, менее способных платформ. Также рассмотрим javax.sound.midi; полный пример показан здесь и здесь приводится учебник *1013* Synthesizing Sound здесь .

7 голосов
/ 25 декабря 2014

Самый простой способ сделать это с помощью встроенных в MIDI библиотек Java:

int channel = 0; // 0 is a piano, 9 is percussion, other channels are for other instruments

    int volume = 80; // between 0 et 127
    int duration = 200; // in milliseconds

    try {
        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();
        MidiChannel[] channels = synth.getChannels();

        // --------------------------------------
        // Play a few notes.
        // The two arguments to the noteOn() method are:
        // "MIDI note number" (pitch of the note),
        // and "velocity" (i.e., volume, or intensity).
        // Each of these arguments is between 0 and 127.
        channels[channel].noteOn( 60, volume ); // C note
        Thread.sleep( duration );
        channels[channel].noteOff( 60 );
        channels[channel].noteOn( 62, volume ); // D note
        Thread.sleep( duration );
        channels[channel].noteOff( 62 );
        channels[channel].noteOn( 64, volume ); // E note
        Thread.sleep( duration );
        channels[channel].noteOff( 64 );

        Thread.sleep( 500 );

        // --------------------------------------
        // Play a C major chord.
        channels[channel].noteOn( 60, volume ); // C
        channels[channel].noteOn( 64, volume ); // E
        channels[channel].noteOn( 67, volume ); // G
        Thread.sleep( 3000 );
        channels[channel].allNotesOff();
        Thread.sleep( 500 );



        synth.close();
    }
    catch (Exception e) {
        e.printStackTrace();
    }
3 голосов
/ 24 февраля 2017

Встроенные возможности Java Midi

В стандартной комплектации Java 8 JRE определенно есть то, что вы специально запросили: встроенная библиотека синтезатора. Это описано более подробно в Synthesizing Sound .

A довольно изысканный пример обеспечивает визуальный доступ к клавиатуре для семпла сэмплированной музыки.

Библиотека javax.sound.midi содержит инструменты, которые могут воспроизводить ноты, основанные на технологии MIDI и сэмплированных инструментах. Звуки не настолько аутентичны, как звуки классической линии музыкальных инструментов Kurzweil, но фреймворк поддерживает этот уровень сложности, если вы хотите сделать свою собственную выборку в нескольких диапазонах высоты звука для одного инструмента и проработать детали довольно плавного перехода между диапазонами.

Простой пример быстрого просмотра использования

Вот тривиальный пример класса.

import javax.sound.midi.MidiSystem;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.MidiChannel;

public class PlayMidiNote
{
    private static void sleep(int iUSecs)
    {
        try
        {
            Thread.sleep(iUSecs);
        }
        catch (InterruptedException e)
        {
        }
    }

    public static void main(String[] args) throws Exception
    {
        if (args.length < 3 && args.length > 4)
        {
            System.out.println("usage: java PlayNote
                    <8.bit.midi.note.number> <8.bit.velocity>
                    <usec.duration> [<midi.channel>]");
            System.exit(1);
        }

        int iMidiKey = Math.min(127, Math.max(0,
                Integer.parseInt(args[0])));
        int iVelocity = Math.min(127, Math.max(0,
                Integer.parseInt(args[1])));
        int iUSecsDuration = Math.max(0,
                Integer.parseInt(args[2]));
        int iChannel = args.length > 3
            ? Math.min(15, Math.max(0,
                    Integer.parseInt(args[3])))
            : 0;

        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();

        MidiChannel[] channels = synth.getChannels();
        MidiChannel channel = channels[iChannel];

        channel.noteOn(iMidiKey, iVelocity);
        sleep(iUSecsDuration);
        channel.noteOff(iMidiKey);

        synth.close();
    }
}

Использование многопоточности или реализаций javax.sound.midi.Sequencer, подобных тем, которые доступны на GitHub.com, обеспечит структуру, необходимую для создания музыки.

Генерация формы волны

Если вы хотите генерировать свои собственные формы волны, а не использовать сэмплы, тогда ответьте на свой вопрос «Нет», однако вы можете поиграть с этим синтезатором тонов, который я написал для этого вопроса. Он имеет несколько функций.

  • Контроль высоты тона (в циклах в секунду)
  • Контроль амплитуды (в цифровых шагах)
  • Минимальная и максимальная статистика, указывающая, когда амплитуда слишком высока (что вызывает искажение тона при отсечении звука)
  • Контроль длительности тона (в секундах)
  • Управление относительной амплитудой произвольного числа гармоник (которое определяет качество тона или форму волны, которая является одним из нескольких факторов, создающих тембр музыкальной ноты)

В этом синтезаторе отсутствуют многие особенности высокопроизводительных синтезаторов, генерирующих сигналы.

  • Модификация в реальном времени амплитуды основной гармоники и других гармоник на протяжении ноты
  • Не воспроизводит последовательности нот (но может быть изменено, чтобы сделать это с относительной легкостью)
  • Также нет возможности для сдвига фаз гармоник (но это несколько несущественный недостаток, так как фазовые характеристики гармоник не то, что человеческое ухо способно обнаружить)

SimpleSynth - хорошая демонстрация

  • Принципы синтеза звука
  • Тембральный эффект гармоник
  • Использование Java для генерации массивов аудиосэмплов
  • Размещение семплов в байтовом массиве
  • Передача такого байтового массива в ОС через строку

Стандартная цифровая звуковая терминология использовалась как для постоянного, так и для переменного именования. Математика объясняется в комментариях.

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

class SimpleSynth
{
    private static int SAMPLE_BITS = 16;
    private static int CHANNELS = 1;
    private static boolean SIGNED_TRUE = true;
    private static boolean BIG_ENDIAN_FALSE = false;
    private static float CDROM_SAMPLE_FREQ = 44100;

    private SourceDataLine line;

    private double dDuration;
    private double dCyclesPerSec;
    private double dAmplitude;
    private double[] adHarmonics;

    private double dMin;
    private double dMax;

    public SimpleSynth(String[] asArguments) throws Exception
    {
        dDuration = Double.parseDouble(asArguments[0]);
        dCyclesPerSec = Double.parseDouble(asArguments[1]);
        dAmplitude = Double.parseDouble(asArguments[2]);
        adHarmonics = new double[asArguments.length - 3];
        for (int i = 0; i < adHarmonics.length; ++ i)
            adHarmonics[i] = Double.parseDouble(
                    asArguments[i + 3]);

        AudioFormat format = new AudioFormat(
                CDROM_SAMPLE_FREQ, SAMPLE_BITS,
                CHANNELS, SIGNED_TRUE, BIG_ENDIAN_FALSE);
        line = AudioSystem.getSourceDataLine(format);
        line.open();
        line.start();
    }

    public void closeLine()
    {
        line.drain();
        line.stop();
    }

    public void playSound()
    {
        // allocate and prepare byte buffer and its index
        int iBytes = (int) (2.0 * (0.5 + dDuration)
                * CDROM_SAMPLE_FREQ);
        byte[] ab = new byte[iBytes];
        int i = 0;

        // iterate through sample radian values
        // for the specified duration
        short i16;
        double dSample;
        double dRadiansPerSample = 2.0 * Math.PI
                * dCyclesPerSec / CDROM_SAMPLE_FREQ;
        double dDurationInRadians = 2.0 * Math.PI
                * dCyclesPerSec * dDuration;
        dMin = 0.0;
        dMax = 0.0;
        for (double d = 0.0;
                d < dDurationInRadians;
                d += dRadiansPerSample)
        {
            // add principle and the dot product of harmonics
            // and their amplitudes relative to the principle
            dSample = Math.sin(d);
            for (int h = 0; h < adHarmonics.length; ++ h)
                dSample += adHarmonics[h]
                        * Math.sin((h + 2) * d);

            // factor in amplitude
            dSample *= dAmplitude;

            // adjust statistics
            if (dMin > dSample)
                dMin = dSample;
            if (dMax < dSample)
                dMax = dSample;

            // store in byte buffer
            i16 = (short) (dSample);
            ab[i ++] = (byte) (i16);
            ab[i ++] = (byte) (i16 >> 8);
        }

        // send the byte array to the audio line
        line.write(ab, 0, i);
    }

    public void printStats()
    {
        System.out.println("sample range was ["
                + dMin + ", " + dMax + "]"
                + " in range of [-32768, 32767]");

        if (dMin < -32768.0 || dMax > 32767.0)
            System.out.println("sound is clipping"
                    + "(exceeding its range),"
                    + " so use a lower amplitude");
    }

    public static void main(String[] asArguments)
            throws Exception
    {
        if (asArguments.length < 3)
        {
            System.err.println("usage: java SimpleSynth"
                    + " <duration>"
                    + " <tone.cycles.per.sec>"
                    + " <amplitude>"
                    + " [<relative.amplitude.harmonic.2>"
                    + " [...]]");
            System.err.println("pure tone:"
                    + " java SimpleSynth 1 440 32767");
            System.err.println("oboe-like:"
                    + " java SimpleSynth 1 440 15000  0 1 0 .9");
            System.err.println("complex:"
                    + " java SimpleSynth 1 440 800 .3"
                    + " .5 .4 .2 .9 .7 5 .1 .9 12 0 3"
                    + " .1 5.2 2.5 .5 1 7 6");

            System.exit(0);
        }

        SimpleSynth synth = new SimpleSynth(asArguments);
        synth.playSound();
        synth.closeLine();
        synth.printStats();

        System.exit(0);
    }
}

Темы обучения для расширения опыта музыкального синтеза

Есть несколько тем для прочтения, если вы хотите стать экспертом по цифровому синтезу.

  • Цифровое аудио
  • Выборка сигнала
  • Критерии Найквиста
  • Как срывать гармоники на струнном инструменте (например, на гитаре)
  • Основная тригонометрия, в частности, функция синуса
2 голосов
/ 14 января 2010

Вы смотрели на JSyn ? Я не думаю, что библиотеки Java Core могут делать то, что вы хотите.

1 голос
/ 18 января 2010

Jcollider - это интерфейс Java к серверу синтеза SuperCollider. Если вы хотите синтезировать музыку, это значительно упростит задачу (она абстрагируется от генератора тонов до синтезатора, заботится о таких вещах, как генерация графа, удаление приглушенных синтезаторов из графа синтеза до тех пор, пока они не понадобятся снова, исправление сигналов между синтезаторами динамически и т. д.).

1 голос
/ 14 января 2010

См. Java Sound API .

Глядя немного больше, я также нашел Jass .

0 голосов
/ 14 января 2010

В этом сообщении на форуме Sun есть интересный код для генерации синусоидальных тонов. Кроме того, учитывая, что формат файла WAV не слишком сложен, вы можете создать таблицу, представляющую желаемый сигнал, а затем записать ее в файл. Есть несколько примеров, например, необработанный аудио конвертер и как записать файл wav .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...