RainThunder

A procedural rain and thunder app: RainThunder.jar

Sample of audio output: RainThunderDemo.ogg

I wasn’t sure whether to put this in a new thread or add it to the “forest ambience” thread from a short while ago. I think this is ready to show, despite there being room for improvement. It is a mild thunderstorm, not supercharged, Hollywood-level drama.

The source sf/x files came from http://freesound.org/people/lennyboy/sounds/244053/, if you are curious as to the original sound cue. I use about half of this cue (7 seconds).

The main new component is a “ClipSectionPlayer” tool. With it, you can specify sections of clips (my equivalent of the Java Clip, not javax.audio.sampled.Clip itself), and play them as a chain. Start and end frames are required. At this point a single cross-fade length is used for all the seams.

I vary the playback frequency to get different “intensities” (ranging from something like 1/2 speed to 3x’s speed). At the faster playbacks, code adds more and longer sections to achieve similar lengths throughout the intensity range. It wouldn’t be too hard to make the event length an independent parameter.

One challenge was making the taper long enough not to sound abrupt with the faster playbacks, without bringing in a long sample. After trying several approaches, I tried a crude granular approach: the first half of the decay sample is broken into many short overlapping fragments which are chained. Most of the thunder events, thus are made from just a few longer sections for the body, followed by numerous but shorter sections from the “tapering” sample.

The rain and wind use a clip-slicer tool (explained in earlier posts). I made a crude low frequency (LF) “noise” tool, to vary the volume of the rain and wind. At this point, the tool varies the “wind” slightly before the “rain”, and errs on the side of subtlety. It seems to me, the LF noise wave should be filtered slightly (maybe with simple box filter) before using with the rain, on the theory that the rain reflects the sum of wind gusts occurring during its fall. Having a variable high-Q filter for the wind would be very helpful for more theatrical wind effects. As it stands, the wind is almost more a texture-thickener for the rain than something that reads clearly in its own right.

You should bundle every one of your effects into one big jar :slight_smile:
One question: what does the purple bar represent? I saw it moving but couldn’t understand it…
Oh, also: do you think we might be able to use your ambiance library/libraries in our games? I’d love to be able to implement that kind of sound effects in my next project!

Thanks!

I’m curious what the purple bar is that you are seeing. The code only should have a simple JavaFX GUI with buttons and sliders. Are you referring to the 3D-Sound program where I was trying out a time-delay based algorithm for implementing 3D audio? In that, the purple cylinders that rise and sink in place, corresponding to audio source locations. They are loudest when tallest, and quiet when they sink into the “chessboard” floor.

I’m quite perplexed in terms of figuring out what to make freely available or not. I don’t want to commit too much of the code yet, as part of my way of development includes rewriting fundamental parts of it as I learn more, and I don’t want to be encumbered by obligations for backwards compatibility. (Ran into this with SiVi a couple years ago.)

Yes, it is possible to combine the various into a single library jar.

J0, when you are ready to seriously consider going forward, maybe you can message me and check in on the status of things. (Same to anyone else.) I’d like to be part of a team, and the audio expertise is one of the main things I can bring. I’m also looking for situations to implement “real-world” examples of the library being used.

By purple bar I meant this here

https://scontent-lhr3-1.xx.fbcdn.net/v/t34.0-12/15239376_1641495132817189_2133382354_n.png?oh=04edf598e87b52b1315e426873be7699&oe=58405879

I now see it could very well be a separator… I’ve never used JavaFX honestly, so idk. On my computer the rightmost, say 4-5 pixels in width, move in a strange manner. Nevermind, my pc’s haunted.

If my current project gets anywhere near something that might see the light of day sometime, I’ll get back to you then :smiley:

Yes, just a separator. I put very little into the GUI. It is just a series of JavaFX HBoxes, stacked one on top of another in a GridPane. The “separator” is an empty HBox with a background color:


    HBox sectionBar = new HBox();
    sectionBar.setMinHeight(3);
    sectionBar.setStyle("-fx-background-color: #8080FF;");

Supposed to be light blue. Is kind of purple now that you mention it. It shouldn’t have moved! Computer needs an exorcist?

I look forward to seeing what you come up with.

Let’s bring this thread back to life shall we!

First of all—great work on this!

Like J0, I’d love to use something like this for one of my games. Would there be any possibility of you bundling up just the important classes into a jar? I’ll make sure to put you in my game’s credits!

[quote]Would there be any possibility of you bundling up just the important classes into a jar? I’ll make sure to put you in my game’s credits!
[/quote]
Sure. A mention would be great! We can also go back and let the recordists at FreeSound.org know as well. There is no fee–these are free–but the recordists like to hear when something has been used in an app. I’ve got links to the original files.

Actually, you can try running the jar as is, if you link it into your project. The main “excess” is the GUI class which is pretty lightweight and can just be ignored, probably.

I was noticing the other night that if I have two or three sound-playing jars open at the same time, this one starts experiencing dropouts. It runs fine, otherwise, and has low cpu usage afaik. But for sure, test it before making it a go for your game. I have been planning to investigate as to what the bottleneck might be but haven’t had a chance to follow through, yet. If it is happening in your game, please let me know and maybe we can look into it further.

Also–I can probably hone it down if you want to lose either the wind or rain parts or really do need to get rid of the GUI. The animation timer isn’t needed for the thunder, just the wind/rain.

Below is the “SoundHandler.java” class that is used to control the app. Are you able figure out how to use the jar via the public methods here? I apologize for some laziness in the coding that might cause confusion, e.g., should have renamed updateVolume() to updateParameter() since it was expanded to do more than just set volumes.

