Skip to content

Commit cfb1f65

Browse files
committed
3.9.5 gamma
1 parent 865d952 commit cfb1f65

File tree

5 files changed

+177
-34
lines changed

5 files changed

+177
-34
lines changed

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# JavaMod V3.9.4.1
1+
# JavaMod V3.9.5
22
JavaMod - a java based multimedia player for Protracker, Fast Tracker,
33
Impulse Tracker, Scream Tracker and other mod files plus
44
SID, MP3, WAV, OGG, APE, FLAC, MIDI, AdLib ROL-Files (OPL), ...
@@ -64,6 +64,8 @@ JavaMod incorporates modified versions of the following libraries:
6464
* With KDE:
6565
* JDialogs, when set visible, will not come to front
6666
* Tray Icon: mouse wheel (volume control) & keyboard shortcuts do not work
67+
because of Java TrayIcon using xembed instead of SNI, TrayIcon events like
68+
mouse clicks are not reaching the application. There is no suitable workaround
6769

6870
## Planned:
6971
* finish loading of OMPT extended instrument / song data / mixer data
@@ -78,6 +80,46 @@ JavaMod incorporates modified versions of the following libraries:
7880
* MO3 support
7981
* read from 7z archives
8082

83+
## New in Version 3.9.5
84+
* NEW: Audio CD rips with CloneCD create an image file ending with IMG. That is
85+
in fact a wave file without riff header. So now we support IMG-files as
86+
such: 44.100 16Bit Stereo. You can now play IMG files or CloneCD CUE
87+
sheets directly with JavaMod
88+
* NEW: One PT2 quirk is that with arpeggios the base note is not recalculated
89+
which leads to finetune not being considered if the instrument changed.
90+
That must be a bug as note 2 and 3 consider the current finetune set
91+
* NEW: for the pattern display I used JButtons and was setting a text with
92+
brackets and used HTML styling for the coloring. That way I could not use
93+
solid blocks like '\u2588'. Furthermore using HTML styles is very slow,
94+
because of needed interpretation. Now we draw those meters by hand.
95+
* FIX: massive code cleanup
96+
* FIX: Midiplayback: set correct "end of track" messages and delete incorrect
97+
ones
98+
* FIX: if clicking on the tray icon, javamod should come to front
99+
* FIX: X-MAS decoration: setting spaces while already enabled drawing did not
100+
work. Semaphore "inDraw" was never reset to false.
101+
* FIX: X_MAS decoration: possible race condition while creating window and
102+
content fixed.
103+
* FIX: Icy-Streams use upper and lower case for tags - so normalize to lower
104+
case for all tags. Otherwise tags are not found (like Icy-MetaInt)
105+
* FIX: Encoding for HTTP headers in ICY-Streams is ISO-8859-1 - but headers need
106+
re-encoding to UTF-8 afterwards
107+
* FIX: setTempo (F00) with protracker means "stop the mod"
108+
* FIX: Multichannel ProTracker generics use XM_AMIGA_TABLE. However, must on the
109+
other hand behave like ProTracker - so according finetune check what
110+
table is used
111+
* FIX: Loading of ProTracker mods being too short, sample position calculation
112+
included sample header bytes (30 bytes per sample) - which is too much.
113+
Furthermore, we should not read that last pattern reaching into the
114+
sample data.
115+
* FIX: if a ProTracker mod contains notes exceeding the ProTracker limits we
116+
now switch to XM_AMIGA_TABLE but stay in ProTracker effect interpretation
117+
* FIX: URLs of windows net drives are no URIs (file:////servername/folder/) and
118+
normalizing those leads to a wrong path (file:/servername/folder/)
119+
* FIX: With XMs it is possible to have 16 bit samples with one additional byte
120+
saved. As we are loading an amount of samples and not bytes, we read one
121+
byte less. We now seek to the end of bytes after each sample read.
122+
81123
## New in Version 3.9.4.1 HotFix
82124
* FIX: IT MidiMacros are dismissed as of version recognition. However, that was
83125
wrongly implemented and now MidiMacros are mostly always deleted.

source/de/quippy/javamod/main/gui/XmasConfigPanel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public XmasConfigPanel(final int myDesiredFPS)
5959
{
6060
super();
6161
loadBulbs();
62-
screenFPS = myDesiredFPS;
62+
screenFPS = (myDesiredFPS>60)?60:myDesiredFPS; // Screens with 144MHz refresh rate is far too much here - so limit to 60FPS maximum refresh rate
6363
screens = getScreens();
6464
initialize();
6565
}

source/de/quippy/javamod/main/gui/XmasScreenConfigPanel.java

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
package de.quippy.javamod.main.gui;
2323

2424
import java.awt.Color;
25-
import java.awt.Dimension;
2625
import java.awt.GraphicsConfiguration;
2726
import java.awt.GraphicsDevice;
2827
import java.awt.Rectangle;
@@ -58,17 +57,19 @@ public class XmasScreenConfigPanel extends JPanel
5857

5958
private static final String PROPERTY_XMAS_ENABLED = "javamod.xmas.enabled.";
6059
private static final String PROPERTY_XMAS_WITHSPACE = "javamod.xmas.withspace.";
60+
private static final String PROPERTY_XMAS_ALWAYSONTOP = "javamod.xmas.alwaysontop.";
6161
private static final String PROPERTY_XMAS_FLICKERTYPE = "javamod.xmas.flickertype.";
6262
private static final String PROPERTY_XMAS_UPDATEFPS = "javamod.xmas.updatefps.";
6363

6464
private JCheckBox xmasEnabledCheckBox = null;
65+
private JCheckBox alwaysOnTopCheckBox = null;
6566
private JCheckBox withSpaceCheckBox = null;
6667
private JLabel flickerTypeLabel = null;
6768
private JLabel updateFPSLabel = null;
6869
private JComboBox<String> flickerTypeSelector = null;
6970
private JSlider updateFPSSelector = null;
7071

71-
private JWindow transparentJFrame = null;
72+
private JWindow transparentWindow = null;
7273
private XmasDecorationPanel xmasDecorationPanel = null;
7374

7475
private final int screenFPS;
@@ -93,11 +94,12 @@ private void initialize()
9394
setName("Xmas Screen Config for Screen");
9495
setLayout(new java.awt.GridBagLayout());
9596
add(getXmasEnabledCheckBox(), Helpers.getGridBagConstraint(0, 0, 1, 3, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
96-
add(getWithSpaceCheckBox(), Helpers.getGridBagConstraint(0, 1, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
97-
add(getFlickerTypeLabel(), Helpers.getGridBagConstraint(1, 1, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
98-
add(getFlickerTypeSelector(), Helpers.getGridBagConstraint(2, 1, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
99-
add(getUpdateFPSLabel(), Helpers.getGridBagConstraint(0, 2, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
100-
add(getUpdateFPSSelector(), Helpers.getGridBagConstraint(1, 2, 1, 2, java.awt.GridBagConstraints.HORIZONTAL, java.awt.GridBagConstraints.WEST, 1.0, 0.0));
97+
add(getAlwaysOnTopBox(), Helpers.getGridBagConstraint(0, 1, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
98+
add(getWithSpaceCheckBox(), Helpers.getGridBagConstraint(0, 2, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
99+
add(getFlickerTypeLabel(), Helpers.getGridBagConstraint(1, 2, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
100+
add(getFlickerTypeSelector(), Helpers.getGridBagConstraint(2, 2, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
101+
add(getUpdateFPSLabel(), Helpers.getGridBagConstraint(0, 3, 1, 1, java.awt.GridBagConstraints.NONE, java.awt.GridBagConstraints.WEST, 0.0, 0.0));
102+
add(getUpdateFPSSelector(), Helpers.getGridBagConstraint(1, 3, 1, 2, java.awt.GridBagConstraints.HORIZONTAL, java.awt.GridBagConstraints.WEST, 1.0, 0.0));
101103
}
102104
private JCheckBox getXmasEnabledCheckBox()
103105
{
@@ -124,13 +126,38 @@ public void itemStateChanged(final ItemEvent e)
124126
}
125127
return xmasEnabledCheckBox;
126128
}
129+
private JCheckBox getAlwaysOnTopBox()
130+
{
131+
if (alwaysOnTopCheckBox == null)
132+
{
133+
alwaysOnTopCheckBox = new javax.swing.JCheckBox();
134+
alwaysOnTopCheckBox.setName("alwaysOnTopCheckBox");
135+
alwaysOnTopCheckBox.setText("always on top");
136+
alwaysOnTopCheckBox.setFont(Helpers.getDialogFont());
137+
alwaysOnTopCheckBox.setSelected(isWithSpaceEnabled());
138+
alwaysOnTopCheckBox.addItemListener(new ItemListener()
139+
{
140+
@Override
141+
public void itemStateChanged(final ItemEvent e)
142+
{
143+
if (e.getStateChange()==ItemEvent.SELECTED || e.getStateChange()==ItemEvent.DESELECTED)
144+
{
145+
final boolean isOnTop = getAlwaysOnTopBox().isSelected();
146+
getTransparentJWindow().setAlwaysOnTop(isOnTop);
147+
if (isOnTop) getTransparentJWindow().toFront();
148+
}
149+
}
150+
});
151+
}
152+
return alwaysOnTopCheckBox;
153+
}
127154
private JCheckBox getWithSpaceCheckBox()
128155
{
129156
if (withSpaceCheckBox == null)
130157
{
131158
withSpaceCheckBox = new javax.swing.JCheckBox();
132159
withSpaceCheckBox.setName("withSpaceCheckBox");
133-
withSpaceCheckBox.setText("with Space");
160+
withSpaceCheckBox.setText("with space");
134161
withSpaceCheckBox.setFont(Helpers.getDialogFont());
135162
withSpaceCheckBox.setSelected(isWithSpaceEnabled());
136163
withSpaceCheckBox.addItemListener(new ItemListener()
@@ -235,23 +262,29 @@ private JWindow getTransparentJWindow()
235262
// But whatever the difference of a JWindow to a Window is, a Window
236263
// does not work with the OpenGL render pipeline.
237264
// On Linux / KDE the OpenGL pipeline reduces flicker, however the
238-
// Window inherits the alpha value of the underlying window...
239-
if (transparentJFrame == null)
265+
// Window inherits the alpha value of the underlying window..
266+
// A JFrame is possibly like using a sledgehammer to crack a nut - and
267+
// will create an icon in the taskbar at Linux
268+
if (transparentWindow == null)
240269
{
241-
transparentJFrame = new JWindow();
242-
transparentJFrame.setAlwaysOnTop(true);
270+
transparentWindow = new JWindow();
243271

244272
final GraphicsConfiguration gc = screen.getDefaultConfiguration();
245273
final Rectangle bounds = gc.getBounds();
246274
bounds.height = defaultScreenHeight;
247-
transparentJFrame.setBounds(bounds);
275+
transparentWindow.setBounds(bounds);
276+
transparentWindow.setAlwaysOnTop(true);
277+
transparentWindow.toFront(); // just in case...
278+
//transparentWindow.setUndecorated(true); - JFrame needs this
279+
transparentWindow.setFocusable(false);
280+
transparentWindow.setFocusableWindowState(false);
281+
transparentWindow.setBackground(new Color(0, true));
248282

249-
transparentJFrame.setBackground(new Color(0, true));
250-
transparentJFrame.setContentPane(getXmasDecorationPanel());
251-
transparentJFrame.setFocusable(false);
252-
transparentJFrame.setFocusableWindowState(false);
283+
final XmasDecorationPanel xmasDecorationPanel = getXmasDecorationPanel();
284+
xmasDecorationPanel.setSize(transparentWindow.getSize());
285+
transparentWindow.setContentPane(xmasDecorationPanel);
253286
}
254-
return transparentJFrame;
287+
return transparentWindow;
255288
}
256289
private XmasDecorationPanel getXmasDecorationPanel()
257290
{
@@ -261,8 +294,6 @@ private XmasDecorationPanel getXmasDecorationPanel()
261294
xmasDecorationPanel.setBorder(BorderFactory.createEmptyBorder());
262295
xmasDecorationPanel.setOpaque(false);
263296
xmasDecorationPanel.setBackground(new Color(0, true));
264-
final Dimension d = getTransparentJWindow().getSize();
265-
xmasDecorationPanel.setSize(d);
266297
}
267298
return xmasDecorationPanel;
268299
}
@@ -274,6 +305,14 @@ private void setXmasEnabled(final boolean xmasEnabled)
274305
{
275306
getXmasEnabledCheckBox().setSelected(xmasEnabled);
276307
}
308+
private boolean isAlwaysOnTopEnabled()
309+
{
310+
return getAlwaysOnTopBox().isSelected();
311+
}
312+
private void setAlwaysOnTopEnabled(final boolean alwaysOnTopEnabled)
313+
{
314+
getAlwaysOnTopBox().setSelected(alwaysOnTopEnabled);
315+
}
277316
private boolean isWithSpaceEnabled()
278317
{
279318
return getWithSpaceCheckBox().isSelected();
@@ -309,12 +348,14 @@ public void readProperties(final Properties props, final int forScreen)
309348
setFlickerType(Integer.parseInt(props.getProperty(PROPERTY_XMAS_FLICKERTYPE+index, "4")));
310349
setUpdateFPS(Integer.parseInt(props.getProperty(PROPERTY_XMAS_UPDATEFPS+index, "2")));
311350
setWithSpaceEnabled(Boolean.parseBoolean(props.getProperty(PROPERTY_XMAS_WITHSPACE+index, "FALSE")));
351+
setAlwaysOnTopEnabled(Boolean.parseBoolean(props.getProperty(PROPERTY_XMAS_ALWAYSONTOP+index, "TRUE")));
312352
setXmasEnabled(Boolean.parseBoolean(props.getProperty(PROPERTY_XMAS_ENABLED+index, "FALSE")));
313353
}
314354
public void writeProperties(final Properties props, final int forScreen)
315355
{
316356
final String index = Integer.toString(forScreen);
317357
props.setProperty(PROPERTY_XMAS_ENABLED+index, Boolean.toString(isXmasEnabled()));
358+
props.setProperty(PROPERTY_XMAS_ALWAYSONTOP+index, Boolean.toString(isAlwaysOnTopEnabled()));
318359
props.setProperty(PROPERTY_XMAS_WITHSPACE+index, Boolean.toString(isWithSpaceEnabled()));
319360
props.setProperty(PROPERTY_XMAS_FLICKERTYPE+index, Integer.toString(getFlickerType()));
320361
props.setProperty(PROPERTY_XMAS_UPDATEFPS+index, Integer.toString(getUpdateFPS()));

source/de/quippy/javamod/multimedia/midi/MidiContainer.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@
2727
import java.util.ArrayList;
2828
import java.util.Properties;
2929

30+
import javax.sound.midi.InvalidMidiDataException;
31+
import javax.sound.midi.MetaMessage;
3032
import javax.sound.midi.MidiDevice;
3133
import javax.sound.midi.MidiDevice.Info;
34+
import javax.sound.midi.MidiEvent;
35+
import javax.sound.midi.MidiMessage;
3236
import javax.sound.midi.MidiSystem;
3337
import javax.sound.midi.MidiUnavailableException;
3438
import javax.sound.midi.Sequence;
39+
import javax.sound.midi.Track;
3540
import javax.sound.sampled.AudioSystem;
3641
import javax.sound.sampled.Line;
3742
import javax.sound.sampled.TargetDataLine;
@@ -172,26 +177,78 @@ public MultimediaContainer getInstance(final URL midiFileUrl)
172177
if (!MultimediaContainerManager.isHeadlessMode()) ((MidiInfoPanel)getInfoPanel()).fillInfoPanelWith(currentSequence, getSongName());
173178
return result;
174179
}
180+
/**
181+
* Some midi files have "EndOfTrack" messages that are far beyond the
182+
* last valid midi message - which results in long silence.
183+
* We try to correct that by simply setting a new "TrackEnd" at the
184+
* highest tick count of the last event before an EndOfTrack message
185+
* plus the amount of ticks for a whole frame.
186+
* All existing EndOfTrack messages are deleted.
187+
* @since 28.11.2025
188+
* @param currentSequence
189+
*/
190+
private Sequence setCorrectEndingMessage(Sequence currentSequence)
191+
{
192+
try
193+
{
194+
long highestTick = 0;
195+
for (Track track : currentSequence.getTracks())
196+
{
197+
int lastIndex = track.size()-1;
198+
if (lastIndex<0) continue;
199+
200+
MidiEvent lastTrackEvent = track.get(lastIndex);
201+
if (lastTrackEvent==null) continue;
202+
203+
MidiMessage midiMessage = lastTrackEvent.getMessage();
204+
// if MetaMessage.ImmutableEndOfTrack would not be a private static class,
205+
// this check would be much easier!
206+
if (midiMessage!=null && midiMessage instanceof MetaMessage)
207+
{
208+
byte [] message = ((MetaMessage)midiMessage).getMessage();
209+
if (message!=null && message.length>2 && message[0]==-1 && message[1]==0x2F)
210+
{
211+
track.remove(lastTrackEvent);
212+
if (lastIndex>0) lastTrackEvent = track.get(lastIndex-1);
213+
}
214+
}
215+
final long lastTick = lastTrackEvent.getTick();
216+
if (lastTick > highestTick) highestTick = lastTick;
217+
}
218+
highestTick += currentSequence.getResolution();
219+
final MidiMessage endOfTrack = new MetaMessage(0x2F, null, 0);
220+
for (Track track : currentSequence.getTracks())
221+
{
222+
track.add(new MidiEvent(endOfTrack, highestTick));
223+
}
224+
}
225+
catch (InvalidMidiDataException ex)
226+
{
227+
// Ignore
228+
}
229+
return currentSequence;
230+
}
175231
/**
176232
* @since 12.02.2011
177233
* @param midiFileUrl
178234
* @return
179235
*/
180236
private Sequence getSequenceFromURL(final URL midiFileUrl)
181237
{
238+
Sequence result = null;
182239
try
183240
{
184241
final String fileName = midiFileUrl.getPath();
185242
final String extension = fileName.substring(fileName.lastIndexOf('.')+1).toLowerCase();
186243
if (extension.equals("rmi"))
187-
return RMIFile.open(midiFileUrl);
244+
result = RMIFile.open(midiFileUrl);
188245
else
189246
{
190247
FileOrPackedInputStream input = null;
191248
try
192249
{
193250
input = new FileOrPackedInputStream(midiFileUrl);
194-
return MidiSystem.getSequence(input);
251+
result = MidiSystem.getSequence(input);
195252
}
196253
finally
197254
{
@@ -203,6 +260,7 @@ private Sequence getSequenceFromURL(final URL midiFileUrl)
203260
{
204261
throw new RuntimeException(ex);
205262
}
263+
return setCorrectEndingMessage(result);
206264
}
207265
private boolean getCapture()
208266
{

0 commit comments

Comments
 (0)