Added #2259 Optional resampling sound to 44kHz on playback and on export

This commit is contained in:
Jindra Petřík
2024-08-05 11:17:25 +02:00
parent 502d44b06d
commit 6d8b631383
29 changed files with 317 additions and 72 deletions
@@ -3251,8 +3251,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
}
}
public void putToCache(SOUNDINFO soundInfo, SoundTag soundTag, byte[] data) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag);
public void putToCache(SOUNDINFO soundInfo, SoundTag soundTag, boolean resample, byte[] data) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag, resample);
soundCache.put(key, data);
}
@@ -3399,8 +3399,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
return null;
}
public byte[] getFromCache(SOUNDINFO soundInfo, SoundTag soundTag) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag);
public byte[] getFromCache(SOUNDINFO soundInfo, SoundTag soundTag, boolean resample) {
SoundInfoSoundCacheEntry key = new SoundInfoSoundCacheEntry(soundInfo, soundTag, resample);
if (soundCache.contains(key)) {
return soundCache.get(key);
}
@@ -993,6 +993,14 @@ public final class Configuration {
@ConfigurationCategory("export")
public static ConfigurationItem<Boolean> flaExportFixShapes = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("export")
public static ConfigurationItem<Boolean> lastExportResampleWav = null;
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> previewResampleSound = null;
private enum OSId {
WINDOWS, OSX, UNIX
}
@@ -113,7 +113,7 @@ public class SoundExporter {
final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName()) + ext);
new RetryTask(() -> {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
exportSound(os, st, settings.mode);
exportSound(os, st, settings.mode, settings.resampleWav);
}
}, handler).run();
@@ -144,13 +144,13 @@ public class SoundExporter {
return ret;
}
public byte[] exportSound(SoundTag t, SoundExportMode mode) throws IOException {
public byte[] exportSound(SoundTag t, SoundExportMode mode, boolean resampleWav) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
exportSound(baos, t, mode);
exportSound(baos, t, mode, resampleWav);
return baos.toByteArray();
}
public void exportSound(OutputStream fos, SoundTag st, SoundExportMode mode) throws IOException {
public void exportSound(OutputStream fos, SoundTag st, SoundExportMode mode, boolean resampleWav) throws IOException {
SoundFormat fmt = st.getSoundFormat();
SoundExportFormat nativeFormat = fmt.getNativeExportFormat();
@@ -194,7 +194,7 @@ public class SoundExporter {
}
} else {
List<ByteArrayRange> soundData = st.getRawSoundData();
fmt.createWav(null, soundData, fos, st.getInitialLatency());
fmt.createWav(null, soundData, fos, st.getInitialLatency(), resampleWav);
}
}
}
@@ -536,7 +536,7 @@ public class AS3ScriptExporter {
return ret;
}
SoundExporter se = new SoundExporter();
se.exportSounds(handler, ASSETS_DIR, rttl, new SoundExportSettings(SoundExportMode.MP3_WAV), evl);
se.exportSounds(handler, ASSETS_DIR, rttl, new SoundExportSettings(SoundExportMode.MP3_WAV, exportSettings.resampleWav), evl);
if (Thread.currentThread().isInterrupted()) {
return ret;
}
@@ -38,19 +38,23 @@ public class ScriptExportSettings {
public boolean exportEmbed;
public boolean exportEmbedFlaMode;
public boolean resampleWav;
public ScriptExportSettings(
ScriptExportMode mode,
boolean singleFile,
boolean ignoreFrameScripts,
boolean exportEmbed,
boolean exportEmbedFlaMode
boolean exportEmbedFlaMode,
boolean resampleWav
) {
this.mode = mode;
this.singleFile = singleFile;
this.ignoreFrameScripts = ignoreFrameScripts;
this.exportEmbed = exportEmbed;
this.exportEmbedFlaMode = exportEmbedFlaMode;
this.resampleWav = resampleWav;
}
public String getFileExtension() {
@@ -27,8 +27,11 @@ public class SoundExportSettings {
public static final String EXPORT_FOLDER_NAME = "sounds";
public SoundExportMode mode;
public boolean resampleWav;
public SoundExportSettings(SoundExportMode mode) {
public SoundExportSettings(SoundExportMode mode, boolean resampleWav) {
this.mode = mode;
this.resampleWav = resampleWav;
}
}
@@ -322,7 +322,7 @@ public class MxmlcAs3ScriptReplacer extends MxmlcRunner implements As3ScriptRepl
//This compiled code won't be used at all in original SWF,
//it is used only by Flex to properly compile current script
AS3ScriptExporter ex = new AS3ScriptExporter();
ex.exportActionScript3(swfCopy, null, tempDir.getAbsolutePath(), removedPacks, new ScriptExportSettings(ScriptExportMode.AS_METHOD_STUBS, false, false, false /* ??? FIXME */, false), false, null);
ex.exportActionScript3(swfCopy, null, tempDir.getAbsolutePath(), removedPacks, new ScriptExportSettings(ScriptExportMode.AS_METHOD_STUBS, false, false, false /* ??? FIXME */, false, true), false, null);
//now really remove the classes from SWF copy
for (ABC a : modAbcs) {
@@ -55,7 +55,7 @@ public class AS3ScriptImporter {
continue;
}
try {
File file = pack.getExportFile(scriptsFolder, new ScriptExportSettings(ScriptExportMode.AS, false, false, false, false));
File file = pack.getExportFile(scriptsFolder, new ScriptExportSettings(ScriptExportMode.AS, false, false, false, false, true));
if (file.exists()) {
Openable openable = pack.getOpenable();
SWF swf = (openable instanceof SWF) ? (SWF) openable : ((ABC) openable).getSwf();
@@ -224,7 +224,78 @@ public class SoundFormat {
return decodedData;
}
public boolean createWav(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, OutputStream os, int skipSamples) throws IOException {
private byte[] resample(byte[] decodedData) throws IOException {
if (samplingRate == 44100) {
return decodedData;
}
boolean resamplingFromStereo = true;
ByteArrayOutputStream baosResampled = new ByteArrayOutputStream();
for (int i = 0; i < decodedData.length; i += (resamplingFromStereo ? 4 : 2)) {
if (i + 1 >= decodedData.length) {
break;
}
int left = ((decodedData[i] & 0xff) + ((decodedData[i + 1] & 0xff) << 8)) << 16 >> 16;
int right = left;
if (resamplingFromStereo)
{
if (i + 3 >= decodedData.length) {
break;
}
right = ((decodedData[i + 2] & 0xff) + ((decodedData[i + 3] & 0xff) << 8)) << 16 >> 16;
}
int nextLeft = left;
int nextRight = right;
int nextI = i + (resamplingFromStereo ? 4 : 2);
if (nextI < decodedData.length) {
nextLeft = ((decodedData[nextI] & 0xff) + ((decodedData[nextI + 1] & 0xff) << 8)) << 16 >> 16;
nextRight = nextLeft;
if (resamplingFromStereo) {
if (nextI + 3 >= decodedData.length) {
//ignore
} else {
nextRight = ((decodedData[nextI + 2] & 0xff) + ((decodedData[nextI + 3] & 0xff) << 8)) << 16 >> 16;
}
}
}
writeLE(baosResampled, left, 2);
writeLE(baosResampled, right, 2);
if (samplingRate == 5512) {
writeLE(baosResampled, left + (nextLeft - left) / 8, 2);
writeLE(baosResampled, right + (nextRight - right) / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 2 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 2 / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 3 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 3 / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 4 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 4 / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 5 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 5 / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 6 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 6 / 8, 2);
writeLE(baosResampled, left + (nextLeft - left) * 7 / 8, 2);
writeLE(baosResampled, right + (nextRight - right) * 7 / 8, 2);
}
if (samplingRate == 11025) {
writeLE(baosResampled, left + (nextLeft - left) / 4, 2);
writeLE(baosResampled, right + (nextRight - right) / 4, 2);
writeLE(baosResampled, left + (nextLeft - left) * 2 / 4, 2);
writeLE(baosResampled, right + (nextRight - right) * 2 / 4, 2);
writeLE(baosResampled, left + (nextLeft - left) * 3 / 4, 2);
writeLE(baosResampled, right + (nextRight - right) * 3 / 4, 2);
}
if (samplingRate == 22050)
{
writeLE(baosResampled, (left + nextLeft) / 2, 2);
writeLE(baosResampled, (right + nextRight) / 2, 2);
}
}
return baosResampled.toByteArray();
}
public boolean createWav(SOUNDINFO soundInfo, List<ByteArrayRange> dataRanges, OutputStream os, int skipSamples, boolean resample) throws IOException {
byte[] decodedData = decode(soundInfo, dataRanges, skipSamples);
boolean convertedStereo = stereo;
@@ -252,8 +323,8 @@ public class SoundFormat {
break;
}
right = ((decodedData[i + 2] & 0xff) + ((decodedData[i + 3] & 0xff) << 8)) << 16 >> 16;
}
}
if (soundInfo.hasEnvelope) {
for (int e = 0; e < soundInfo.envelopeRecords.length - 1; e++) {
int envPosBytes = inPointBytes + (int) (soundInfo.envelopeRecords[e].pos44 * samplingRate / 44100.0 * 2 * (stereo ? 2 : 1));
@@ -274,13 +345,16 @@ public class SoundFormat {
}
writeLE(baosFiltered, left, 2);
writeLE(baosFiltered, right, 2);
writeLE(baosFiltered, right, 2);
}
convertedStereo = true;
}
byte[] resampled = resample ? resample(baosFiltered.toByteArray()) : baosFiltered.toByteArray();
try {
createWavFromPcmData(os, samplingRate, true, convertedStereo, baosFiltered.toByteArray());
createWavFromPcmData(os, resample ? 44100 : samplingRate, true, convertedStereo, resampled);
return true;
} catch (IOException ex) {
return false;
@@ -28,17 +28,20 @@ public class SoundInfoSoundCacheEntry {
public SOUNDINFO soundInfo;
public SoundTag soundTag;
public boolean resample;
public SoundInfoSoundCacheEntry(SOUNDINFO soundInfo, SoundTag soundTag) {
public SoundInfoSoundCacheEntry(SOUNDINFO soundInfo, SoundTag soundTag, boolean resample) {
this.soundInfo = soundInfo;
this.soundTag = soundTag;
this.resample = resample;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + Objects.hashCode(this.soundInfo);
hash = 47 * hash + Objects.hashCode(this.soundTag);
hash = 97 * hash + Objects.hashCode(this.soundInfo);
hash = 97 * hash + Objects.hashCode(this.soundTag);
hash = 97 * hash + (this.resample ? 1 : 0);
return hash;
}
@@ -54,13 +57,13 @@ public class SoundInfoSoundCacheEntry {
return false;
}
final SoundInfoSoundCacheEntry other = (SoundInfoSoundCacheEntry) obj;
if (this.resample != other.resample) {
return false;
}
if (!Objects.equals(this.soundInfo, other.soundInfo)) {
return false;
}
if (!Objects.equals(this.soundTag, other.soundTag)) {
return false;
}
return true;
return Objects.equals(this.soundTag, other.soundTag);
}
}
@@ -2139,7 +2139,7 @@ public class XFLConverter {
SoundFormat fmt = st.getSoundFormat();
byte[] data = SWFInputStream.BYTE_ARRAY_EMPTY;
try {
data = new SoundExporter().exportSound(st, convertMp3ToWav ? SoundExportMode.WAV : SoundExportMode.MP3_WAV);
data = new SoundExporter().exportSound(st, convertMp3ToWav ? SoundExportMode.WAV : SoundExportMode.MP3_WAV, true);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
@@ -5345,7 +5345,7 @@ public class XFLConverter {
}
if (useAS3 && settings.exportScript) {
try {
ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, true, false, true);
ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, true, false, true, true);
swf.exportActionScript(handler, scriptsDir.getAbsolutePath(), scriptExportSettings, parallel, null);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error during ActionScript3 export", ex);