Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Looper interface #1

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 69 additions & 27 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,53 +1,95 @@
plugins {
kotlin("multiplatform") version "1.7.0"
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.dokka)
alias(libs.plugins.gradle.versions.plugin)
`maven-publish`
}

group = "com.xemantic.music"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

kotlin {

jvmToolchain(libs.versions.jvmTarget.get().toInt())

jvm {
compilations.all {
kotlinOptions.jvmTarget = "15"
}
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
compilations {
all {
kotlinOptions {
jvmTarget = libs.versions.jvmTarget.get()
}
}
}
}

js(IR) {
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
browser {}
}

val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
@Suppress("UNUSED_VARIABLE")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
hostOs == "Mac OS X" -> macosX64()
hostOs == "Linux" -> linuxX64()
isMingwX64 -> mingwX64()
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}

sourceSets {
val commonMain by getting
val commonTest by getting {

all {
languageSettings {
languageVersion = libs.versions.kotlinLanguageVersion.get()
apiVersion = libs.versions.kotlinLanguageVersion.get()
}
}

commonMain {
dependencies {
implementation(kotlin("test"))
api(libs.kotlin.coroutines)
api(libs.kotlin.datetime)
implementation(libs.kotlin.logging)
}
}
val jvmMain by getting
val jvmTest by getting
val jsMain by getting
val jsTest by getting
val nativeMain by getting
val nativeTest by getting

commonTest {
dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlin.coroutines.test)
implementation(libs.kotest.assertions.core)
}
}

jvmTest {
dependencies {
runtimeOnly(libs.log4j.slf4j2)
runtimeOnly(libs.log4j.core)
runtimeOnly(libs.jackson.databind)
runtimeOnly(libs.jackson.json)
}
}

}

}

tasks {

dependencyUpdates {
gradleReleaseChannel = "current"
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}

}

private val nonStableKeywords = listOf("alpha", "beta", "rc")

fun isNonStable(
version: String
): Boolean = nonStableKeywords.any {
version.lowercase().contains(it)
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
kotlin.code.style=official
kotlin.js.generate.executable.default=false
kotlin.mpp.stability.nowarn=true
group=com.xemantic.music
version=1.0-SNAPSHOT
41 changes: 41 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[versions]
# kotlin
kotlin = "1.9.20"
kotlinLanguageVersion = "1.9"
jvmTarget = "21"
kotlinCoroutines = "1.7.3"
kotlinDatetime = "0.4.1"

# logging
kotlinLogging = "5.1.0"
slf4j = "2.0.7"
log4j = "2.21.1"
jackson = "2.15.3"

# test
kotest = "5.8.0"

# plugins
dokka = "1.9.10"
gradleVersionsPlugin = "0.49.0"

[libraries]
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" }
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinCoroutines" }
kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-annotations-common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }

kotlin-logging = { module = "io.github.oshai:kotlin-logging", version.ref = "kotlinLogging" }
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
log4j-slf4j2 = { group = "org.apache.logging.log4j", name = "log4j-slf4j2-impl", version.ref = "log4j" }
log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" }
jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" }
jackson-json = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" }

kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
gradle-versions-plugin = { id = "com.github.ben-manes.versions", version.ref = "gradleVersionsPlugin" }
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
37 changes: 37 additions & 0 deletions src/commonMain/kotlin/CircleOfFifths.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* xemantic-music - a Kotlin library implementing some theory of music and composition
* Copyright (C) 2023 Kazimierz Pogoda
*
* This file is part of xemantic-music.
*
* xemantic-music is free software: you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* xemantic-music is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with xemantic-music.
* If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.music

private val indexesInTheCircleOfFifths = arrayOf(
0, // C
7, // C#
2, // D
9, // D# / Eb
4, // E
11, // F
6, // Gb / F#
1, // G
8, // Ab
3, // A
10, // Bb
5 // B
)

val Note.indexInTheCircleOfFifths: Int
get() = indexesInTheCircleOfFifths[indexInOctave]
20 changes: 20 additions & 0 deletions src/commonMain/kotlin/WesternMusic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* xemantic-music - a Kotlin library implementing some theory of music and composition
* Copyright (C) 2022 Kazimierz Pogoda
*
* This file is part of xemantic-music.
*
* xemantic-music is free software: you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* xemantic-music is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with xemantic-music.
* If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.music

