diff --git a/core/src/bms/player/beatoraja/audio/MSADPCMDecoder.java b/core/src/bms/player/beatoraja/audio/MSADPCMDecoder.java index 367a3665f..3914cef37 100644 --- a/core/src/bms/player/beatoraja/audio/MSADPCMDecoder.java +++ b/core/src/bms/player/beatoraja/audio/MSADPCMDecoder.java @@ -1,10 +1,9 @@ package bms.player.beatoraja.audio; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Arrays; +import java.nio.ShortBuffer; import java.util.logging.Logger; @@ -17,193 +16,192 @@ public class MSADPCMDecoder { private static final int[] AdaptionTable = { - 230, 230, 230, 230, 307, 409, 521, 614, + 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; private static final int[] InitializationCoeff1 = { - 256, 512, 0, 192, 240, 460, 392 + 64, 128, 0, 48, 60, 115, 98 }; private static final int[] InitializationCoeff2 = { - 0, -256, 0, 64, 0, -208, -232 + 0, -64, 0, 16, 0, -52, -58 }; - private int[] AdaptCoeff1; - private int[] AdaptCoeff2; + private int[] adaptCoeff1; + private int[] adaptCoeff2; - private short[] InitialDelta = new short[2]; - private short[] Sample1 = new short[2]; - private short[] Sample2 = new short[2]; - - private short[] pcmBlock; - private final byte[] adpcmBlock; + private int[] initialDelta; + private int[] sample1; + private int[] sample2; + private short[][] channelSamples; private final int samplesPerBlock; private final int channels; - private final int blocksize; + private final int blockSize; private final int sampleRate; public MSADPCMDecoder(int channels, int sampleRate, int blockAlign) { this.channels = channels; this.sampleRate = sampleRate; - this.blocksize = blockAlign; - this.samplesPerBlock = (blocksize - channels * 4) * (channels ^ 3) + 1; - - pcmBlock = new short[samplesPerBlock * channels]; - adpcmBlock = new byte[blocksize]; + this.blockSize = blockAlign; + // sizeof(header) = 7 + // each header contains two samples + // channels * 2 + (blockSize - channels * sizeof(header)) * 2 ==> (blockSize - channels * 6) * 2 + this.samplesPerBlock = (blockSize - channels * 6) * 2 / channels; } - public ByteArrayOutputStream decode(ByteBuffer in, ByteArrayOutputStream out) throws IOException { + public ByteBuffer decode(ByteBuffer in) throws IOException { + // init decode context + adaptCoeff1 = new int[channels]; + adaptCoeff2 = new int[channels]; + initialDelta = new int[channels]; + sample1 = new int[channels]; + sample2 = new int[channels]; + if (channels > 2) + channelSamples = new short[channels][samplesPerBlock]; + + if ((in.remaining() % blockSize) != 0){ + Logger.getGlobal().severe("Malformed MS ADPCM block"); + throw new IOException("too few elements left in input buffer"); + // Note: ffmpeg doesn't process incomplete blocks. + } + int blockCount = in.remaining() / blockSize; + int blockSampleSize = samplesPerBlock * channels * 2; + ByteBuffer out = ByteBuffer.allocate(blockCount * blockSampleSize); + out.order(ByteOrder.LITTLE_ENDIAN); + while (in.hasRemaining()) { - int samplesPerBlock = (blocksize - channels * 4) * (channels ^ 3) + 1; - int blockAdpcmSamples = samplesPerBlock; - int blockPcmSamples = samplesPerBlock; - int currentBlockSize = blocksize; - - int numSamples = (in.remaining() - 6 * channels) * 2 / channels; - if (currentBlockSize > in.remaining()) { - samplesPerBlock = (in.remaining() - channels * 4) * (channels ^ 3) + 1; - } +// in.get(adpcmBlock, 0, blockSize); + ByteBuffer block = in.slice().limit(blockSize).order(ByteOrder.LITTLE_ENDIAN); + decode_block(out.asShortBuffer(), block); + in.position(in.position() + blockSize); + out.position(out.position() + blockSampleSize); + } +// Logger.getGlobal().info("Return hit"); + out.flip(); + return out; + } + + private void decode_block(ShortBuffer out, ByteBuffer blockData) throws IOException { + //Logger.getGlobal().info("decoding block"); - pcmBlock = new short[samplesPerBlock*channels]; -/* if (currentBlockSize > in.remaining()) { - int numSamples = (in.remaining() - 6 * channels) * 2 / channels; - if (blockAdpcmSamples > numSamples) { - blockAdpcmSamples = ((numSamples + 6) & ~7) + 1; - currentBlockSize = (blockAdpcmSamples - 1) / (channels ^ 3) + (channels * 4); - blockPcmSamples = numSamples; + if (channels > 2) { + // When channels > 2, channels are NOT interleaved. + for (int ch = 0; ch < channels; ch++) { + int predictor = Byte.toUnsignedInt(blockData.get()); + if (predictor > 6) { + Logger.getGlobal().warning("Malformed block header"); + throw new IOException("Malformed block header. Expected range for predictor 0..6, found "+ predictor); } - Logger.getGlobal().warning("numSamples: " + numSamples ); - } */ -/* Logger.getGlobal().info("Good decode pass"); - Logger.getGlobal().warning("block size: " +currentBlockSize); - Logger.getGlobal().warning("blockadpcm samples " + blockAdpcmSamples); - Logger.getGlobal().warning("in.remaining() " + in.remaining()); */ - //Logger.getGlobal().warning("out.remaining() " + out.remaining()); - - if (in.remaining() < currentBlockSize) { - //Logger.getGlobal().severe("Malformed MS ADPCM block"); - //throw new IOException("too few elements left in input buffer"); - Logger.getGlobal().info("End runt block"); - in.get(adpcmBlock, 0, in.remaining()); - decode_block(pcmBlock, adpcmBlock, in.remaining()); - } else { - in.get(adpcmBlock, 0, currentBlockSize); - decode_block(pcmBlock, adpcmBlock, currentBlockSize); - } + // Initialize the Adaption coefficients for each channel by indexing + // into the coeff. table with the predictor value (range 0..6) + adaptCoeff1[ch] = InitializationCoeff1[predictor]; + adaptCoeff2[ch] = InitializationCoeff2[predictor]; - ByteBuffer bff = ByteBuffer.allocate(pcmBlock.length * 2); - bff.order(ByteOrder.LITTLE_ENDIAN); - bff.asShortBuffer().put(pcmBlock); - out.write(bff.array()); - } + initialDelta[ch] = blockData.getShort(); - Logger.getGlobal().info("Return hit"); - return out; - } - - private void decode_block(short[] out, byte[] block_data, int inSize) throws IOException { - //Logger.getGlobal().info("decoding block"); - - AdaptCoeff1 = new int[channels]; - AdaptCoeff2 = new int[channels]; - - int outPtr = 0; - int inPtr = 0; - - /* - * Obtain ADPCM block preamble for all channels. - * Channels are interleaved for the block predictor. - * iDelta, Sample's 1 and 2 are all signed 16 bit Shorts in little endian - * - * Here is an example block preamble layout for stereo - * Byte Description - * ---------------------------------- - * 0 left channel block predictor - * 1 right channel block predictor - * 2 left channel idelta LOW - * 3 left channel idelta HIGH - * 4 right channel idelta LOW - * 5 right channel idelta HIGH - * 6 left channel sample1 LOW - * 7 left channel sample1 HIGH - * 8 right channel sample1 LOW - * 9 right channel sample1 HIGH - * 10 left channel sample2 LOW - * 11 left channel sample2 HIGH - * 12 right channel sample2 LOW - * 13 right channel sample2 HIGH - */ - for (int ch = 0; ch < channels; ch++) { - int predictor = Byte.toUnsignedInt(block_data[inPtr]); - if (predictor > 6) { - Logger.getGlobal().warning("Malformed block header"); - throw new IOException("Malformed block header. Expected range for predictor 0..6, found "+ predictor); + // Acquire initial uncompressed signed 16 bit PCM samples for initialization + sample1[ch] = blockData.getShort(); + + sample2[ch] = blockData.getShort(); + + int samplePtr = 0; + channelSamples[ch][samplePtr++] = (short) sample2[ch]; + channelSamples[ch][samplePtr++] = (short) sample1[ch]; + + for (int n = (samplesPerBlock - 2) >> 1; n > 0; n--){ + int currentByte = Byte.toUnsignedInt(blockData.get()); + + channelSamples[ch][samplePtr++] = expandNibble((currentByte & 0xFF) >> 4, ch); + channelSamples[ch][samplePtr++] = expandNibble((currentByte & 0xFF) & 0xf, ch); + } } - inPtr += 1; - - // Initialize the Adaption coefficients for each channel by indexing - // into the coeff. table with the predictor value (range 0..6) - AdaptCoeff1[ch] = InitializationCoeff1[predictor]; - AdaptCoeff2[ch] = InitializationCoeff2[predictor]; - } + // interleave samples + for (int i = 0; i < samplesPerBlock; i++){ + for (int j = 0; j < channels; j++){ + out.put(channelSamples[j][i]); + } + } + } else { + /* + * Obtain ADPCM block preamble for all channels. + * Channels are interleaved for the block predictor. + * iDelta, Sample's 1 and 2 are all signed 16 bit Shorts in little endian + * + * Here is an example block preamble layout for stereo + * Byte Description + * ---------------------------------- + * 0 left channel block predictor + * 1 right channel block predictor + * 2 left channel idelta LOW + * 3 left channel idelta HIGH + * 4 right channel idelta LOW + * 5 right channel idelta HIGH + * 6 left channel sample1 LOW + * 7 left channel sample1 HIGH + * 8 right channel sample1 LOW + * 9 right channel sample1 HIGH + * 10 left channel sample2 LOW + * 11 left channel sample2 HIGH + * 12 right channel sample2 LOW + * 13 right channel sample2 HIGH + */ + for (int ch = 0; ch < channels; ch++) { + int predictor = Byte.toUnsignedInt(blockData.get()); + if (predictor > 6) { + Logger.getGlobal().warning("Malformed block header"); + throw new IOException("Malformed block header. Expected range for predictor 0..6, found "+ predictor); + } - // TODO: Revisit this index maths - for (int ch = 0; ch < channels; ch++) { - ByteBuffer iDeltaBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); - iDeltaBuf.put(block_data[inPtr]); - iDeltaBuf.put(block_data[inPtr + 1]); - InitialDelta[ch] = iDeltaBuf.getShort(0); - inPtr += 2; - } + // Initialize the Adaption coefficients for each channel by indexing + // into the coeff. table with the predictor value (range 0..6) + adaptCoeff1[ch] = InitializationCoeff1[predictor]; + adaptCoeff2[ch] = InitializationCoeff2[predictor]; + } - for (int ch = 0; ch < channels; ch++) { - // Acquire initial uncompressed signed 16 bit PCM samples for initialization - ByteBuffer Sample1Buf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); - Sample1Buf.put(block_data[inPtr]); - Sample1Buf.put(block_data[inPtr + 1]); - Sample1[ch] = Sample1Buf.getShort(0); - inPtr += 2; - } + for (int ch = 0; ch < channels; ch++) { + initialDelta[ch] = blockData.getShort(); + } - for (int ch = 0; ch < channels; ch++) { - ByteBuffer Sample2Buf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); - Sample2Buf.put(block_data[inPtr]); - Sample2Buf.put(block_data[inPtr + 1]); - Sample2[ch] = Sample2Buf.getShort(0); - inPtr += 2; + // Acquire initial uncompressed signed 16 bit PCM samples for initialization + for (int ch = 0; ch < channels; ch++) { + sample1[ch] = blockData.getShort(); + } - } + for (int ch = 0; ch < channels; ch++) { + sample2[ch] = blockData.getShort(); + } + for (int ch = 0; ch < channels; ch++) { + out.put((short) sample2[ch]); + } - for (int ch = 0; ch < channels; ch++) { - out[outPtr++] = Sample2[ch]; - out[outPtr++] = Sample1[ch]; - } + for (int ch = 0; ch < channels; ch++) { + out.put((short) sample1[ch]); + } - int ch = 0; - - // for (n = (nb_samples - 2) >> (1 - stereo); n > 0; n--) - while (inPtr < inSize) { - // need larger type than byte to avoid signed byte being used - byte currentByte = block_data[inPtr++]; + int ch = 0; - //Logger.getGlobal().info(String.format("0x%02X", currentByte)); + // for (n = (nb_samples - 2) >> (1 - stereo); n > 0; n--) + while (blockData.hasRemaining()) { + int currentByte = Byte.toUnsignedInt(blockData.get()); - - out[outPtr++] = expandNibble((currentByte & 0xFF) >> 4, ch); - ch = (ch + 1) % channels; + out.put(expandNibble((currentByte & 0xFF) >> 4, ch)); + ch = (ch + 1) % channels; - out[outPtr++] = expandNibble((currentByte & 0xFF) & 0xf, ch); - ch = (ch + 1) % channels; + out.put(expandNibble((currentByte & 0xFF) & 0xf, ch)); + ch = (ch + 1) % channels; + } } + + //Logger.getGlobal().info("===== BLOCK FINISH ====="); } private short expandNibble(int nibble, int channel) { - int signed = 0; + int signed; if (nibble >= 8) { signed = nibble - 16; } else { @@ -211,33 +209,20 @@ private short expandNibble(int nibble, int channel) { } - short predictor = 0; - //Logger.getGlobal().info("Preditor reassign, channels: " + channel); - try { - //Logger.getGlobal().info("Sample1 + Sample2 " + Arrays.toString(Sample1) + " " + Arrays.toString(Sample2)); - //Logger.getGlobal().info("AdaptCoeff1 + AdaptCoeff2 " + Arrays.toString(AdaptCoeff1) + " " + Arrays.toString(AdaptCoeff2)); - //Logger.getGlobal().info("IntialDelta " + Arrays.toString(InitialDelta)); - int result = (Sample1[channel] * AdaptCoeff1[channel]) + (Sample2[channel] * AdaptCoeff2[channel]); - predictor = clamp((result >> 8) + (signed * InitialDelta[channel])); - - Sample2[channel] = Sample1[channel]; - Sample1[channel] = predictor; - } catch (Exception ex) { - Logger.getGlobal().warning("caught: " + ex); - throw ex; - } + short predictor; + int result = (sample1[channel] * adaptCoeff1[channel]) + (sample2[channel] * adaptCoeff2[channel]); + predictor = clamp((result >> 6) + (signed * initialDelta[channel])); - try { - //Logger.getGlobal().info("idelta reassign"); - //Logger.getGlobal().info("signed " + signed); - InitialDelta[channel] = (short) Math.floor(AdaptionTable[nibble] * InitialDelta[channel] / 256); - if (InitialDelta[channel] < 16) { - InitialDelta[channel] = 16; - } + sample2[channel] = sample1[channel]; + sample1[channel] = predictor; - } catch (Exception ex) { - Logger.getGlobal().warning("caught: " + ex); - throw ex; + initialDelta[channel] = (AdaptionTable[nibble] * initialDelta[channel]) >> 8; + if (initialDelta[channel] < 16) { + initialDelta[channel] = 16; + } + if (initialDelta[channel] > Integer.MAX_VALUE/768){ + Logger.getGlobal().warning("idelta overflow"); + initialDelta[channel] = Integer.MAX_VALUE/768; } return predictor; } diff --git a/core/src/bms/player/beatoraja/audio/PCM.java b/core/src/bms/player/beatoraja/audio/PCM.java index 2dc75967f..9228f63ce 100644 --- a/core/src/bms/player/beatoraja/audio/PCM.java +++ b/core/src/bms/player/beatoraja/audio/PCM.java @@ -1,14 +1,12 @@ package bms.player.beatoraja.audio; import java.io.*; -import java.nio.ShortBuffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.util.logging.Logger; -import org.apache.commons.compress.utils.IOUtils; import org.jflac.FLACDecoder; import org.jflac.metadata.StreamInfo; @@ -199,36 +197,27 @@ public void loadPCM(Path p) throws IOException { break; } - /*case 2: + case 2: { channels = input.channels; sampleRate = input.sampleRate; - //bitsPerSample = input.bitsPerSample; bitsPerSample = 16; blockAlign = input.blockAlign; - Logger.getGlobal().info("channels: " + channels); - Logger.getGlobal().info("sample rate: " + sampleRate); - Logger.getGlobal().info("block align" + blockAlign); - +// Logger.getGlobal().info("channels: " + channels); +// Logger.getGlobal().info("sample rate: " + sampleRate); +// Logger.getGlobal().info("block align" + blockAlign); OptimizedByteArrayOutputStream inputByteStream = new OptimizedByteArrayOutputStream(input.dataRemaining); StreamUtils.copyStream(input, inputByteStream); ByteBuffer inputByteBuffer = ByteBuffer.wrap(inputByteStream.getBuffer()).order(ByteOrder.LITTLE_ENDIAN); - //inputByteBuffer.flip(); - Logger.getGlobal().info("buffer limit" + inputByteBuffer.limit()); - Logger.getGlobal().info("buffer hasRemain" + inputByteBuffer.hasRemaining()); - Logger.getGlobal().info("buffer remain" + inputByteBuffer.remaining()); - ByteArrayOutputStream result = new ByteArrayOutputStream(channels * 2 * inputByteBuffer.limit()); MSADPCMDecoder decoder = new MSADPCMDecoder(channels, sampleRate, blockAlign); - decoder.decode(inputByteBuffer, result); + pcm = decoder.decode(inputByteBuffer); - pcm = ByteBuffer.wrap(result.toByteArray()); - //pcm.flip(); Logger.getGlobal().info("Filename: " + p ); break; - }*/ + } // IMA-ADPCM Decoder /* case 11: { diff --git a/core/src/bms/player/beatoraja/song/SQLiteSongDatabaseAccessor.java b/core/src/bms/player/beatoraja/song/SQLiteSongDatabaseAccessor.java index 4c0cb516e..77ad389ca 100644 --- a/core/src/bms/player/beatoraja/song/SQLiteSongDatabaseAccessor.java +++ b/core/src/bms/player/beatoraja/song/SQLiteSongDatabaseAccessor.java @@ -43,7 +43,7 @@ public class SQLiteSongDatabaseAccessor extends SQLiteDatabaseAccessor implement private final QueryRunner qr; - private List plugins = new ArrayList(); + private List plugins = new ArrayList<>(); public SQLiteSongDatabaseAccessor(String filepath, String[] bmsroot) throws ClassNotFoundException { super(new Table("folder", @@ -95,6 +95,10 @@ public SQLiteSongDatabaseAccessor(String filepath, String[] bmsroot) throws Clas conf.setSharedCache(true); conf.setSynchronous(SynchronousMode.OFF); // conf.setJournalMode(JournalMode.MEMORY); + + // Using WAL is a good chunk faster than the default of DELETE. Small optimization but we're doing a lot + // of SQL queries in a loop and in paralel, which is exatly our use case here. + conf.setJournalMode(SQLiteConfig.JournalMode.WAL); ds = new SQLiteDataSource(conf); ds.setUrl("jdbc:sqlite:" + filepath); qr = new QueryRunner(ds); @@ -175,9 +179,9 @@ public SongData[] getSongDatas(String[] hashes) { md5str.append('\'').append(hash).append('\''); } } - List m = qr.query("SELECT * FROM song WHERE md5 IN (" + md5str.toString() + ") OR sha256 IN (" - + sha256str.toString() + ")", songhandler); - + List m = qr.query("SELECT * FROM song WHERE md5 IN (" + md5str + ") OR sha256 IN (" + + sha256str + ")", songhandler); + // 検索並び順保持 List sorted = m.stream().sorted((a, b) -> { int aIndexSha256 = -1,aIndexMd5 = -1,bIndexSha256 = -1,bIndexMd5 = -1; @@ -192,8 +196,7 @@ public SongData[] getSongDatas(String[] hashes) { return bIndex - aIndex; }).collect(Collectors.toList()); - SongData[] validated = Validatable.removeInvalidElements(sorted).toArray(new SongData[m.size()]); - return validated; + return Validatable.removeInvalidElements(sorted).toArray(new SongData[m.size()]); } catch (Exception e) { e.printStackTrace(); Logger.getGlobal().severe("song.db更新時の例外:" + e.getMessage()); @@ -225,11 +228,11 @@ public SongData[] getSongDatas(String sql, String score, String scorelog, String ResultSet rs = stmt.executeQuery(s); m = songhandler.handle(rs); } - stmt.execute("DETACH DATABASE scorelogdb"); + stmt.execute("DETACH DATABASE scorelogdb"); stmt.execute("DETACH DATABASE scoredb"); return Validatable.removeInvalidElements(m).toArray(new SongData[m.size()]); } catch(Throwable e) { - e.printStackTrace(); + e.printStackTrace(); } return SongData.EMPTY; @@ -300,7 +303,7 @@ public void updateSongDatas(String path, String[] bmsroot, boolean updateAll, So return; } SongDatabaseUpdater updater = new SongDatabaseUpdater(updateAll, bmsroot, info); - updater.updateSongDatas(path == null ? Stream.of(bmsroot).map(p -> Paths.get(p)) : Stream.of(Paths.get(path))); + updater.updateSongDatas(path == null ? Stream.of(bmsroot).map(Paths::get) : Stream.of(Paths.get(path))); } /** @@ -339,7 +342,7 @@ public void updateSongDatas(Stream paths) { conn.setAutoCommit(false); // 楽曲のタグ,FAVORITEの保持 for (SongData record : qr.query(conn, "SELECT sha256, tag, favorite FROM song", songhandler)) { - if (record.getTag().length() > 0) { + if (!record.getTag().isEmpty()) { property.tags.put(record.getSha256(), record.getTag()); } if (record.getFavorite() > 0) { @@ -363,8 +366,8 @@ public void updateSongDatas(Stream paths) { qr.update(conn, "DELETE FROM folder WHERE path NOT LIKE 'LR2files%' AND path NOT LIKE '%.lr2folder' AND " - + dsql.toString(), param); - qr.update(conn, "DELETE FROM song WHERE " + dsql.toString(), param); + + dsql, param); + qr.update(conn, "DELETE FROM song WHERE " + dsql, param); } paths.parallel().forEach((p) -> { @@ -440,7 +443,7 @@ private void processDirectory(SongDatabaseUpdaterProperty property) e.printStackTrace(); } - final boolean containsBMS = bmsfiles.size() > 0; + final boolean containsBMS = !bmsfiles.isEmpty(); property.count.addAndGet(this.processBMSFolder(records, property)); final int len = folders.size(); @@ -450,9 +453,7 @@ private void processDirectory(SongDatabaseUpdaterProperty property) for (int i = 0; i < len;i++) { final FolderData record = folders.get(i); if (record != null && record.getPath().equals(s)) { -// long t = System.nanoTime(); folders.set(i, null); -// System.out.println(System.nanoTime() - t); try { if (record.getDate() == Files.getLastModifiedTime(bf.path).toMillis() / 1000) { bf.updateFolder = false; @@ -520,7 +521,7 @@ private int processBMSFolder(List records, SongDatabaseUpdaterProperty } boolean update = true; final String pathname = (path.startsWith(root) ? root.relativize(path).toString() : path.toString()); - for (int i = 0;i < len;i++) { + for (int i = 0; i < len; i++) { final SongData record = records.get(i); if (record != null && record.getPath().equals(pathname)) { records.set(i, null); @@ -585,7 +586,7 @@ private int processBMSFolder(List records, SongDatabaseUpdaterProperty } } } - if((sd.getPreview() == null || sd.getPreview().length() == 0) && previewpath != null) { + if((sd.getPreview() == null || sd.getPreview().isEmpty()) && previewpath != null) { sd.setPreview(previewpath); } final String tag = property.tags.get(sd.getSha256()); @@ -600,7 +601,7 @@ private int processBMSFolder(List records, SongDatabaseUpdaterProperty sd.setFolder(SongUtils.crc32(path.getParent().toString(), bmsroot, root.toString())); sd.setParent(SongUtils.crc32(path.getParent().getParent().toString(), bmsroot, root.toString())); sd.setDate((int) lastModifiedTime); - sd.setFavorite(favorite != null ? favorite.intValue() : 0); + sd.setFavorite(favorite != null ? favorite : 0); sd.setAdddate((int) property.updatetime); try { SQLiteSongDatabaseAccessor.this.insert(qr, property.conn, "song", sd); @@ -610,7 +611,7 @@ private int processBMSFolder(List records, SongDatabaseUpdaterProperty if(property.info != null) { property.info.update(model); } - count++; + ++count; } else { try { qr.update(property.conn, "DELETE FROM song WHERE path = ?", pathname); diff --git a/justfile b/justfile index 81e89ddfe..03fa35485 100644 --- a/justfile +++ b/justfile @@ -14,3 +14,4 @@ nuke: rm -rf ~/.gradle/caches/ rm -rf ./core/build/ gradle clean +