From da7760baca9bcd77cbdee3d1e5482e657413ab6b Mon Sep 17 00:00:00 2001 From: quippy-git Date: Fri, 15 Dec 2023 18:18:32 +0100 Subject: [PATCH] V3.7.3 - XMAS Edition - Hot Fix III --- README.md | 7 +- .../de/quippy/javamod/main/gui/MainForm.java | 37 +- .../javamod/multimedia/mod/ModConstants.java | 19 +- .../javamod/multimedia/mod/loader/Module.java | 34 +- .../mod/loader/tracker/ProTrackerMod.java | 253 +++++++--- .../mod/loader/tracker/ScreamTrackerMod.java | 9 + .../loader/tracker/ScreamTrackerOldMod.java | 9 + .../multimedia/mod/loader/tracker/XMMod.java | 9 + .../multimedia/mod/mixer/ProTrackerMixer.java | 41 +- .../mod/mixer/ScreamTrackerMixer.java | 18 +- source/de/quippy/javamod/system/Helpers.java | 8 +- .../de/quippy/javamod/test/TestResonance.java | 438 ------------------ 12 files changed, 320 insertions(+), 562 deletions(-) delete mode 100644 source/de/quippy/javamod/test/TestResonance.java diff --git a/README.md b/README.md index 135dae7..778398a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JavaMod V3.7.2 +# JavaMod V3.7.3 JavaMod - a java based multimedia player for Protracker, Fast Tracker, Impulse Tracker, Scream Tracker and other mod files plus SID, MP3, WAV, OGG, APE, FLAC, MIDI, AdLib ROL-Files (OPL), ... @@ -66,6 +66,11 @@ JavaMod incorporates modified versions of the following libraries: * Midi and AdLib/OPL with Mods * read 7z archives +## New in Version 3.7.3 +* NEW: Supporting additional Protracker type mods +* FIX: Tremolo fixed, added FT2-bug - speed and depth for XM and IT +* FIX: sample delta loading with special cases + ## New in Version 3.7.2 * FIX: Sample- and instrument dialog - iterating through the instruments was broken diff --git a/source/de/quippy/javamod/main/gui/MainForm.java b/source/de/quippy/javamod/main/gui/MainForm.java index 5fe4fdd..945f8ed 100644 --- a/source/de/quippy/javamod/main/gui/MainForm.java +++ b/source/de/quippy/javamod/main/gui/MainForm.java @@ -54,6 +54,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -186,6 +188,7 @@ public class MainForm extends JFrame implements DspProcessorCallBack, PlayThread private static final String PROPERTY_PITCHSHIFT_OVERSAMPLING = "javamod.player.pitchshift.oversampling"; private static final int PROPERTY_LASTLOADED_MAXENTRIES = 10; + private static final String PROPERTY_LAST_UPDATECHECK = "javamod.last_update_check"; private static final String WINDOW_TITLE = Helpers.FULLVERSION; private static final String WINDOW_NAME = "JavaMod"; @@ -331,6 +334,10 @@ public class MainForm extends JFrame implements DspProcessorCallBack, PlayThread private boolean useSystemTray = false; private float currentVolume; /* 0.0 - 1.0 */ private float currentBalance; /* -1.0 - 1.0 */ + private LocalDate lastUpdateCheck; + private static final DateTimeFormatter DATE_FORMATER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final LocalDate today = LocalDate.now(); + private ArrayList lastLoaded; private ArrayList windows; @@ -478,6 +485,8 @@ private void readPropertyFile() getSALMeterPanel().setDrawWhatTo(saMeterLeftDrawType); getSARMeterPanel().setDrawWhatTo(saMeterRightDrawType); + lastUpdateCheck = LocalDate.from(DATE_FORMATER.parse(props.getProperty(PROPERTY_LAST_UPDATECHECK, DATE_FORMATER.format(today)))); + if (currentEqualizer!=null) { boolean isActive = Boolean.parseBoolean(props.getProperty(PROPERTY_EQUALIZER_ISACTIVE, "FALSE")); @@ -558,6 +567,7 @@ private void writePropertyFile() props.setProperty(PROPERTY_XMASCONFIG_VISABLE, Boolean.toString(getXmasConfigDialog().isVisible())); props.setProperty(PROPERTY_SAMETER_LEFT_DRAWTYPE, Integer.toString(getSALMeterPanel().getDrawWhat())); props.setProperty(PROPERTY_SAMETER_RIGHT_DRAWTYPE, Integer.toString(getSARMeterPanel().getDrawWhat())); + props.setProperty(PROPERTY_LAST_UPDATECHECK, DATE_FORMATER.format(lastUpdateCheck)); if (currentEqualizer!=null) { @@ -759,8 +769,33 @@ public void windowDeiconified(WindowEvent e) createFileFilter(); currentContainer = null; //set Back to null! - showMessage("Ready..."); + //if (today.minusDays(30).isAfter(lastUpdateCheck)) checkForUpdate(); + + showMessage("Ready..."); } +//TODO: We might want to enable this once... +// private void checkForUpdate() +// { +// new Thread( new Runnable() +// { +// public void run() +// { +// try +// { +// lastUpdateCheck = LocalDate.now(); +// String serverVersion = Helpers.getCurrentServerVersion(); +// if (Helpers.compareVersions(Helpers.VERSION, serverVersion)<0) +// { +// getLEDScrollPanel().addScrollText("Version ("+serverVersion+") available" + Helpers.SCROLLY_BLANKS); +// } +// } +// catch (Throwable ex) +// { +// /* NOOP */ +// } +// } +// }).run(); +// } private void createAllWindows() { windows = new ArrayList(); diff --git a/source/de/quippy/javamod/multimedia/mod/ModConstants.java b/source/de/quippy/javamod/multimedia/mod/ModConstants.java index 9f99e3e..dc0e399 100644 --- a/source/de/quippy/javamod/multimedia/mod/ModConstants.java +++ b/source/de/quippy/javamod/multimedia/mod/ModConstants.java @@ -531,15 +531,26 @@ public static enum PanBits { Pan4Bit, Pan6Bit, Pan8Bit } -255, -253, -250, -244, -235, -224, -212, -197, -180, -161, -141, -120, -97, -74, -49, -24 }; +// /** +// * Triangle wave table (ramp down) +// */ +// public static final int [] ModRampDownTable = new int[] +// { +// 0, -8, -16, -24, -32, -40, -48, -56, -64, -72, -80, -88, -96, -104, -112, -120, +// -128, -136, -144, -152, -160, -168, -176, -184, -192, -200, -208, -216, -224, -232, -240, -248, +// 255, 247, 239, 231, 223, 215, 207, 199, 191, 183, 175, 167, 159, 151, 143, 135, +// 127, 119, 113, 103, 95, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7, +// }; + /** * Triangle wave table (ramp down) */ public static final int [] ModRampDownTable = new int[] { - 0, -8, -16, -24, -32, -40, -48, -56, -64, -72, -80, -88, -96, -104, -112, -120, - -128, -136, -144, -152, -160, -168, -176, -184, -192, -200, -208, -216, -224, -232, -240, -248, - 255, 247, 239, 231, 223, 215, 207, 199, 191, 183, 175, 167, 159, 151, 143, 135, - 127, 119, 113, 103, 95, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7, + 255, 247, 239, 231, 223, 215, 207, 199, 191, 183, 175, 167, 159, 151, 143, 135, + 127, 119, 111, 103, 95, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7, + -1, -9, -17, -25, -33, -41, -49, -57, -65, -73, -81, -89, -97, -105, -113, -121, + -129, -137, -145, -153, -161, -169, -177, -185, -193, -201, -209, -217, -225, -233, -241, -249 }; /** diff --git a/source/de/quippy/javamod/multimedia/mod/loader/Module.java b/source/de/quippy/javamod/multimedia/mod/loader/Module.java index 2495408..c1b3cbf 100644 --- a/source/de/quippy/javamod/multimedia/mod/loader/Module.java +++ b/source/de/quippy/javamod/multimedia/mod/loader/Module.java @@ -568,43 +568,27 @@ protected void readSampleData(final Sample current, final ModfileInputStream inp else if ((flags&ModConstants.SM_PCM16D)==ModConstants.SM_PCM16D) { - int delta = 0; + short delta = 0; for (int s=0; s32767) delta = 32767; else if (delta<-32768) delta = -32768; - current.sampleL[s] = ModConstants.promoteSigned16BitToSigned32Bit((long)delta); - } + current.sampleL[s] = ModConstants.promoteSigned16BitToSigned32Bit((long)(delta += inputStream.readIntelWord())); if (isStereo) { delta = 0; for (int s=0; s32767) delta = 32767; else if (delta<-32768) delta = -32768; - current.sampleR[s] = ModConstants.promoteSigned16BitToSigned32Bit((long)delta); - } + current.sampleR[s] = ModConstants.promoteSigned16BitToSigned32Bit((long)(delta += inputStream.readIntelWord())); } } else if ((flags&ModConstants.SM_PCMD)==ModConstants.SM_PCMD) { - int delta = 0; + byte delta = 0; for (int s=0; s127) delta = 127; else if (delta<-128) delta = -128; - current.sampleL[s] = ModConstants.promoteSigned8BitToSigned32Bit((long)delta); - } + current.sampleL[s] = ModConstants.promoteSigned8BitToSigned32Bit((long)(delta += inputStream.readByte())); if (isStereo) { delta = 0; for (int s=0; s127) delta = 127; else if (delta<-128) delta = -128; - current.sampleR[s] = ModConstants.promoteSigned8BitToSigned32Bit((long)delta); - } + current.sampleR[s] = ModConstants.promoteSigned8BitToSigned32Bit((long)(delta += inputStream.readByte())); } } else @@ -721,7 +705,11 @@ protected void readSampleData(final Sample current, final ModfileInputStream inp * @since 15.06.2020 */ public abstract MidiMacros getMidiConfig(); - + /** + * @since 15.12.2023 + * @return + */ + public abstract boolean getFT2Tremolo(); /** * @since 25.06.2006 * @param length diff --git a/source/de/quippy/javamod/multimedia/mod/loader/tracker/ProTrackerMod.java b/source/de/quippy/javamod/multimedia/mod/loader/tracker/ProTrackerMod.java index 6604d57..deeab1c 100644 --- a/source/de/quippy/javamod/multimedia/mod/loader/tracker/ProTrackerMod.java +++ b/source/de/quippy/javamod/multimedia/mod/loader/tracker/ProTrackerMod.java @@ -54,6 +54,14 @@ public class ProTrackerMod extends Module } private boolean isAmigaLike; + private boolean isDeltaPacked; + private boolean isStarTrekker; + private boolean isNoiseTracker; // No pattern breaks with noise tracker + private boolean isGenericMultiChannel; + private boolean isMdKd; +// private boolean modVBlankTiming; // changes playing behavior to set always speed (ticks), never BPM + private boolean swapBytes; // For .DTM files from Apocalypse Abyss, where the first 2108 bytes are swapped + private boolean ft2Tremolos; // Tremolo Ramp Down Waveform behavior change for some mods (FT2 style) /** * Constructor for ProTrackerMod @@ -148,68 +156,147 @@ public MidiMacros getMidiConfig() return null; } /** - * Get the ModType - * @param kennung + * @return true for some mods + * @see de.quippy.javamod.multimedia.mod.loader.Module#getFT2Tremolo() + */ + @Override + public boolean getFT2Tremolo() + { + return ft2Tremolos; + } + /** + * @param inputStream + * @return true, if this is a protracker mod, false if this is not clear + * @see de.quippy.javamod.multimedia.mod.loader.Module#checkLoadingPossible(de.quippy.javamod.io.ModfileInputStream) + */ + @Override + public boolean checkLoadingPossible(ModfileInputStream inputStream) throws IOException + { + inputStream.seek(1080); + final String modID = inputStream.readString(4); + inputStream.seek(1080); + final int magicNumber = inputStream.readMotorolaDWord(); + inputStream.seek(0); + return modID.equals("M.K.") || modID.equals("M!K!") || modID.equals("PATT") || modID.equals("NSMS") || modID.equals("LARD") || + modID.equals("M&K!") || modID.equals("FEST") || modID.equals("N.T.") || + modID.equals("OKTA") || modID.equals("OCTA") || + modID.equals("CD81") || modID.equals("CD61") || + magicNumber == 0x4D000000 || magicNumber == 0x38000000 || + modID.startsWith("FA0") || + modID.startsWith("FLT") || modID.startsWith("EX0") || + modID.endsWith("CHN") || + modID.endsWith("CH") || modID.endsWith("CN") || + modID.startsWith("TDZ") || + modID.equals(".M.K") || + modID.equals("WARD") || + modID.equals("!PM!"); + } + /** + * @since 21.04.2006 + * @param modID + * @param magicNumber * @return */ - private String getModType(String kennung) + private String getModType(final String modID, final int magicNumber) { songFlags = ModConstants.SONG_AMIGALIMITS; songFlags |= ModConstants.SONG_ISSTEREO; + + isAmigaLike = false; + isDeltaPacked = false; + isNoiseTracker = false; + isStarTrekker = false; + isMdKd = false; +// modVBlankTiming = false; +// swapBytes = false; + isGenericMultiChannel = false; - if (kennung.length()==4) + if (modID.length()==4) { setNSamples(31); - if (kennung.equals("M.K.") || kennung.equals("M!K!") || kennung.equals("M&K!") || kennung.equals("N.T.")) + if (modID.equals("M.K.") || modID.equals("M!K!") || modID.equals("PATT") || modID.equals("NSMS") || modID.equals("LARD")) { isAmigaLike = true; + isMdKd = modID.equals("M.K."); setNChannels(4); - return "ProTracker"; + return "Generic ProTracker or compatible"; } - if (kennung.startsWith("FLT")) + if (modID.equals("M&K!") || modID.equals("FEST") || modID.equals("N.T.")) { - isAmigaLike = false; - setNChannels(Integer.parseInt(Character.toString(kennung.charAt(3)))); - return "StarTrekker"; + isAmigaLike = true; + isNoiseTracker = true; +// modVBlankTiming = true; + setNChannels(4); + return "NoiseTracker"; + } + if (modID.equals("OKTA") || modID.equals("OCTA")) + { + setNChannels(8); + return "Oktalyzer"; + } + if (modID.equals("CD81") || modID.equals("CD61")) + { + setNChannels(Integer.parseInt(Character.toString(modID.charAt(2)))); + return "Oktalyzer (Atari)"; + } + if (magicNumber == 0x4D000000 || magicNumber == 0x38000000) + { + isDeltaPacked = true; + if (modID.charAt(0)=='8') setNChannels(8); else setNChannels(4); + return "Inconexia demo"; + } + if (modID.startsWith("FA0")) + { + setNChannels(Integer.parseInt(Character.toString(modID.charAt(3)))); + return "Digital Tracker (Atari Falcon)"; } - if (kennung.startsWith("TDZ")) + if (modID.startsWith("FLT") || modID.startsWith("EX0")) { - isAmigaLike = false; - setNChannels(Integer.parseInt(Character.toString(kennung.charAt(3)))); - return "StarTrekker"; + isStarTrekker = true; +// modVBlankTiming = true; + setNChannels(Integer.parseInt(Character.toString(modID.charAt(3)))); + return "Startrekker"; } - if (kennung.endsWith("CHN")) + if (modID.endsWith("CHN")) { - isAmigaLike = false; - setNChannels(Integer.parseInt(Character.toString(kennung.charAt(0)))); - return "StarTrekker"; + isGenericMultiChannel = true; + setNChannels(Integer.parseInt(Character.toString(modID.charAt(0)))); + return "Generic MOD-compatible Tracker"; } - if (kennung.equals("CD81") || kennung.equals("OKTA")) + if (modID.endsWith("CH") || modID.endsWith("CN")) { - isAmigaLike = false; + isGenericMultiChannel = true; + setNChannels(Integer.parseInt(modID.substring(0,2))); + return "Generic MOD-compatible Tracker"; + } + if (modID.startsWith("TDZ")) + { + setNChannels(Integer.parseInt(Character.toString(modID.charAt(3)))); + return "TakeTracker"; + } + if (modID.equals(".M.K")) + { + setNChannels(4); + swapBytes = true; + return "Game Apocalypse Abyss"; + } + if (modID.equals("WARD")) + { + isGenericMultiChannel = true; setNChannels(8); - return "Atari Oktalyzer"; + return "Generic MOD-compatible Tracker"; } - if (kennung.equals("!PM!")) + if (modID.equals("!PM!")) // 14.12.2023: Someone came up with these.. { - isAmigaLike = true; + isDeltaPacked = true; setNChannels(4); return "Unknown Tracker"; } - - String firstKennung = kennung.substring(0,2); - String lastKennung = kennung.substring(2,4); - - if (lastKennung.equals("CH") || lastKennung.equals("CN")) - { - isAmigaLike = false; - setNChannels(Integer.parseInt(firstKennung)); - return "TakeTracker"; - } } - // Noise Tracker is the rest... + // Noise Tracker 15 samples 4 channels has no magic ID, so it's the rest... isAmigaLike = true; + isNoiseTracker = true; setNSamples(15); setNChannels(4); setModID("NONE"); @@ -222,7 +309,7 @@ private String getModType(String kennung) * @param fileSize * @return */ - private int calculatePatternCount(int fileSize) + private int calculatePatternCount(final int fileSize) { int headerLen = 150; // Name+SongLen+CIAA+SongArrangement if (getNSamples()>15) headerLen += 4L; // Kennung @@ -245,7 +332,7 @@ private int calculatePatternCount(int fileSize) maxPatternNumber++; // Highest number becomes highest count // It could be the WOW-Format: - if (getModID().equals("M.K.")) + if (isMdKd) { // so check for 8 channels: int totalPatternBytes = maxPatternNumber * (64*4*8); @@ -307,7 +394,7 @@ private PatternElement createNewPatternElement(int pattNum, int row, int channel pe.setPeriod((note&0xFFFF0000)>>16); } - if (pe.getPeriod()<25) + if (pe.getPeriod()<14 || pe.getPeriod()>6848) pe.setPeriod(0); else if (pe.getPeriod()>0) @@ -321,27 +408,31 @@ private PatternElement createNewPatternElement(int pattNum, int row, int channel pe.setEffekt((note&0xF00)>>8); pe.setEffektOp(note&0xFF); - if (pe.getEffekt()==12 && pe.getEffektOp()>64) pe.setEffektOp(64); + + if (pe.getEffekt()==0x0C && pe.getEffektOp()>64) pe.setEffektOp(64); + if (isStarTrekker) + { + if (pe.getEffekt()==0x0E) + { + // No support for StarTrekker assembly macros + pe.setEffekt(0); + pe.setEffektOp(0); + } + else + if (pe.getEffekt()==0x0F && pe.getEffektOp()>0x1F) + { + // StarTrekker caps speed at 31 ticks per row + pe.setEffektOp(0x1F); + } + } + if (isNoiseTracker && pe.getEffekt()==0x0D) + { + // No pattern break operator in NoiseTracker + pe.setEffektOp(0); + } return pe; } - /** - * @param inputStream - * @return true, if this is a protracker mod, false if this is not clear - * @see de.quippy.javamod.multimedia.mod.loader.Module#checkLoadingPossible(de.quippy.javamod.io.ModfileInputStream) - */ - @Override - public boolean checkLoadingPossible(ModfileInputStream inputStream) throws IOException - { - inputStream.seek(1080); - String modID = inputStream.readString(4); - inputStream.seek(0); - return modID.equals("M.K.") || modID.equals("M!K!") || modID.equals("M&K!") || modID.equals("N.T.") || - modID.startsWith("FLT") || modID.startsWith("TDZ") || - modID.endsWith("CHN") || - modID.equals("CD81") || modID.equals("OKTA") || - modID.endsWith("CH") || modID.endsWith("CN"); - } /** * @param fileName * @return @@ -362,8 +453,17 @@ protected void loadModFileInternal(ModfileInputStream inputStream) throws IOExce { inputStream.seek(1080); setModID(inputStream.readString(4)); + inputStream.seek(1080); + final int magicNumber = inputStream.readMotorolaDWord(); inputStream.seek(0); - setTrackerName(getModType(getModID())); + setTrackerName(getModType(getModID(), magicNumber)); + + if (swapBytes) throw new IOException("This Mod type is not yet supported " + getTrackerName()); + + final boolean isFLT8 = isStarTrekker && getNChannels()==8; + ft2Tremolos = (isGenericMultiChannel || isMdKd); + final boolean isHMNT = getModID().equals("M&K!") || getModID().equals("FEST"); + setModType(ModConstants.MODTYPE_MOD); setTempo(6); setBPMSpeed(125); @@ -386,13 +486,26 @@ protected void loadModFileInternal(ModfileInputStream inputStream) throws IOExce // Length current.setLength(inputStream.readMotorolaUnsignedWord() << 1); - // finetune Value>7 means negative 8..15= -8..-1 - int fine = inputStream.read() & 0xF; - fine = fine>7?fine-16:fine; - // BaseFrequenzy from Table: FineTune is -8...+7 - current.setFineTune(fine<7?fine-16:fine; + current.setFineTune(fine< 65535) isNoiseTracker = false; // volume 64 is maximum int vol = inputStream.read() & 0x7F; @@ -442,7 +555,11 @@ protected void loadModFileInternal(ModfileInputStream inputStream) throws IOExce // always space for 128 pattern... allocArrangement(128); - for (int i=0; i<128; i++) getArrangement()[i]=inputStream.read(); + for (int i=0; i<128; i++) + { + int pattNum = inputStream.read(); + getArrangement()[i]=(isFLT8)?pattNum>>1:pattNum; // FLT8 has only even order items, so divide by two. + } // order sanity check int realOrder = getSongLength(); @@ -462,15 +579,17 @@ protected void loadModFileInternal(ModfileInputStream inputStream) throws IOExce // skip ModID, if not NoiseTracker: if (getNSamples()>15) inputStream.skip(4); + // Digital Tracker MODs contain four bytes (00 40 00 00) right after the magic bytes which don't seem to do anything special. + if (getModID().startsWith("FA0")) inputStream.skip(4); - // Read the patterndata + // Read the pattern data int bytesLeft = calculatePatternCount((int)inputStream.getLength()); // Get the amount of pattern and keep "bytesLeft" in mind! PatternContainer patternContainer = new PatternContainer(getNPattern(), 64, getNChannels()); setPatternContainer(patternContainer); for (int pattNum=0; pattNum> 1)) & 0x7f) - 0x40; // Ramp Up + case 2: periodAdd = ((0x40 + (aktMemo.autoVibratoTablePos >> 1)) & 0x7f) - 0x40; // Ramp Up break; - case 3: periodAdd = ((0x40 - (aktMemo.autoVibratoTablePos >> 1)) & 0x7F) - 0x40; // Ramp Down + case 3: periodAdd = ((0x40 - (aktMemo.autoVibratoTablePos >> 1)) & 0x7F) - 0x40; // Ramp Down break; - case 4: periodAdd = ModConstants.ModRandomTable[aktMemo.autoVibratoTablePos & 0x3F]; // Random + case 4: periodAdd = ModConstants.ModRandomTable[aktMemo.autoVibratoTablePos & 0x3F]>>2; // Random aktMemo.autoVibratoTablePos++; break; } - periodAdd = (periodAdd * aktMemo.autoVibratoAmplitude) >> 8; - currentPeriod += (periodAdd>>ModConstants.PERIOD_SHIFT); - setNewPlayerTuningFor(aktMemo, currentPeriod); + periodAdd = (periodAdd * aktMemo.autoVibratoAmplitude) >> (8+ModConstants.PERIOD_SHIFT); + setNewPlayerTuningFor(aktMemo, currentPeriod + periodAdd); } /** * Convenient Method for the vibrato effekt @@ -626,7 +625,6 @@ protected void doVibratoEffekt(final ChannelMemory aktMemo) protected void doPanbrelloEffekt(final ChannelMemory aktMemo) { int newPanning = getVibratoDelta(aktMemo.panbrelloType, aktMemo.panbrelloTablePos); - aktMemo.panbrelloTablePos = (aktMemo.panbrelloTablePos + aktMemo.panbrelloStep) & 0x3F; newPanning = ((newPanning * aktMemo.panbrelloAmplitude) + 2) >> 3; @@ -639,11 +637,24 @@ protected void doPanbrelloEffekt(final ChannelMemory aktMemo) */ protected void doTremoloEffekt(final ChannelMemory aktMemo) { - int volumeAdd = getVibratoDelta(aktMemo.tremoloType, aktMemo.tremoloTablePos); + int delta; + if ((aktMemo.tremoloType & 0x03)==1 && mod.getFT2Tremolo()) + { + // FT2 compatibility: Tremolo ramp down / triangle implementation is weird and affected by vibrato position (copypaste bug) + int ramp = (aktMemo.tremoloTablePos<<2)&0xFF; + int vibPos = aktMemo.vibratoTablePos; + if (aktMemo.vibratoOn) vibPos += aktMemo.vibratoStep; + if ((vibPos & 0x3F)>=32) ramp ^= 0xFF; + if ((aktMemo.tremoloTablePos & 0x3F)>=32) + delta = -ramp; + else + delta = ramp; + } + else + delta = getVibratoDelta(aktMemo.tremoloType, aktMemo.tremoloTablePos); + aktMemo.tremoloTablePos = (aktMemo.tremoloTablePos + aktMemo.tremoloStep) & 0x3F; - - volumeAdd = (volumeAdd * aktMemo.tremoloAmplitude) >> 7; - aktMemo.currentVolume = aktMemo.savedCurrentVolume + volumeAdd; + aktMemo.currentVolume = aktMemo.savedCurrentVolume + ((delta * aktMemo.tremoloAmplitude) >> 6); } /** * The tremor effekt diff --git a/source/de/quippy/javamod/multimedia/mod/mixer/ScreamTrackerMixer.java b/source/de/quippy/javamod/multimedia/mod/mixer/ScreamTrackerMixer.java index f0460b4..4df4eb1 100644 --- a/source/de/quippy/javamod/multimedia/mod/mixer/ScreamTrackerMixer.java +++ b/source/de/quippy/javamod/multimedia/mod/mixer/ScreamTrackerMixer.java @@ -734,13 +734,13 @@ protected void doAutoVibratoEffekt(final ChannelMemory aktMemo, final Sample cur default: case 0: periodAdd = ModConstants.ITSinusTable[position]; // Sine break; - case 1: periodAdd = position<128? 0x40:0; // Square + case 1: periodAdd = position<128? 0x40:0; // Square break; - case 2: periodAdd = ((position + 1)>>1) - 0x40; // Ramp Up + case 2: periodAdd = ((position + 1)>>1) - 0x40; // Ramp Up break; - case 3: periodAdd = 0x40 - ((position + 1)>>1); // Ramp Down + case 3: periodAdd = 0x40 - ((position + 1)>>1); // Ramp Down break; - case 4: periodAdd = (swinger.nextInt() % 0x40); // Random + case 4: periodAdd = (swinger.nextInt() % 0x40); // Random break; } int slideIndex = (periodAdd * depth) >> 7; // periodAdd 0..128 @@ -794,8 +794,8 @@ private int getVibratoDelta(final int type, int position) position &= 0xFF; switch (type & 0x03) { - case 0: //Sinus default: + case 0: //Sinus return ModConstants.ITSinusTable[position]; case 1: // Ramp Down / Sawtooth return 0x40 - ((position + 1)>>1); @@ -893,11 +893,9 @@ protected void doPanbrelloEffekt(final ChannelMemory aktMemo) */ protected void doTremoloEffekt(final ChannelMemory aktMemo) { - int volumeAdd = getVibratoDelta(aktMemo.tremoloType, aktMemo.tremoloTablePos); - - aktMemo.tremoloTablePos = (aktMemo.tremoloTablePos + aktMemo.tremoloStep) & 0xFF; - volumeAdd = (volumeAdd * aktMemo.tremoloAmplitude) >> 6; - aktMemo.currentVolume = aktMemo.savedCurrentVolume + volumeAdd; + int delta = getVibratoDelta(aktMemo.tremoloType, aktMemo.tremoloTablePos); + aktMemo.tremoloTablePos = (aktMemo.tremoloTablePos + (aktMemo.tremoloStep<<2)) & 0xFF; + aktMemo.currentVolume = aktMemo.savedCurrentVolume + ((delta * aktMemo.tremoloAmplitude) >> 6); } /** * The tremor effekt diff --git a/source/de/quippy/javamod/system/Helpers.java b/source/de/quippy/javamod/system/Helpers.java index 02f2488..893a2ab 100644 --- a/source/de/quippy/javamod/system/Helpers.java +++ b/source/de/quippy/javamod/system/Helpers.java @@ -88,7 +88,7 @@ private Helpers() } /** Version Information */ - public static final String VERSION = "V3.7.2"; + public static final String VERSION = "V3.7.3"; public static final String PROGRAM = "Java Mod Player"; public static final String FULLVERSION = PROGRAM+' '+VERSION; public static final String COPYRIGHT = "© by Daniel Becker since 2006"; @@ -1255,7 +1255,7 @@ public static String getCurrentServerVersion() } catch (Throwable ex) { - Log.error("getCurrentServerVersion", ex); + /* NOOP */ } finally { @@ -1268,10 +1268,12 @@ public static String getCurrentServerVersion() * @since 16.01.2010 * @param v1 * @param v2 - * @return v1>v2: 1; v1==v2: 0; v1>3; - this.sampleRate = 48000; - aktMemo.currentTuning = (int)((((long)audioFormat.getSampleRate())<1); - sample.setLength((int)audioInputStream.getFrameLength()); - sample.setLoopType(0); - sample.setLoopStart(0); - sample.setLoopStop(0); - sample.setSustainLoopStart(0); - sample.setSustainLoopStop(0); - sample.allocSampleData(); - - this.bufferSize = 250 * channels * sampleRate / 1000; // 250ms buffer - // Now for the bits (linebuffer): - bufferSize *= sampleSizeInBytes; - output = new byte[bufferSize]; - - int nBytesRead = 0; - int idx = 0; - while (nBytesRead != -1) - { - nBytesRead = audioInputStream.read(output, 0, output.length); - if (nBytesRead >= 0) - { - int i=0; - while (i> 8; - if (cutOff<0) cutOff=0; else if (cutOff>0xFF) cutOff=0xFF; - int resonance = aktMemo.resonance & 0x7F; - if (resonance<0) resonance=0; else if (resonance>0xFF) resonance=0xFF; - - final double fac = (((aktMemo.songFlags&ModConstants.SONG_EXFILTERRANGE)!=0)? (128.0d / (20.0d * 256.0d)) : (128.0d / (24.0d * 256.0d))); - double frequency = 110.0d * Math.pow(2.0d, (double)cutOff * fac + 0.25d); - if (frequency < 120d) frequency = 120d; - if (frequency > 20000d) frequency = 20000d; - if (frequency > sampleRate>>1) frequency = sampleRate>>1; - frequency *= 2.0d * Math.PI; - - final double dmpFac = ModConstants.ResonanceTable[resonance]; - double e, d; - if ((aktMemo.songFlags&ModConstants.SONG_EXFILTERRANGE)==0) // mod.getSongFlags() - { - final double r = ((double)sampleRate) / frequency; - d = dmpFac * r + dmpFac - 1.0d; - e = r * r; - } - else - { - final double d_dmpFac = 2.0d * dmpFac; - final double r = frequency / ((double)(sampleRate)); - d = (1.0d - d_dmpFac) * r; - if (d > 2.0d) d = 2.0d; - d = (d_dmpFac - d) / r; - e = 1.0d / (r * r); - } - - final double fg = 1.0d / (1.0d + d + e); - final double fb0 = (d + e + e) / (1.0d + d + e); - final double fb1 = -e / (1.0d + d + e); - - switch(aktMemo.filterMode) - { - case ModConstants.FLTMODE_HIGHPASS: - aktMemo.filter_A0 = (long)((1.0d - fg) * ModConstants.FILTER_PRECISION); - aktMemo.filter_B0 = (long)(fb0 * ModConstants.FILTER_PRECISION); - aktMemo.filter_B1 = (long)(fb1 * ModConstants.FILTER_PRECISION); - aktMemo.filter_HP = -1; - break; - case ModConstants.FLTMODE_BANDPASS: - case ModConstants.FLTMODE_LOWPASS: - default: - aktMemo.filter_A0 = (long)(fg * ModConstants.FILTER_PRECISION); - aktMemo.filter_B0 = (long)(fb0 * ModConstants.FILTER_PRECISION); - aktMemo.filter_B1 = (long)(fb1 * ModConstants.FILTER_PRECISION); - aktMemo.filter_HP = 0; - if (aktMemo.filter_A0 == 0) aktMemo.filter_A0 = 1; - break; - } - - if (reset) aktMemo.filter_Y1 = aktMemo.filter_Y2 = aktMemo.filter_Y3 = aktMemo.filter_Y4 = 0; - } - private void doResonance(final ChannelMemory aktMemo, final long buffer[]) - { - long sampleAmp = buffer[0]<> ModConstants.FILTER_SHIFT_BITS; - aktMemo.filter_Y2 = aktMemo.filter_Y1; - aktMemo.filter_Y1 = fy - (sampleAmp & aktMemo.filter_HP); - if (aktMemo.filter_Y1 < ModConstants.FILTER_CLIP_MIN) aktMemo.filter_Y1 = ModConstants.FILTER_CLIP_MIN; - else if (aktMemo.filter_Y1 > ModConstants.FILTER_CLIP_MAX) aktMemo.filter_Y1 = ModConstants.FILTER_CLIP_MAX; - buffer[0] = (fy + (1<<(ModConstants.FILTER_PREAMP_BITS-1))) >> ModConstants.FILTER_PREAMP_BITS; - - sampleAmp = buffer[1]<> ModConstants.FILTER_SHIFT_BITS; - aktMemo.filter_Y4 = aktMemo.filter_Y3; - aktMemo.filter_Y3 = fy - (sampleAmp & aktMemo.filter_HP); - if (aktMemo.filter_Y3 < ModConstants.FILTER_CLIP_MIN) aktMemo.filter_Y3 = ModConstants.FILTER_CLIP_MIN; - else if (aktMemo.filter_Y3 > ModConstants.FILTER_CLIP_MAX) aktMemo.filter_Y3 = ModConstants.FILTER_CLIP_MAX; - buffer[1] = (fy + (1<<(ModConstants.FILTER_PREAMP_BITS-1))) >> ModConstants.FILTER_PREAMP_BITS; - } - protected static void fitIntoLoops(ChannelMemory aktMemo) - { - aktMemo.currentTuningPos += aktMemo.currentTuning; - if (aktMemo.currentTuningPos >= ModConstants.SHIFT_ONE) - { - final int addToSamplePos = aktMemo.currentTuningPos >> ModConstants.SHIFT; - aktMemo.currentTuningPos &= ModConstants.SHIFT_MASK; - - // If Forward direction: - if (aktMemo.isForwardDirection) - { - aktMemo.currentSamplePos += addToSamplePos; - // Set the end position to check against... - int loopStart = 0; // if this is not changed, we have no loops - int loopEnd = aktMemo.sample.length; - int inLoop = 0; - - if ((aktMemo.sample.loopType&ModConstants.LOOP_SUSTAIN_ON)!=0 && !aktMemo.keyOff) // Sustain Loop on? - { - loopStart = aktMemo.sample.sustainLoopStart; - loopEnd = aktMemo.sample.sustainLoopStop; - inLoop = ModConstants.LOOP_SUSTAIN_ON; - aktMemo.interpolationMagic = aktMemo.sample.getSustainLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - else - if ((aktMemo.sample.loopType&ModConstants.LOOP_ON)!=0) - { - loopStart = aktMemo.sample.loopStart; - loopEnd = aktMemo.sample.loopStop; - inLoop = ModConstants.LOOP_ON; - aktMemo.interpolationMagic = aktMemo.sample.getLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - else - aktMemo.interpolationMagic = 0; - - // do we have an overrun of border? - if (aktMemo.currentSamplePos >= loopEnd) - { - // We need to check against a loop set - maybe a sustain loop is finished - // but no normal loop is set: - if (inLoop==0) - { - aktMemo.instrumentFinished = true; - } - else - { - // check if loop, that was enabled, is a ping pong - if ((inLoop == ModConstants.LOOP_ON && (aktMemo.sample.loopType & ModConstants.LOOP_IS_PINGPONG)!=0) || - (inLoop == ModConstants.LOOP_SUSTAIN_ON && (aktMemo.sample.loopType & ModConstants.LOOP_SUSTAIN_IS_PINGPONG)!=0)) - { - aktMemo.isForwardDirection = false; - aktMemo.currentSamplePos = loopEnd - 1; - aktMemo.loopCounter++; - aktMemo.interpolationMagic = (inLoop == ModConstants.LOOP_ON)?aktMemo.sample.getLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter):aktMemo.sample.getSustainLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - else - { - aktMemo.currentSamplePos = loopStart; - aktMemo.loopCounter++; - aktMemo.interpolationMagic = (inLoop == ModConstants.LOOP_ON)?aktMemo.sample.getLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter):aktMemo.sample.getSustainLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - } - } - } - else // Backwards in Ping Pong - { - aktMemo.currentSamplePos -= addToSamplePos; - - int loopStart = 0; // support Sound Control "Play Backwards" with no loops set - int inLoop = 0; - if ((aktMemo.sample.loopType&ModConstants.LOOP_SUSTAIN_ON)!=0 && !aktMemo.keyOff) - { - loopStart = aktMemo.sample.sustainLoopStart; - inLoop = ModConstants.LOOP_SUSTAIN_ON; - aktMemo.interpolationMagic = aktMemo.sample.getSustainLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - else - if ((aktMemo.sample.loopType&ModConstants.LOOP_ON)!=0) - { - loopStart = aktMemo.sample.loopStart; - inLoop = ModConstants.LOOP_ON; - aktMemo.interpolationMagic = aktMemo.sample.getLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter); - } - else - aktMemo.interpolationMagic = 0; - - if (aktMemo.currentSamplePos < loopStart) - { - aktMemo.isForwardDirection = true; - aktMemo.currentSamplePos = loopStart; - aktMemo.interpolationMagic = (inLoop == ModConstants.LOOP_ON)?aktMemo.sample.getLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter):(inLoop == ModConstants.LOOP_SUSTAIN_ON)?aktMemo.sample.getSustainLoopMagic(aktMemo.currentSamplePos, aktMemo.loopCounter):0; - } - } - } - } - private static final int SET_cutOff = 96; - private static final int SET_resonance = 97; - private void testResonance() - { - currentSamplesWritten = 0; - final long [] buffer = new long[2]; - - aktMemo.cutOff = SET_cutOff; - aktMemo.resonance = SET_resonance; - aktMemo.songFlags = 0; //ModConstants.SONG_EXFILTERRANGE; - aktMemo.filterMode = 0; //ModConstants.FLTMODE_HIGHPASS; - setupChannelFilter(aktMemo, true, 256); - - try - { -// URL modUrl = new File("m:\\Multimedia\\Files MOD\\__testmods__\\panflute.it").toURI().toURL(); -// MultimediaContainer multimediaContainer = MultimediaContainerManager.getMultimediaContainer(modUrl); -// final ModMixer mixer = (ModMixer)multimediaContainer.createNewMixer(); -// Module mod = mixer.getMod(); -// InstrumentsContainer instruments = mod.getInstrumentContainer(); -// Sample compareMe = instruments.getSample(0); -// aktMemo.sample = compareMe; -// -// for (int i=0; i>=1; - buffer[1]>>=1; - - doResonance(aktMemo, buffer); - - // Now clipping - might not be necessary, but we want correct samples... - if (buffer[0] < ModConstants.CLIPP32BIT_MIN) buffer[0] = ModConstants.CLIPP32BIT_MIN; - else if (buffer[0] > ModConstants.CLIPP32BIT_MAX) buffer[0] = ModConstants.CLIPP32BIT_MAX; - if (buffer[1] < ModConstants.CLIPP32BIT_MIN) buffer[1] = ModConstants.CLIPP32BIT_MIN; - else if (buffer[1] > ModConstants.CLIPP32BIT_MAX) buffer[1] = ModConstants.CLIPP32BIT_MAX; - - // output - output[i++] = (byte)(buffer[0]>>16); - output[i++] = (byte)(buffer[0]>>24); - output[i++] = (byte)(buffer[1]>>16); - output[i++] = (byte)(buffer[1]>>24); - - fitIntoLoops(aktMemo); - if (aktMemo.instrumentFinished) break; - } - - currentSamplesWritten += sourceLine.write(output, 0, i); - } - sourceLine.drain(); - sourceLine.close(); - } - System.out.println(currentSamplesWritten); - } - catch (Exception ex) - { - ex.printStackTrace(System.err); - } - } - - /** - * @since 02.07.2020 - * @param args - */ - public static void main(String[] args) - { - TestResonance me = new TestResonance(); - me.initialize(); - me.testResonance(); - } - -}