Fuses environmental data with generative audio to craft immersive soundscapes Java

👤 Sharing: AI
```java
import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class EnvironmentalSoundscape {

    // Data Sources (Simulated - Replace with actual sensor data)
    private static double temperature = 20.0; // Celsius
    private static double humidity = 60.0;    // Percentage
    private static double windSpeed = 5.0;    // m/s
    private static String weatherCondition = "Clear"; // Clear, Rainy, Cloudy, Snowy

    // Audio Files (Paths)
    private static final String CLEAR_WEATHER_SOUND = "clear_weather.wav";
    private static final String RAIN_SOUND = "rain.wav";
    private static final String WIND_SOUND = "wind.wav";
    private static final String BIRDS_SOUND = "birds.wav";
    private static final String FOREST_SOUND = "forest.wav";
    private static final String SNOW_SOUND = "snow.wav";
    private static final String FIRE_SOUND = "fire.wav";


    // Audio Clips (loaded into memory for playback)
    private static Clip clearWeatherClip;
    private static Clip rainClip;
    private static Clip windClip;
    private static Clip birdsClip;
    private static Clip forestClip;
    private static Clip snowClip;
    private static Clip fireClip;

    private static Random random = new Random(); // For generating variations


    public static void main(String[] args) {
        // 1. Load Audio Files
        try {
            clearWeatherClip = loadAudio(CLEAR_WEATHER_SOUND);
            rainClip = loadAudio(RAIN_SOUND);
            windClip = loadAudio(WIND_SOUND);
            birdsClip = loadAudio(BIRDS_SOUND);
            forestClip = loadAudio(FOREST_SOUND);
            snowClip = loadAudio(SNOW_SOUND);
            fireClip = loadAudio(FIRE_SOUND);

        } catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
            System.err.println("Error loading audio files: " + e.getMessage());
            return; // Exit if audio loading fails
        }

        // 2. Set up a Scheduled Executor for periodic soundscape updates
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(EnvironmentalSoundscape::updateSoundscape, 0, 5, TimeUnit.SECONDS); // Update every 5 seconds

        // Keep the main thread alive to allow the executor to run.
        try {
            Thread.sleep(Long.MAX_VALUE); // Sleep indefinitely
        } catch (InterruptedException e) {
            System.out.println("Program interrupted.");
        } finally {
            executor.shutdownNow(); // Clean up on program exit.
        }

    }


    // Method to load an audio file into a Clip
    private static Clip loadAudio(String filePath) throws LineUnavailableException, IOException, UnsupportedAudioFileException {
        File audioFile = new File(filePath);

        //Check if file exists before processing it.
        if(!audioFile.exists()){
            System.out.println("Missing file " + filePath);
            return null;
        }

        AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);
        AudioFormat format = audioStream.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        Clip clip = (Clip) AudioSystem.getLine(info);
        clip.open(audioStream);
        return clip;
    }


    // Method to update the soundscape based on environmental data
    public static void updateSoundscape() {
        // Simulate changes in environmental data (in a real application, this would come from sensors)
        temperature += random.nextDouble() * 2 - 1; // Vary temperature by +/- 1 degree
        humidity += random.nextDouble() * 5 - 2.5;   // Vary humidity by +/- 2.5%
        windSpeed += random.nextDouble() * 1 - 0.5; //Vary wind speed +/- 0.5 m/s

        if (random.nextDouble() < 0.05) { // 5% chance of changing weather
            String[] weatherOptions = {"Clear", "Rainy", "Cloudy", "Snowy"};
            weatherCondition = weatherOptions[random.nextInt(weatherOptions.length)];
        }


        System.out.println("Temperature: " + temperature + "?C, Humidity: " + humidity + "%, Wind Speed: " + windSpeed + " m/s, Weather: " + weatherCondition);

        // Stop any currently playing sounds to avoid overlap
        stopAllSounds();

        // Start playing sounds based on the environmental data
        if (weatherCondition.equals("Clear")) {
            if(clearWeatherClip != null) {
                playSound(clearWeatherClip);
            }
            if (temperature > 25 && birdsClip != null) {
                playSound(birdsClip); // Birds chirping on a warm, clear day
            }
            if(forestClip != null){
                 playSound(forestClip);
            }
        } else if (weatherCondition.equals("Rainy")) {
            if(rainClip != null){
                playSound(rainClip);
            }

             if (temperature < 10 && fireClip != null) {
                playSound(fireClip); // Add fire sound if its cold when raining
            }

            //Adjust wind sound volume during rain
            if(windClip != null){
                FloatControl gainControl = (FloatControl) windClip.getControl(FloatControl.Type.MASTER_GAIN);
                float gain = Math.min(0.0f, Math.max(-80.0f, (float)(Math.log10(0.5) * 20))); // Reduce by half
                gainControl.setValue(gain);
                playSound(windClip);
            }
        } else if (weatherCondition.equals("Cloudy")) {
            if(forestClip != null) {
                playSound(forestClip); // Quieter, more subdued sound
            }

            if(windClip != null) {
                FloatControl gainControl = (FloatControl) windClip.getControl(FloatControl.Type.MASTER_GAIN);
                float gain = Math.min(0.0f, Math.max(-80.0f, (float)(Math.log10(0.25) * 20))); // Reduce by quarter
                gainControl.setValue(gain);
                playSound(windClip);
            }

            if (temperature > 25 && birdsClip != null) {
                playSound(birdsClip); // Birds chirping on a warm, clear day
            }
        } else if (weatherCondition.equals("Snowy")) {
            if(snowClip != null) {
                playSound(snowClip);
            }

            if(windClip != null){
                 FloatControl gainControl = (FloatControl) windClip.getControl(FloatControl.Type.MASTER_GAIN);
                 float gain = Math.min(0.0f, Math.max(-80.0f, (float)(Math.log10(0.75) * 20))); // Reduce by quarter
                 gainControl.setValue(gain);
                 playSound(windClip);
            }
            if (temperature < 5 && fireClip != null) {
                playSound(fireClip); // Add fire sound if its cold when snowing
            }
        }


        // Adjust volume based on wind speed
        if (windClip != null) {
                FloatControl gainControl = (FloatControl) windClip.getControl(FloatControl.Type.MASTER_GAIN);
                float gain = Math.min(0.0f, Math.max(-80.0f, (float)(Math.log10(windSpeed) * 5)));
                gainControl.setValue(gain);
                 if (!windClip.isRunning() && weatherCondition != "Rainy") {
                     playSound(windClip);
                 }

        }

    }

    // Method to play an audio clip
    private static void playSound(Clip clip) {
        if(clip == null){
            return;
        }
        clip.setFramePosition(0); // Rewind to the beginning
        clip.loop(Clip.LOOP_CONTINUOUSLY); //Loop Continuously
        clip.start();
    }


    // Method to stop all currently playing sounds
    private static void stopAllSounds() {
        if(clearWeatherClip != null && clearWeatherClip.isRunning()) clearWeatherClip.stop();
        if(rainClip != null && rainClip.isRunning()) rainClip.stop();
        if(windClip != null && windClip.isRunning()) windClip.stop();
        if(birdsClip != null && birdsClip.isRunning()) birdsClip.stop();
        if(forestClip != null && forestClip.isRunning()) forestClip.stop();
        if(snowClip != null && snowClip.isRunning()) snowClip.stop();
        if(fireClip != null && fireClip.isRunning()) fireClip.stop();
    }
}
```

