From ec56b4c1a115e93669d23c440f90e2549a7aceb7 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Mon, 27 May 2024 21:49:13 -0500 Subject: [PATCH] Updating for Java 11 Also continuing updates to take advantage of the newer Java version we build against, fix typos in documentation, and switch to explicit API annotations to indicate project entry points. --- .../beatlink/data/WaveformDetail.java | 29 ++- .../data/WaveformDetailComponent.java | 165 +++++++----- .../beatlink/data/WaveformDetailUpdate.java | 6 +- .../beatlink/data/WaveformFinder.java | 243 ++++++++---------- .../beatlink/data/WaveformListener.java | 6 +- .../beatlink/data/WaveformPreview.java | 27 +- .../data/WaveformPreviewComponent.java | 151 ++++++----- .../beatlink/data/WaveformPreviewUpdate.java | 6 +- 8 files changed, 343 insertions(+), 290 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetail.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetail.java index 64e0b049..1282eb00 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetail.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetail.java @@ -1,5 +1,6 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; import org.deepsymmetry.beatlink.Util; import org.deepsymmetry.beatlink.dbserver.BinaryField; import org.deepsymmetry.beatlink.dbserver.Message; @@ -18,6 +19,7 @@ * * @author James Elliott */ +@API(status = API.Status.STABLE) public class WaveformDetail { @SuppressWarnings({"unused"}) @@ -27,7 +29,7 @@ public class WaveformDetail { * The number of bytes at the start of the waveform data which do not seem to be valid or used when it is served * by the dbserver protocol. They are not present when the ANLZ.EXT file is loaded directly by Crate Digger. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final int LEADING_DBSERVER_JUNK_BYTES = 19; /** @@ -35,13 +37,13 @@ public class WaveformDetail { * nxs2 ANLZ tag request. We actually know what these mean, now that we know how to parse EXT files, but we * can simply skip them anyway. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final int LEADING_DBSERVER_COLOR_JUNK_BYTES = 28; /** * The unique identifier that was used to request this waveform detail. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final DataReference dataReference; /** @@ -49,7 +51,7 @@ public class WaveformDetail { * that have not yet been reliably understood, and is also used for storing the cue list in a file. * This will be {@code null} if the data was obtained from Crate Digger. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final Message rawMessage; /** @@ -61,7 +63,7 @@ public class WaveformDetail { /** * Indicates whether this is an NXS2-style color waveform, or a monochrome (blue) waveform. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final boolean isColor; /** @@ -70,6 +72,7 @@ public class WaveformDetail { * @return the bytes from which the detail can be drawn, as described in the * Packet Analysis document. */ + @API(status = API.Status.STABLE) public ByteBuffer getData() { detailBuffer.rewind(); return detailBuffer.slice(); @@ -80,6 +83,7 @@ public ByteBuffer getData() { * * @return the number of half-frames (pixel columns) that make up the track */ + @API(status = API.Status.STABLE) public int getFrameCount() { final int bytes = getData().remaining(); if (isColor) { @@ -95,6 +99,7 @@ public int getFrameCount() { * * @return the number of milliseconds it will take to play all half-frames that make up the track */ + @API(status = API.Status.STABLE) public long getTotalTime() { return Util.halfFrameToTime(getFrameCount()); } @@ -111,6 +116,7 @@ public long getTotalTime() { * * @return the component which will draw the annotated waveform preview */ + @API(status = API.Status.STABLE) public JComponent createViewComponent(TrackMetadata metadata, BeatGrid beatGrid) { return new WaveformDetailComponent(this, metadata, beatGrid); } @@ -121,9 +127,9 @@ public JComponent createViewComponent(TrackMetadata metadata, BeatGrid beatGrid) * @param reference the unique database reference that was used to request this waveform detail * @param message the response that contains the preview */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public WaveformDetail(DataReference reference, Message message) { - isColor = message.knownType == Message.KnownType.ANLZ_TAG; // If we got one of these, its an NXS2 color wave. + isColor = message.knownType == Message.KnownType.ANLZ_TAG; // If we got one of these, it's an NXS2 color wave. dataReference = reference; rawMessage = message; // Load the bytes we were sent, and skip over the proper number of leading junk bytes @@ -138,7 +144,7 @@ public WaveformDetail(DataReference reference, Message message) { * @param reference the unique database reference that was used to request this waveform preview * @param anlzFile the parsed rekordbox track analysis file containing the waveform preview */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public WaveformDetail(DataReference reference, RekordboxAnlz anlzFile) { dataReference = reference; rawMessage = null; @@ -172,6 +178,7 @@ public WaveformDetail(DataReference reference, RekordboxAnlz anlzFile) { * @param data the waveform data as will be returned by {@link #getData()} * @param isColor indicates whether the data represents a color waveform */ + @API(status = API.Status.STABLE) public WaveformDetail(DataReference reference, ByteBuffer data, boolean isColor) { dataReference = reference; rawMessage = null; @@ -184,7 +191,7 @@ public WaveformDetail(DataReference reference, ByteBuffer data, boolean isColor) /** * The different colors the monochrome (blue) waveform can be based on its intensity. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color[] COLOR_MAP = { new Color(0, 104, 144), new Color(0, 136, 176), @@ -222,7 +229,7 @@ private int getColorWaveformBits(final ByteBuffer waveBytes, final int segment) * @return a value from 0 to 31 representing the height of the waveform at that segment, which may be an average * of a number of values starting there, determined by the scale */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public int segmentHeight(final int segment, final int scale) { final ByteBuffer waveBytes = getData(); final int limit = getFrameCount(); @@ -247,7 +254,7 @@ public int segmentHeight(final int segment, final int scale) { * @return the color of the waveform at that segment, which may be based on an average * of a number of values starting there, determined by the scale */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public Color segmentColor(final int segment, final int scale) { final ByteBuffer waveBytes = getData(); final int limit = getFrameCount(); diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailComponent.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailComponent.java index ae71f192..d32bcabf 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailComponent.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailComponent.java @@ -1,5 +1,6 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; import org.deepsymmetry.beatlink.*; import org.deepsymmetry.cratedigger.pdb.RekordboxAnlz; @@ -30,7 +31,7 @@ * player, if that player is a CDJ-3000, dynamic loops (set up on the fly by the DJ) are also displayed, and * inactive loops from the track metadata are drawn in gray rather than orange. */ -@SuppressWarnings("WeakerAccess") +@API(status = API.Status.STABLE) public class WaveformDetailComponent extends JComponent { private static final Logger logger = LoggerFactory.getLogger(WaveformDetailComponent.class); @@ -103,36 +104,36 @@ public class WaveformDetailComponent extends JComponent { * The color to which the background is cleared before drawing the waveform. The default is black, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference backgroundColor = new AtomicReference(Color.BLACK); + private final AtomicReference backgroundColor = new AtomicReference<>(Color.BLACK); /** * The color with which the playback position and tick markers are drawn. The default is white, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference indicatorColor = new AtomicReference(Color.WHITE); + private final AtomicReference indicatorColor = new AtomicReference<>(Color.WHITE); /** * The color with which the playback position is drawn while playback is active. The default is red, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference emphasisColor = new AtomicReference(Color.RED); + private final AtomicReference emphasisColor = new AtomicReference<>(Color.RED); /** * Determines the font to use when drawing hot cue, memory point, and loop labels. If {@code null}, they are * not drawn at all. */ private final AtomicReference labelFont = - new AtomicReference(javax.swing.UIManager.getDefaults().getFont("Label.font")); + new AtomicReference<>(javax.swing.UIManager.getDefaults().getFont("Label.font")); /** * The waveform preview that we are drawing. */ - private final AtomicReference waveform = new AtomicReference(); + private final AtomicReference waveform = new AtomicReference<>(); /** * Track the playback state for the players that have the track loaded. */ - private final Map playbackStateMap = new ConcurrentHashMap(4); + private final Map playbackStateMap = new ConcurrentHashMap<>(6); /** * Track how many segments we average into a column of pixels; larger values zoom out, 1 is full scale. @@ -142,12 +143,12 @@ public class WaveformDetailComponent extends JComponent { /** * Information about the cues, memory points, and loops in the track. */ - private final AtomicReference cueList = new AtomicReference(); + private final AtomicReference cueList = new AtomicReference<>(); /** * Information about where all the beats in the track fall, so we can draw them. */ - private final AtomicReference beatGrid = new AtomicReference(); + private final AtomicReference beatGrid = new AtomicReference<>(); /** * Controls whether we should obtain and display song structure information (phrases) at the bottom of the @@ -158,12 +159,12 @@ public class WaveformDetailComponent extends JComponent { /** * Information about the musical phrases that make up the current track, if we have it, so we can draw them. */ - private final AtomicReference songStructure = new AtomicReference(); + private final AtomicReference songStructure = new AtomicReference<>(); /** * The overlay painter that has been registered, if any. */ - private final AtomicReference overlayPainter = new AtomicReference(); + private final AtomicReference overlayPainter = new AtomicReference<>(); /** * Control whether the component should automatically center itself on the playback position of the player @@ -173,6 +174,7 @@ public class WaveformDetailComponent extends JComponent { * * @param auto should the waveform be centered on the playback position */ + @API(status = API.Status.STABLE) public void setAutoScroll(boolean auto) { if (autoScroll.getAndSet(auto) != auto) { setSize(getPreferredSize()); @@ -188,6 +190,7 @@ public void setAutoScroll(boolean auto) { * * @return {@code true} if the waveform will be centered on the playback position */ + @API(status = API.Status.STABLE) public boolean getAutoScroll() { return autoScroll.get(); } @@ -198,6 +201,7 @@ public boolean getAutoScroll() { * * @return the color used to draw the component background */ + @API(status = API.Status.STABLE) public Color getBackgroundColor() { return backgroundColor.get(); } @@ -208,6 +212,7 @@ public Color getBackgroundColor() { * * @param color the color used to draw the component background */ + @API(status = API.Status.STABLE) public void setBackgroundColor(Color color) { backgroundColor.set(color); } @@ -218,6 +223,7 @@ public void setBackgroundColor(Color color) { * * @return the color used to draw the playback and tick markers */ + @API(status = API.Status.STABLE) public Color getIndicatorColor() { return indicatorColor.get(); } @@ -228,6 +234,7 @@ public Color getIndicatorColor() { * @param color the color used to draw the playback marker when actively playing */ + @API(status = API.Status.STABLE) public void setIndicatorColor(Color color) { indicatorColor.set(color); } @@ -238,6 +245,7 @@ public void setIndicatorColor(Color color) { * * @return the color used to draw the playback and tick markers */ + @API(status = API.Status.STABLE) public Color getEmphasisColor() { return emphasisColor.get(); } @@ -248,6 +256,7 @@ public Color getEmphasisColor() { * @param color the color used to draw the playback marker when actively playing */ + @API(status = API.Status.STABLE) public void setEmphasisColor(Color color) { emphasisColor.set(color); } @@ -258,6 +267,7 @@ public void setEmphasisColor(Color color) { * * @param font if not {@code null}, draw labels for hot cues and named memory points and loops, and use this font */ + @API(status = API.Status.STABLE) public void setLabelFont(Font font) { labelFont.set(font); repaint(); @@ -269,6 +279,7 @@ public void setLabelFont(Font font) { * * @return if not {@code null}, labels are being drawn for hot cues and named memory points and loops, in this font */ + @API(status = API.Status.STABLE) public Font getLabelFont() { return labelFont.get(); } @@ -279,6 +290,7 @@ public Font getLabelFont() { * * @param songStructure the phrase information to be painted at the bottom of the waveform, or {@code null} to display none */ + @API(status = API.Status.STABLE) public void setSongStructure(RekordboxAnlz.SongStructureTag songStructure) { this.songStructure.set(songStructure); repaint(); @@ -296,7 +308,7 @@ private void setSongStructureWrapper(RekordboxAnlz.TaggedSection taggedSection) } else if (taggedSection.fourcc() == RekordboxAnlz.SectionTags.SONG_STRUCTURE) { setSongStructure((RekordboxAnlz.SongStructureTag) taggedSection.body()); } else { - logger.warn("Received unexpected analysis tag type:" + taggedSection); + logger.warn("Received unexpected analysis tag type: {}", taggedSection); } } @@ -306,6 +318,7 @@ private void setSongStructureWrapper(RekordboxAnlz.TaggedSection taggedSection) * * @param fetchSongStructures {@code true} if we should try to obtain and display phrase analysis information */ + @API(status = API.Status.STABLE) public synchronized void setFetchSongStructures(boolean fetchSongStructures) { this.fetchSongStructures.set(fetchSongStructures); if (fetchSongStructures && monitoredPlayer.get() > 0) { @@ -324,6 +337,7 @@ public synchronized void setFetchSongStructures(boolean fetchSongStructures) { * * @return {@code true} if we should try to obtain and display phrase analysis information */ + @API(status = API.Status.STABLE) public boolean getFetchSongStructures() { return fetchSongStructures.get(); } @@ -334,6 +348,7 @@ public boolean getFetchSongStructures() { * @param painter if not {@code null}, its {@link OverlayPainter#paintOverlay(Component, Graphics)} method will * be called once this component has done its own painting */ + @API(status = API.Status.STABLE) public void setOverlayPainter(OverlayPainter painter) { overlayPainter.set(painter); } @@ -394,6 +409,7 @@ private void repaintDueToPlaybackStateChange(PlaybackState oldState, PlaybackSta * * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void setPlaybackState(int player, long position, boolean playing) { if (getMonitoredPlayer() != 0 && player != getMonitoredPlayer()) { throw new IllegalStateException("Cannot setPlaybackState for another player when monitoring player " + getMonitoredPlayer()); @@ -415,6 +431,7 @@ public synchronized void setPlaybackState(int player, long position, boolean pla * @param player the player number whose playback state is no longer valid * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void clearPlaybackState(int player) { PlaybackState oldFurthestState = getFurthestPlaybackState(); PlaybackState oldState = playbackStateMap.remove(player); @@ -425,6 +442,7 @@ public synchronized void clearPlaybackState(int player) { * Removes all stored playback state. * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void clearPlaybackState() { for (PlaybackState state : playbackStateMap.values()) { clearPlaybackState(state.player); @@ -438,6 +456,7 @@ public synchronized void clearPlaybackState() { * @return the corresponding playback state, if any has been stored * @since 0.5.0 */ + @API(status = API.Status.STABLE) public PlaybackState getPlaybackState(int player) { return playbackStateMap.get(player); } @@ -448,8 +467,9 @@ public PlaybackState getPlaybackState(int player) { * @return the playback state recorded for any player * @since 0.5.0 */ + @API(status = API.Status.STABLE) public Set getPlaybackState() { - Set result = new HashSet(playbackStateMap.values()); + Set result = new HashSet<>(playbackStateMap.values()); return Collections.unmodifiableSet(result); } @@ -492,6 +512,7 @@ private void setPlaybackPosition(long milliseconds) { * * @throws IllegalArgumentException if scale is less than 1 or greater than 256 */ + @API(status = API.Status.STABLE) public void setScale(int scale) { if ((scale < 1) || (scale > 256)) { throw new IllegalArgumentException("Scale must be between 1 and 256"); @@ -511,6 +532,7 @@ public void setScale(int scale) { * * @return the current zoom scale. */ + @API(status = API.Status.STABLE) public int getScale() { return scale.get(); } @@ -539,6 +561,7 @@ private void setPlaying(boolean playing) { * @param metadata information about the track whose waveform we are drawing, so we can draw cue and memory points * @param beatGrid the locations of the beats, so they can be drawn */ + @API(status = API.Status.STABLE) public void setWaveform(WaveformDetail waveform, TrackMetadata metadata, BeatGrid beatGrid) { this.waveform.set(waveform); if (metadata != null) { @@ -562,6 +585,7 @@ public void setWaveform(WaveformDetail waveform, TrackMetadata metadata, BeatGri * @param cueList used to draw cue and memory points * @param beatGrid the locations of the beats, so they can be drawn */ + @API(status = API.Status.STABLE) public void setWaveform(WaveformDetail waveform, CueList cueList, BeatGrid beatGrid) { this.waveform.set(waveform); this.cueList.set(cueList); @@ -578,6 +602,7 @@ public void setWaveform(WaveformDetail waveform, CueList cueList, BeatGrid beatG * * @return the waveform detail being displayed by this component. */ + @API(status = API.Status.STABLE) public WaveformDetail getWaveform() { return this.waveform.get(); } @@ -594,6 +619,7 @@ public WaveformDetail getWaveform() { * * @param player the player number to monitor, or zero if monitoring should stop */ + @API(status = API.Status.STABLE) public synchronized void setMonitoredPlayer(final int player) { if (player < 0) { throw new IllegalArgumentException("player cannot be negative"); @@ -633,19 +659,16 @@ public synchronized void setMonitoredPlayer(final int player) { TimeFinder.getInstance().start(); if (!animating.getAndSet(true)) { // Create the thread to update our position smoothly as the track plays - new Thread(new Runnable() { - @Override - public void run() { - while (animating.get()) { - try { - //noinspection BusyWait - Thread.sleep(33); // Animate at 30 fps - } catch (InterruptedException e) { - logger.warn("Waveform animation thread interrupted; ending"); - animating.set(false); - } - setPlaybackPosition(TimeFinder.getInstance().getTimeFor(getMonitoredPlayer())); + new Thread(() -> { + while (animating.get()) { + try { + //noinspection BusyWait + Thread.sleep(33); // Animate at 30 fps + } catch (InterruptedException e) { + logger.warn("Waveform animation thread interrupted; ending"); + animating.set(false); } + setPlaybackPosition(TimeFinder.getInstance().getTimeFor(getMonitoredPlayer())); } }).start(); } @@ -675,6 +698,7 @@ public void run() { * * @return the player number being monitored, or zero if none */ + @API(status = API.Status.STABLE) public int getMonitoredPlayer() { return monitoredPlayer.get(); } @@ -682,17 +706,14 @@ public int getMonitoredPlayer() { /** * Reacts to changes in the track metadata associated with the player we are monitoring. */ - private final TrackMetadataListener metadataListener = new TrackMetadataListener() { - @Override - public void metadataChanged(TrackMetadataUpdate update) { - if (update.player == getMonitoredPlayer()) { - if (update.metadata != null) { - cueList.set(update.metadata.getCueList()); - } else { - cueList.set(null); - } - repaint(); + private final TrackMetadataListener metadataListener = update -> { + if (update.player == getMonitoredPlayer()) { + if (update.metadata != null) { + cueList.set(update.metadata.getCueList()); + } else { + cueList.set(null); } + repaint(); } }; @@ -721,36 +742,27 @@ public void detailChanged(WaveformDetailUpdate update) { /** * Reacts to changes in the beat grid associated with the player we are monitoring. */ - private final BeatGridListener beatGridListener = new BeatGridListener() { - @Override - public void beatGridChanged(BeatGridUpdate update) { - if (update.player == getMonitoredPlayer()) { - beatGrid.set(update.beatGrid); - repaint(); - } + private final BeatGridListener beatGridListener = update -> { + if (update.player == getMonitoredPlayer()) { + beatGrid.set(update.beatGrid); + repaint(); } }; /** * Reacts to player status updates to reflect the current playback state. */ - private final DeviceUpdateListener updateListener = new DeviceUpdateListener() { - @Override - public void received(DeviceUpdate update) { - if ((update instanceof CdjStatus) && (update.getDeviceNumber() == getMonitoredPlayer()) && - (cueList.get() != null) && (beatGrid.get() != null)) { - CdjStatus status = (CdjStatus) update; - setPlaying(status.isPlaying()); - } + private final DeviceUpdateListener updateListener = update -> { + if ((update instanceof CdjStatus) && (update.getDeviceNumber() == getMonitoredPlayer()) && + (cueList.get() != null) && (beatGrid.get() != null)) { + CdjStatus status = (CdjStatus) update; + setPlaying(status.isPlaying()); } }; - private final AnalysisTagListener analysisTagListener = new AnalysisTagListener() { - @Override - public void analysisChanged(AnalysisTagUpdate update) { - if (update.player == getMonitoredPlayer()) { - setSongStructureWrapper(update.taggedSection); - } + private final AnalysisTagListener analysisTagListener = update -> { + if (update.player == getMonitoredPlayer()) { + setSongStructureWrapper(update.taggedSection); } }; @@ -760,6 +772,7 @@ public void analysisChanged(AnalysisTagUpdate update) { * * @param player the player number to monitor, or zero if it should start out monitoring no player */ + @API(status = API.Status.STABLE) public WaveformDetailComponent(int player) { setMonitoredPlayer(player); } @@ -771,6 +784,7 @@ public WaveformDetailComponent(int player) { * @param metadata information about the track whose waveform we are drawing, so we can draw cues and memory points * @param beatGrid the locations of the beats, so they can be drawn */ + @API(status = API.Status.STABLE) public WaveformDetailComponent(WaveformDetail waveform, TrackMetadata metadata, BeatGrid beatGrid) { this.waveform.set(waveform); if (metadata != null) { @@ -786,6 +800,7 @@ public WaveformDetailComponent(WaveformDetail waveform, TrackMetadata metadata, * @param cueList used to draw cues and memory points * @param beatGrid the locations of the beats, so they can be drawn */ + @API(status = API.Status.STABLE) public WaveformDetailComponent(WaveformDetail waveform, CueList cueList, BeatGrid beatGrid) { this.waveform.set(waveform); this.cueList.set(cueList); @@ -812,6 +827,7 @@ public Dimension getPreferredSize() { * * @return the playback state, if any, with the highest playing {@link PlaybackState#position} value */ + @API(status = API.Status.STABLE) public PlaybackState getFurthestPlaybackState() { PlaybackState result = null; for (PlaybackState state : playbackStateMap.values()) { @@ -829,6 +845,7 @@ public PlaybackState getFurthestPlaybackState() { * * @return The position in milliseconds of the furthest playback state reached, or 0 if there are no playback states */ + @API(status = API.Status.STABLE) public long getFurthestPlaybackPosition() { PlaybackState state = getFurthestPlaybackState(); if (state != null) { @@ -859,6 +876,7 @@ private int getSegmentForX(int x) { * @param x the horizontal position within the component coordinate space * @return the number of milliseconds into the track this would correspond to (may fall outside the actual track) */ + @API(status = API.Status.STABLE) public long getTimeForX(int x) { return Util.halfFrameToTime(getSegmentForX(x)); } @@ -871,6 +889,7 @@ public long getTimeForX(int x) { * @param x the horizontal position within the component coordinate space * @return the beat number being played at that point, or -1 if the point is before the first beat */ + @API(status = API.Status.STABLE) public int getBeatForX(int x) { BeatGrid grid = beatGrid.get(); if (grid != null) { @@ -886,6 +905,7 @@ public int getBeatForX(int x) { * @return the horizontal position within the component coordinate space where that beat begins * @throws IllegalArgumentException if the beat number exceeds the number of beats in the track. */ + @API(status = API.Status.STABLE) public int getXForBeat(int beat) { BeatGrid grid = beatGrid.get(); if (grid != null) { @@ -901,6 +921,7 @@ public int getXForBeat(int beat) { * * @return the component x coordinate at which it should be drawn */ + @API(status = API.Status.STABLE) public int millisecondsToX(long milliseconds) { if (autoScroll.get()) { int playHead = (getWidth() / 2) + 2; @@ -926,6 +947,7 @@ public int millisecondsToX(long milliseconds) { * @deprecated use {@link CueList.Entry#getColor()} instead */ @Deprecated + @API(status = API.Status.DEPRECATED) public static Color cueColor(CueList.Entry entry) { return entry.getColor(); } @@ -936,6 +958,7 @@ public static Color cueColor(CueList.Entry entry) { * * @return whether we have seen a status packet from a tracked player that is a CDJ-3000 or equivalent */ + @API(status = API.Status.STABLE) public boolean isDynamicLoopDataAvailable() { if (!VirtualCdj.getInstance().isRunning()) return false; for (final PlaybackState state : playbackStateMap.values()) { @@ -1130,16 +1153,7 @@ private void paintPhrases(Graphics g, Rectangle clipRect, RekordboxAnlz.SongStru // Have the phrase labels stick to the left edge of the viewable area as they scroll by. // Start by finding our parent scroll pane, if there is one, so we can figure out its horizontal scroll position. - int scrolledX = 0; - Container parent = getParent(); - while (parent != null) { - if (parent instanceof JScrollPane) { - scrolledX = ((JScrollPane) parent).getViewport().getViewPosition().x; - parent = null; // We are done searching for our scroll pane. - } else { - parent = parent.getParent(); - } - } + final int scrolledX = getScrolledX(); for (int i = 0; i < songStructure.lenEntries(); i++) { final RekordboxAnlz.SongStructureEntry entry = songStructure.body().entries().get(i); @@ -1187,6 +1201,25 @@ private void paintPhrases(Graphics g, Rectangle clipRect, RekordboxAnlz.SongStru } } + /** + * Find how far the waveform has been scrolled. + * + * @return the X position of our scroll pane + */ + private int getScrolledX() { + int scrolledX = 0; + Container parent = getParent(); + while (parent != null) { + if (parent instanceof JScrollPane) { + scrolledX = ((JScrollPane) parent).getViewport().getViewPosition().x; + parent = null; // We are done searching for our scroll pane. + } else { + parent = parent.getParent(); + } + } + return scrolledX; + } + @Override public String toString() { diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailUpdate.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailUpdate.java index 062ead37..f7069198 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailUpdate.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformDetailUpdate.java @@ -1,15 +1,18 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; + /** * Provides notification when the waveform detail associated with a player changes. * * @author James Elliott */ -@SuppressWarnings("WeakerAccess") +@API(status = API.Status.STABLE) public class WaveformDetailUpdate { /** * The player number for which a waveform detail change has occurred. */ + @API(status = API.Status.STABLE) public final int player; /** @@ -17,6 +20,7 @@ public class WaveformDetailUpdate { * {@code null} if we don't have any detail available (including for a brief period after a new track has been * loaded while we are requesting the waveform detail). */ + @API(status = API.Status.STABLE) public final WaveformDetail detail; WaveformDetailUpdate(int player, WaveformDetail detail) { diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformFinder.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformFinder.java index 150a2be0..728f919c 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformFinder.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformFinder.java @@ -1,5 +1,6 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; import org.deepsymmetry.beatlink.*; import org.deepsymmetry.beatlink.dbserver.*; import org.slf4j.Logger; @@ -25,6 +26,7 @@ * * @author James Elliott */ +@API(status = API.Status.STABLE) public class WaveformFinder extends LifecycleParticipant { private static final Logger logger = LoggerFactory.getLogger(WaveformFinder.class); @@ -33,20 +35,18 @@ public class WaveformFinder extends LifecycleParticipant { * Keeps track of the current waveform previews cached for each player. We hot cache data for any track which is * currently on-deck in the player, as well as any that were loaded into a player's hot-cue slot. */ - private final Map previewHotCache = - new ConcurrentHashMap(); + private final Map previewHotCache = new ConcurrentHashMap<>(); /** * Keeps track of the current waveform details cached for each player. We hot cache data for any track which is * currently on-deck in the player, as well as any that were loaded into a player's hot-cue slot. */ - private final Map detailHotCache = - new ConcurrentHashMap(); + private final Map detailHotCache = new ConcurrentHashMap<>(); /** * Should we ask for details as well as the previews? */ - final private AtomicBoolean findDetails = new AtomicBoolean(true); + private final AtomicBoolean findDetails = new AtomicBoolean(true); /** * Set whether we should retrieve the waveform details in addition to the waveform previews. @@ -54,20 +54,18 @@ public class WaveformFinder extends LifecycleParticipant { * @param findDetails if {@code true}, both types of waveform will be retrieved, if {@code false} only previews * will be retrieved */ + @API(status = API.Status.STABLE) public final void setFindDetails(boolean findDetails) { this.findDetails.set(findDetails); if (findDetails) { primeCache(); // Get details for any tracks that were already loaded on players. } else { // Inform our listeners, on the proper thread, that the detailed waveforms are no longer available - final Set dyingCache = new HashSet(detailHotCache.keySet()); + final Set dyingCache = new HashSet<>(detailHotCache.keySet()); detailHotCache.clear(); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - for (DeckReference deck : dyingCache) { - deliverWaveformDetailUpdate(deck.player, null); - } + SwingUtilities.invokeLater(() -> { + for (DeckReference deck : dyingCache) { + deliverWaveformDetailUpdate(deck.player, null); } }); } @@ -79,7 +77,7 @@ public void run() { * @return {@code true} if both types of waveform are being retrieved, {@code false} if only previews * are being retrieved */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final boolean isFindingDetails() { return findDetails.get(); } @@ -87,7 +85,7 @@ public final boolean isFindingDetails() { /** * Should we ask for color versions of the waveforms and previews if they are available? */ - final private AtomicBoolean preferColor = new AtomicBoolean(true); + private final AtomicBoolean preferColor = new AtomicBoolean(true); /** * Set whether we should obtain color versions of waveforms and previews when they are available. This will only @@ -97,6 +95,7 @@ public final boolean isFindingDetails() { * @param preferColor if {@code true}, the full-color versions of waveforms will be requested, if {@code false} * only the older blue versions will be retrieved */ + @API(status = API.Status.STABLE) public final void setColorPreferred(boolean preferColor) { if (this.preferColor.compareAndSet(!preferColor, preferColor) && isRunning()) { stop(); @@ -114,6 +113,7 @@ public final void setColorPreferred(boolean preferColor) { * @return {@code true} if full-color of waveform are being retrieved, {@code false} if the older blue versions * are being retrieved */ + @API(status = API.Status.STABLE) public final boolean isColorPreferred() { return preferColor.get(); } @@ -122,20 +122,16 @@ public final boolean isColorPreferred() { * A queue used to hold metadata updates we receive from the {@link MetadataFinder} so we can process them on a * lower priority thread, and not hold up delivery to more time-sensitive listeners. */ - private final LinkedBlockingDeque pendingUpdates = - new LinkedBlockingDeque(100); + private final LinkedBlockingDeque pendingUpdates = new LinkedBlockingDeque<>(100); /** * Our metadata listener just puts metadata updates on our queue, so we can process them on a lower * priority thread, and not hold up delivery to more time-sensitive listeners. */ - private final TrackMetadataListener metadataListener = new TrackMetadataListener() { - @Override - public void metadataChanged(TrackMetadataUpdate update) { - logger.debug("Received metadata update {}", update); - if (!pendingUpdates.offerLast(update)) { - logger.warn("Discarding metadata update because our queue is backed up."); - } + private final TrackMetadataListener metadataListener = update -> { + logger.debug("Received metadata update {}", update); + if (!pendingUpdates.offerLast(update)) { + logger.warn("Discarding metadata update because our queue is backed up."); } }; @@ -152,14 +148,14 @@ public void mediaMounted(SlotReference slot) { @Override public void mediaUnmounted(SlotReference slot) { // Iterate over a copy to avoid concurrent modification issues - for (Map.Entry entry : new HashMap(previewHotCache).entrySet()) { + for (Map.Entry entry : new HashMap<>(previewHotCache).entrySet()) { if (slot == SlotReference.getSlotReference(entry.getValue().dataReference)) { logger.debug("Evicting cached waveform preview in response to unmount report {}", entry.getValue()); previewHotCache.remove(entry.getKey()); } } // Again iterate over a copy to avoid concurrent modification issues - for (Map.Entry entry : new HashMap(detailHotCache).entrySet()) { + for (Map.Entry entry : new HashMap<>(detailHotCache).entrySet()) { if (slot == SlotReference.getSlotReference(entry.getValue().dataReference)) { logger.debug("Evicting cached waveform detail in response to unmount report {}", entry.getValue()); detailHotCache.remove(entry.getKey()); @@ -199,6 +195,7 @@ public void deviceLost(DeviceAnnouncement announcement) { * * @see MetadataFinder#isPassive() */ + @API(status = API.Status.STABLE) public boolean isRunning() { return running.get(); } @@ -255,7 +252,7 @@ private void clearDeck(TrackMetadataUpdate update) { private void clearWaveforms(DeviceAnnouncement announcement) { final int player = announcement.getDeviceNumber(); // Iterate over a copy to avoid concurrent modification issues - for (DeckReference deck : new HashSet(previewHotCache.keySet())) { + for (DeckReference deck : new HashSet<>(previewHotCache.keySet())) { if (deck.player == player) { previewHotCache.remove(deck); if (deck.hotCue == 0) { @@ -264,7 +261,7 @@ private void clearWaveforms(DeviceAnnouncement announcement) { } } // Again iterate over a copy to avoid concurrent modification issues - for (DeckReference deck : new HashSet(detailHotCache.keySet())) { + for (DeckReference deck : new HashSet<>(detailHotCache.keySet())) { if (deck.player == player) { detailHotCache.remove(deck); if (deck.hotCue == 0) { @@ -318,11 +315,11 @@ private void updateDetail(TrackMetadataUpdate update, WaveformDetail detail) { * * @throws IllegalStateException if the WaveformFinder is not running */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public Map getLoadedPreviews() { ensureRunning(); // Make a copy so callers get an immutable snapshot of the current state. - return Collections.unmodifiableMap(new HashMap(previewHotCache)); + return Map.copyOf(previewHotCache); } /** @@ -333,14 +330,14 @@ public Map getLoadedPreviews() { * * @throws IllegalStateException if the WaveformFinder is not running or requesting waveform details */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public Map getLoadedDetails() { ensureRunning(); if (!isFindingDetails()) { throw new IllegalStateException("WaveformFinder is not configured to find waveform details."); } // Make a copy so callers get an immutable snapshot of the current state. - return Collections.unmodifiableMap(new HashMap(detailHotCache)); + return Map.copyOf(detailHotCache); } /** @@ -352,6 +349,7 @@ public Map getLoadedDetails() { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformPreview getLatestPreviewFor(int player) { ensureRunning(); return previewHotCache.get(DeckReference.getDeckReference(player, 0)); @@ -366,6 +364,7 @@ public WaveformPreview getLatestPreviewFor(int player) { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformPreview getLatestPreviewFor(DeviceUpdate update) { return getLatestPreviewFor(update.getDeviceNumber()); } @@ -379,6 +378,7 @@ public WaveformPreview getLatestPreviewFor(DeviceUpdate update) { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformDetail getLatestDetailFor(int player) { ensureRunning(); return detailHotCache.get(DeckReference.getDeckReference(player, 0)); @@ -393,6 +393,7 @@ public WaveformDetail getLatestDetailFor(int player) { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformDetail getLatestDetailFor(DeviceUpdate update) { return getLatestDetailFor(update.getDeviceNumber()); } @@ -425,12 +426,8 @@ private WaveformPreview requestPreviewInternal(final DataReference trackReferenc } // We have to actually request the preview using the dbserver protocol. - ConnectionManager.ClientTask task = new ConnectionManager.ClientTask() { - @Override - public WaveformPreview useClient(Client client) throws Exception { - return getWaveformPreview(trackReference.rekordboxId, SlotReference.getSlotReference(trackReference), client); - } - }; + ConnectionManager.ClientTask task = + client -> getWaveformPreview(trackReference.rekordboxId, SlotReference.getSlotReference(trackReference), client); try { return ConnectionManager.getInstance().invokeWithClientSession(trackReference.player, task, "requesting waveform preview"); @@ -450,6 +447,7 @@ public WaveformPreview useClient(Client client) throws Exception { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformPreview requestWaveformPreviewFrom(final DataReference dataReference) { ensureRunning(); for (WaveformPreview cached : previewHotCache.values()) { @@ -485,10 +483,10 @@ WaveformPreview getWaveformPreview(int rekordboxId, SlotReference slot, Client c if (response.knownType != Message.KnownType.UNAVAILABLE && response.arguments.get(3).getSize() > 0) { return new WaveformPreview(new DataReference(slot, rekordboxId), response); } else { - logger.info("No color waveform preview available for slot " + slot + ", id " + rekordboxId + "; requesting blue version."); + logger.info("No color waveform preview available for slot {}, id {}; requesting blue version.", slot, rekordboxId); } } catch (Exception e) { - logger.info("No color waveform preview available for slot " + slot + ", id " + rekordboxId + "; requesting blue version.", e); + logger.info("No color waveform preview available for slot {}, id {}; requesting blue version.", slot, rekordboxId, e); } } @@ -526,12 +524,8 @@ private WaveformDetail requestDetailInternal(final DataReference trackReference, } // We have to actually request the detail using the dbserver protocol. - ConnectionManager.ClientTask task = new ConnectionManager.ClientTask() { - @Override - public WaveformDetail useClient(Client client) throws Exception { - return getWaveformDetail(trackReference.rekordboxId, SlotReference.getSlotReference(trackReference), client); - } - }; + ConnectionManager.ClientTask task = + client -> getWaveformDetail(trackReference.rekordboxId, SlotReference.getSlotReference(trackReference), client); try { return ConnectionManager.getInstance().invokeWithClientSession(trackReference.player, task, "requesting waveform detail"); @@ -551,6 +545,7 @@ public WaveformDetail useClient(Client client) throws Exception { * * @throws IllegalStateException if the WaveformFinder is not running */ + @API(status = API.Status.STABLE) public WaveformDetail requestWaveformDetailFrom(final DataReference dataReference) { ensureRunning(); for (WaveformDetail cached : detailHotCache.values()) { @@ -585,10 +580,10 @@ WaveformDetail getWaveformDetail(int rekordboxId, SlotReference slot, Client cli if (response.knownType != Message.KnownType.UNAVAILABLE && response.arguments.get(3).getSize() > 0) { return new WaveformDetail(new DataReference(slot, rekordboxId), response); } else { - logger.info("No color waveform available for slot " + slot + ", id " + rekordboxId + "; requesting blue version."); + logger.info("No color waveform available for slot {}, id {}; requesting blue version.", slot, rekordboxId); } } catch (Exception e) { - logger.info("Problem requesting color waveform for slot " + slot + ", id " + rekordboxId + "; requesting blue version.", e); + logger.info("Problem requesting color waveform for slot {}, id {}; requesting blue version.", slot, rekordboxId, e); } } @@ -600,18 +595,17 @@ WaveformDetail getWaveformDetail(int rekordboxId, SlotReference slot, Client cli /** * Keep track of the devices we are currently trying to get previews from in response to metadata updates. */ - private final Set activePreviewRequests = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set activePreviewRequests = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Keep track of the devices we are currently trying to get details from in response to metadata updates. */ - private final Set activeDetailRequests = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set activeDetailRequests = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Keeps track of the registered waveform listeners. */ - private final Set waveformListeners = - Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set waveformListeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** *

Adds the specified waveform listener to receive updates when the waveform information for a player changes. @@ -619,14 +613,15 @@ WaveformDetail getWaveformDetail(int rekordboxId, SlotReference slot, Client cli * thrown and no action is performed.

* *

Updates are delivered to listeners on the Swing Event Dispatch thread, so it is safe to interact with - * user interface elements within the event handler. + * user interface elements within the event handler.

* - * Even so, any code in the listener method must finish quickly, or it will freeze the user interface, + *

Even so, any code in the listener method must finish quickly, or it will freeze the user interface, * add latency for other listeners, and updates will back up. If you want to perform lengthy processing of any sort, * do so on another thread.

* * @param listener the waveform update listener to add */ + @API(status = API.Status.STABLE) public void addWaveformListener(WaveformListener listener) { if (listener != null) { waveformListeners.add(listener); @@ -640,6 +635,7 @@ public void addWaveformListener(WaveformListener listener) { * * @param listener the waveform update listener to remove */ + @API(status = API.Status.STABLE) public void removeWaveformListener(WaveformListener listener) { if (listener != null) { waveformListeners.remove(listener); @@ -651,10 +647,10 @@ public void removeWaveformListener(WaveformListener listener) { * * @return the listeners that are currently registered for waveform updates */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public Set getWaveformListeners() { // Make a copy so callers get an immutable snapshot of the current state. - return Collections.unmodifiableSet(new HashSet(waveformListeners)); + return Set.copyOf(waveformListeners); } /** @@ -666,17 +662,14 @@ public Set getWaveformListeners() { private void deliverWaveformPreviewUpdate(final int player, final WaveformPreview preview) { final Set listeners = getWaveformListeners(); if (!listeners.isEmpty()) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - final WaveformPreviewUpdate update = new WaveformPreviewUpdate(player, preview); - for (final WaveformListener listener : listeners) { - try { - listener.previewChanged(update); - - } catch (Throwable t) { - logger.warn("Problem delivering waveform preview update to listener", t); - } + SwingUtilities.invokeLater(() -> { + final WaveformPreviewUpdate update = new WaveformPreviewUpdate(player, preview); + for (final WaveformListener listener : listeners) { + try { + listener.previewChanged(update); + + } catch (Throwable t) { + logger.warn("Problem delivering waveform preview update to listener", t); } } }); @@ -691,17 +684,14 @@ public void run() { */ private void deliverWaveformDetailUpdate(final int player, final WaveformDetail detail) { if (!getWaveformListeners().isEmpty()) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - final WaveformDetailUpdate update = new WaveformDetailUpdate(player, detail); - for (final WaveformListener listener : getWaveformListeners()) { - try { - listener.detailChanged(update); - - } catch (Throwable t) { - logger.warn("Problem delivering waveform detail update to listener", t); - } + SwingUtilities.invokeLater(() -> { + final WaveformDetailUpdate update = new WaveformDetailUpdate(player, detail); + for (final WaveformListener listener : getWaveformListeners()) { + try { + listener.detailChanged(update); + + } catch (Throwable t) { + logger.warn("Problem delivering waveform detail update to listener", t); } } }); @@ -737,19 +727,16 @@ private void handleUpdate(final TrackMetadataUpdate update) { if (!foundInCache && activePreviewRequests.add(update.player)) { clearDeckPreview(update); // We won't know what it is until our request completes. // We had to make sure we were not already asking for this track. - new Thread(new Runnable() { - @Override - public void run() { - try { - WaveformPreview preview = requestPreviewInternal(update.metadata.trackReference, true); - if (preview != null) { - updatePreview(update, preview); - } - } catch (Exception e) { - logger.warn("Problem requesting waveform preview from update" + update, e); - } finally { - activePreviewRequests.remove(update.player); + new Thread(() -> { + try { + WaveformPreview preview = requestPreviewInternal(update.metadata.trackReference, true); + if (preview != null) { + updatePreview(update, preview); } + } catch (Exception e) { + logger.warn("Problem requesting waveform preview from update {}", update, e); + } finally { + activePreviewRequests.remove(update.player); } }).start(); } @@ -773,19 +760,16 @@ public void run() { if (!foundInCache && activeDetailRequests.add(update.player)) { clearDeckDetail(update); // We won't know what it is until our request completes. // We had to make sure we were not already asking for this track. - new Thread(new Runnable() { - @Override - public void run() { - try { - WaveformDetail detail = requestDetailInternal(update.metadata.trackReference, true); - if (detail != null) { - updateDetail(update, detail); - } - } catch (Exception e) { - logger.warn("Problem requesting waveform detail from update" + update, e); - } finally { - activeDetailRequests.remove(update.player); + new Thread(() -> { + try { + WaveformDetail detail = requestDetailInternal(update.metadata.trackReference, true); + if (detail != null) { + updateDetail(update, detail); } + } catch (Exception e) { + logger.warn("Problem requesting waveform detail from update {}", update, e); + } finally { + activeDetailRequests.remove(update.player); } }).start(); } @@ -816,13 +800,10 @@ public void stopped(LifecycleParticipant sender) { * details, since we missed them. */ private void primeCache() { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - for (Map.Entry entry : MetadataFinder.getInstance().getLoadedTracks().entrySet()) { - if (entry.getKey().hotCue == 0) { // The track is currently loaded in a main player deck - handleUpdate(new TrackMetadataUpdate(entry.getKey().player, entry.getValue())); - } + SwingUtilities.invokeLater(() -> { + for (Map.Entry entry : MetadataFinder.getInstance().getLoadedTracks().entrySet()) { + if (entry.getKey().hotCue == 0) { // The track is currently loaded in a main player deck + handleUpdate(new TrackMetadataUpdate(entry.getKey().player, entry.getValue())); } } }); @@ -836,6 +817,7 @@ public void run() { * * @throws Exception if there is a problem starting the required components */ + @API(status = API.Status.STABLE) public synchronized void start() throws Exception { if (!isRunning()) { ConnectionManager.getInstance().addLifecycleListener(lifecycleListener); @@ -845,17 +827,14 @@ public synchronized void start() throws Exception { MetadataFinder.getInstance().start(); MetadataFinder.getInstance().addTrackMetadataListener(metadataListener); MetadataFinder.getInstance().addMountListener(mountListener); - queueHandler = new Thread(new Runnable() { - @Override - public void run() { - while (isRunning()) { - try { - handleUpdate(pendingUpdates.take()); - } catch (InterruptedException e) { - // Interrupted due to MetadataFinder shutdown, presumably - } catch (Throwable t) { - logger.error("Problem processing metadata update", t); - } + queueHandler = new Thread(() -> { + while (isRunning()) { + try { + handleUpdate(pendingUpdates.take()); + } catch (InterruptedException e) { + // Interrupted due to MetadataFinder shutdown, presumably + } catch (Throwable t) { + logger.error("Problem processing metadata update", t); } } }); @@ -869,7 +848,7 @@ public void run() { /** * Stop finding waveforms for all active players. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public synchronized void stop() { if (isRunning()) { MetadataFinder.getInstance().removeTrackMetadataListener(metadataListener); @@ -879,22 +858,19 @@ public synchronized void stop() { queueHandler = null; // Report the loss of our waveforms, on the proper thread, outside our lock. - final Set dyingPreviewCache = new HashSet(previewHotCache.keySet()); + final Set dyingPreviewCache = new HashSet<>(previewHotCache.keySet()); previewHotCache.clear(); - final Set dyingDetailCache = new HashSet(detailHotCache.keySet()); + final Set dyingDetailCache = new HashSet<>(detailHotCache.keySet()); detailHotCache.clear(); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - for (DeckReference deck : dyingPreviewCache) { // Report the loss of our previews. - if (deck.hotCue == 0) { - deliverWaveformPreviewUpdate(deck.player, null); - } + SwingUtilities.invokeLater(() -> { + for (DeckReference deck : dyingPreviewCache) { // Report the loss of our previews. + if (deck.hotCue == 0) { + deliverWaveformPreviewUpdate(deck.player, null); } - for (DeckReference deck : dyingDetailCache) { // Report the loss of our details. - if (deck.hotCue == 0) { - deliverWaveformDetailUpdate(deck.player, null); - } + } + for (DeckReference deck : dyingDetailCache) { // Report the loss of our details. + if (deck.hotCue == 0) { + deliverWaveformDetailUpdate(deck.player, null); } } }); @@ -912,6 +888,7 @@ public void run() { * * @return the only instance of this class which exists. */ + @API(status = API.Status.STABLE) public static WaveformFinder getInstance() { return ourInstance; } diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformListener.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformListener.java index 55e0aed9..5a8212b2 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformListener.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformListener.java @@ -1,5 +1,7 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; + /** *

The listener interface for receiving updates when the waveforms available for a track loaded in any player * change.

@@ -14,13 +16,14 @@ * * @author James Elliott */ -@SuppressWarnings("WeakerAccess") +@API(status = API.Status.STABLE) public interface WaveformListener { /** * Called when the waveform preview available for a player has changed. * * @param update provides information about what has changed */ + @API(status = API.Status.STABLE) void previewChanged(WaveformPreviewUpdate update); /** @@ -28,5 +31,6 @@ public interface WaveformListener { * * @param update provides information about what has changed */ + @API(status = API.Status.STABLE) void detailChanged(WaveformDetailUpdate update); } diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreview.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreview.java index 3d139067..6c9d72c9 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreview.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreview.java @@ -1,5 +1,6 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; import org.deepsymmetry.beatlink.Util; import org.deepsymmetry.beatlink.dbserver.BinaryField; import org.deepsymmetry.beatlink.dbserver.Message; @@ -16,6 +17,7 @@ * * @author James Elliott */ +@API(status = API.Status.STABLE) public class WaveformPreview { /** @@ -23,13 +25,13 @@ public class WaveformPreview { * nxs2 ANLZ tag request. We actually know what these mean, now that we know how to parse EXT files, but we * can simply skip them anyway. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final int LEADING_DBSERVER_COLOR_JUNK_BYTES = 28; /** * The unique identifier that was used to request this waveform preview. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final DataReference dataReference; /** @@ -37,7 +39,7 @@ public class WaveformPreview { * This can be used to analyze fields that have not yet been reliably understood, and is also used for storing * the cue list in a file. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final Message rawMessage; /** @@ -50,7 +52,7 @@ public class WaveformPreview { /** * Indicates whether this is an NXS2-style color waveform, or a monochrome (blue) waveform. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final boolean isColor; /** @@ -59,7 +61,7 @@ public class WaveformPreview { * @return the bytes from which the preview can be drawn, as described in the * Packet Analysis document. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public ByteBuffer getData() { expandedData.rewind(); return expandedData.slice(); @@ -68,14 +70,14 @@ public ByteBuffer getData() { /** * The pixel width (number of waveform preview columns) available. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final int segmentCount; /** * Holds the maximum height of any point along the waveform, so that it can be drawn in a normalized manner to fit * its display area. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public final int maxHeight; /** @@ -89,6 +91,7 @@ public ByteBuffer getData() { * * @return the component which will draw the annotated waveform preview */ + @API(status = API.Status.STABLE) public JComponent createViewComponent(TrackMetadata metadata) { return new WaveformPreviewComponent(this, metadata); } @@ -138,6 +141,7 @@ private int getMaxHeight() { * @param reference the unique database reference that was used to request this waveform preview * @param anlzFile the parsed rekordbox track analysis file containing the waveform preview */ + @API(status = API.Status.STABLE) public WaveformPreview(DataReference reference, RekordboxAnlz anlzFile) { dataReference = reference; rawMessage = null; @@ -182,6 +186,7 @@ public WaveformPreview(DataReference reference, RekordboxAnlz anlzFile) { * @param data the expanded data as will be returned by {@link #getData()} * @param isColor indicates whether the data represents a color preview */ + @API(status = API.Status.STABLE) public WaveformPreview(DataReference reference, ByteBuffer data, boolean isColor) { dataReference = reference; rawMessage = null; @@ -196,13 +201,13 @@ public WaveformPreview(DataReference reference, ByteBuffer data, boolean isColor /** * The color at which segments of the blue waveform marked most intense are drawn. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color INTENSE_COLOR = new Color(116, 246, 244); /** * The color at which non-intense blue waveform segments are drawn. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color NORMAL_COLOR = new Color(43, 89, 255); /** @@ -215,7 +220,7 @@ public WaveformPreview(DataReference reference, ByteBuffer data, boolean isColor * @return a value from 0 to 31 representing the height of the waveform at that segment, which may be an average * of a number of values starting there, determined by the scale */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public int segmentHeight(final int segment, final boolean front) { final ByteBuffer bytes = getData(); if (isColor) { @@ -241,7 +246,7 @@ public int segmentHeight(final int segment, final boolean front) { * @return the color of the waveform at that segment, which may be based on an average * of a number of values starting there, determined by the scale */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public Color segmentColor(final int segment, final boolean front) { final ByteBuffer bytes = getData(); if (isColor) { diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewComponent.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewComponent.java index b9cf89f2..bc03dbab 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewComponent.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewComponent.java @@ -1,5 +1,6 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; import org.deepsymmetry.beatlink.*; import org.deepsymmetry.cratedigger.pdb.RekordboxAnlz; import org.slf4j.Logger; @@ -26,7 +27,7 @@ * as long as it is able to load appropriate metadata, which includes beat grids for translating beat numbers * into track time. */ -@SuppressWarnings("WeakerAccess") +@API(status = API.Status.STABLE) public class WaveformPreviewComponent extends JComponent { private static final Logger logger = LoggerFactory.getLogger(WaveformPreviewComponent.class); @@ -72,11 +73,13 @@ private int waveformWidth() { /** * The minimum acceptable height for the waveform. */ - private static final int MIN_WAVEFORM_HEIGHT = 31; + @API(status = API.Status.STABLE) + public static final int MIN_WAVEFORM_HEIGHT = 31; /** * The minimum acceptable width for the waveform. */ + @API(status = API.Status.STABLE) public static final int MIN_WAVEFORM_WIDTH = 200; /** @@ -119,21 +122,21 @@ private int positionMarkerHeight() { * The color for brighter sections of the already-played section of the playback progress bar. Note that * if the indicator color has been changed, only the transparency (alpha) channel of this is used. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color BRIGHT_PLAYED = new Color(255, 255, 255, 75); /** * The color for darker sections of the already-played section of the playback progress bar. Note that * if the indicator color has been changed, only the transparency (alpha) channel of this is used. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color DIM_PLAYED = new Color(255, 255, 255, 35); /** * The color for the darker sections of the not-yet-played sections of the playback progress bar. Note that * if the indicator color has been changed, only the transparency (alpha) channel of this is used. */ - @SuppressWarnings("WeakerAccess") + @API(status = API.Status.STABLE) public static final Color DIM_UNPLAYED = new Color(255, 255, 255, 170); /** @@ -146,34 +149,34 @@ private int positionMarkerHeight() { * The color to which the background is cleared before drawing the waveform. The default is black, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference backgroundColor = new AtomicReference(Color.BLACK); + private final AtomicReference backgroundColor = new AtomicReference<>(Color.BLACK); /** * The color with which the playback position and tick markers are drawn. The default is white, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference indicatorColor = new AtomicReference(Color.WHITE); + private final AtomicReference indicatorColor = new AtomicReference<>(Color.WHITE); /** * The color with which the playback position is drawn while playback is active. The default is red, * but can be changed (including to a transparent color) for use in other contexts, like the OBS overlay. */ - private final AtomicReference emphasisColor = new AtomicReference(Color.RED); + private final AtomicReference emphasisColor = new AtomicReference<>(Color.RED); /** * The waveform preview that we are drawing. */ - private final AtomicReference preview = new AtomicReference(); + private final AtomicReference preview = new AtomicReference<>(); /** * The rendered image of the waveform itself at its natural size. */ - private final AtomicReference waveformImage = new AtomicReference(); + private final AtomicReference waveformImage = new AtomicReference<>(); /** * Track the playback state for the players that have the track loaded. */ - private final Map playbackStateMap = new ConcurrentHashMap(4); + private final Map playbackStateMap = new ConcurrentHashMap<>(4); /** * Information about the playback duration of the track (in seconds) whose waveform we are drawing, so we can @@ -185,7 +188,7 @@ private int positionMarkerHeight() { * Information about where all the beats in the track fall, so we can figure out our current position from * player updates and draw phrase boundaries if we have song structure information. */ - private final AtomicReference beatGrid = new AtomicReference(); + private final AtomicReference beatGrid = new AtomicReference<>(); /** * Establish a beat grid to use when translating between times and beat numbers, for example to paint the @@ -194,20 +197,22 @@ private int positionMarkerHeight() { * * @param beatGrid the beat grid information for the displayed waveform, or {@code null} if none is available */ + @API(status = API.Status.STABLE) public void setBeatGrid(BeatGrid beatGrid) { this.beatGrid.set(beatGrid); } /** - * Information about where all the cues are so we can draw them. + * Information about where all the cues are, so we can draw them. */ - private final AtomicReference cueList = new AtomicReference(); + private final AtomicReference cueList = new AtomicReference<>(); /** * Get the cue list associated with this track. * * @return the cues defined for the track. */ + @API(status = API.Status.STABLE) public CueList getCueList() { return cueList.get(); } @@ -221,7 +226,7 @@ public CueList getCueList() { /** * Information about the musical phrases that make up the current track, if we have it, so we can draw them. */ - private final AtomicReference songStructure = new AtomicReference(); + private final AtomicReference songStructure = new AtomicReference<>(); /** * Establish a song structure (phrase analysis) to be displayed on the waveform. If we are configured to monitor @@ -229,6 +234,7 @@ public CueList getCueList() { * * @param songStructure the phrase information to be painted at the bottom of the waveform, or {@code null} to display none */ + @API(status = API.Status.STABLE) public void setSongStructure(RekordboxAnlz.SongStructureTag songStructure) { this.songStructure.set(songStructure); delegatingRepaint(); @@ -246,7 +252,7 @@ private void setSongStructureWrapper(RekordboxAnlz.TaggedSection taggedSection) } else if (taggedSection.fourcc() == RekordboxAnlz.SectionTags.SONG_STRUCTURE) { setSongStructure((RekordboxAnlz.SongStructureTag) taggedSection.body()); } else { - logger.warn("Received unexpected analysis tag type:" + taggedSection); + logger.warn("Received unexpected analysis tag type: {}", taggedSection); } } @@ -256,6 +262,7 @@ private void setSongStructureWrapper(RekordboxAnlz.TaggedSection taggedSection) * * @param fetchSongStructures {@code true} if we should try to obtain and display phrase analysis information */ + @API(status = API.Status.STABLE) public synchronized void setFetchSongStructures(boolean fetchSongStructures) { this.fetchSongStructures.set(fetchSongStructures); if (fetchSongStructures && monitoredPlayer.get() > 0) { @@ -274,6 +281,7 @@ public synchronized void setFetchSongStructures(boolean fetchSongStructures) { * * @return {@code true} if we should try to obtain and display phrase analysis information */ + @API(status = API.Status.STABLE) public boolean getFetchSongStructures() { return fetchSongStructures.get(); } @@ -281,7 +289,7 @@ public boolean getFetchSongStructures() { /** * The overlay painter that has been registered, if any. */ - private final AtomicReference overlayPainter = new AtomicReference(); + private final AtomicReference overlayPainter = new AtomicReference<>(); /** * Arrange for an overlay to be painted on top of the component. @@ -289,6 +297,7 @@ public boolean getFetchSongStructures() { * @param painter if not {@code null}, its {@link OverlayPainter#paintOverlay(Component, Graphics)} method will * be called once this component has done its own painting */ + @API(status = API.Status.STABLE) public void setOverlayPainter(OverlayPainter painter) { overlayPainter.set(painter); } @@ -299,6 +308,7 @@ public void setOverlayPainter(OverlayPainter painter) { * * @return the color used to draw the component background */ + @API(status = API.Status.STABLE) public Color getBackgroundColor() { return backgroundColor.get(); } @@ -309,6 +319,7 @@ public Color getBackgroundColor() { * * @param color the color used to draw the component background */ + @API(status = API.Status.STABLE) public void setBackgroundColor(Color color) { backgroundColor.set(color); updateWaveform(preview.get()); @@ -320,6 +331,7 @@ public void setBackgroundColor(Color color) { * * @return the color used to draw the playback and tick markers */ + @API(status = API.Status.STABLE) public Color getIndicatorColor() { return indicatorColor.get(); } @@ -330,6 +342,7 @@ public Color getIndicatorColor() { * @param color the color used to draw the playback marker when actively playing */ + @API(status = API.Status.STABLE) public void setIndicatorColor(Color color) { indicatorColor.set(color); } @@ -340,6 +353,7 @@ public void setIndicatorColor(Color color) { * * @return the color used to draw the active playback marker */ + @API(status = API.Status.STABLE) public Color getEmphasisColor() { return emphasisColor.get(); } @@ -350,6 +364,7 @@ public Color getEmphasisColor() { * @param color the color used to draw the playback marker when actively playing */ + @API(status = API.Status.STABLE) public void setEmphasisColor(Color color) { emphasisColor.set(color); } @@ -360,6 +375,7 @@ public void setEmphasisColor(Color color) { * * @return the playback state, if any, with the highest {@link PlaybackState#position} value */ + @API(status = API.Status.STABLE) public PlaybackState getFurthestPlaybackState() { PlaybackState result = null; for (PlaybackState state : playbackStateMap.values()) { @@ -373,7 +389,7 @@ public PlaybackState getFurthestPlaybackState() { /** * Keep track of whether we are supposed to be delegating our repaint calls to a host component. */ - private final AtomicReference repaintDelegate = new AtomicReference(); + private final AtomicReference repaintDelegate = new AtomicReference<>(); /** * Establish a host component to which all {@link #repaint(int, int, int, int)} calls should be delegated, @@ -382,6 +398,7 @@ public PlaybackState getFurthestPlaybackState() { * @param delegate the permanent component that can actually accumulate repaint regions, or {@code null} if * we are being hosted normally in a container, so we should use the normal repaint process. */ + @API(status = API.Status.STABLE) public void setRepaintDelegate(RepaintDelegate delegate) { repaintDelegate.set(delegate); } @@ -403,7 +420,7 @@ private void delegatingRepaint() { * @param width the width of the region that we want to have redrawn * @param height the height of the region that we want to have redrawn */ - @SuppressWarnings("SameParameterValue") + @API(status = API.Status.STABLE) private void delegatingRepaint(int x, int y, int width, int height) { final RepaintDelegate delegate = repaintDelegate.get(); if (delegate != null) { @@ -474,6 +491,7 @@ private void repaintDueToPlaybackStateChange(long oldMaxPosition, long newMaxPos * * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void setPlaybackState(int player, long position, boolean playing) { if (getMonitoredPlayer() != 0 && player != getMonitoredPlayer()) { throw new IllegalStateException("Cannot setPlaybackState for another player when monitoring player " + getMonitoredPlayer()); @@ -502,6 +520,7 @@ public synchronized void setPlaybackState(int player, long position, boolean pla * @param player the player number whose playback state is no longer valid * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void clearPlaybackState(int player) { long oldMaxPosition = 0; PlaybackState furthestState = getFurthestPlaybackState(); @@ -521,6 +540,7 @@ public synchronized void clearPlaybackState(int player) { * Removes all stored playback state. * @since 0.5.0 */ + @API(status = API.Status.STABLE) public synchronized void clearPlaybackState() { for (PlaybackState state : playbackStateMap.values()) { clearPlaybackState(state.player); @@ -534,6 +554,7 @@ public synchronized void clearPlaybackState() { * @return the corresponding playback state, if any has been stored * @since 0.5.0 */ + @API(status = API.Status.STABLE) public PlaybackState getPlaybackState(int player) { return playbackStateMap.get(player); } @@ -544,8 +565,9 @@ public PlaybackState getPlaybackState(int player) { * @return the playback state recorded for any player * @since 0.5.0 */ + @API(status = API.Status.STABLE) public Set getPlaybackState() { - Set result = new HashSet(playbackStateMap.values()); + Set result = new HashSet<>(playbackStateMap.values()); return Collections.unmodifiableSet(result); } @@ -633,6 +655,7 @@ private void updateWaveform(WaveformPreview preview) { * @param metadata information about the track whose waveform we are drawing, so we can translate times into * positions and display hot cues and memory points */ + @API(status = API.Status.STABLE) public void setWaveformPreview(WaveformPreview preview, TrackMetadata metadata) { updateWaveform(preview); this.duration.set(metadata.getDuration()); @@ -650,6 +673,7 @@ public void setWaveformPreview(WaveformPreview preview, TrackMetadata metadata) * translate times into positions * @param cueList the hot cues and memory points stored for the track, if any, so we can draw them */ + @API(status = API.Status.STABLE) public void setWaveformPreview(WaveformPreview preview, int duration, CueList cueList) { updateWaveform(preview); this.duration.set(duration); @@ -663,6 +687,7 @@ public void setWaveformPreview(WaveformPreview preview, int duration, CueList cu * * @return the waveform preview being displayed by this component. */ + @API(status = API.Status.STABLE) public WaveformPreview getWaveformPreview() { return preview.get(); } @@ -679,6 +704,7 @@ public WaveformPreview getWaveformPreview() { * * @param player the player number to monitor, or zero if monitoring should stop */ + @API(status = API.Status.STABLE) public synchronized void setMonitoredPlayer(final int player) { if (player < 0) { throw new IllegalArgumentException("player cannot be negative"); @@ -723,19 +749,16 @@ public synchronized void setMonitoredPlayer(final int player) { TimeFinder.getInstance().start(); if (!animating.getAndSet(true)) { // Create the thread to update our position smoothly as the track plays - new Thread(new Runnable() { - @Override - public void run() { - while (animating.get()) { - try { - //noinspection BusyWait - Thread.sleep(33); // Animate at 30 fps - } catch (InterruptedException e) { - logger.warn("Waveform animation thread interrupted; ending"); - animating.set(false); - } - setPlaybackPosition(TimeFinder.getInstance().getTimeFor(getMonitoredPlayer())); + new Thread(() -> { + while (animating.get()) { + try { + //noinspection BusyWait + Thread.sleep(33); // Animate at 30 fps + } catch (InterruptedException e) { + logger.warn("Waveform animation thread interrupted; ending"); + animating.set(false); } + setPlaybackPosition(TimeFinder.getInstance().getTimeFor(getMonitoredPlayer())); } }).start(); } @@ -762,6 +785,7 @@ public void run() { * * @return the player number being monitored, or zero if none */ + @API(status = API.Status.STABLE) public int getMonitoredPlayer() { return monitoredPlayer.get(); } @@ -769,19 +793,16 @@ public int getMonitoredPlayer() { /** * Reacts to changes in the track metadata associated with the player we are monitoring. */ - private final TrackMetadataListener metadataListener = new TrackMetadataListener() { - @Override - public void metadataChanged(TrackMetadataUpdate update) { - if (update.player == getMonitoredPlayer()) { - if (update.metadata != null) { - duration.set(update.metadata.getDuration()); - cueList.set(update.metadata.getCueList()); - } else { - duration.set(0); - cueList.set(null); - } - delegatingRepaint(); + private final TrackMetadataListener metadataListener = update -> { + if (update.player == getMonitoredPlayer()) { + if (update.metadata != null) { + duration.set(update.metadata.getDuration()); + cueList.set(update.metadata.getCueList()); + } else { + duration.set(0); + cueList.set(null); } + delegatingRepaint(); } }; @@ -807,36 +828,27 @@ public void detailChanged(WaveformDetailUpdate update) { /** * Reacts to changes in the beat grid associated with the player we are monitoring. */ - private final BeatGridListener beatGridListener = new BeatGridListener() { - @Override - public void beatGridChanged(BeatGridUpdate update) { - if (update.player == getMonitoredPlayer()) { - beatGrid.set(update.beatGrid); - delegatingRepaint(); - } + private final BeatGridListener beatGridListener = update -> { + if (update.player == getMonitoredPlayer()) { + beatGrid.set(update.beatGrid); + delegatingRepaint(); } }; /** * Reacts to player status updates to reflect the current playback state. */ - private final DeviceUpdateListener updateListener = new DeviceUpdateListener() { - @Override - public void received(DeviceUpdate update) { - if ((update instanceof CdjStatus) && (update.getDeviceNumber() == getMonitoredPlayer()) && - (duration.get() > 0) && (beatGrid.get() != null)) { - CdjStatus status = (CdjStatus) update; - setPlaying(status.isPlaying()); - } + private final DeviceUpdateListener updateListener = update -> { + if ((update instanceof CdjStatus) && (update.getDeviceNumber() == getMonitoredPlayer()) && + (duration.get() > 0) && (beatGrid.get() != null)) { + CdjStatus status = (CdjStatus) update; + setPlaying(status.isPlaying()); } }; - private final AnalysisTagListener analysisTagListener = new AnalysisTagListener() { - @Override - public void analysisChanged(AnalysisTagUpdate update) { - if (update.player == getMonitoredPlayer()) { - setSongStructureWrapper(update.taggedSection); - } + private final AnalysisTagListener analysisTagListener = update -> { + if (update.player == getMonitoredPlayer()) { + setSongStructureWrapper(update.taggedSection); } }; @@ -846,6 +858,7 @@ public void analysisChanged(AnalysisTagUpdate update) { * * @param player the player number to monitor, or zero if it should start out monitoring no player */ + @API(status = API.Status.STABLE) public WaveformPreviewComponent(int player) { setMonitoredPlayer(player); } @@ -857,6 +870,7 @@ public WaveformPreviewComponent(int player) { * @param metadata information about the track whose waveform we are drawing, so we can translate times into * positions */ + @API(status = API.Status.STABLE) public WaveformPreviewComponent(WaveformPreview preview, TrackMetadata metadata) { updateWaveform(preview); this.duration.set(metadata.getDuration()); @@ -870,6 +884,7 @@ public WaveformPreviewComponent(WaveformPreview preview, TrackMetadata metadata) * translate times into positions * @param cueList the hot cues and memory points stored for the track, if any, so we can draw them */ + @API(status = API.Status.STABLE) public WaveformPreviewComponent(WaveformPreview preview, int duration, CueList cueList) { updateWaveform(preview); this.duration.set(duration); @@ -918,6 +933,7 @@ public Dimension getMinimumSize() { * @return the horizontal position within the component coordinate space where that beat begins * @throws IllegalArgumentException if the beat number exceeds the number of beats in the track. */ + @API(status = API.Status.STABLE) public int getXForBeat(int beat) { BeatGrid grid = beatGrid.get(); if (grid != null) { @@ -934,6 +950,7 @@ public int getXForBeat(int beat) { * * @return the component x coordinate at which it should be drawn */ + @API(status = API.Status.STABLE) public int millisecondsToX(long milliseconds) { if (duration.get() < 1) { // Don't crash if we are missing duration information. return 0; @@ -948,6 +965,7 @@ public int millisecondsToX(long milliseconds) { * @param x the horizontal position within the component coordinate space * @return the number of milliseconds into the track this would correspond to (may fall outside the actual track) */ + @API(status = API.Status.STABLE) public long getTimeForX(int x) { if (duration.get() < 1) { // Don't crash if we are missing duration information. return 0; @@ -1000,7 +1018,7 @@ protected synchronized void paintComponent(Graphics g) { } } - if (duration.get() > 0) { // We have time scale information available. + if (duration.get() > 0) { // We have timescale information available. // Draw the song structure if we have one for the track. if (currentSongStructure != null) { @@ -1106,6 +1124,7 @@ private void paintPhrases(Graphics g, Rectangle clipRect, RekordboxAnlz.SongStru * @return the text of a tool tip to describe that area of this component, or {@code null} if there's nothing * to say about that point */ + @API(status = API.Status.STABLE) public String toolTipText(Point point) { // Check if the point is over a cue indicator final CueList currentCueList = cueList.get(); diff --git a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewUpdate.java b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewUpdate.java index c01c5820..ad5200ef 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewUpdate.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/WaveformPreviewUpdate.java @@ -1,15 +1,18 @@ package org.deepsymmetry.beatlink.data; +import org.apiguardian.api.API; + /** * Provides notification when the waveform preview associated with a player changes. * * @author James Elliott */ -@SuppressWarnings("WeakerAccess") +@API(status = API.Status.STABLE) public class WaveformPreviewUpdate { /** * The player number for which a waveform preview change has occurred. */ + @API(status = API.Status.STABLE) public final int player; /** @@ -17,6 +20,7 @@ public class WaveformPreviewUpdate { * {@code null} if we don't have any preview available (including for a brief period after a new track has been * loaded while we are requesting the waveform preview). */ + @API(status = API.Status.STABLE) public final WaveformPreview preview; WaveformPreviewUpdate(int player, WaveformPreview preview) {