diff --git a/api/src/main/kotlin/io/smnp/data/entity/Note.kt b/api/src/main/kotlin/io/smnp/data/entity/Note.kt index 0b94e52..a913684 100644 --- a/api/src/main/kotlin/io/smnp/data/entity/Note.kt +++ b/api/src/main/kotlin/io/smnp/data/entity/Note.kt @@ -1,26 +1,38 @@ package io.smnp.data.entity import io.smnp.data.enumeration.Pitch +import io.smnp.math.Fraction -data class Note(val pitch: Pitch, val octave: Int, val duration: Int, val dot: Boolean) { - data class Builder( - var pitch: Pitch = Pitch.A, - var octave: Int = 4, - var duration: Int = 4, - var dot: Boolean = false - ) { - fun pitch(pitch: Pitch) = apply { this.pitch = pitch } - fun octave(octave: Int) = apply { this.octave = octave } - fun duration(duration: Int) = apply { this.duration = duration } - fun dot(dot: Boolean) = apply { this.dot = dot } - fun build() = Note(pitch, octave, duration, dot) - } +class Note(val pitch: Pitch, val octave: Int, duration: Fraction, dot: Boolean) { + val duration = if(dot) duration * Fraction(3, 2) else duration + + operator fun plus(duration2: Fraction) = Note(pitch, octave, duration + duration2, false) override fun toString(): String { - return "${pitch}${octave}:${duration}${if (dot) "d" else ""}" + return "${pitch}${octave}:(${duration})" } fun intPitch(): Int { return octave * 12 + pitch.ordinal } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Note + + if (pitch != other.pitch) return false + if (octave != other.octave) return false + if (duration != other.duration) return false + + return true + } + + override fun hashCode(): Int { + var result = pitch.hashCode() + result = 31 * result + octave + result = 31 * result + duration.hashCode() + return result + } } \ No newline at end of file diff --git a/api/src/main/kotlin/io/smnp/math/Fraction.kt b/api/src/main/kotlin/io/smnp/math/Fraction.kt new file mode 100644 index 0000000..6987d13 --- /dev/null +++ b/api/src/main/kotlin/io/smnp/math/Fraction.kt @@ -0,0 +1,65 @@ +package io.smnp.math + +class Fraction(val numerator: Int, val denominator: Int) : Comparable { + init { + if (denominator == 0) { + throw RuntimeException("Invalid fraction: denominator can not be of 0") + } + } + + operator fun component1() = numerator + operator fun component2() = denominator + + val decimal by lazy { numerator.toDouble() / denominator } + val inversed by lazy { Fraction(denominator, numerator) } + + val simplified by lazy { + val gcd = greatestCommonDenominator(numerator, denominator) + Fraction(numerator / gcd, denominator / gcd) + } + + private fun greatestCommonDenominator(a: Int, b: Int): Int = if (b == 0) a else greatestCommonDenominator(b, a % b) + + operator fun unaryMinus() = Fraction(-this.numerator, this.denominator) + + operator fun plus(second: Fraction) = + if (this.denominator == second.denominator) Fraction(this.numerator + second.numerator, denominator) + else { + val numerator = numerator * second.denominator + second.numerator * denominator + val denominator = denominator * second.denominator + Fraction(numerator, denominator) + } + + operator fun minus(second: Fraction) = (this + (-second)) + + operator fun times(num: Int) = Fraction(numerator * num, denominator) + + operator fun times(second: Fraction) = + Fraction(numerator * second.numerator, denominator * second.denominator) + + operator fun div(num: Int) = this.inversed * num + + operator fun div(second: Fraction) = this * second.inversed + + override fun compareTo(other: Fraction) = decimal.compareTo(other.decimal) + + override fun toString() = + this.simplified.let { if (it.denominator == 1) it.numerator.toString() else "${it.numerator}/${it.denominator}" } + + override fun equals(other: Any?): Boolean { + if (other !is Fraction) { + return false + } + + val (aNum, aDen) = this.simplified + val (bNum, bDen) = other.simplified + + return aNum == bNum && aDen == bDen + } + + companion object { + fun of(int: Int): Fraction { + return Fraction(int, 1) + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/smnp/type/model/Value.kt b/api/src/main/kotlin/io/smnp/type/model/Value.kt index 61bdd0b..dde361a 100644 --- a/api/src/main/kotlin/io/smnp/type/model/Value.kt +++ b/api/src/main/kotlin/io/smnp/type/model/Value.kt @@ -101,8 +101,7 @@ data class Value(val type: DataType, val value: Any, val properties: Map Value.int(l + r) }, float = { (l, r) -> Value.float(l + r) }) - else if (lhs.type == DataType.STRING) - Value.string(lhs.value as String + rhs.value.toString()) - else if (areLists(lhs, rhs)) - Value.list(lhs.value as List + rhs.value as List) - else throw PositionException( - EnvironmentException( - EvaluationException( - "The ${lhs.typeName} and ${rhs.typeName} are not supported by + operator" - ), - environment - ), plusNode.position - ) + return when { + areNumeric(lhs, rhs) -> unify(lhs, rhs, int = { (l, r) -> Value.int(l + r) }, float = { (l, r) -> Value.float(l + r) }) + lhs.type == STRING -> string(lhs.value as String + rhs.value.toString()) + areLists(lhs, rhs) -> list(lhs.value as List + rhs.value as List) + lhs.type == NOTE && rhs.type == INT -> note(lhs.value as Note + Fraction(1, rhs.value as Int)) + else -> throw PositionException( + EnvironmentException( + EvaluationException( + "The ${lhs.typeName} and ${rhs.typeName} are not supported by + operator" + ), + environment + ), plusNode.position + ) + } } private fun minus(lhs: Value, minusNode: Node, rhs: Value, environment: Environment): Value { @@ -68,5 +73,5 @@ class SumOperatorEvaluator : Evaluator() { } private fun areNumeric(lhs: Value, rhs: Value) = lhs.type.isNumeric() && rhs.type.isNumeric() - private fun areLists(lhs: Value, rhs: Value) = lhs.type == DataType.LIST && rhs.type == DataType.LIST + private fun areLists(lhs: Value, rhs: Value) = lhs.type == LIST && rhs.type == LIST } \ No newline at end of file diff --git a/modules/lang/src/main/kotlin/io/smnp/ext/lang/constructor/NoteConstructor.kt b/modules/lang/src/main/kotlin/io/smnp/ext/lang/constructor/NoteConstructor.kt index 24b3a23..8047a88 100644 --- a/modules/lang/src/main/kotlin/io/smnp/ext/lang/constructor/NoteConstructor.kt +++ b/modules/lang/src/main/kotlin/io/smnp/ext/lang/constructor/NoteConstructor.kt @@ -5,6 +5,7 @@ import io.smnp.callable.function.FunctionDefinitionTool import io.smnp.callable.signature.Signature.Companion.simple import io.smnp.data.entity.Note import io.smnp.data.enumeration.Pitch +import io.smnp.math.Fraction import io.smnp.type.enumeration.DataType.* import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.model.Value @@ -18,7 +19,7 @@ class NoteConstructor : Function("Note") { ofType(BOOL) ) body { _, (pitchString, octave, duration, dot) -> val pitch = Pitch.parse((pitchString.value as String).toLowerCase()) - val note = Note(pitch, octave.value as Int, duration.value as Int, dot.value as Boolean) + val note = Note(pitch, octave.value as Int, Fraction(1, duration.value as Int), dot.value as Boolean) Value.note(note) } } diff --git a/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiHelpFunction.kt b/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiHelpFunction.kt index b2750e4..63e8040 100644 --- a/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiHelpFunction.kt +++ b/modules/midi/src/main/kotlin/io/smnp/ext/function/MidiHelpFunction.kt @@ -7,6 +7,7 @@ import io.smnp.data.entity.Note import io.smnp.data.enumeration.Pitch import io.smnp.error.CustomException import io.smnp.ext.midi.Midi +import io.smnp.math.Fraction import io.smnp.type.enumeration.DataType.* import io.smnp.type.matcher.Matcher.Companion.ofType import io.smnp.type.matcher.Matcher.Companion.optional @@ -33,8 +34,8 @@ class MidiHelpFunction : Function("midiHelp") { ) body { environment, args -> val instrument = args[0].value as Int val bpm = args.getOrNull(1)?.value as Int? ?: 120 - val begin = args.getOrNull(2) ?: Value.note(Note(Pitch.C, 1, 4, false)) - val end = args.getOrNull(3) ?: Value.note(Note(Pitch.H, 9, 4, false)) + val begin = args.getOrNull(2) ?: Value.note(Note(Pitch.C, 1, Fraction(1, 4), false)) + val end = args.getOrNull(3) ?: Value.note(Note(Pitch.H, 9, Fraction(1, 4), false)) val channel = args.getOrNull(4)?.value as Int? ?: 1 if(channel > 16) { diff --git a/modules/midi/src/main/kotlin/io/smnp/ext/midi/DefaultSequenceCompiler.kt b/modules/midi/src/main/kotlin/io/smnp/ext/midi/DefaultSequenceCompiler.kt index 3cf888e..9109896 100644 --- a/modules/midi/src/main/kotlin/io/smnp/ext/midi/DefaultSequenceCompiler.kt +++ b/modules/midi/src/main/kotlin/io/smnp/ext/midi/DefaultSequenceCompiler.kt @@ -13,7 +13,7 @@ class DefaultSequenceCompiler : SequenceCompiler() { track: Track, ppq: Int ): Long { - val noteDuration = ((if (item.dot) 1.5 else 1.0) * 4L * ppq / item.duration).toLong() + val noteDuration = (4L * ppq * item.duration.decimal).toLong() val noteOffTick = noteOnTick + noteDuration track.add(noteOn(item, channel, noteOnTick)) track.add(noteOff(item, channel, noteOffTick)) diff --git a/modules/midi/src/main/kotlin/io/smnp/ext/midi/PpqSequenceCompiler.kt b/modules/midi/src/main/kotlin/io/smnp/ext/midi/PpqSequenceCompiler.kt index 3ff0d8a..2977181 100644 --- a/modules/midi/src/main/kotlin/io/smnp/ext/midi/PpqSequenceCompiler.kt +++ b/modules/midi/src/main/kotlin/io/smnp/ext/midi/PpqSequenceCompiler.kt @@ -13,7 +13,7 @@ class PpqSequenceCompiler : SequenceCompiler() { track: Track, ppq: Int ): Long { - val noteOffTick = noteOnTick + item.duration + val noteOffTick = noteOnTick + item.duration.denominator track.add(noteOn(item, channel, noteOnTick)) track.add(noteOff(item, channel, noteOffTick)) return noteOffTick