Browse Source

lambda functions and things

main
James Fenn 2 months ago
parent
commit
06304e8921
10 changed files with 153 additions and 96 deletions
  1. +0
    -55
      audio-automation/src/me/jfenn/audio/Main.kt
  2. +17
    -28
      audio-automation/src/me/jfenn/audio/model/Note.kt
  3. +5
    -5
      audio-automation/src/me/jfenn/audio/utils/MidiNumber.kt
  4. +26
    -0
      audio-automation/src/me/jfenn/audio/utils/StateLambda.kt
  5. +1
    -1
      build.gradle
  6. +4
    -1
      example/build.gradle
  7. +0
    -6
      example/src/Application.kt
  8. +10
    -0
      example/src/Main.kt
  9. +64
    -0
      example/src/RandomPitchStrings.kt
  10. +26
    -0
      example/src/SequenceBuilder.kt

+ 0
- 55
audio-automation/src/me/jfenn/audio/Main.kt View File

@@ -1,55 +0,0 @@
package me.jfenn.audio

import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.runBlocking
import me.jfenn.audio.constants.*
import me.jfenn.audio.model.Note
import me.jfenn.audio.model.toNote
import me.jfenn.audio.model.withVelocity
import me.jfenn.audio.utils.midi

fun main() = runBlocking<Unit> {
AudioManager(scope = this).start {
val metro = metronome(500)

val pitch = metro.every(8).transform {
emit(arrayOf(
midi("3C#"),
midi("3D#"),
midi("3E")
).random())
}.shared()

// strings
pitch.transform { number ->
emit(number)
emit(midi("4B"))
emit(midi("4G#"))
emit(number + 12)
}.toNote(STRING_ENSEMBLE_2, 3000).withVelocity(70).done()

// chord
pitch.transform { number ->
emit(Note(HARPSICHORD, number + 12, 250).delay(150))
emit(Note(HARPSICHORD, number + 24, 250).delay(200))
emit(Note(HARPSICHORD, number + 36, 250).delay(250))
}.withVelocity(60).done()

// melody
pitch.transform { number ->
emit(Note(ACOUSTIC_GUITAR_NYLON, number, 250).delay(1000))
emit(Note(ACOUSTIC_GUITAR_NYLON, "4B", 250).delay(2000))
emit(Note(ACOUSTIC_GUITAR_NYLON, "3G#", 250).delay(3000))
}.withVelocity(100).done()

// bass
metro.every(4).transform {
emit(Note(MUSIC_BOX, 28, 1500, 100))
}.withVelocity(100).done()

// tempo beat / percussion
metro.every(2, offset = 1).transform {
emit(Note(SFX_GUNSHOT, 44, 100))
}.done()
}
}

+ 17
- 28
audio-automation/src/me/jfenn/audio/model/Note.kt View File

@@ -2,17 +2,17 @@ package me.jfenn.audio.model

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.transform
import me.jfenn.audio.utils.midi
import me.jfenn.audio.utils.note
import me.jfenn.audio.utils.octave
import me.jfenn.audio.utils.midiNote
import me.jfenn.audio.utils.midiOctave
import me.jfenn.audio.utils.toMidi
import java.lang.System.currentTimeMillis

