Browse Source

fake flow apis in TimedFlow class to avoid overloading issues

main
James Fenn 2 months ago
parent
commit
d588b0b48e
9 changed files with 58 additions and 47 deletions
  1. +5
    -6
      audio-automation/src/me/jfenn/audio/AudioManager.kt
  2. +4
    -3
      audio-automation/src/me/jfenn/audio/model/Note.kt
  3. +0
    -24
      audio-automation/src/me/jfenn/audio/model/TimedInfo.kt
  4. +2
    -3
      audio-automation/src/me/jfenn/audio/utils/Metronome.kt
  5. +40
    -0
      audio-automation/src/me/jfenn/audio/utils/TimedFlow.kt
  6. +2
    -4
      audio-automation/src/me/jfenn/audio/utils/extensions/Flow.kt
  7. +1
    -1
      audio-automation/src/me/jfenn/audio/utils/extensions/MidiNumber.kt
  8. +3
    -3
      audio-automation/src/me/jfenn/audio/utils/extensions/TimedFlow.kt
  9. +1
    -3
      example/src/RandomPitchStrings.kt

+ 5
- 6
audio-automation/src/me/jfenn/audio/AudioManager.kt View File

@@ -6,8 +6,7 @@ import kotlinx.coroutines.flow.*
import me.jfenn.audio.constants.OUTPUT_DELAY
import me.jfenn.audio.interfaces.Instrument
import me.jfenn.audio.model.NoteInfo
import me.jfenn.audio.model.TimedFlow
import me.jfenn.audio.utils.Metronome
import me.jfenn.audio.utils.TimedFlow

class AudioManager(
val scope: CoroutineScope = GlobalScope
@@ -32,13 +31,13 @@ class AudioManager(
}
}

