diff --git a/source/de/quippy/javamod/main/gui/MainForm.java b/source/de/quippy/javamod/main/gui/MainForm.java index 64ce2a0..53ccf25 100644 --- a/source/de/quippy/javamod/main/gui/MainForm.java +++ b/source/de/quippy/javamod/main/gui/MainForm.java @@ -2792,14 +2792,17 @@ private void doCheckUpdate(final boolean beSilent) { public void run() { - String serverVersion = Helpers.getCurrentServerVersion(); - int compareResult = Helpers.compareVersions(Helpers.VERSION, serverVersion); + // first update last check date... + lastUpdateCheck = LocalDate.now(); + + final String serverVersion = Helpers.getCurrentServerVersion(); + final int compareResult = Helpers.compareVersions(Helpers.VERSION, serverVersion); if (compareResult<0) { - File f = new File("."); - String programmDestination = f.getAbsolutePath(); + final File f = new File("."); + final String programmDestination = f.getAbsolutePath(); // Show Version History - int resultHistory = JOptionPane.showConfirmDialog(MainForm.this, "There is a new version available!\n\nYour version: "+Helpers.VERSION+" - online verison: " + serverVersion + "\n\nWatch version history?\n\n", "New Version", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + final int resultHistory = JOptionPane.showConfirmDialog(MainForm.this, "There is a new version available!\n\nYour version: "+Helpers.VERSION+" - online verison: " + serverVersion + "\n\nWatch version history?\n\n", "New Version", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (resultHistory == JOptionPane.YES_OPTION) { SimpleTextViewerDialog dialog = getSimpleTextViewerDialog(); @@ -2807,7 +2810,7 @@ public void run() dialog.setVisible(true); } // Ask for download - int result = JOptionPane.showConfirmDialog(MainForm.this, "Your version: "+Helpers.VERSION+" - online verison: " + serverVersion + "\n\nShould I start the download?\n\n", "New Version", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + final int result = JOptionPane.showConfirmDialog(MainForm.this, "Your version: "+Helpers.VERSION+" - online verison: " + serverVersion + "\n\nShould I start the download?\n\n", "New Version", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (result == JOptionPane.YES_OPTION) { JFileChooser chooser = new JFileChooser(); @@ -2839,7 +2842,7 @@ public void run() getDownloadDialog().setLocation(Helpers.getFrameCenteredLocation(getDownloadDialog(), MainForm.this)); getDownloadDialog().setCurrentFileName(Helpers.JAVAMOD_URL); getDownloadDialog().setVisible(true); - int copied = Helpers.downloadJavaMod(destination, getDownloadDialog()); + final int copied = Helpers.downloadJavaMod(destination, getDownloadDialog()); getDownloadDialog().setVisible(false); if (copied==-1) JOptionPane.showMessageDialog(MainForm.this, "Download failed!\n"+destination, "Failed", JOptionPane.ERROR_MESSAGE); diff --git a/source/de/quippy/javamod/multimedia/mod/loader/instrument/Envelope.java b/source/de/quippy/javamod/multimedia/mod/loader/instrument/Envelope.java index 54a5803..bb15fce 100644 --- a/source/de/quippy/javamod/multimedia/mod/loader/instrument/Envelope.java +++ b/source/de/quippy/javamod/multimedia/mod/loader/instrument/Envelope.java @@ -70,7 +70,7 @@ public int updatePosition(final ChannelMemory aktMemo, final int currentTick, fi { int tick = currentTick + 1; - if (xm_style) // XM does this way more complicated + sustain is only one point (start==end) + FT not only has ticks but stores the position as well + if (xm_style) // XM does this way more complicated + sustain is only one point (start==end) + FT2 not only has ticks but stores the position as well { if (loop && tick==positions[loopEndPoint] && (!sustain || tick!=positions[sustainStartPoint] || !aktMemo.keyOff)) { @@ -167,13 +167,31 @@ public int getInitPosition() { return (xm_style)?-1:0; } -// public int getXMResetPosition(final int tick) -// { -// int index = endPoint; -// for (int i=0; i=tick) index = i; // results in a break -// return positions[index] - 1; -// } + /** + * XMs reset the envelope to the previous point position at + * a key off event + * As we do not store the current position, we need to derive + * that from the current tick position + * @since 20.06.2024 + * @param tick + * @return + */ + public int getXMResetPosition(final int tick) + { + // This does not work as intended... +// if (endPoint!=-1) +// { +// int index = 0; +// while (index=positions[index]) index++; +// if (tick0*/) { - aktMemo.fadeOutVolume = 0; - aktMemo.noteFade = false; + aktMemo.fadeOutVolume -= (currentInstrument.volumeFadeOut<<1); + if (aktMemo.fadeOutVolume<0) aktMemo.fadeOutVolume = 0; + currentVolume = (currentVolume * aktMemo.fadeOutVolume) >> ModConstants.MAXFADEOUTVOLSHIFT; } - currentVolume = (currentVolume * aktMemo.fadeOutVolume) >> ModConstants.MAXFADEOUTVOLSHIFT; - + else + currentVolume = aktMemo.fadeOutVolume = 0; + // With IT a finished noteFade also sets the instrument as finished if (isIT && aktMemo.fadeOutVolume<=0 && isChannelActive(aktMemo)) { @@ -1978,18 +1966,18 @@ protected void resetAutoVibrato(final ChannelMemory aktMemo, final Sample sample { if (isXM) { - if (sample.vibratoDepth > 0) + if (sample.vibratoDepth>0) { aktMemo.autoVibratoTablePos = 0; - if (aktMemo.autoVibratoSweep > 0) + if (aktMemo.autoVibratoSweep>0) { - aktMemo.autoVibratoAmplitude = 0; - aktMemo.autoVibratoSweep = (sample.vibratoDepth << 8) / sample.vibratoSweep; + aktMemo.autoVibratoAmplitude=0; + aktMemo.autoVibratoSweep = (sample.vibratoDepth<<8) / sample.vibratoSweep; } else { - aktMemo.autoVibratoAmplitude = sample.vibratoDepth << 8; + aktMemo.autoVibratoAmplitude = sample.vibratoDepth<<8; aktMemo.autoVibratoSweep = 0; } } @@ -2078,6 +2066,34 @@ protected boolean setFilterAndRandomVariations(final ChannelMemory aktMemo, fina return useFilter; } + /** + * @since 20.06.2024 + * @param aktMemo + */ + protected void doKeyOff(final ChannelMemory aktMemo) + { + aktMemo.keyOff = true; + if (isXM) + { + // XM has a certain tick reset with key off and existing envelopes - tick is reset to the previous start position + final Instrument currentInstrument = aktMemo.assignedInstrument; + final Envelope volumeEnv = (currentInstrument!=null)?currentInstrument.volumeEnvelope:null; + if (volumeEnv!=null && volumeEnv.on) + { + aktMemo.volEnvPos = volumeEnv.getXMResetPosition(aktMemo.volEnvPos); + } + else + { + aktMemo.currentVolume = 0; + aktMemo.doFastVolRamp = true; + } + final Envelope panningEnv = (currentInstrument!=null)?currentInstrument.panningEnvelope:null; + if (panningEnv!=null && !panningEnv.on) // another FT2 Bug + { + aktMemo.panEnvPos = panningEnv.getXMResetPosition(aktMemo.panEnvPos); + } + } + } /** * @since 11.06.2006 * @param aktMemo @@ -2098,14 +2114,45 @@ protected void setNewInstrumentAndPeriod(final ChannelMemory aktMemo) { doNNAAutoInstrument(aktMemo); } - - // copy last seen values from pattern - aktMemo.assignedNotePeriod = aktMemo.currentAssignedNotePeriod; - aktMemo.assignedNoteIndex = aktMemo.currentAssignedNoteIndex; + + // copy last seen values from pattern - only effect values first aktMemo.assignedEffekt = aktMemo.currentAssignedEffekt; aktMemo.assignedEffektParam = aktMemo.currentAssignedEffektParam; aktMemo.assignedVolumeEffekt = aktMemo.currentAssignedVolumeEffekt; aktMemo.assignedVolumeEffektOp = aktMemo.currentAssignedVolumeEffektOp; + + // special K00/KeyOff handling of XMs - instrument and note are "invisible" with K00 + final boolean isXMK00 = isKeyOffEffekt(aktMemo.currentAssignedEffekt, aktMemo.currentAssignedEffektParam) && aktMemo.currentAssignedEffektParam==0; + if (isXM && (isKeyOff || isXMK00) + ) + { + aktMemo.currentAssignedNotePeriod = aktMemo.assignedNotePeriod; + aktMemo.currentAssignedNoteIndex = aktMemo.assignedNoteIndex; + aktMemo.currentAssignedInstrumentIndex = aktMemo.assignedInstrumentIndex; + aktMemo.currentAssignedInstrument = aktMemo.assignedInstrument; + + doKeyOff(aktMemo); + + if (isNoteDelay) + { + if (element.getInstrument()>0) + { + resetVolumeAndPanning(aktMemo, aktMemo.assignedInstrument, aktMemo.assignedSample); + resetAutoVibrato(aktMemo, aktMemo.assignedSample); + resetTablePositions(aktMemo); + } + resetEnvelopes(aktMemo, aktMemo.currentAssignedInstrument); + aktMemo.noteCut = aktMemo.keyOff = aktMemo.noteFade = false; + } + else + if (element.getInstrument()>0) resetVolumeAndPanning(aktMemo, aktMemo.assignedInstrument, aktMemo.assignedSample); + + return; + } + + // copy last seen values from pattern - now note and instrument + aktMemo.assignedNotePeriod = aktMemo.currentAssignedNotePeriod; + aktMemo.assignedNoteIndex = aktMemo.currentAssignedNoteIndex; // what Sample would be assigned? Get the correct sample from the mapping table, if there is an instrument set aktMemo.assignedSample = (aktMemo.currentAssignedInstrument!=null)? ((aktMemo.assignedNoteIndex>0)? // but only if we also have a note index, if not, ignore it! @@ -2115,12 +2162,11 @@ protected void setNewInstrumentAndPeriod(final ChannelMemory aktMemo) boolean hasInstrument = element.getInstrument()>0 && aktMemo.assignedSample!=null; - // At this point we reset volume and panning for XMs and Fastracker family (IT, STM, S3M) - if (hasInstrument) + if (hasInstrument) // At this point we reset volume and panning for XMs and Fastracker family (IT, STM, S3M) { if (isXM) { - if (isPortaToNoteEffect) // PortaToNote: ignore new instrument completely but reset old one + if (isPortaToNoteEffect || !isNewNote) // PortaToNote or no note: ignore new instrument completely but reset old one { aktMemo.currentAssignedInstrumentIndex = aktMemo.assignedInstrumentIndex; aktMemo.currentAssignedInstrument = aktMemo.assignedInstrument; @@ -2180,7 +2226,7 @@ protected void setNewInstrumentAndPeriod(final ChannelMemory aktMemo) } } - // Now safe those instruments for later re-use - except: XM ignores these samples completely + // Now safe those instruments for later re-use aktMemo.assignedInstrumentIndex = aktMemo.currentAssignedInstrumentIndex; aktMemo.assignedInstrument = aktMemo.currentAssignedInstrument; @@ -2190,19 +2236,12 @@ protected void setNewInstrumentAndPeriod(final ChannelMemory aktMemo) if (isXM && isNoteDelay) { isPortaToNoteEffect=!(hasInstrument=isNewNote=true); - // we could have a key_off here, which will make that fake above not work - // so we need to reset volume and envelope ourself here - if (isKeyOff) - { - resetVolumeAndPanning(aktMemo, aktMemo.assignedInstrument, aktMemo.currentSample); - resetEnvelopes(aktMemo); - } } - // Key Off, Note Cut or Period / noteIndex to set? + // Key Off, Note Cut, Note Fade or Period / noteIndex to set? if (isKeyOff) { - aktMemo.keyOff = true; + doKeyOff(aktMemo); } else if (element.getPeriod()==ModConstants.NOTE_CUT || element.getNoteIndex()==ModConstants.NOTE_CUT) @@ -2293,6 +2332,7 @@ protected void setNewInstrumentAndPeriod(final ChannelMemory aktMemo) { resetInstrumentPointers(aktMemo); resetEnvelopes(aktMemo); + resetAutoVibrato(aktMemo, aktMemo.currentSample); if (isMOD) resetTablePositions(aktMemo); // with MODs we reset vibrato/tremolo here } // With XMs reset the finetune with a new note. A finetune effect might change that later @@ -2487,12 +2527,20 @@ protected void doRowEvents() aktMemo.assignedEffektParam = retEffektOp; } } + // With FastTracker, globalVolume is applied when it occurs. + // That is, the envelopes are processed in this loop, not afterwards + // in a whole, as seen below with Non-FastTracker + if (isXM) processEnvelopes(channelMemory[c]); } // with Row Effects, first all rows effect parameter need to be // processed - then we can do the envelopes and volume effects - // Otherwise global (volume) effects would not be considered... - for (int c=0; c>4)!=0) @@ -729,7 +729,7 @@ protected void preparePortaToNoteEffect(final ChannelMemory aktMemo) final boolean isKeyOff = (element.getPeriod()==ModConstants.KEY_OFF || element.getNoteIndex()==ModConstants.KEY_OFF); if (isKeyOff) { - aktMemo.keyOff = true; + doKeyOff(aktMemo); } else if (hasNewNote(element)) // KeyOff is not a note... @@ -895,7 +895,7 @@ protected void doAutoVibratoEffekt(final ChannelMemory aktMemo, final Sample cur case 3: periodAdd = ((0x40 - (aktMemo.autoVibratoTablePos>>1)) & 0x7F) - 0x40; // Ramp Down break; } - periodAdd = ((periodAdd<> (6+8); // copy from FT2 source code + periodAdd = ((periodAdd<> (6+8+2); // copy from FT2 source code plus our PERIOD_SHIFT is 4, not 2 int newPeriod = currentPeriod + periodAdd; if (newPeriod>=(32000<