【问题标题】:Generating a tone in Java and change it's timbre?用Java生成音调并改变它的音色?
【发布时间】:2014-09-13 08:43:28
【问题描述】:

我想制作一个 Java 程序,它可以生成一个音符,并在播放时改变它的音色和音高。

我想用数字来改变音色,这样我就可以从一种音色平滑过渡到另一种音色,音高也是如此。

我的目标是运行一些这样的代码

for(int i = 0; i < 20; i++ ) {
   toneGenerator.changeTimber( toneGenerator.timbre + 10.0 );
   toneGenerator.changePitch ( toneGenerator.pitch  + 10.0 );
}

目前我不了解声音是如何在 PC 上组成的,所以我真的只是想问是否有人知道我学习如何做这两件事的最短路径。

我希望数学很简单,但看起来不太可能。

【问题讨论】:

标签: java audio-processing


【解决方案1】:

这是一个 Java 音色合成器,具有多种功能。

  • 音高控制(以每秒周期为单位)
  • 幅度控制(数字步进)
  • 最小和最大统计数据表明振幅何时过高(这会导致音频削波导致音调失真)
  • 控制音调持续时间(以秒为单位)
  • 控制任意数量的谐波的相对幅度(这决定了音质或波形,这是创建音符音色的几个因素之一)

在持续时间内实时修改主谐波和其他谐波的幅度不是 SimpleSynth 类的功能之一。物理乐器的音符具有该功能。 SimpleSynth 类不播放音符序列,但可以相对轻松地对其进行修改。

主要是,它很好地演示了音频合成、谐波和使用 Java 通过运行时的默认音频输出播放一系列数字音频样本的原理。也没有对谐波进行相移的工具,但这是一个有点无关紧要的缺点,因为谐波的相位特性不是人耳能够检测到的。

在 playSound() 方法的 cmets 部分中解释了数学。如果你参加了最基本的三角学课程,你就有了理解这个程序的数学理论。阅读一些有关数字音频、信号采样以及如何在吉他等弦乐器上拨动谐波的知识也可能有助于理解。

标准数字音频术语用于常量和变量命名。如果希望在一个音符的过程中修改音高和音色,产生这种效果的数学并不是微不足道的,但是一旦学会了下面的数学,就会有一个理解的基础,并且可以逐步添加进一步的合成功能。

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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 2011-09-23
    • 1970-01-01
    相关资源
    最近更新 更多