3

I'm wanting to make a Java program that can generate a note, and change it's timbre and pitch, whilst it's playing.

I want to change the timbre by using numbers, so I could smoothly transition from one timbre to another, and the same goes for pitch.

My goal is to run some code like this

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

At the moment I have no understanding of how sound is composed on a PC, so really I'm just asking if anyone knows the shortest path for me to learn how to do just these 2 things.

I'm hoping the maths will be simple, but it's looking unlikely.

pandagoespoop
  • 59
  • 1
  • 4
  • The mathematics are difficult. Google "timbre and periodic functions" as a start. The pitch part will be simpler: the frequency doubles for every octave. Perhaps that's a more realistic starting point. – Bathsheba Jul 22 '14 at 13:42
  • possible duplicate of [Does java have built in libraries for audio \_synthesis\_?](http://stackoverflow.com/questions/2064066/does-java-have-built-in-libraries-for-audio-synthesis) – DavidPostill Jul 22 '14 at 14:54

1 Answers1

0

This is a tone synthesizer in Java with several features.

  • Control of pitch (in cycles per second)
  • Control of amplitude (in digital steps)
  • Min and max statistics to indicate when the amplitude is too high (which causes the distortion of the tone from audio clipping)
  • Control of tone duration (in seconds)
  • Control of the relative amplitude of an arbitrary number of harmonics (which determines tone quality or waveform, which is one of several factors that create a musical note's timbre)

The real time modification of the amplitude of the principle harmonic and other harmonics over the duration is not one of the features of the SimpleSynth class. A physical instrument's note has that feature. The SimpleSynth class does not play sequences of notes, but it could be modified to do so with relative ease.

Mainly, it is a good demonstration of the principles of audio synthesis, harmonics, and use of Java to play a sequence of digital audio samples through whatever the default audio output is at run time. There is no facility to phase shift the harmonics either, but that is a somewhat irrelevant shortcoming, since phase characteristics of harmonics is not something the human ear is capable of detecting.

The math is explained in the comments section of the playSound() method. If you took even the most basic trigonometry class, you have the math theory to understand this program. Reading up a little on digital audio, signal sampling, and how to pluck harmonics on a stringed instrument such as a guitar may aid in comprehension too.

Standard digital audio terminology was used for both constant and variable naming. If the desire is to modify pitch and timbre during the course of a note, the math to produce that effect is not trivial, but once the math below is learned, a foundation of understanding will exist and further synthesis features can be added incrementally.

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);
    }
}
Douglas Daseeco
  • 3,475
  • 21
  • 27