Skip to content

Commit

Permalink
New audio player and sound panel classes for improved sound playback
Browse files Browse the repository at this point in the history
- SingleAudioPlayer class provides more responsive audio playback.
- SoundPanel class provides a customizable UI control panel for audio playback.
- Updated SoundResource class and ResourceRef datatype to utilize new classes.
  • Loading branch information
Argent77 committed Nov 26, 2024
1 parent eb07b69 commit 4745cc1
Show file tree
Hide file tree
Showing 11 changed files with 1,888 additions and 404 deletions.
95 changes: 35 additions & 60 deletions src/org/infinity/datatype/ResourceRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
Expand All @@ -33,6 +32,7 @@
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.infinity.gui.SoundPanel;
import org.infinity.gui.StructViewer;
import org.infinity.gui.TextListPanel;
import org.infinity.gui.ViewFrame;
Expand All @@ -44,7 +44,6 @@
import org.infinity.resource.Resource;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.resource.sound.SoundResource;
import org.infinity.util.Logger;
import org.infinity.util.Misc;
import org.infinity.util.io.StreamUtils;
Expand All @@ -62,7 +61,7 @@
* </ul>
*/
public class ResourceRef extends Datatype
implements Editable, IsTextual, IsReference, ActionListener, ListSelectionListener, PropertyChangeListener {
implements Editable, IsTextual, IsReference, Closeable, ActionListener, ListSelectionListener {
private static final Comparator<ResourceRefEntry> IGNORE_CASE_EXT_COMPARATOR = new IgnoreCaseExtComparator();

/** List of resource types that are can be used to display associated icons. */
Expand All @@ -86,11 +85,8 @@ public class ResourceRef extends Datatype
/** Button that used to open editor of current selected element in the list. */
private JButton bView;

/** Button that used to play sound of current selected element in the list. */
private JButton bPlay;

/** Button that used to stop sound playback of current selected element in the list. */
private JButton bStop;
/** Handles playback of sound resources. */
private SoundPanel soundPanel;

/**
* GUI component that lists all available resources that can be set to this resource reference and have edit field for
Expand Down Expand Up @@ -132,30 +128,6 @@ public void actionPerformed(ActionEvent event) {
if (isEditable(selected)) {
new ViewFrame(list.getTopLevelAncestor(), ResourceFactory.getResource(selected.entry));
}
} else if (event.getSource() == bPlay) {
final ResourceRefEntry selected = list.getSelectedValue();
if (isSound(selected)) {
// prevent overlapping sound playback
closeResource(currentResource);
SoundResource res = (SoundResource) ResourceFactory.getResource(selected.entry);
res.playSound(this);
currentResource = res;
}
} else if (event.getSource() == bStop) {
if (currentResource instanceof SoundResource) {
((SoundResource) currentResource).stopSound(this);
}
}
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
if (SoundResource.PROPERTY_NAME_PLAYBACK.equals(evt.getPropertyName())) {
final boolean value = (Boolean)evt.getNewValue();
updatePlayback(value);
if (!value) {
((SoundResource) evt.getSource()).removePropertyChangeListener(this);
}
}
}

Expand Down Expand Up @@ -223,29 +195,16 @@ public void mouseClicked(MouseEvent event) {
bView = new JButton("View/Edit", Icons.ICON_ZOOM_16.getIcon());
bView.addActionListener(this);

bPlay = new JButton(Icons.ICON_PLAY_16.getIcon());
bPlay.setToolTipText("Play sound");
bPlay.addActionListener(this);
bStop = new JButton(Icons.ICON_STOP_16.getIcon());
bStop.setToolTipText("Stop playback");
bStop.addActionListener(this);
bStop.setEnabled(false);
soundPanel = new SoundPanel(SoundPanel.Option.TIME_LABEL, SoundPanel.Option.PROGRESS_BAR);
soundPanel.setDisplayFormat(SoundPanel.DisplayFormat.ELAPSED_TOTAL_PRECISE);
soundPanel.setVisible(ResourceEntry.isSound(types));

list.addListSelectionListener(this);
setResourceEntryUpdated(list.getSelectedValue());

GridBagConstraints gbc = null;
JPanel panel = new JPanel(new GridBagLayout());

final JPanel soundPanel = new JPanel(new GridBagLayout());
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
new Insets(0, 0, 0, 0), 0, 0);
soundPanel.add(bPlay, gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
new Insets(0, 8, 0, 0), 0, 0);
soundPanel.add(bStop, gbc);
soundPanel.setVisible(ResourceEntry.isSound(types));

gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 5, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 0), 0, 0);
panel.add(list, gbc);
Expand All @@ -258,13 +217,13 @@ public void mouseClicked(MouseEvent event) {
panel.add(spacerTop, gbc);

gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(3, 6, 3, 0), 0, 0);
new Insets(3, 6, 3, 6), 0, 0);
panel.add(bUpdate, gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(3, 6, 3, 0), 0, 0);
new Insets(3, 6, 3, 6), 0, 0);
panel.add(bView, gbc);
gbc = ViewerUtil.setGBC(gbc, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(24, 6, 3, 0), 0, 0);
new Insets(24, 6, 3, 6), 0, 0);
panel.add(soundPanel, gbc);

// spacer keeps controls in the center
Expand Down Expand Up @@ -414,6 +373,13 @@ public String getResourceName() {
return resname;
}

@Override
public void close() throws Exception {
if (soundPanel != null) {
soundPanel.setPlaying(false);
}
}

public boolean isEmpty() {
return (resname.equals(NONE.name));// FIXME: use null instead of NONE.name
}
Expand Down Expand Up @@ -453,16 +419,30 @@ private void setResourceEntryUpdated(ResourceRefEntry entry) {
closeResource(currentResource);
if (entry != null) {
bView.setEnabled(isEditable(entry));
bPlay.setEnabled(isSound(entry));
bStop.setEnabled(false);
if (soundPanel != null && soundPanel.isVisible()) {
try {
soundPanel.loadSound(entry.getEntry());
soundPanel.setEnabled(isSound(entry));
} catch (NullPointerException e) {
// expected
soundPanel.setEnabled(false);
} catch (Exception e) {
Logger.error(e);
soundPanel.setEnabled(false);
}
}
} else {
bView.setEnabled(false);
bPlay.setEnabled(false);
bStop.setEnabled(false);
if (soundPanel != null && soundPanel.isVisible()) {
soundPanel.setEnabled(false);
}
}
}

private void closeResource(Resource resource) {
if (soundPanel != null) {
soundPanel.unload();
}
if (resource instanceof Closeable) {
try {
((Closeable) resource).close();
Expand Down Expand Up @@ -492,11 +472,6 @@ private void setValue(String newValue) {
}
}

private void updatePlayback(boolean isPlaying) {
bPlay.setEnabled(!isPlaying);
bStop.setEnabled(isPlaying);
}

// -------------------------- INNER CLASSES --------------------------
/** Class that represents resource reference in the list of choice. */
public static final class ResourceRefEntry {
Expand Down
43 changes: 43 additions & 0 deletions src/org/infinity/exceptions/ResourceException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 Jon Olav Hauglid
// See LICENSE.txt for license information

package org.infinity.exceptions;

/**
* A generic exception that is thrown if a resource-related problem occurs.
*/
public class ResourceException extends Exception {
/** Constructs an {@code ResourceException} with no detail message. */
public ResourceException() {
super();
}

/**
* Constructs an {@code ResourceException} with the specified detail message.
*
* @param message the detail message.
*/
public ResourceException(String message) {
super(message);
}

/**
* Constructs an {@code ResourceException} with the specified detail message and cause.
*
* @param message the detail message.
* @param cause the cause for this exception to be thrown.
*/
public ResourceException(String message, Throwable cause) {
super(message, cause);
}

/**
* Constructs an {@code ResourceException} with a cause but no detail message.
*
* @param cause the cause for this exception to be thrown.
*/
public ResourceException(Throwable cause) {
super(cause);
}
}
43 changes: 43 additions & 0 deletions src/org/infinity/exceptions/ResourceNotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 Jon Olav Hauglid
// See LICENSE.txt for license information

package org.infinity.exceptions;

/**
* Thrown if a game resource could not be found.
*/
public class ResourceNotFoundException extends ResourceException {
/** Constructs an {@code ResourceNotFoundException} with no detail message. */
public ResourceNotFoundException() {
super();
}

/**
* Constructs an {@code ResourceNotFoundException} with the specified detail message.
*
* @param message the detail message.
*/
public ResourceNotFoundException(String message) {
super(message);
}

/**
* Constructs an {@code ResourceNotFoundException} with the specified detail message and cause.
*
* @param message the detail message.
* @param cause the cause for this exception to be thrown.
*/
public ResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}

/**
* Constructs an {@code ResourceNotFoundException} with a cause but no detail message.
*
* @param cause the cause for this exception to be thrown.
*/
public ResourceNotFoundException(Throwable cause) {
super(cause);
}
}
52 changes: 40 additions & 12 deletions src/org/infinity/gui/ChildFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,36 +232,52 @@ public ChildFrame(String title, boolean closeOnInvisible, boolean closeOnReset)
pane.getActionMap().put(pane, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (ChildFrame.this.closeOnInvisible) {
try {
try {
if (ChildFrame.this.closeOnInvisible) {
if (!ChildFrame.this.windowClosing(false)) {
return;
}
} catch (AbortException e2) {
Logger.debug(e2);
return;
} catch (Exception e2) {
Logger.error(e2);
return;
} else {
if (!ChildFrame.this.windowHiding(false)) {
return;
}
}
} catch (AbortException e2) {
Logger.debug(e2);
return;
} catch (Exception e2) {
Logger.error(e2);
return;
}

if (ChildFrame.this.closeOnInvisible) {
WINDOWS.remove(ChildFrame.this);
}

ChildFrame.this.setVisible(false);
}
});
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (ChildFrame.this.closeOnInvisible) {
try {
try {
if (ChildFrame.this.closeOnInvisible) {
if (!ChildFrame.this.windowClosing(false)) {
return;
}
} catch (Exception e2) {
throw new IllegalAccessError(); // ToDo: This is just too ugly
} else {
if (!ChildFrame.this.windowHiding(false)) {
return;
}
}
} catch (Exception e2) {
throw new IllegalAccessError(); // ToDo: This is just too ugly
}

if (ChildFrame.this.closeOnInvisible) {
WINDOWS.remove(ChildFrame.this);
}

ChildFrame.this.setVisible(false);
}
});
Expand Down Expand Up @@ -318,6 +334,18 @@ protected boolean windowClosing(boolean forced) throws Exception {
return true;
}

/**
* This method is called whenever the dialog is about to be hidden without being removed from memory.
*
* @param forced If {@code false}, the return value will be honored. If {@code true}, the return value will be
* disregarded.
* @return If {@code true}, the hiding procedure continues. If {@code false}, the hiding procedure will be cancelled.
* @throws Exception
*/
protected boolean windowHiding(boolean forced) throws Exception {
return true;
}

/**
* Returns the size of the last created child frame. Falls back to the default size if information is not available.
*
Expand Down
Loading

0 comments on commit 4745cc1

Please sign in to comment.