Core library for HARMOLOID built in Kotlin Multiplatform.
HARMOLOID is an application for generating simple chorus based on projects of singing voice synthesizers.
The current version 2.x
of the application is built as a web application in Kotlin/JS.
This project is a Kotlin implementation of the core algorithm of HARMOLOID. It can be applied on any MIDI-like formats with external IO modules.
- Tonality analysis (auto: with detection of modulation)
- Tonality analysis (semi-auto: for single tonality)
- Note shift based on tonality and harmonic type
- Parameter configuration
This library targets jvm
, js
, and native
. Basically you can use it in any Kotlin Gradle project or Java Gradle
project.
Kotlin DSL:
repositories {
mavenCentral()
}
dependencies {
implementation("com.sdercolin.harmoloid:harmoloid-core:1.2")
}
Groovy DSL:
repositories {
mavenCentral()
}
dependencies {
implementation "com.sdercolin.harmoloid:harmoloid-core:1.2"
}
You have to implement IO modules to handle data tranformation between the original structure and HARMOLOID structure.
// Extract content from the MIDI-like data
val tracks = Track.build(index, name, notes, timeSignatures)
// Initialize core object
val core = Core(tracks)
You can configure all the parameters used. See Config.kt for details.
val config = Config(
keyShiftForUpperThirdHarmony = Config.keyShiftForUpperThirdHarmonyStandard,
keyShiftForLowerThirdHarmony = Config.keyShiftForLowerThirdHarmonyStandard
)
// Pass config to constructor
val core = Core(tracks, config)
// Or reload config later
core.reloadConfig(config)
Tracks have to be marked with tonality before harmony generation.
val track = core.getTrack(trackIndex)
val bar = track.bars
// Method 1: auto
when (val result = core.setPassagesAuto(trackIndex)) {
is TrackTonalityAnalysisResult.Success -> {
val passageResults = result.passageResults
// Notify results
}
is TrackTonalityAnalysisResult.Failure -> {
// Notify error
}
}
// Method 2: semi-auto, have to construct passages by yourself
val passages = listOf(
Passage(index = 0, bars = bars.subList(0, 20)),
Passage(index = 1, bars = bars.subList(21, bars.size))
)
when (val result = core.setPassagesSemiAuto(trackIndex, passages)) {
is TrackTonalityAnalysisResult.Success -> {
val passageResults = result.passageResults
// Notify results
}
is TrackTonalityAnalysisResult.Failure -> {
// Notify error
}
}
// Method 3: manual, have to construct passages with tonalities by yourself
val passages = listOf(
Passage(index = 0, bars = bars.subList(0, 20), tonality = Tonality.C),
Passage(index = 1, bars = bars.subList(21, bars.size), tonality = Tonality.D)
)
core.savePassages(trackIndex, passages)
// Check if track is setup
if (!core.getTrack(trackIndex).isTonalityMarked) {
// Tonality is not fully analysed or set, check result given by Method 1 or Method 2
// and do Method 3 again
}
// Save harmonic settings
core.saveHarmonicTypes(trackIndex, setOf(HarmonicType.UpperThird, HarmonicType.LowerThird))
// Get note shifts
core.getAllChorusTracks(trackIndex).forEach { (harmony, noteShifts) ->
// Do your output work with the note shifts
// The following code is an example with a pseudo NoteElement model
val shiftedNoteElements = noteElements.mapNotNull { noteElement ->
val keyDelta = noteShifts.find { it.noteIndex == noteElement }?.keyDelta
if (keyDelta == null) {
// delete the note
null
} else {
noteElement.copy(key = noteElement.key + keyDelta)
}
}
}