107 changes: 107 additions & 0 deletions src/commonMain/kotlin/loop/Looper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* xemantic-music - a Kotlin library implementing some theory of music and composition
* Copyright (C) 2022 Kazimierz Pogoda
*
* This file is part of xemantic-music.
*
* xemantic-music is free software: you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* xemantic-music is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with xemantic-music.
* If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.music.loop

/**
* A looper pedal-like API. It's a general interface but focused on MIDI devices.
*/
interface Looper {

/**
* The list of recordings sorted by the start time.
*/
val recordings: List<Recording>

/**
* The list of loops being currently played, sorted by the start time.
*/
val playingLoops: List<Loop>

/**
* Indicates if something is being currently recorded.
*/
val recording: Boolean

/**
* Starts recording under a given `name`.
* The [Recording] will be created only if [stopRecording] is being called afterwards.
* Note: if any loop is being played, the recording will stop immediately at the end
* of the first played loop.
*
* @param name the name of the recording.
* @param onStopRecording an optional parameter to describe what happens when recording is finished.
* @throws IllegalArgumentException if the recording of given name already exists.
* @throws IllegalStateException if the looper is currently recording.
*/
fun startRecording(name: String, onStopRecording: (Recording) -> Unit = {}) // TODO it should automatically

/**
* Stops the recording initiated with [startRecording].
*
* @return the [Recording] instance describing the finished recording.
* @throws IllegalStateException if nothing is being recorded. See [recording] flog.
*/
fun stopRecording(): Recording

/**
* Plays the loop.
*
* @param name the name of the loop.
* @param repetitionCount how many times it should be repeated, `-1` by default which
* implies endless playback (at least until [stopLoop] is called.
* @param onLoopStopped an optional callback called when the loop is stopped. Either
* triggered after reaching `repetitionCount` or when [stopLoop] is being called.
* @return the loop id.
*/
fun playLoop(
name: String,
repetitionCount: Int = -1,
onLoopStopped: (Int) -> Unit = {}
): Int

/**
* Stops the loop of given id.
* Note: it will cause the loop to be removed from [playingLoops].
*
* @param loopId the loop id.
* @throws IllegalArgumentException if the loop does not exists.
*/
fun stopLoop(loopId: Int)

/**
* Removes recording of given `name`.
*
* @param name the name of the recording to remove.
* @throws IllegalArgumentException if the recording of given name does not exists.
*/
fun removeRecording(name: String)

}

data class Recording(
val name: String,
val start: Long,
val stop: Long
)

data class Loop(
val id: Int,
val start: Long,
val recording: Recording
)
42 changes: 42 additions & 0 deletions src/commonTest/kotlin/CircleOfFifthsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* xemantic-music - a Kotlin library implementing some theory of music and composition
* Copyright (C) 2023 Kazimierz Pogoda
*
* This file is part of xemantic-music.
*
* xemantic-music is free software: you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* xemantic-music is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with xemantic-music.
* If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.music

import io.kotest.matchers.shouldBe
import kotlin.test.Test

class CircleOfFifthsTest {

@Test
fun shouldReturnProperIndexInTheCircleOfFifths() {
Note.C.indexInTheCircleOfFifths shouldBe 0
Note.Cs.indexInTheCircleOfFifths shouldBe 7
Note.D.indexInTheCircleOfFifths shouldBe 2
Note.Ds.indexInTheCircleOfFifths shouldBe 9
Note.E.indexInTheCircleOfFifths shouldBe 4
Note.F.indexInTheCircleOfFifths shouldBe 11
Note.Fs.indexInTheCircleOfFifths shouldBe 6
Note.G.indexInTheCircleOfFifths shouldBe 1
Note.Gs.indexInTheCircleOfFifths shouldBe 8
Note.A.indexInTheCircleOfFifths shouldBe 3
Note.As.indexInTheCircleOfFifths shouldBe 10
Note.B.indexInTheCircleOfFifths shouldBe 5
}

}
Loading
Loading