data class Note(
val instrument: Int,
val number: Int,
val duration: Long,
val velocity: Int = 100,
val time: Long = currentTimeMillis()
var instrument: Int,
var number: Int,
var duration: Long,
var velocity: Int = 100,
var time: Long = currentTimeMillis()
) {

constructor(
@@ -20,10 +20,10 @@ data class Note(
note: String,
duration: Long,
velocity: Int = 100
) : this(instrument, midi(note), duration, velocity)
) : this(instrument, note.toMidi(), duration, velocity)

override fun toString(): String {
return "${instrument}: \t${octave(number)}${note(number)}\t ${duration}ms\t ${velocity}vol"
return "${instrument}: \t${midiOctave(number)}${midiNote(number)}\t ${duration}ms\t ${velocity}vol"
}

fun delay(delay: Long) : Note {
@@ -31,26 +31,15 @@ data class Note(
}
}

fun Flow<Int>.toNote(instrument: Int = 0, duration: Long = 250) : Flow<Note> = transform { number ->
emit(Note(instrument, number, duration))
fun Flow<Int>.toNote(block: Note.() -> Unit) : Flow<Note> = transform { midiNumber ->
emit(Note(0, midiNumber, 250).apply(block))
}

fun Flow<Note>.withInstrument(value: Int = 0, lambda: (Note) -> Int = { value }) = transform { note ->
emit(note.copy(instrument = lambda(note)))
@JvmName("toNoteString")
fun Flow<String>.toNote(block: Note.() -> Unit) : Flow<Note> = transform { midiString ->
emit(Note(0, midiString, 250).apply(block))
}

fun Flow<Note>.withNumber(value: Int = 0, lambda: (Note) -> Int = { value }) = transform { note ->
emit(note.copy(number = lambda(note)))
}

fun Flow<Note>.withDuration(value: Long = 0, lambda: (Note) -> Long = { value }) = transform { note ->
emit(note.copy(duration = lambda(note)))
}

fun Flow<Note>.withVelocity(value: Int = 0, lambda: (Note) -> Int = { value }) = transform { note ->
emit(note.copy(velocity = lambda(note)))
}

fun Flow<Note>.withDelay(value: Long = 0, lambda: (Note) -> Long = { value }) = transform { note ->
emit(note.copy(time = note.time + lambda(note)))
fun Flow<Note>.applyNote(block: Note.() -> Unit) = transform { note ->
emit(note.apply(block))
}

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

@@ -2,18 +2,18 @@ package me.jfenn.audio.utils

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

fun note(midi: Int) : String {
fun midiNote(midi: Int) : String {
return MIDI_NOTES[(midi + 3) % 12]
}

fun octave(midi: Int) : Int {
fun midiOctave(midi: Int) : Int {
return (midi - 9) / 12;
}

fun midi(note: String) : Int {
val regex = Regex("[\\d\\-]+").find(note)
fun String.toMidi() : Int {
val regex = Regex("[\\d\\-]+").find(this)
val octave = regex?.value?.toInt() ?: 0
val index = MIDI_NOTES.indexOf(note.substring(regex?.value?.length ?: 0))
val index = MIDI_NOTES.indexOf(substring(regex?.value?.length ?: 0))

assert(index >= 0)
return (index + 9) + (octave * 12)

+ 26
- 0
audio-automation/src/me/jfenn/audio/utils/StateLambda.kt View File

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

fun <T, A, R> withCounter(lambda: suspend T.(A, Int) -> R) : suspend T.(A) -> R {
var count = 0
return { arg ->
lambda(arg, count++)
}
}

fun <T, A, R, V> withCycle(vararg choices: V, lambda: suspend T.(A, V) -> R) : suspend T.(A) -> R {
return withCounter { arg, count ->
lambda(arg, choices[count % choices.size])
}
}

fun <T, A, R, V> withRandom(vararg choices: V, isUnique: Boolean = false, lambda: suspend T.(A, V) -> R) : suspend T.(A) -> R {
var lastChoice: V? = null
return { arg ->
var choice: V? = null
while (!isUnique || choice?.equals(lastChoice) != false)
choice = listOf(*choices).random()

lastChoice = choice
lambda(arg, choice)
}
}

+ 1
- 1
build.gradle View File

@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.4.0'
ext.kotlin_version = '1.4.21'
ext.coroutines_version = '1.4.2'
ext.serialization_version = '0.20.0'



+ 4
- 1
example/build.gradle View File

@@ -1,9 +1,11 @@
apply plugin: 'kotlin'
apply plugin: 'application'

group 'me.jfenn'
group 'me.jfenn.exampleaudio'
version '0.0.1'

mainClassName = "me.jfenn.exampleaudio.MainKt"

sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
@@ -13,6 +15,7 @@ sourceSets {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

implementation project(':audio-automation')
}

+ 0
- 6
example/src/Application.kt View File

@@ -1,6 +0,0 @@
package me.jfenn

fun main(args: Array<String>) : Unit {
println("Hello world!")
}


+ 10
- 0
example/src/Main.kt View File

@@ -0,0 +1,10 @@
package me.jfenn.exampleaudio

import java.util.*

fun main(args: Array<String>) : Unit {
when (args.getOrNull(0) ?: "RandomPitchStrings") {
"RandomPitchStrings" -> RandomPitchStrings.main(args)
}
}


+ 64
- 0
example/src/RandomPitchStrings.kt View File

@@ -0,0 +1,64 @@
package me.jfenn.exampleaudio

import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.runBlocking
import me.jfenn.audio.AudioManager
import me.jfenn.audio.constants.*
import me.jfenn.audio.model.Note
import me.jfenn.audio.model.applyNote
import me.jfenn.audio.model.toNote
import me.jfenn.audio.utils.toMidi
import me.jfenn.audio.utils.withRandom

object RandomPitchStrings {
@JvmStatic
fun main(args: Array<String>) = runBlocking<Unit> {
AudioManager(scope = this).start {
val metro = metronome(500)

val pitch = metro.every(8).transform<Int, Int>(
withRandom("2G#", "3B", "3C#", "3D#", "3E", isUnique = true) { _, arg ->
emit(arg.toMidi())
}
).shared()

// strings
pitch.transform { number ->
emit(number)
emit("4B".toMidi())
emit("4G#".toMidi())
emit(number + 12)
}.toNote {
instrument = STRING_ENSEMBLE_2
duration = 3000
velocity = 70
}.done()

// chord
pitch.transform { number ->
emit(Note(HARPSICHORD, number + 12, 250).delay(150))
emit(Note(HARPSICHORD, number + 24, 250).delay(200))
emit(Note(HARPSICHORD, number + 36, 250).delay(250))
}.applyNote {
velocity = 60
}.done()

// melody
pitch.transform { number ->
emit(Note(ACOUSTIC_GUITAR_NYLON, number, 250).delay(1000))
emit(Note(ACOUSTIC_GUITAR_NYLON, "4B", 250).delay(2000))
emit(Note(ACOUSTIC_GUITAR_NYLON, "3G#", 250).delay(3000))
}.done()

// bass
metro.every(4).transform {
emit(Note(MUSIC_BOX, 28, 1500, 100))
}.done()

// tempo beat / percussion
metro.every(2, offset = 1).transform {
emit(Note(SFX_GUNSHOT, 44, 100))
}.done()
}
}
}

+ 26
- 0
example/src/SequenceBuilder.kt View File

@@ -0,0 +1,26 @@
package me.jfenn.exampleaudio

import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.runBlocking
import me.jfenn.audio.AudioManager
import me.jfenn.audio.constants.HARPSICHORD
import me.jfenn.audio.model.Note

object SequenceBuilder {

@JvmStatic
fun main(args: Array<String>) = runBlocking<Unit> {
AudioManager(scope = this).start {
val metro = metronome(1500)

val sequence = listOf("2G#", "3B", "3C#", "3D#", "3E", "3G#", "4B", "4C#", "4D#", "4E", "4G#", "5B")

metro.transform { count ->
for (i in (0..(count % sequence.size))) {
emit(Note(HARPSICHORD, sequence[i], 250).delay(i * 250L))
}
}.done()
}
}

}

Loading…
Cancel
Save