fun <T> Flow<T>.shared(scope: CoroutineScope = GlobalScope) : SharedFlow<T> {
return shareIn(scope, SharingStarted.Lazily)
fun <T> TimedFlow<T>.shared(scope: CoroutineScope = GlobalScope) : TimedFlow<T> {
return TimedFlow(onEvent.shareIn(scope, SharingStarted.Lazily))
}

fun <T> TimedFlow<T>.then(scope: CoroutineScope = GlobalScope, block: suspend (T) -> Unit) {
scope.launch {
collect { info ->
onEvent.collect { info ->
delay(info.time - currentTimeMillis())
block(info.value)
}
@@ -47,7 +46,7 @@ fun <T> TimedFlow<T>.then(scope: CoroutineScope = GlobalScope, block: suspend (T

fun <T: NoteInfo> TimedFlow<T>.playOn(instrument: Instrument<T>, scope: CoroutineScope = GlobalScope) {
scope.launch {
collect { info ->
onEvent.collect { info ->
if (info.value.duration > 0) launch {
delay(OUTPUT_DELAY - (currentTimeMillis() - info.time))
instrument.start(info.value)


+ 4
- 3
audio-automation/src/me/jfenn/audio/model/Note.kt View File

@@ -1,8 +1,9 @@
package me.jfenn.audio.model

import me.jfenn.audio.utils.midiNote
import me.jfenn.audio.utils.midiOctave
import me.jfenn.audio.utils.toMidi
import me.jfenn.audio.utils.TimedFlow
import me.jfenn.audio.utils.extensions.midiNote
import me.jfenn.audio.utils.extensions.midiOctave
import me.jfenn.audio.utils.extensions.toMidi

interface NoteInfo {
val number: Int


+ 0
- 24
audio-automation/src/me/jfenn/audio/model/TimedInfo.kt View File

@@ -1,30 +1,6 @@
package me.jfenn.audio.model

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.transform
import kotlin.experimental.ExperimentalTypeInference

data class TimedInfo<T>(
val value: T,
val time: Long = System.currentTimeMillis()
)

typealias TimedFlow<T> = Flow<TimedInfo<T>>

interface TimedInfoCollector<in T> {
suspend fun emit(value: T, delay: Long = 0L)
}

@OptIn(ExperimentalTypeInference::class)
inline fun <T, R> TimedFlow<T>.transform(
@BuilderInference crossinline action: suspend TimedInfoCollector<R>.(value: T) -> Unit
) = transform<TimedInfo<T>, TimedInfo<R>> { arg ->
val collector = this
val timedCollector: TimedInfoCollector<R> = object : TimedInfoCollector<R> {
override suspend fun emit(value: R, delay: Long) {
collector.emit(TimedInfo(value, arg.time + delay))
}
}

timedCollector.action(arg.value)
}

+ 2
- 3
audio-automation/src/me/jfenn/audio/utils/Metronome.kt View File

@@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.shareIn
import me.jfenn.audio.AudioManager
import me.jfenn.audio.model.TimedFlow
import me.jfenn.audio.model.TimedInfo

class Metronome(
@@ -15,14 +14,14 @@ class Metronome(

private var isRunning = true

val onTick: TimedFlow<Int> = flow {
val onTick: TimedFlow<Int> = TimedFlow(flow {
var i = ((System.currentTimeMillis() - manager.startTime) / interval).toInt() + 1
while (isRunning) {
val nextTime = manager.startTime + (interval * i)
delay(nextTime - System.currentTimeMillis())
if (isRunning) emit(TimedInfo(i++, nextTime))
}
}.shareIn(manager.scope, SharingStarted.Lazily)
}.shareIn(manager.scope, SharingStarted.Lazily))

fun stop() {
isRunning = false


+ 40
- 0
audio-automation/src/me/jfenn/audio/utils/TimedFlow.kt View File

@@ -0,0 +1,40 @@
package me.jfenn.audio.utils

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.transform
import me.jfenn.audio.model.TimedInfo
import kotlin.experimental.ExperimentalTypeInference

class TimedFlow<T>(
var onEvent: Flow<TimedInfo<T>>
) {

@OptIn(ExperimentalTypeInference::class)
inline fun <R> transform(
@BuilderInference crossinline action: suspend TimedFlowCollector<R>.(value: T) -> Unit
) = TimedFlow<R>(
onEvent.transform { arg ->
val collector = this
val timedCollector: TimedFlowCollector<R> = object : TimedFlowCollector<R> {
override suspend fun emit(value: R, delay: Long) {
collector.emit(TimedInfo(value, arg.time + delay))
}
}

timedCollector.action(arg.value)
}
)

fun take(count: Int): TimedFlow<T> {
var consumed = 0
return TimedFlow(onEvent.transform { value ->
if (consumed++ < count)
emit(value)
})
}

}

interface TimedFlowCollector<in T> {
suspend fun emit(value: T, delay: Long = 0L)
}

+ 2
- 4
audio-automation/src/me/jfenn/audio/utils/extensions/Flow.kt View File

@@ -3,9 +3,7 @@ package me.jfenn.audio.utils.extensions
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.merge
import me.jfenn.audio.model.TimedFlow
import me.jfenn.audio.model.TimedInfo
import me.jfenn.audio.model.transform
import me.jfenn.audio.utils.TimedFlow

@ExperimentalCoroutinesApi
fun <T> joinFlows(vararg flows: Flow<T>) : Flow<T> = listOf(*flows).merge()
@@ -18,7 +16,7 @@ class JoinFlow<T> {
@ExperimentalCoroutinesApi
fun <T> join(block: JoinFlow<T>.() -> Unit) = JoinFlow<T>().apply(block).list.merge()

fun <T> Flow<TimedInfo<T>>.counter() : Flow<TimedInfo<Int>> {
fun <T> TimedFlow<T>.counter() : TimedFlow<Int> {
var count = 0
return transform { _ ->
emit(count++)


+ 1
- 1
audio-automation/src/me/jfenn/audio/utils/extensions/MidiNumber.kt View File

@@ -1,4 +1,4 @@
package me.jfenn.audio.utils
package me.jfenn.audio.utils.extensions

val MIDI_NOTES = listOf("A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#")



+ 3
- 3
audio-automation/src/me/jfenn/audio/utils/extensions/TimedFlow.kt View File

@@ -1,13 +1,13 @@
package me.jfenn.audio.utils.extensions

import kotlinx.coroutines.flow.transform
import me.jfenn.audio.model.TimedFlow
import me.jfenn.audio.utils.TimedFlow
import kotlin.math.max

fun <T> TimedFlow<T>.spread(delay: Long = 0, lambda: (T) -> Long = { delay }) : TimedFlow<T> {
var lastTime = 0L
return transform { info ->
return TimedFlow(onEvent.transform { info ->
lastTime = max(lastTime + lambda(info.value), info.time)
emit(info.copy(time = lastTime))
}
})
}

+ 1
- 3
example/src/RandomPitchStrings.kt View File

@@ -1,6 +1,5 @@
package me.jfenn.exampleaudio

import kotlinx.coroutines.flow.take
import kotlinx.coroutines.runBlocking
import me.jfenn.audio.AudioManager
import me.jfenn.audio.constants.*
@@ -8,15 +7,14 @@ import me.jfenn.audio.impl.MidiInstrument
import me.jfenn.audio.model.Note
import me.jfenn.audio.model.applyAll
import me.jfenn.audio.model.toNote
import me.jfenn.audio.model.transform
import me.jfenn.audio.shared
import me.jfenn.audio.playOn
import me.jfenn.audio.then
import me.jfenn.audio.utils.extensions.random
import me.jfenn.audio.utils.extensions.spread
import me.jfenn.audio.utils.extensions.toMidi
import me.jfenn.audio.utils.metronome
import me.jfenn.audio.utils.notes.ScaleType
import me.jfenn.audio.utils.toMidi
import javax.swing.JButton
import javax.swing.JFrame



Loading…
Cancel
Save