Skip to content

Fix #2408 - com.jme3.audio.LowPassFilter not cloneable #2410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions jme3-core/src/main/java/com/jme3/audio/Filter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2020 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -35,9 +35,12 @@
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.util.NativeObject;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;

import java.io.IOException;

public abstract class Filter extends NativeObject implements Savable {
public abstract class Filter extends NativeObject implements Savable, JmeCloneable {

public Filter() {
super();
Expand All @@ -49,12 +52,28 @@ protected Filter(int id) {

@Override
public void write(JmeExporter ex) throws IOException {
// nothing to save
// no-op
}

@Override
public void read(JmeImporter im) throws IOException {
// nothing to read
// no-op
}

/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public Object jmeClone() {
return super.clone();
}

/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields(Cloner cloner, Object original) {
// no-op
}

@Override
Expand Down
62 changes: 60 additions & 2 deletions jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2023 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -36,26 +36,63 @@
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.util.NativeObject;

import java.io.IOException;

/**
* A filter that attenuates frequencies above a specified threshold, allowing lower
* frequencies to pass through with less attenuation. Commonly used to simulate effects
* such as muffling or underwater acoustics.
*/
public class LowPassFilter extends Filter {

protected float volume, highFreqVolume;
/**
* The overall volume scaling of the filtered sound
*/
protected float volume;
/**
* The volume scaling of the high frequencies allowed to pass through. Valid values range
* from 0.0 to 1.0, where 0.0 completely eliminates high frequencies and 1.0 lets them pass
* through unchanged.
*/
protected float highFreqVolume;

/**
* Constructs a low-pass filter.
*
* @param volume the overall volume scaling of the filtered sound (0.0 - 1.0).
* @param highFreqVolume the volume scaling of high frequencies (0.0 - 1.0).
* @throws IllegalArgumentException if {@code volume} or {@code highFreqVolume} is out of range.
*/
public LowPassFilter(float volume, float highFreqVolume) {
super();
setVolume(volume);
setHighFreqVolume(highFreqVolume);
}

/**
* For internal cloning
* @param id the native object ID
*/
protected LowPassFilter(int id) {
super(id);
}

/**
* Retrieves the current volume scaling of high frequencies.
*
* @return the high-frequency volume scaling.
*/
public float getHighFreqVolume() {
return highFreqVolume;
}

/**
* Sets the high-frequency volume.
*
* @param highFreqVolume the new high-frequency volume scaling (0.0 - 1.0).
* @throws IllegalArgumentException if {@code highFreqVolume} is out of range.
*/
public void setHighFreqVolume(float highFreqVolume) {
if (highFreqVolume < 0 || highFreqVolume > 1)
throw new IllegalArgumentException("High freq volume must be between 0 and 1");
Expand All @@ -64,10 +101,21 @@ public void setHighFreqVolume(float highFreqVolume) {
this.updateNeeded = true;
}

/**
* Retrieves the current overall volume scaling of the filtered sound.
*
* @return the overall volume scaling.
*/
public float getVolume() {
return volume;
}

/**
* Sets the overall volume.
*
* @param volume the new overall volume scaling (0.0 - 1.0).
* @throws IllegalArgumentException if {@code volume} is out of range.
*/
public void setVolume(float volume) {
if (volume < 0 || volume > 1)
throw new IllegalArgumentException("Volume must be between 0 and 1");
Expand All @@ -92,11 +140,21 @@ public void read(JmeImporter im) throws IOException {
highFreqVolume = ic.readFloat("hf_volume", 0);
}

/**
* Creates a native object clone of this filter for internal usage.
*
* @return a new {@code LowPassFilter} instance with the same native ID.
*/
@Override
public NativeObject createDestructableClone() {
return new LowPassFilter(id);
}

/**
* Retrieves a unique identifier for this filter. Used internally for native object management.
*
* @return a unique long identifier.
*/
@Override
public long getUniqueId() {
return ((long) OBJTYPE_FILTER << 32) | (0xffffffffL & (long) id);
Expand Down
48 changes: 48 additions & 0 deletions jme3-core/src/test/java/com/jme3/audio/AudioNodeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.jme3.audio;

import com.jme3.asset.AssetManager;
import com.jme3.math.Vector3f;
import com.jme3.system.JmeSystem;
import org.junit.Assert;
import org.junit.Test;

/**
* Automated tests for the {@code AudioNode} class.
*
* @author capdevon
*/
public class AudioNodeTest {

@Test
public void testAudioNodeClone() {
AssetManager assetManager = JmeSystem.newAssetManager(AudioNodeTest.class.getResource("/com/jme3/asset/Desktop.cfg"));

AudioNode audio = new AudioNode(assetManager,
"Sound/Effects/Bang.wav", AudioData.DataType.Buffer);
audio.setDirection(new Vector3f(0, 1, 0));
audio.setVelocity(new Vector3f(1, 1, 1));
audio.setDryFilter(new LowPassFilter(1f, .1f));
audio.setReverbFilter(new LowPassFilter(.5f, .5f));

AudioNode clone = audio.clone();

Assert.assertNotNull(clone.previousWorldTranslation);
Assert.assertNotSame(audio.previousWorldTranslation, clone.previousWorldTranslation);
Assert.assertEquals(audio.previousWorldTranslation, clone.previousWorldTranslation);

Assert.assertNotNull(clone.getDirection());
Assert.assertNotSame(audio.getDirection(), clone.getDirection());
Assert.assertEquals(audio.getDirection(), clone.getDirection());

Assert.assertNotNull(clone.getVelocity());
Assert.assertNotSame(audio.getVelocity(), clone.getVelocity());
Assert.assertEquals(audio.getVelocity(), clone.getVelocity());

Assert.assertNotNull(clone.getDryFilter());
Assert.assertNotSame(audio.getDryFilter(), clone.getDryFilter());

Assert.assertNotNull(clone.getReverbFilter());
Assert.assertNotSame(audio.getReverbFilter(), clone.getReverbFilter());
}

}