Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
ShirasawaSama committed Jan 1, 2024
1 parent a8674fa commit 094faf6
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 53 deletions.
4 changes: 2 additions & 2 deletions builtin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
plugins {
java
kotlin("jvm") version "1.9.21"
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":chord-analyzer"))
api(project(":chord-analyzer"))
}
1 change: 1 addition & 0 deletions builtin/chord-analyzer/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ repositories {
}

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.eimsound.daw.builtin.chordanalyzer

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.BufferedWriter
import java.nio.file.Path
import kotlin.io.path.absolutePathString

data class Chords(
val chords: List<String>,
val durations: List<Float>
)

interface ChordAnalyzer : AutoCloseable {
suspend fun analyze(chord: List<String>, starts: List<Int>, durations: List<Int>): Chords
}

class ChordAnalyzerImpl(file: Path) : ChordAnalyzer {
private val mutex = Mutex()
private val input: BufferedReader
private val output: BufferedWriter
private val process = ProcessBuilder(file.absolutePathString(), "chords").run {
redirectError()
start().apply {
input = inputStream.bufferedReader()
output = outputStream.bufferedWriter()
}
}

override suspend fun analyze(chord: List<String>, starts: List<Int>, durations: List<Int>) = withContext(Dispatchers.IO) {
mutex.withLock {
// val gg = StringWriter()
// val output = BufferedWriter(gg)
output.write("chords_detect\n")
output.write(chord.joinToString(","))
output.write("\n")
starts.forEachIndexed { i, it ->
if (i != 0) output.write(",")
output.write(it.toString())
}
output.write("\n")
durations.forEachIndexed { i, it ->
if (i != 0) output.write(",")
output.write(it.toString())
}
output.write("\n")
output.flush()
// println(gg.buffer.toString())
// gg.close()

Chords(
input.readLine().split(","),
input.readLine().split(",").map { it.toFloatOrNull() ?: Float.MAX_VALUE }
)
}
}

override fun close() { process.destroy() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.eimsound.daw.builtin.chordanalyzer

import kotlin.io.path.Path

var chordAnalyzerInitialized = false
private set
val chordAnalyzer: ChordAnalyzer by lazy {
val fallback = if (System.getProperty("os.name").contains("Windows")) "EIMUtils/EIMUtils.exe"
else "EIMUtils/EIMUtils"
ChordAnalyzerImpl(Path(System.getProperty("eim.eimutils.file", fallback))).apply {
chordAnalyzerInitialized = true
}
}
2 changes: 1 addition & 1 deletion builtin/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include(
":chord-analyzer"
)
)
3 changes: 3 additions & 0 deletions daw/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/EIMHost-MacOS.zip
/EIMHost.app
/EIMHost

/EIMUtils-MacOS.zip
/EIMUtils
22 changes: 20 additions & 2 deletions daw/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ fun downloadFileFromGithub(repo: String, destName: String, sourceName: String =
val isArm by lazy { System.getProperty("os.arch") == "aarch64" }

project(":daw") {
val os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem()
task<Copy>("downloadEIMHost") {
val os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem()
if (os.isWindows) {
downloadFileFromGithub("EIMHost", "EIMHost.exe", "EIMHost-x64.exe")
// downloadFileFromGithub("EIMHost", "EIMHost-x86.exe", "EIMHost-x86.exe")
Expand All @@ -99,6 +99,24 @@ project(":daw") {
downloadFileFromGithub("EIMHost", "EIMHost", "EIMHost-Linux")
}
}

task<Copy>("downloadEIMUtils") {
if (os.isWindows) {
if (downloadFileFromGithub("EIMUtils", "EIMUtils-Windows.zip", "EIMUtils-Windows.zip")) {
from(zipTree(File("EIMUtils-Windows.zip"))).into(".")
}
} else if (os.isMacOsX) {
if (downloadFileFromGithub("EIMUtils", "EIMUtils-MacOS.zip",
if (isArm) "EIMUtils-MacOS.zip" else "EIMUtils-MacOS-x86_64.zip")) {
from(zipTree(File("EIMUtils-MacOS.zip"))).into(".")
println(233333)
}
} else {
if (downloadFileFromGithub("EIMUtils", "EIMUtils", "EIMUtils-Linux.zip")) {
from(zipTree(File("EIMUtils-Linux.zip"))).into(".")
}
}
}
}

tasks.withType<Jar> {
Expand Down Expand Up @@ -135,5 +153,5 @@ tasks.withType<JavaExec> {
// Run before build
tasks.withType<GradleBuild> {
dependsOn(":downloadEIMHost")
dependsOn(":downloadTimeStretcher")
dependsOn(":downloadEIMUtils")
}
3 changes: 3 additions & 0 deletions daw/src/jvmMain/kotlin/com/eimsound/daw/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ object Configuration : JsonSerializable {
else "EIMHost"
)
}
var execExt = ""
val x86Host = if (SystemUtils.IS_OS_WINDOWS) {
execExt = ".exe"
nativeHostPath.absolute().parent.resolve(nativeHostPath.name.removeSuffix(".exe") + "-x86.exe")
} else nativeHostPath

Expand All @@ -98,6 +100,7 @@ object Configuration : JsonSerializable {
System.setProperty("eim.dsp.nativeaudioplugins.host", nativeHostPath.absolutePathString())
System.setProperty("eim.dsp.nativeaudioplugins.host.x86", (if (Files.exists(x86Host)) x86Host else nativeHostPath).absolutePathString())
System.setProperty("eim.dsp.nativeaudioplayer.file", nativeHostPath.absolutePathString())
System.setProperty("eim.eimutils.file", Path("EIMUtils/EIMUtils$execExt").absolutePathString())
System.setProperty("eim.tempfiles.prefix", "EchoInMirror")

val libraryExt = if (SystemUtils.IS_OS_WINDOWS) "dll" else if (SystemUtils.IS_OS_MAC) "dylib" else "so"
Expand Down
3 changes: 3 additions & 0 deletions daw/src/jvmMain/kotlin/com/eimsound/daw/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import com.eimsound.daw.api.clips.ClipManager
import com.eimsound.daw.api.EchoInMirror
import com.eimsound.daw.api.controllers.DefaultParameterControllerFactory
import com.eimsound.daw.api.clips.defaultEnvelopeClipFactory
import com.eimsound.daw.builtin.chordanalyzer.chordAnalyzer
import com.eimsound.daw.builtin.chordanalyzer.chordAnalyzerInitialized
import com.eimsound.daw.commons.ExperimentalEIMApi
import com.eimsound.daw.components.app.EIMTray
import com.eimsound.daw.components.controllers.parameterControllerCreateClipHandler
Expand Down Expand Up @@ -65,6 +67,7 @@ fun main() {
}
val windowManager = EchoInMirror.windowManager
Runtime.getRuntime().addShutdownHook(thread(false) {
if (chordAnalyzerInitialized) chordAnalyzer.close()
androidApplication?.close()
EchoInMirror.close()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,39 @@ private fun TrackItem(track: Track, backingTracks: IManualStateValue<WeakHashMap
}

@Composable
internal fun EditorControls(editor: DefaultMidiClipEditor) {
private fun NoteVelocityComponets(editor: DefaultMidiClipEditor) {
Row(verticalAlignment = Alignment.CenterVertically) {
editor.clip.clip.notes.read()
var delta by remember { mutableStateOf(0) }
val cur = editor.currentSelectedNote
val velocity = if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity else
(cur ?: editor.selectedNotes.first()).velocity
val trueValue = velocity + (if (editor.selectedNotes.isEmpty()) 0 else delta)
CustomOutlinedTextField(
trueValue.toString(), { str ->
val v = str.toIntOrNull()?.coerceIn(0, 127) ?: return@CustomOutlinedTextField
if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity = v
else editor.clip.clip.doNoteVelocityAction(editor.selectedNotes.toTypedArray(), v - velocity)
},
Modifier.width(60.dp).padding(end = 10.dp),
label = { Text("力度") },
singleLine = true,
)
Slider(trueValue.toFloat() / 127,
{
if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity = (it * 127).roundToInt()
else delta = (it * 127).roundToInt() - velocity
}, Modifier.weight(1f), onValueChangeFinished = {
if (editor.selectedNotes.isNotEmpty()) editor.clip.clip
.doNoteVelocityAction(editor.selectedNotes.toTypedArray(), delta)
delta = 0
}
)
}
}

@Composable
private fun TrackSelector(editor: DefaultMidiClipEditor) {
val track = EchoInMirror.selectedTrack
FloatingLayer({ size, _ ->
Surface(Modifier.width(IntrinsicSize.Max).widthIn(min = size.width), shape = MaterialTheme.shapes.extraSmall,
Expand Down Expand Up @@ -99,54 +131,34 @@ internal fun EditorControls(editor: DefaultMidiClipEditor) {
}
}
}
}

@Composable
internal fun EditorControls(editor: DefaultMidiClipEditor) {
TrackSelector(editor)
Column(Modifier.padding(10.dp)) {
NoteWidthSlider(editor.noteWidth)

editor.apply {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("试听音符", Modifier.weight(1f), style = MaterialTheme.typography.labelLarge)
Checkbox(DefaultMidiClipEditor.playOnEdit, { DefaultMidiClipEditor.playOnEdit = !DefaultMidiClipEditor.playOnEdit })
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("试听音符", Modifier.weight(1f), style = MaterialTheme.typography.labelLarge)
Checkbox(DefaultMidiClipEditor.playOnEdit, { DefaultMidiClipEditor.playOnEdit = !DefaultMidiClipEditor.playOnEdit })
}

Row(verticalAlignment = Alignment.CenterVertically) {
editor.clip.clip.notes.read()
var delta by remember { mutableStateOf(0) }
val cur = currentSelectedNote
val velocity = if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity else
(cur ?: editor.selectedNotes.first()).velocity
val trueValue = velocity + (if (editor.selectedNotes.isEmpty()) 0 else delta)
CustomOutlinedTextField(
trueValue.toString(), { str ->
val v = str.toIntOrNull()?.coerceIn(0, 127) ?: return@CustomOutlinedTextField
if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity = v
else editor.clip.clip.doNoteVelocityAction(editor.selectedNotes.toTypedArray(), v - velocity)
},
Modifier.width(60.dp).padding(end = 10.dp),
label = { Text("力度") },
singleLine = true,
)
Slider(trueValue.toFloat() / 127,
{
if (editor.selectedNotes.isEmpty()) DefaultMidiClipEditor.defaultVelocity = (it * 127).roundToInt()
else delta = (it * 127).roundToInt() - velocity
}, Modifier.weight(1f), onValueChangeFinished = {
if (editor.selectedNotes.isNotEmpty()) editor.clip.clip
.doNoteVelocityAction(editor.selectedNotes.toTypedArray(), delta)
delta = 0
}
)
}
NoteVelocityComponets(editor)

Row(verticalAlignment = Alignment.CenterVertically) {
editor.clip.clip.notes.read()
Text("启用", Modifier.weight(1f), style = MaterialTheme.typography.labelLarge)
val cur = currentSelectedNote ?: editor.selectedNotes.firstOrNull()
val curState = cur?.isDisabled ?: false
Checkbox(!curState, {
if (cur == null) return@Checkbox
editor.clip.clip.doNoteDisabledAction(editor.selectedNotes.toList(), !curState)
})
}
Row(verticalAlignment = Alignment.CenterVertically) {
editor.clip.clip.notes.read()
Text("启用", Modifier.weight(1f), style = MaterialTheme.typography.labelLarge)
val cur = editor.currentSelectedNote ?: editor.selectedNotes.firstOrNull()
val curState = cur?.isDisabled ?: false
Checkbox(!curState, {
if (cur == null) return@Checkbox
editor.clip.clip.doNoteDisabledAction(editor.selectedNotes.toList(), !curState)
})
}

Button({ editor.detectChords() }) {
Text("分析和弦")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.eimsound.daw.api.clips.MidiClipEditor
import com.eimsound.daw.api.clips.TrackClip
import com.eimsound.daw.api.processor.Track
import com.eimsound.daw.api.window.EditorExtension
import com.eimsound.daw.builtin.chordanalyzer.Chords
import com.eimsound.daw.builtin.chordanalyzer.chordAnalyzer
import com.eimsound.daw.commons.IManualStateValue
import com.eimsound.daw.commons.ManualStateValue
import com.eimsound.daw.commons.json.JsonIgnoreDefaults
Expand All @@ -37,9 +39,14 @@ import com.eimsound.daw.utils.*
import com.eimsound.daw.window.panels.playlist.playlistTrackControllerMinWidth
import com.eimsound.daw.window.panels.playlist.Playlist
import com.eimsound.dsp.data.midi.*
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.roundToInt

// Focusable
Expand Down Expand Up @@ -268,4 +275,23 @@ class DefaultMidiClipEditor(override val clip: TrackClip<MidiClip>) : MidiClipEd
selectedNotes.add(newNote)
return newNote
}

var chords by mutableStateOf(Chords(emptyList(), emptyList()))
@OptIn(DelicateCoroutinesApi::class)
fun detectChords() {
val list = ArrayList(clip.clip.notes)
GlobalScope.launch(Dispatchers.IO) {
list.sortBy { it.time }
var pre = 0
chords = chordAnalyzer.analyze(
list.map { getNoteName(it.note) },
list.map { it.duration },
list.map {
val result = it.time - pre
pre = it.time
result
}
)
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ org.gradle.caching=true
org.gradle.configureondemand=true

eim.dependencies.kotlin.logging=5.1.0
eim.dependencies.kotlinx.coroutines=1.7.3
eim.dependencies.kotlinx.coroutines=1.8.0-RC2
eim.dependencies.kotlinx.serialization=1.6.0
eim.dependencies.commons.lang=3.13.0
eim.dependencies.slf4j=2.0.9
Expand Down
4 changes: 1 addition & 3 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,4 @@ include(
":daw"
)

includeBuild(
"builtin"
)
includeBuild("builtin")

0 comments on commit 094faf6

Please sign in to comment.