0

Been testing a new system for increasing/decreasing the audio within my JavaSwing application using a fade in and fade out effect. This works on one hand though by doing my own research, it's not as optimal as one hoped to be since it utilizes Thread.sleep(x); rather than using Timer(); from JavaSwing.

        
        FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        value = (value <= 0.0) ? 0.0001 : ((value > 1.0) ? 1.0 : value);
        
        try {
            float db = (float) (Math.log(percent) / Math.log(10.0) * 20.0);
            gainControl.setValue(db);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    public static void shiftVolumeTo(double value) {
        
        value = (value <= 0.0) ? 0.0001 : ((value > 1.0) ? 1.0 : value);
        targetDB = (float)(Math.log(value)/Math.log(10.0)*20.0);
        if (!fading) {
            Thread t = new Thread();
            t.start();
        }
    }
    
    public static void run() {
        
        FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        
        fading = true;
        if(currDB > targetDB) {
            while (currDB > targetDB) {
                currDB -= fadePerStep;
                gainControl.setValue(currDB);
                
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
            } 
        } else if (currDB < targetDB) { 
            while(currDB < targetDB) {
                currDB += fadePerStep;
                gainControl.setValue(currDB);
                
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        fading = false;
        currDB = targetDB;
    }
    
    public static void volTest() {
        setVolume(1);
        shiftVolumeTo(.2);
        run();
    }

Again, this works properly when run(); is called, but it puts the entire application to sleep within the duration of the while loop's runtime. Which is what I'm having an issue with. I've tried setting up a JavaSwing Timer(); within the code though I couldn't figure out how to set it up properly to make the delay run like the Thread.sleep(x); method. Whether it is adding the code from run(); into the ActionListener created or utilized by the timer or just having run(); within the ActionListener. Unlike Thread.sleep(x);, the program will just jump values without taking it's time to decrease the volume as there's no delay between the incremental increase/decrease of the while loop.

What would be the optimal way to utilize a JavaSwing Timer(); that would work similar to a Thread.sleep(x); in this situation?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Providence
  • 59
  • 5
  • 2
    When you use a Swing Timer there is no loop. The Timer is the loop. You simply update the value every time the Timer event is fired. When you reach a certain level you stop the Timer. Check out: https://stackoverflow.com/questions/7816585/program-freezes-during-thread-sleep-and-with-timer/7816604#7816604 for a basic example that just runs for 10 seconds. – camickr May 19 '22 at 14:35
  • @camickr I see what you mean. What I'm guessing is that all the code within run(); is supposed to be into the timer's actionPerformed. I believe I tried that and I still didn't necessarily get the result I was looking for, though I'll give it another shot. The timer was working yesterday but now today, the timer isn't even being responsive when called. I'll keep working on it. Thank you. – Providence May 19 '22 at 21:09

1 Answers1

2

I'm not clear what you are doing from the given code. I suspect you are mixing up aspects of the util.Timer with the swing.Timer (not uncommon).

With the util.Timer, one extends TimerTask, and within that, one overrides the run() method. The run() method is never static, afaik.

With the swing.Timer, the code to be executed resides in the actionPerformed() method of the ActionListener.

Another alternative, and likely a better way to manage the separate thread would be to use a ScheduledThreadPoolExecutor.

I'd consider using the util.Timer in this case. I think the calls to change the value of the FloatControl could be set up to be thread safe if you are careful about limiting the calls to a single sound at a time. (Here is a link for more about Swing's Event Dispatch Thread) It would be necessary to use the swing.Timer if thread safety is an issue.

But you understand here that you can only fade in or out a single sound at a time, and ALL sounds playing at that moment will also be affected? The FloatControl.MASTER_GAIN controls all playing sounds. Also, I don't think there are no guarantees that the FloatControls are implemented on a given computer. There are many listed in the documentation that I have discovered do not function on various PCs I own.

Another issue is that sometimes it can be difficult to find the best interval for the updates and corresponding amount to vary the volume. Sometimes we get as soft, fast sequence of clicks, like the sound of scraping a thumbnail over the tines of a comb, if the individual volume changes are too large.

I'd like to mention a library for you to consider that bypasses the use of the FloatControl: AudioCue. The class is basically a rewrite of the Clip but with a built in, dynamic, volume controller. When you alter the volume of a given playback instance, it will only affect that single instance. (The class supports playing back concurrent instances.) All the amplitude computations are done internally, within the code of the class.

The library can be run as a Maven resource. The project is a work in progress: I've just added a lot of test code that I'm about to push to the master. I'm working to publish it on Maven Central. (Learning as I go.) But if you fork the project and run the Maven install command, it will create a resource that you can reference locally via the Maven pom file.

I'm considering adding an optional duration argument to the dynamic controls. Then (if implemented) one could specify both the desired volume and the number of frames or milliseconds over which the change should be applied. Seems like a good addition for version 1.2 (after 1.1 is up on Maven central!)

With AudioCue, changes are applied per individual frames. I'm not sure what the situation is with FloatControl and Clips. The changes might be limited in granularity to some internal buffer size.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41