OceanSound: L/R-Panning fix via Dual-Source + constant-power panning
Zwei AudioNodes pro Sound (L + R) fest auf ±90° der Kamera positioniert; Lautstärkeverhältnis (constant-power) bestimmt die wahrgenommene Richtung. Behebt HRTF-0°-Stille wenn Kamera aufs Wasser zeigt. Außerdem weitere Soundsystem- und Editor-Korrekturen aus dieser Session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1262,6 +1262,11 @@ public class EditorApp extends Application {
|
||||
camOrbitBtn.setOnAction(e -> input.camMode = SharedInput.CAM_ORBIT);
|
||||
camFreeBtn.setOnAction(e -> input.camMode = SharedInput.CAM_FREEFLY);
|
||||
|
||||
Button camResetBtn = new Button("⌂ Reset");
|
||||
camResetBtn.setStyle("-fx-font-weight:bold;");
|
||||
camResetBtn.setTooltip(new javafx.scene.control.Tooltip("Kamera auf x=0, z=0, y=Terrain+10m zurücksetzen"));
|
||||
camResetBtn.setOnAction(e -> input.resetCameraRequested.set(true));
|
||||
|
||||
Label hint = new Label("WASD/QE: Kamera | Mitte-Drag / L+R-Drag: Drehen | L-Klick: hoch | R-Klick: tief");
|
||||
hint.setStyle("-fx-text-fill: #555;");
|
||||
|
||||
@@ -1274,7 +1279,7 @@ public class EditorApp extends Application {
|
||||
new Separator(Orientation.VERTICAL), soundAreaBtn, areaBtn, locationZoneBtn,
|
||||
new Separator(Orientation.VERTICAL), playToolBtn,
|
||||
new Separator(Orientation.VERTICAL), voxelBtn,
|
||||
new Separator(Orientation.VERTICAL), camOrbitBtn, camFreeBtn,
|
||||
new Separator(Orientation.VERTICAL), camOrbitBtn, camFreeBtn, camResetBtn,
|
||||
new Separator(Orientation.VERTICAL), hint);
|
||||
|
||||
worldToolBar = toolBar;
|
||||
@@ -5094,12 +5099,18 @@ public class EditorApp extends Application {
|
||||
catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
/** Konvertiert eine beliebige Audiodatei mit ffmpeg zu OGG Vorbis.
|
||||
* Gibt den Pfad zur erzeugten .ogg-Datei zurück. */
|
||||
/**
|
||||
* Konvertiert / normalisiert eine Audiodatei zu OGG Vorbis bei 48000 Hz.
|
||||
* Das Ziel-Format stimmt mit PipeWire/PulseAudio überein, sodass OpenALSofts
|
||||
* Spatial-Mixer kein On-the-fly-Resampling durchführen muss (kein Knistern).
|
||||
*/
|
||||
private static Path convertToOgg(Path src, Path destOgg) throws IOException {
|
||||
try {
|
||||
Process proc = new ProcessBuilder(
|
||||
"ffmpeg", "-i", src.toString(), "-q:a", "4", destOgg.toString(), "-y")
|
||||
"ffmpeg", "-i", src.toString(),
|
||||
"-ar", "48000",
|
||||
"-q:a", "4",
|
||||
destOgg.toString(), "-y")
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
proc.getInputStream().transferTo(java.io.OutputStream.nullOutputStream());
|
||||
@@ -5158,12 +5169,8 @@ public class EditorApp extends Application {
|
||||
if (isAudio) {
|
||||
String baseName = file.getName().replaceFirst("\\.[^.]+$", "");
|
||||
Path destOgg = destDir.resolve(baseName + ".ogg");
|
||||
if (name.endsWith(".ogg")) {
|
||||
Files.copy(file.toPath(), destOgg, StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
setStatus("Konvertiere " + file.getName() + " → OGG …");
|
||||
convertToOgg(file.toPath(), destOgg);
|
||||
}
|
||||
setStatus("Normalisiere " + file.getName() + " → 48 kHz OGG …");
|
||||
convertToOgg(file.toPath(), destOgg);
|
||||
String finalName = baseName + ".ogg";
|
||||
TreeItem<String> newItem = new TreeItem<>(finalName);
|
||||
itemPaths.put(newItem, destOgg);
|
||||
@@ -5716,12 +5723,8 @@ public class EditorApp extends Application {
|
||||
String name = file.getName().toLowerCase();
|
||||
String baseName = file.getName().replaceFirst("\\.[^.]+$", "");
|
||||
Path dest = destDir.resolve(baseName + ".ogg");
|
||||
if (name.endsWith(".ogg")) {
|
||||
Files.copy(file.toPath(), dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
setStatus("Konvertiere " + file.getName() + " → OGG …");
|
||||
convertToOgg(file.toPath(), dest);
|
||||
}
|
||||
setStatus("Normalisiere " + file.getName() + " → 48 kHz OGG …");
|
||||
convertToOgg(file.toPath(), dest);
|
||||
TreeItem<String> item = new TreeItem<>(dest.getFileName().toString());
|
||||
itemPaths.put(item, dest);
|
||||
audioNode.getChildren().add(item);
|
||||
|
||||
@@ -49,6 +49,10 @@ public class SharedInput {
|
||||
public static final int CAM_ORBIT = 0;
|
||||
public static final int CAM_FREEFLY = 1;
|
||||
|
||||
/** Gesetzt von JavaFX; konsumiert von TerrainEditorState: Kamera auf x=0,z=0,y=terrain+10 zurücksetzen. */
|
||||
public final java.util.concurrent.atomic.AtomicBoolean resetCameraRequested =
|
||||
new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||
|
||||
// ── Kamerabewegung (WASD + QE) ──────────────────────────────────────────
|
||||
public volatile boolean forward, backward, left, right, up, down;
|
||||
|
||||
|
||||
@@ -1607,12 +1607,24 @@ public class TerrainEditorState extends BaseAppState {
|
||||
if (scroll != 0)
|
||||
camPos.addLocal(cam.getDirection().mult(scroll * FastMath.clamp(terrainDist, 5f, CAM_SPEED) * 0.02f));
|
||||
|
||||
// Kamera-Reset auf x=0,z=0,y=terrain+10
|
||||
if (input.resetCameraRequested.getAndSet(false)) {
|
||||
float h = getTerrainHeightFast(0f, 0f);
|
||||
camPos.set(0f, h + 10f, 0f);
|
||||
camYaw = 0f;
|
||||
camPitch = DEFAULT_PITCH;
|
||||
}
|
||||
|
||||
// NaN-Sanitierung (z.B. durch terrain.getHeight()-Anomalie propagiert)
|
||||
if (!Float.isFinite(camPos.x) || !Float.isFinite(camPos.y) || !Float.isFinite(camPos.z)) {
|
||||
camPos.set(0f, DEFAULT_CAM_Y, 0f);
|
||||
}
|
||||
camPos.y = FastMath.clamp(camPos.y, -200f, MAX_CAM_Y);
|
||||
|
||||
// Kamera nicht unter das Terrain fallen lassen
|
||||
float terrainFloor = getTerrainHeightFast(camPos.x, camPos.z) + 2f;
|
||||
if (camPos.y < terrainFloor) camPos.y = terrainFloor;
|
||||
|
||||
cam.setLocation(camPos);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user