From eb94c2a81447df375f52b0099c9650231b60b6c8 Mon Sep 17 00:00:00 2001 From: Henrique Lorenzi Date: Wed, 12 Apr 2017 14:29:18 -0300 Subject: [PATCH] try out a new binary representation scheme --- src/song/song.js | 87 +++++++++++++------------- src/test/test.js | 37 ++---------- src/util/binaryio.js | 141 +++++++++++++++++++++++++++++++------------ src/util/rational.js | 2 +- 4 files changed, 153 insertions(+), 114 deletions(-) diff --git a/src/song/song.js b/src/song/song.js index 4b5bf58..6b1eccd 100644 --- a/src/song/song.js +++ b/src/song/song.js @@ -338,54 +338,55 @@ Song.prototype.saveBinary = function() var writer = new BinaryWriter(); - writer.writeInteger(0); // Version. - writer.writeRational(this.length) - writer.writeInteger(this.bpm); + writer.writeInteger(0, false); // Version. + writer.writeRational(this.length, false) + writer.writeInteger(this.bpm, false); - writer.writeInteger(this.notes.items.length); + writer.writeInteger(this.notes.items.length, false); for (var i = 0; i < this.notes.items.length; i++) { var note = this.notes.items[i]; - writer.writeRational(note.startTick); - writer.writeRational(note.endTick); - writer.writeInteger(note.trackIndex); + writer.writeRational(note.startTick, false); + writer.writeRational(note.endTick, false); + writer.writeInteger(note.trackIndex, false); writer.writeInteger(note.midiPitch - 60); // Arbitrary bias to save bytes in the most common cases. } - writer.writeInteger(this.chords.items.length); + writer.writeInteger(this.chords.items.length, false); for (var i = 0; i < this.chords.items.length; i++) { var chord = this.chords.items[i]; - writer.writeRational(chord.startTick); - writer.writeRational(chord.endTick); - writer.writeInteger(chord.chordKindIndex); - writer.writeInteger(chord.rootMidiPitch); - writer.writeInteger(0); // Embelishment count. + writer.writeRational(chord.startTick, false); + writer.writeRational(chord.endTick, false); + writer.writeInteger(chord.chordKindIndex, false); + writer.writeInteger(chord.rootMidiPitch, false); + writer.writeInteger(0, false); // Embelishment count. } - writer.writeInteger(this.keyChanges.items.length); + writer.writeInteger(this.keyChanges.items.length, false); for (var i = 0; i < this.keyChanges.items.length; i++) { var keyCh = this.keyChanges.items[i]; - writer.writeRational(keyCh.tick); - writer.writeInteger(keyCh.scaleIndex); - writer.writeInteger(keyCh.tonicMidiPitch); + writer.writeRational(keyCh.tick, false); + writer.writeInteger(keyCh.scaleIndex, false); + writer.writeInteger(keyCh.tonicMidiPitch, false); } - writer.writeInteger(this.meterChanges.items.length); + writer.writeInteger(this.meterChanges.items.length, false); for (var i = 0; i < this.meterChanges.items.length; i++) { var meterCh = this.meterChanges.items[i]; - writer.writeRational(meterCh.tick); - writer.writeInteger(meterCh.numerator); - writer.writeInteger(meterCh.denominator); + writer.writeRational(meterCh.tick, false); + writer.writeInteger(meterCh.numerator, false); + writer.writeInteger(meterCh.denominator, false); } - var data = writer.data; + var data = writer.finish(); + //data = byteArrayToString(data); data = pako.deflateRaw(data, { to: "string" }); data = window.btoa(data); return data; @@ -403,49 +404,49 @@ Song.prototype.loadBinary = function(base64str) var reader = new BinaryReader(data); - reader.readInteger(); // Version. - reader.readRational(); // Length. - this.bpm = reader.readInteger(); + reader.readInteger(false); // Version. + reader.readRational(false); // Length. + this.bpm = reader.readInteger(false); - var noteNum = reader.readInteger(); + var noteNum = reader.readInteger(false); for (var i = 0; i < noteNum; i++) { this.notes.insert(new SongNote( - reader.readRational(), - reader.readRational(), - reader.readInteger(), + reader.readRational(false), + reader.readRational(false), + reader.readInteger(false), reader.readInteger() + 60)); } - var chordNum = reader.readInteger(); + var chordNum = reader.readInteger(false); for (var i = 0; i < chordNum; i++) { this.chords.insert(new SongChord( - reader.readRational(), - reader.readRational(), - reader.readInteger(), - reader.readInteger(), + reader.readRational(false), + reader.readRational(false), + reader.readInteger(false), + reader.readInteger(false), [])); - reader.readInteger(); // Embelishment count. + reader.readInteger(false); // Embelishment count. } - var keyChNum = reader.readInteger(); + var keyChNum = reader.readInteger(false); for (var i = 0; i < keyChNum; i++) { this.keyChanges.insert(new SongKeyChange( - reader.readRational(), - reader.readInteger(), - reader.readInteger())); + reader.readRational(false), + reader.readInteger(false), + reader.readInteger(false))); } - var meterChNum = reader.readInteger(); + var meterChNum = reader.readInteger(false); for (var i = 0; i < meterChNum; i++) { this.meterChanges.insert(new SongMeterChange( - reader.readRational(), - reader.readInteger(), - reader.readInteger())); + reader.readRational(false), + reader.readInteger(false), + reader.readInteger(false))); } this.setLengthAuto(); diff --git a/src/test/test.js b/src/test/test.js index ef0b639..8342ca8 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -74,42 +74,13 @@ function testIntegerBinaryIO() { var writer = new BinaryWriter(); writer.writeInteger(num); - var reader = new BinaryReader(writer.data); + var reader = new BinaryReader(writer.finish()); var readNum = reader.readInteger(); console.assert(num == readNum, "integer binary io roundtrip (", num, " != ", readNum, ")"); }; - test(0); - - test(1); - test(2); - test(10); - test(0x7f); - test(0x80); - test(0x81); - test(0xff); - test(0x100); - test(0x101); - test(0xfff); - test(0xffff); - test(0xfffff); - test(0xffffff); - test(0xfffffff); - - test(-1); - test(-2); - test(-10); - test(-0x7f); - test(-0x80); - test(-0x81); - test(-0xff); - test(-0x100); - test(-0x101); - test(-0xfff); - test(-0xffff); - test(-0xfffff); - test(-0xffffff); - test(-0xfffffff); + for (var i = -0xfff; i <= 0xfff; i++) + test(i); } @@ -120,7 +91,7 @@ function testRationalBinaryIO() { var writer = new BinaryWriter(); writer.writeRational(rational); - var reader = new BinaryReader(writer.data); + var reader = new BinaryReader(writer.finish()); var readRational = reader.readRational(); console.assert(rational.compare(readRational) == 0, "rational binary io roundtrip (", rational.toString(), " != ", readRational.toString(), ")"); }; diff --git a/src/util/binaryio.js b/src/util/binaryio.js index 4fc0952..82eef6f 100644 --- a/src/util/binaryio.js +++ b/src/util/binaryio.js @@ -11,34 +11,77 @@ function byteArrayToString(array) function BinaryWriter() { + this.currentByte = 0; + this.currentBitNum = 0; this.data = []; } -BinaryWriter.prototype.writeInteger = function(value) +BinaryWriter.prototype.writeBit = function(bit) { - var negative = (value < 0); + this.currentByte = (this.currentByte << 1) | (bit & 0x1); + this.currentBitNum += 1; - if (negative) - value = -value; + if (this.currentBitNum >= 8) + { + this.data.push(this.currentByte); + this.currentByte = 0; + this.currentBitNum = 0; + } +} + + +BinaryWriter.prototype.finish = function() +{ + while (this.currentBitNum > 0) + this.writeBit(0); - this.data.push((negative ? 0x40 : 0) | (value & 0x3f)); - value >>= 6; + return this.data; +} + + +BinaryWriter.prototype.writeInteger = function(value, canBeNegative = true, blockSize = 5) +{ + if (canBeNegative) + { + if (value < 0) + { + this.writeBit(1); + value = -value; + } + else + this.writeBit(0); + } - var spliceAt = this.data.length - 1; - while (value > 0) + while (true) { - this.data.splice(spliceAt, 0, 0x80 | (value & 0x7f)); - value >>= 7; + for (var i = 0; i < blockSize; i++) + { + this.writeBit(value & 0x1); + value >>= 1; + } + + this.writeBit(value > 0 ? 1 : 0); + if (value == 0) + break; } } -BinaryWriter.prototype.writeRational = function(rational) +BinaryWriter.prototype.writeRational = function(rational, canBeNegative = true) { - this.writeInteger(rational.integer); - this.writeInteger(rational.numerator); - this.writeInteger(rational.denominator); + if (rational.numerator == 0) + { + this.writeBit(0); + this.writeInteger(rational.integer, canBeNegative); + } + else + { + this.writeBit(1); + this.writeInteger(rational.integer, canBeNegative); + this.writeInteger(rational.numerator, false); + this.writeInteger(rational.denominator, false); + } } @@ -46,42 +89,66 @@ function BinaryReader(data) { this.data = data; this.index = 0; + this.currentBitNum = 8; } -BinaryReader.prototype.readInteger = function() +BinaryReader.prototype.readBit = function() { + if (this.index >= this.data.length) + return 0; + + this.currentBitNum -= 1; + var bit = (this.data[this.index] >> this.currentBitNum) & 0x1; + + if (this.currentBitNum <= 0) + { + this.index += 1; + this.currentBitNum = 8; + } + + return bit; +} + + +BinaryReader.prototype.readInteger = function(canBeNegative = true, blockSize = 5) +{ + var sign = 1; + if (canBeNegative) + { + if (this.readBit() == 1) + sign = -1; + } + var value = 0; + var index = 0; while (true) { - if (this.index >= this.data.length) - throw "unexpected end of binary stream"; - - var block = this.data[this.index]; - this.index += 1; - - - if ((block & 0x80) == 0) + for (var i = 0; i < blockSize; i++) { - value = (value << 6) | (block & 0x3f); - - if ((block & 0x40) != 0) - value = -value; - - break; + value |= (this.readBit() << index); + index += 1; } - else - value = (value << 7) | (block & 0x7f); + + if (this.readBit() == 0) + break; } - return value; + return value * sign; } -BinaryReader.prototype.readRational = function() +BinaryReader.prototype.readRational = function(canBeNegative = true) { - return new Rational( - this.readInteger(), - this.readInteger(), - this.readInteger()); + if (this.readBit() == 0) + { + return new Rational(this.readInteger(canBeNegative)); + } + else + { + return new Rational( + this.readInteger(canBeNegative), + this.readInteger(false), + this.readInteger(false)); + } } \ No newline at end of file diff --git a/src/util/rational.js b/src/util/rational.js index 194ff5b..c980e3c 100644 --- a/src/util/rational.js +++ b/src/util/rational.js @@ -1,7 +1,7 @@ function Rational(integer = 0, numerator = 0, denominator = 1) { if (denominator <= 0) - throw "Rational denominator must be larger than 0"; + denominator = 1; this.integer = integer; this.numerator = numerator;