Controlling volume of different sound files independent from each other using sliders

Viewed 43

I've been working on this project with a peer for comp sci class, and I can't figure out how to make SFX and music have their own independent volume controls. Both the music and the SFX both play just fine when they need to (hovering/clicking on buttons, menu music, etc), and I made sliders for both of them (music volume and SFX volume), but for some reason only music can be controlled. I have sat here for hours trying to figure out why it doesn't work. The FloatControl gain does update with the slider (I printed out it's value and it was working just fine, just not updating the actual volume of the sound effects). I also got rid of everything to do with the music by commenting out everything regarding it's slider controls, audio input stream, gain control, etc; it was as if SFX was the only audio related thing in the program, but it still didn't work. I don't understand why since the code is identical to the music's code (just that "music" is now "sound").

The below code is not meant to be functional on it's own since the actual code is 605 lines so far and that's too long to post on here. It does contain the core components, so if you want to set it up to work you have to make the JFrame and all the buttons, or have me post the full code and sound files.

import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.sound.sampled.*; 
import java.awt.event.*;
import javax.swing.border.Border; 
import javax.swing.BorderFactory;
import java.io.*;
import java.io.File.*; 
import java.io.IOException;

public class AudioTesting {
  
  // Declare Global Objects 
  
  static final String[] SFXName = {"menuHover", "buttonEnter", "buttonLeave", "gameStart"};
  static final String[] TrackName = {"Bossfight_Incomplete"};
  static final String userName = System.getProperty("user.name");
  
  static Clip sound;
  static Clip music;
  
  static JSlider musicVolumeSlider; 
  static JSlider soundVolumeSlider;
  
  public static void main(String []args){
    
    GameSetting();
  }
  
  public static void GameSetting(){
    
    musicVolumeSlider = new JSlider(-45,6,6);
    soundVolumeSlider = new JSlider(-45,6,6);
    
    musicVolumeSlider.setMajorTickSpacing(5);
    musicVolumeSlider.setMinorTickSpacing(1);
    musicVolumeSlider.setPaintTicks(true);
    //musicVolumeSlider.setPreferredSize(new Dimension(SLIDER_SIZE_X,SLIDER_SIZE_Y));
   // musicVolumeSlider.setBackground(menuColor);
    musicVolumeSlider.addChangeListener(new SliderListener());
    
    soundVolumeSlider.setMajorTickSpacing(5);
    soundVolumeSlider.setMinorTickSpacing(1);
    soundVolumeSlider.setPaintTicks(true);
 //   soundVolumeSlider.setPreferredSize(new Dimension(SLIDER_SIZE_X,SLIDER_SIZE_Y));
   // soundVolumeSlider.setBackground(menuColor);
    soundVolumeSlider.addChangeListener(new SliderListener());
    
  }
  
  //manages the loading and playing of sfx 
  public static void SFXManager(int sfx){
    try{
      sound = AudioSystem.getClip();
      File sfxFile = new File("C:\\Users\\" + userName + "\\Documents\\" + SFXName[sfx] + ".wav");
      sound.open(AudioSystem.getAudioInputStream(sfxFile));
      
    }catch (Exception ex){}
    sound.flush();
    sound.start();
  }
  
   // manages the loading and playing of music tracks
  public static void MusicManager(int track){
    try{
    music = AudioSystem.getClip();
    File musicFile = new File("C:\\Users\\" + userName + "\\Documents\\" + TrackName[track] + ".wav");
    music.open(AudioSystem.getAudioInputStream(musicFile));
    
    }catch (Exception ex){}
    music.start();
    music.loop(Clip.LOOP_CONTINUOUSLY); 
  }
  
  static class SliderListener implements ChangeListener {
    
    public void stateChanged(ChangeEvent e) throws IllegalArgumentException {
      float currentMusicVolume = 0;
      float currentSoundVolume = 0;
      
      FloatControl musicGainControl = (FloatControl) music.getControl(FloatControl.Type.MASTER_GAIN);
      FloatControl soundGainControl = (FloatControl) sound.getControl(FloatControl.Type.MASTER_GAIN);
 
     //-------------------------------------------------------------PROBLEM AREA v v v

      //For changing the volume of the music (this works just fine)
      
      if (e.getSource() == musicVolumeSlider){
        currentMusicVolume = musicVolumeSlider.getValue(); 
        
        //if the slider is all the way at it's lowest, set the volume to -80 (i.e. mute)
        if (currentMusicVolume == -45){
         currentMusicVolume = -80; 
        }
         musicGainControl.setValue(currentMusicVolume); // Reduce volume by slider value
      }
      
     
      //for changing the volume of the sfx
      if (e.getSource() == soundVolumeSlider){
        currentSoundVolume = soundVolumeSlider.getValue(); 
        
        //if the slider is all the way at it's lowest, set the volume to -80 (i.e. mute)
        if (currentSoundVolume == -45){
         currentSoundVolume = -80; 
        }
        soundGainControl.setValue(currentSoundVolume); // Reduce volume by slider value
      }
    }
    
  }

     //-------------------------------------------------------------PROBLEM AREA ^ ^ ^
  
  // I didn't implement it in here, but here is where all the mouse and event listeners go for the different buttons
  // If a button is hovered over or pressed, it plays a sound from the index that corresponds with that sound
  // I.e. if you hover over a button, call SFXManager(0); which plays the first sound in the array (buttonHover.wav) etc.
  

}   
1 Answers

I've found these float controls to be kind of hit-or-miss, in part because they depend on the local PC and its OS. It's often the case that only the master gain works, if you want to change volumes in real time. The master gain changes will affect ALL the playing sounds.

You could follow the suggestion from the last paragraphs of the Oracle sound tutorial on audio controls and write your own volume controls. To do so requires intercepting the audio input stream, compiling the bytes into PCM, multiplying the PCM by a volume factor, converting the PCM back to bytes, and finally, shipping the bytes out via a SourceDataLine.

This is basically the plan I followed when I wrote AudioCue. You are welcome to use that code base as an example for rolling your own, or to import and directly make use of the files within your project. I tried to follow the Clip API as closely as I could, while adding real time volume controls for each playing audio instance. One limitation is that it only works with wav files. But if you can work with that, feel free to use the library. I'm happy to answer any questions if you have any trouble setting it up.

I'm currently learning how to make the program available as a Maven resource. I'm not clear on how the Gradle instructions work--another contributor did that part. It is pretty easy to just copy the five files into your project and use them with just a little tinkering to the import/package lines.

Related