Set volume of Java Clip

2019-06-07 07:05发布

问题:

Is there any way to set the respective volume of a Clip in Java?

I have this method:

public static void play(Clip clip) {
    if (Settings.getSettings().isVolumeOn()) {
        FloatControl volume = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        volume.setValue(-1 * Settings.getSettings().getVolume());
        clip.start();
    }
}

The Settings.getSettings().getVolume() returns an Integer in the range of 0 - 100

Volumes:

  1. 0 : No Sound
  2. 40 : Optimal Sound with headphones
  3. 60 : Optimal Sound
  4. 100: Full Sound

So essentially this should be like the scale of VLC (but half since VLC is from 0 to 200).

I've found that I can reduce the decibel of the clip by using volume.setValue(-10f);

But I would prefer something of the type volume.setValue(clip.getMaxVolume() * Settings.getSettings().getVolume()/100).

Where clip.getMaxVolume() would return the max volume of the clip.

回答1:

According to Killer Game Programming by Andrew Davison you can adjust volume this simple:

float range = gainControl.getMaximum() - gainControl.getMinimum();
float gain = (range * volume) + gainControl.getMinimum();
gainControl.setValue(gain);

volume being the desired volume in float (0.0f means no sound, 1.0f means full audio) gainControl is the FloatControl

Hope you can get it to work!



回答2:

The MASTER_GAIN FloatControl value is in decibels, meaning it's a logarithmic scale, not a linear one.

While decibels is great for audio professionals, us programmers usually want a nice linear scale where 0.0 is silent, 0.5 is half volume, and 1.0 is full volume; by which I mean normal volume for the sound sample. When playing sound effects, we don't usually amplify sound clips, just attenuate them.

Decibel (dB) to Float Value Calculator explains the maths behind converting decibels to a linear scale, but here is the code:

In Fantom:

using [java] javax.sound.sampled::Clip
using [java] javax.sound.sampled::FloatControl
using [java] javax.sound.sampled::FloatControl$Type as FType

...
private Clip clip

Float volume {
    get {
        gainControl := (FloatControl) clip.getControl(FType.MASTER_GAIN)
        return 10f.pow(gainControl.getValue / 20f)
    }
    set {
        if (it < 0f || it > 1f) throw ArgErr("Invalid volume: $it")
        gainControl := (FloatControl) clip.getControl(FType.MASTER_GAIN)
        gainControl.setValue(20f * it.log10)
    }
}

And converted to Java:

import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;

...

private Clip clip

public float getVolume() {
    FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);        
    return (float) Math.pow(10f, gainControl.getValue() / 20f);
}

public void setVolume(float volume) {
    if (volume < 0f || volume > 1f)
        throw new IllegalArgumentException("Volume not valid: " + volume);
    FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);        
    gainControl.setValue(20f * (float) Math.log10(volume));
}

Note there is no need to involve clip.getMaxVolume() or clip.getMinVolume() because a volume of 1.0 corresponds to 0 Db (no change) and a volume of 0.1 corresponds to -20 Db (very, very quiet).

If you did want to amplify the sound clip, then there's nothing stopping you from passing in 2.0 to double the normal volume, a-la VLC.



回答3:

So after a bit of trial and error, I came up with this:

public static void play(Clip clip) {
    if (Settings.getSettings().isVolumeOn()) {
        FloatControl control = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        int volume = Settings.getSettings().getVolume();
        float range = control.getMinimum();
        float result = range * (1 - volume / 100.0f);
        control.setValue(result);
        clip.start();
    }
}


回答4:

I believe you're actually looking for the FloatControl.Type.VOLUME control, rather than MASTER_GAIN. Try this:

public static void setVolume(Clip clip, int level) {
    Objects.requireNonNull(clip);
    FloatControl volume = (FloatControl) clip.getControl(FloatControl.Type.VOLUME);
    if (volume != null) {
        volume.setValue(level / 100.0);     
    }
}