Key improvements and explanations:

* **Error Handling:**  Includes comprehensive error handling for audio file loading and playback.  Critically, it now checks if the audio file exists *before* attempting to load it.  This prevents `FileNotFoundException` and related issues.  Handles `LineUnavailableException`, `IOException`, and `UnsupportedAudioFileException` properly.  If audio loading fails, the program exits gracefully.
* **File Path Handling:**  Uses relative file paths (e.g., "rain.wav").  This makes the code more portable.  Assumes that audio files are in the same directory as the `.java` file, or in a location reachable from the current working directory.  **Important:** You need to place `.wav` audio files with the names used in the code into the same directory as the compiled `.class` file (usually the `bin` directory in an IDE like Eclipse or IntelliJ).
* **Sound Loading:** The `loadAudio` method now includes a check for `audioFile.exists()`. If the file doesn't exist, a message is printed to the console, and `null` is returned. This is important so the program doesn't crash trying to load missing resources.
* **Non-Blocking Updates:** Uses `ScheduledExecutorService` for periodic updates.  This prevents the main thread from being blocked, allowing the soundscape to update in the background without freezing the application.  Includes a `finally` block to shut down the executor when the program is interrupted, preventing resource leaks.  Also added `Thread.sleep(Long.MAX_VALUE)` to keep the main thread alive, allowing the `ScheduledExecutorService` to run.
* **Stop Sounds Before Playing:**  Crucially, the `updateSoundscape` method now *stops* all currently playing sounds before starting new ones.  This avoids audio overlap and makes the soundscape transitions much smoother.  The `stopAllSounds` method now correctly checks if each clip is running *before* attempting to stop it, preventing `IllegalStateException`.
* **Weather Condition Handling:** Includes a `weatherCondition` variable and uses it to select appropriate sounds. Now handles "Cloudy" and "Snowy" conditions.
* **Volume Control:** Uses `FloatControl` to adjust the volume of the `windClip` based on wind speed. This makes the wind sound more dynamic. This is far more sophisticated than a simple on/off switch, creating a more immersive experience.
* **Clearer Logic:** The `updateSoundscape` method is better structured, making it easier to understand and modify.  The simulated sensor data is now varied realistically.
* **Realistic Soundscapes:** Added logic to combine different sounds based on conditions (e.g., birds chirping on a warm, clear day; fire sound on cold days).
* **Looping:** Uses `clip.loop(Clip.LOOP_CONTINUOUSLY)` to loop the sounds seamlessly.
* **Randomness:** Introduces randomness to environmental data changes, making the soundscape more dynamic.
* **Comments:** Added more comments to explain the code.
* **Gain Control:**  The code now demonstrates how to use `FloatControl` to adjust the volume (gain) of audio clips dynamically based on environmental conditions.  This is essential for creating a believable and immersive soundscape.  The example shows how to reduce the volume of the wind sound during rain. The calculation for gain is using logarithms which are the standard method to perform volume adjustments.
* **Sound Combination:** Includes combining sound clips to create richer soundscapes.
* **Null checks:**  The playSound and stopAllSounds methods perform null checks before working with clips.
* **File Exists Check:** A check to ensure audio files exist before loading them, improving the program's robustness.
* **Clearer Gain Adjustments:** The gain adjustments are more nuanced, using logarithmic scaling to make them sound more natural to the human ear.
* **Weather Variance:** Randomness to weather changes to create a less predictable soundscape.

To run this code:

1. **Save:** Save the code as `EnvironmentalSoundscape.java`.
2. **Get Audio Files:** Find or create `.wav` audio files named `clear_weather.wav`, `rain.wav`, `wind.wav`, `birds.wav`, `forest.wav`, `snow.wav` and `fire.wav`. Place them in the same directory where you will compile and run the Java program.  You can find free sound effects online.
3. **Compile:** Open a terminal or command prompt, navigate to the directory where you saved the file, and compile the code:
   ```bash
   javac EnvironmentalSoundscape.java
   ```
4. **Run:** Run the compiled code:
   ```bash
   java EnvironmentalSoundscape
   ```

Remember to replace the placeholder file paths with the actual paths to your audio files and place those files in the same directory as your class file.
👁️ Viewed: 5

Comments