So, make an instance of SoundHandler, and call soundHandler.start() to get the audio engine rolling. Then use the other start/stops to turn things on/off, and updateVolume, using the enum of parameters, for the aspect you wish to control. The parameters updates are set to respond to normals (0…1) for the ranges.

Be sure and include the stop() method when shutting down, or else the thread pool executor in EventSchedulingThreadPool will keep running.

package com.adonax.weather;

import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.UnsupportedAudioFileException;

import com.adonax.pfaudio.cliptools.SuperSlicer;
import com.adonax.pfaudio.core.CoreMixer;
import com.adonax.pfaudio.core.JavaWrapper;
import com.adonax.pfaudio.events.EventSchedulingThreadPool;
import com.adonax.pfaudio.io.PFClipData;

import converter.OggVorbisToPCMFloats;
import javafx.animation.AnimationTimer;

public class SoundHandler {
	private CoreMixer mixer;
	private JavaWrapper jw;
		
	private SuperSlicer windSlicer, rainSlicer;
	private float baseWindVol, baseRainVol;
	private float turbulence;
	
	private ThunderAmbience thunder;
	
	public enum Attribute { WIND_VOLUME, RAIN_VOLUME, TURBULENCE, THUNDER_VOLUME, THUNDER_INTENSITY,
		THUNDER_FIXED_PAUSE, THUNDER_RANDOM_PAUSE};
	
	public SoundHandler() throws UnsupportedAudioFileException, IOException 
	{
		mixer = new CoreMixer();
		jw = new JavaWrapper(mixer);	
		new EventSchedulingThreadPool(10);
		
		float[] pcmFloats;
		PFClipData clipData;
		InputStream inputStream = null;

		// * * * * * * * * 
		//   Forest Wind in Trees
		// * * * * * * * * 
		inputStream = this.getClass().getResourceAsStream(
				"../wind/audio/25945__inchadney__owl__WIND_IN_TREES.ogg");
		
		if (inputStream == null) System.out.println("inputStream not found");
		
		
		pcmFloats = OggVorbisToPCMFloats.makePCMFloats(inputStream, 141_324);
		clipData = new PFClipData("wind in trees ogg", pcmFloats);
		windSlicer = new SuperSlicer(clipData, 44100 / 2, 1024 * 8);
		
		mixer.addTrack(windSlicer);
		
		// rain 
		inputStream = this.getClass().getResourceAsStream(
				"../wind/audio/244053__lennyboy__thunder_rainclip.ogg");
		
		if (inputStream == null) System.out.println("inputStream not found");
		
		
		pcmFloats = OggVorbisToPCMFloats.makePCMFloats(inputStream, 93_904);
		clipData = new PFClipData("rain ogg", pcmFloats);
		rainSlicer = new SuperSlicer(clipData, 44100 / 2, 1024 * 4);
		
		mixer.addTrack(rainSlicer);
		
		// thunder
		thunder = new ThunderAmbience();
		mixer.addTrack(thunder);
		
		LFPeriodicNoise wave = new LFPeriodicNoise(30);
		
		AnimationTimer timer = new AnimationTimer() 
		{
			final int TUBE_LEN = 24; // 60 = 1 second delay
			float[] delayTube = new float[TUBE_LEN];
			int iWrite, iRead;
			
			void updateDelayTube()
			{
				iWrite++;
				iRead++;
				if (iWrite >= TUBE_LEN) iWrite = 0;
				if (iRead >= TUBE_LEN) iRead = 0;
			}
			
			@Override
			public void handle(long arg0) 
			{
				wave.setPeriod((int)((1 - turbulence) * 60) + 30 );
	
				float flt = wave.tick() * (turbulence/3);
				delayTube[iWrite] = flt;
				rainSlicer.setVolume( baseRainVol * (1 + delayTube[iRead]) );
				windSlicer.setVolume( baseWindVol * (1 + flt/2) );
				
				updateDelayTube();
			}

		};
		timer.start();
	}
	
	// *************************
	//    INTERFACE METHODS
	// *************************
	
	public void start()
	{				
		jw.start(null);
	}	
	
	public void stop()
	{
		jw.stop();
		EventSchedulingThreadPool.shutdown();
	}
	
	// Inputs should be normalized [0, 1]
	public void updateVolume(Attribute attribute, float value)
	{
		switch(attribute)
		{
		case WIND_VOLUME:
			baseWindVol = value / 8;
			
			break;

		case RAIN_VOLUME:
			baseRainVol = value / 6;
			
			break;

		case TURBULENCE:
			turbulence = value;
			break;
			
		case THUNDER_INTENSITY:
			thunder.setIntensity(value);
			break;
		
		case THUNDER_VOLUME:
			thunder.setMasterVolume(value);
			break;

		case THUNDER_FIXED_PAUSE:
			thunder.setFixedPausePart(value);
			break;
		case THUNDER_RANDOM_PAUSE:
			thunder.setRandomPausePart(value);
			break;

		default:
			break;
			
		}
	}	
	
	public void startWind()
	{
		windSlicer.play(0, 0);
	}
	
	public void stopWind()
	{
		windSlicer.stop();
	}
	
	public void startRain()
	{
		rainSlicer.play(0, 0);
	}
	
	public void stopRain()
	{
		rainSlicer.stop();
	}
	
	public void startThunder()
	{
		thunder.start();
	}
	
	public void stopThunder()
	{
		thunder.stop();
	}
}

I’ll make sure to give it a try in just a few days—I’ll get back to you should I experience some difficulties. :point: