Renamed VolumeTypes to VolumeControl. Made it thread safe. Added docs to VolumeControl and co.

removed .updateAllVolumes() in favor of auto updating inside enum instead
This commit is contained in:
lieght
2025-10-12 02:20:30 +02:00
parent b050e06ceb
commit e3bce3889e
7 changed files with 284 additions and 104 deletions

View File

@@ -2,7 +2,6 @@ package org.toop;
import org.toop.app.App; import org.toop.app.App;
import org.toop.framework.audio.*; import org.toop.framework.audio.*;
import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.networking.NetworkingInitializationException;
import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceLoader;
@@ -25,10 +24,10 @@ public final class Main {
musicManager, musicManager,
soundEffectManager, soundEffectManager,
new AudioVolumeManager() new AudioVolumeManager()
.registerManager(VolumeTypes.MASTERVOLUME, musicManager) .registerManager(VolumeControl.MASTERVOLUME, musicManager)
.registerManager(VolumeTypes.MASTERVOLUME, soundEffectManager) .registerManager(VolumeControl.MASTERVOLUME, soundEffectManager)
.registerManager(VolumeTypes.FX, soundEffectManager) .registerManager(VolumeControl.FX, soundEffectManager)
.registerManager(VolumeTypes.MUSIC, musicManager) .registerManager(VolumeControl.MUSIC, musicManager)
).initListeners(); ).initListeners();
}).start(); }).start();
} }

View File

@@ -57,25 +57,22 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
} }
private void handleVolumeChange(AudioEvents.ChangeVolume event) { private void handleVolumeChange(AudioEvents.ChangeVolume event) {
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeTypes.MASTERVOLUME); this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.MASTERVOLUME);
this.audioVolumeManager.updateAllVolumes();
} }
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) { private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeTypes.FX); this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.FX);
this.audioVolumeManager.updateAllVolumes();
} }
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) { private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) {
this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeTypes.MUSIC); this.audioVolumeManager.setVolume(event.newVolume() / 100, VolumeControl.MUSIC);
this.audioVolumeManager.updateAllVolumes();
} }
private void handleGetVolume(AudioEvents.GetCurrentVolume event) { private void handleGetVolume(AudioEvents.GetCurrentVolume event) {
new EventFlow() new EventFlow()
.addPostEvent( .addPostEvent(
new AudioEvents.GetCurrentVolumeResponse( new AudioEvents.GetCurrentVolumeResponse(
audioVolumeManager.getVolume(VolumeTypes.MASTERVOLUME), audioVolumeManager.getVolume(VolumeControl.MASTERVOLUME),
event.snowflakeId())) event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }
@@ -84,7 +81,7 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
new EventFlow() new EventFlow()
.addPostEvent( .addPostEvent(
new AudioEvents.GetCurrentFxVolumeResponse( new AudioEvents.GetCurrentFxVolumeResponse(
audioVolumeManager.getVolume(VolumeTypes.FX), audioVolumeManager.getVolume(VolumeControl.FX),
event.snowflakeId())) event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }
@@ -93,7 +90,7 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
new EventFlow() new EventFlow()
.addPostEvent( .addPostEvent(
new AudioEvents.GetCurrentMusicVolumeResponse( new AudioEvents.GetCurrentMusicVolumeResponse(
audioVolumeManager.getVolume(VolumeTypes.MUSIC), audioVolumeManager.getVolume(VolumeControl.MUSIC),
event.snowflakeId())) event.snowflakeId()))
.asyncPostEvent(); .asyncPostEvent();
} }

View File

@@ -4,32 +4,81 @@ import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.audio.interfaces.VolumeManager; import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.resource.types.AudioResource; import org.toop.framework.resource.types.AudioResource;
/**
* Concrete implementation of {@link VolumeManager} that delegates volume control
* to the {@link VolumeControl} enum.
* <p>
* This class acts as a central point for updating volume levels for different
* audio categories (MASTER, FX, MUSIC) and for registering audio managers
* to the appropriate volume types.
* </p>
*
* <p>Key responsibilities:</p>
* <ul>
* <li>Set and get volume levels for each {@link VolumeControl} category.</li>
* <li>Register {@link AudioManager} instances to specific volume types so
* that their active audio resources receive volume updates automatically.</li>
* <li>Automatically scales non-master volumes according to the current master volume.</li>
* </ul>
*
* <p>Example usage:</p>
* <pre>{@code
* AudioVolumeManager volumeManager = new AudioVolumeManager();
*
* // Register music manager to MUSIC volume type
* volumeManager.registerManager(VolumeControl.MUSIC, musicManager);
*
* // Set master volume to 80%
* volumeManager.setVolume(0.8, VolumeControl.MASTERVOLUME);
*
* // Set FX volume to 50% of master
* volumeManager.setVolume(0.5, VolumeControl.FX);
*
* // Retrieve current MUSIC volume
* double musicVol = volumeManager.getVolume(VolumeControl.MUSIC);
* }</pre>
*/
public class AudioVolumeManager implements VolumeManager { public class AudioVolumeManager implements VolumeManager {
public void setVolume(double newVolume, VolumeTypes type) { /**
type.setVolume(newVolume, VolumeTypes.MASTERVOLUME.getVolume()); * Sets the volume for a specific volume type.
* <p>
* This method automatically takes into account the master volume
* for non-master types.
*
* @param newVolume the desired volume level (0.0 to 1.0)
* @param type the {@link VolumeControl} category to update
*/
@Override
public void setVolume(double newVolume, VolumeControl type) {
type.setVolume(newVolume, VolumeControl.MASTERVOLUME.getVolume());
} }
public double getVolume(VolumeTypes type) { /**
* Returns the current volume for the specified {@link VolumeControl} category.
*
* @param type the volume category
* @return the current volume (0.0 to 1.0)
*/
@Override
public double getVolume(VolumeControl type) {
return type.getVolume(); return type.getVolume();
} }
public AudioVolumeManager registerManager(VolumeTypes type, AudioManager<? extends AudioResource> manager) { /**
* Registers an {@link AudioManager} with the specified {@link VolumeControl} category.
* <p>
* All active audio resources managed by the given {@link AudioManager} will
* automatically receive volume updates when the volume type changes.
*
* @param type the volume type to register the manager under
* @param manager the audio manager to register
* @return the current {@link AudioVolumeManager} instance (for method chaining)
*/
public AudioVolumeManager registerManager(VolumeControl type, AudioManager<? extends AudioResource> manager) {
if (manager != null) { if (manager != null) {
type.addManager(manager); type.addManager(manager);
} }
return AudioVolumeManager.this; return this;
} }
}
@Override
public void updateAllVolumes() {
double masterVolume = VolumeTypes.MASTERVOLUME.getVolume();
for (VolumeTypes type : VolumeTypes.values()) {
if (type != VolumeTypes.MASTERVOLUME) { // skip master itself
type.setVolume(type.getVolume(), masterVolume);
}
}
}
}

View File

@@ -0,0 +1,159 @@
package org.toop.framework.audio;
import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.resource.types.AudioResource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Enum representing different categories of audio volume in the application.
* <p>
* Each volume type maintains its own volume level and a list of {@link AudioManager}s
* that manage audio resources of that type. The enum provides methods to set, get,
* and propagate volume changes, including master volume adjustments that automatically
* update dependent volume types (FX and MUSIC).
* </p>
*
* <p>Volume types:</p>
* <ul>
* <li>{@link #MASTERVOLUME}: The global/master volume that scales all other volume types.</li>
* <li>{@link #FX}: Volume for sound effects, scaled by the master volume.</li>
* <li>{@link #MUSIC}: Volume for music tracks, scaled by the master volume.</li>
* </ul>
*
* <p>Key features:</p>
* <ul>
* <li>Thread-safe management of audio managers using {@link CopyOnWriteArrayList}.</li>
* <li>Automatic propagation of master volume changes to dependent volume types.</li>
* <li>Clamping volume values between 0.0 and 1.0 to ensure valid audio levels.</li>
* <li>Dynamic registration and removal of audio managers for each volume type.</li>
* </ul>
*
* <p>Example usage:</p>
* <pre>{@code
* // Add a music manager to the MUSIC volume type
* VolumeControl.MUSIC.addManager(musicManager);
*
* // Set master volume to 80%
* VolumeControl.MASTERVOLUME.setVolume(0.8, 0);
*
* // Set FX volume to 50% of master
* VolumeControl.FX.setVolume(0.5, VolumeControl.MASTERVOLUME.getVolume());
*
* // Retrieve current music volume
* double musicVol = VolumeControl.MUSIC.getVolume();
* }</pre>
*/
public enum VolumeControl {
MASTERVOLUME(),
FX(),
MUSIC();
private final List<AudioManager<? extends AudioResource>> managers = new CopyOnWriteArrayList<>();
private double volume = 1.0;
private double masterVolume = 1.0;
/**
* Sets the volume for this volume type.
* <p>
* If this type is {@link #MASTERVOLUME}, all dependent volume types
* (FX, MUSIC, etc.) are automatically updated to reflect the new master volume.
* Otherwise, the volume is scaled by the provided master volume.
*
* @param newVolume the new volume level (0.0 to 1.0)
* @param currentMasterVolume the current master volume for scaling non-master types
*/
public void setVolume(double newVolume, double currentMasterVolume) {
this.volume = clamp(newVolume);
if (this == MASTERVOLUME) {
for (VolumeControl type : VolumeControl.values()) {
if (type != MASTERVOLUME) {
type.masterVolume = this.volume;
type.broadcastVolume(type.computeEffectiveVolume());
}
}
} else {
this.masterVolume = clamp(currentMasterVolume);
broadcastVolume(computeEffectiveVolume());
}
}
/**
* Computes the effective volume for this type, taking into account
* the master volume if this is not {@link #MASTERVOLUME}.
*
* @return the effective volume (0.0 to 1.0)
*/
private double computeEffectiveVolume() {
return (this == MASTERVOLUME) ? volume : volume * masterVolume;
}
/**
* Updates all registered audio managers with the given effective volume.
*
* @param effectiveVolume the volume to apply to all active audio resources
*/
private void broadcastVolume(double effectiveVolume) {
managers.stream()
.filter(Objects::nonNull)
.forEach(manager -> manager.getActiveAudio()
.forEach(aud -> aud.updateVolume(effectiveVolume)));
}
/**
* Clamps a volume value to the valid range [0.0, 1.0].
*
* @param vol the volume to clamp
* @return the clamped volume
*/
private double clamp(double vol) {
return Math.max(0, Math.min(vol, 1.0));
}
/**
* Gets the current volume for this type.
*
* @return the current volume (0.0 to 1.0)
*/
public double getVolume() {
return volume;
}
/**
* Registers an {@link AudioManager} to this volume type.
* <p>
* Duplicate managers are ignored. Managers will receive volume updates
* when this type's volume changes.
*
* @param manager the audio manager to register
*/
public void addManager(AudioManager<? extends AudioResource> manager) {
if (manager != null && !managers.contains(manager)) {
managers.add(manager);
}
}
/**
* Removes a previously registered {@link AudioManager} from this type.
*
* @param manager the audio manager to remove
*/
public void removeManager(AudioManager<? extends AudioResource> manager) {
if (manager != null) {
managers.remove(manager);
}
}
/**
* Returns an unmodifiable view of all registered audio managers for this type.
*
* @return a list of registered audio managers
*/
public List<AudioManager<? extends AudioResource>> getManagers() {
return Collections.unmodifiableList(managers);
}
}

View File

@@ -1,65 +0,0 @@
package org.toop.framework.audio;
import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.resource.types.AudioResource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public enum VolumeTypes {
MASTERVOLUME(),
FX(),
MUSIC();
private final List<AudioManager<? extends AudioResource>> managers = new ArrayList<>();
private double volume = 1.0;
private double masterVolume = 1.0;
public void setVolume(double newVolume, double currentMasterVolume) {
this.volume = clamp(newVolume);
if (this != MASTERVOLUME) {
this.masterVolume = clamp(currentMasterVolume);
}
double effectiveVolume = computeEffectiveVolume();
broadcastVolume(effectiveVolume);
}
private double computeEffectiveVolume() {
return (this == MASTERVOLUME) ? volume : volume * masterVolume;
}
private void broadcastVolume(double effectiveVolume) {
managers.stream()
.filter(Objects::nonNull)
.forEach(manager -> manager.getActiveAudio()
.forEach(aud -> aud.updateVolume(effectiveVolume)));
}
private double clamp(double vol) {
return Math.max(0, Math.min(vol, 1.0));
}
public double getVolume() {
return volume;
}
public void addManager(AudioManager<? extends AudioResource> manager) {
if (manager != null && !managers.contains(manager)) {
managers.add(manager);
}
}
public void removeManager(AudioManager<? extends AudioResource> manager) {
if (manager != null) {
managers.remove(manager);
}
}
public List<AudioManager<? extends AudioResource>> getManagers() {
return Collections.unmodifiableList(managers);
}
}

View File

@@ -1,7 +1,5 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
import org.toop.framework.audio.VolumeTypes;
import java.util.Collection; import java.util.Collection;
public interface AudioManager<T> { public interface AudioManager<T> {

View File

@@ -1,10 +1,53 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
import org.toop.framework.audio.VolumeTypes; import org.toop.framework.audio.VolumeControl;
import org.toop.framework.resource.types.AudioResource;
/**
* Interface for managing audio volumes in the application.
* <p>
* Implementations of this interface are responsible for controlling the volume levels
* of different categories of audio (e.g., master volume, music, sound effects) and
* updating the associated audio managers or resources accordingly.
* </p>
*
* <p>Typical responsibilities include:</p>
* <ul>
* <li>Setting the volume for a specific category (master, music, FX).</li>
* <li>Retrieving the current volume of a category.</li>
* <li>Ensuring that changes in master volume propagate to dependent audio categories.</li>
* <li>Interfacing with {@link org.toop.framework.audio.interfaces.AudioManager} to update active audio resources.</li>
* </ul>
*
* <p>Example usage:</p>
* <pre>{@code
* VolumeManager volumeManager = ...;
* // Set master volume to 80%
* volumeManager.setVolume(0.8, VolumeControl.MASTERVOLUME);
*
* // Set music volume to 50% of master
* volumeManager.setVolume(0.5, VolumeControl.MUSIC);
*
* // Retrieve current FX volume
* double fxVolume = volumeManager.getVolume(VolumeControl.FX);
* }</pre>
*/
public interface VolumeManager { public interface VolumeManager {
void setVolume(double newVolume, VolumeTypes type);
double getVolume(VolumeTypes type); /**
void updateAllVolumes(); *
* Sets the volume to for the specified {@link VolumeControl}.
*
* @param newVolume The volume to be set to.
* @param type The type of volume to change.
*/
void setVolume(double newVolume, VolumeControl type);
/**
* Gets the current volume for the specified {@link VolumeControl}.
*
* @param type the type of volume to get.
* @return The volume as a {@link Double}
*/
double getVolume(VolumeControl type);
} }