Generate sound

This commit is contained in:
2022-12-22 20:10:43 +01:00
parent a05807667f
commit 5895300ca7
8 changed files with 249 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput.Target.VAR import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput.Target.VAR
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.LEGACY import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.LEGACY
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.IR import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.IR
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("multiplatform") version "1.7.20" kotlin("multiplatform") version "1.7.20"
@@ -73,4 +74,4 @@ tasks.named<Copy>("jvmProcessResources") {
tasks.named<JavaExec>("run") { tasks.named<JavaExec>("run") {
dependsOn(tasks.named<Jar>("jvmJar")) dependsOn(tasks.named<Jar>("jvmJar"))
classpath(tasks.named<Jar>("jvmJar")) classpath(tasks.named<Jar>("jvmJar"))
} }

View File

@@ -0,0 +1,162 @@
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
/**
* User: rnentjes
* Date: 14-11-15
* Time: 11:50
*/
var sampleRate: Int = 44100
enum class Note(
val description: String
) {
C0("C-0"),
C0s("C#0"),
D0("D-0"),
D0s("D#0"),
E0("E-0"),
F0("F-0"),
F0s("F#0"),
G0("G-0"),
G0s("G#0"),
A0("A-0"),
A0s("A#0"),
B0("B-0"),
C1("C-1"),
C1s("C#1"),
D1("D-1"),
D1s("D#1"),
E1("E-1"),
F1("F-1"),
F1s("F#1"),
G1("G-1"),
G1s("G#1"),
A1("A-1"),
A1s("A#1"),
B1("B-1"),
C2("C-2"),
C2s("C#2"),
D2("D-2"),
D2s("D#2"),
E2("E-2"),
F2("F-2"),
F2s("F#2"),
G2("G-2"),
G2s("G#2"),
A2("A-2"),
A2s("A#2"),
B2("B-2"),
C3("C-3"),
C3s("C#3"),
D3("D-3"),
D3s("D#3"),
E3("E-3"),
F3("F-3"),
F3s("F#3"),
G3("G-3"),
G3s("G#3"),
A3("A-3"),
A3s("A#3"),
B3("B-3"),
C4("C-4"),
C4s("C#4"),
D4("D-4"),
D4s("D#4"),
E4("E-4"),
F4("F-4"),
F4s("F#4"),
G4("G-4"),
G4s("G#4"),
A4("A-4"),
A4s("A#4"),
B4("B-4"),
C5("C-5"),
C5s("C#5"),
D5("D-5"),
D5s("D#5"),
E5("E-5"),
F5("F-5"),
F5s("F#5"),
G5("G-5"),
G5s("G#5"),
A5("A-5"),
A5s("A#5"),
B5("B-5"),
C6("C-6"),
C6s("C#6"),
D6("D-6"),
D6s("D#6"),
E6("E-6"),
F6("F-6"),
F6s("F#6"),
G6("G-6"),
G6s("G#6"),
A6("A-6"),
A6s("A#6"),
B6("B-6"),
C7("C-7"),
C7s("C#7"),
D7("D-7"),
D7s("D#7"),
E7("E-7"),
F7("F-7"),
F7s("F#7"),
G7("G-7"),
G7s("G#7"),
A7("A-7"),
A7s("A#7"),
B7("B-7"),
C8("C-8"),
C8s("C#8"),
D8("D-8"),
D8s("D#8"),
E8("E-8"),
F8("F-8"),
F8s("F#8"),
G8("G-8"),
G8s("G#8"),
A8("A-8"),
A8s("A#8"),
B8("B-8"),
NONE("---"),
END("XXX"),
UP("^^^"),
;
val freq: Double by lazy {
val ordinal = ordinal
val relNote = ordinal - A4.ordinal
440.0 * 2.0.pow(relNote/12.0)
}
val cycleLength: Double by lazy { 1.0 / freq }
val sampleDelta: Double by lazy { (1.0 / sampleRate.toDouble()) / cycleLength }
fun transpose(semiNotes: Int): Note = if (ordinal >= C0.ordinal && ordinal <= B8.ordinal) {
var result = this.ordinal + semiNotes
result = min(result, B8.ordinal)
result = max(result, C0.ordinal)
values().firstOrNull { it.ordinal == result } ?: this
} else {
this
}
/*
* Amount of one cycle to advance per sample
*/
/*
fun sampleDelta(): Double {
// 44100
val sampleRate = sampleRate
val time = 1f / sampleRate.toDouble()
return time / cycleLength
}
*/
}

View File

@@ -0,0 +1,81 @@
@file:OptIn(ExperimentalJsExport::class)
import org.khronos.webgl.Float64Array
import org.khronos.webgl.set
import org.w3c.dom.MessageEvent
import org.w3c.dom.MessagePort
import kotlin.math.PI
import kotlin.math.sin
@ExperimentalJsExport
@JsExport
object WorkletProcessor {
var port: MessagePort? = null
var offset: Double = 0.0
var counter: Int = 0
var note = Note.C0
var directionUp = true
@JsName("setPort")
fun setPort(port: MessagePort) {
WorkletProcessor.port = port
WorkletProcessor.port?.onmessage = WorkletProcessor::onMessage
}
@JsName("onMessage")
fun onMessage(message: MessageEvent) {
console.log("WorkletProcessor: Received message", message)
when (message.data) {
"start" -> {
println("Start worklet!")
}
"stop" -> {
}
else ->
console.error("Don't kow how to handle message", message)
}
}
@JsName("process")
fun process(samples: Int, left: Float64Array, right: Float64Array) {
for (sample in 0 until samples) {
val noteProgress = counter % 5000
if (noteProgress == 0) {
note = note.transpose(
if (directionUp) {
1
} else {
-1
}
)
if (note == Note.B8) {
directionUp = false
}
if (note == Note.C0) {
directionUp = true
}
}
offset = (offset + note.sampleDelta) % 1.0
var value = sin(offset * 2 * PI) +
sin(offset * 4 * PI) * 0.6 +
sin(offset * 6 * PI) * 0.35 +
sin(offset * 8 * PI) * 0.2
// simple amplitude
value *= if (noteProgress < 0.1) {
(noteProgress / 500.0)
} else {
(1.0 - (noteProgress - 0.1) / 4500.0)
}
//val value = if (offset < 0.5) { 1.0 } else { -1.0 }
left[sample] = value
right[sample] = value
counter++
}
}
}

View File

@@ -1,46 +0,0 @@
@file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.processor
import org.khronos.webgl.Float64Array
import org.w3c.dom.MessageEvent
import org.w3c.dom.MessagePort
@ExperimentalJsExport
@JsExport
object WorkletProcessor {
var port: MessagePort? = null
@JsName("setPort")
fun setPort(port: MessagePort) {
this.port = port
this.port?.onmessage = ::onMessage
}
@JsName("onMessage")
fun onMessage(message: MessageEvent) {
console.log("WorkletProcessor: Received message", message)
when (message.data) {
"start" -> {
println("Start worklet!")
}
"stop" -> {
}
else ->
console.error("Don't kow how to handle message", message)
}
}
@JsName("process")
fun process(samples: Int, left: Float64Array, right: Float64Array) {
//console.log("WorkletProcessor.process", samples)
// val buffer = Float64SampleBuffer(samples, left, right)
//
// audioGenerator?.also { generator ->
// generator.fillBuffer(buffer, 0, samples, false)
// }
}
}

View File

@@ -15,7 +15,7 @@ class WorkletProcessor extends AudioWorkletProcessor {
console.log("worklet-processor.constructor", this, audioWorklet); console.log("worklet-processor.constructor", this, audioWorklet);
audioWorklet.nl.astraeus.processor.WorkletProcessor.setPort(this.port); audioWorklet.WorkletProcessor.setPort(this.port);
console.log("STARTED worklet-processor.js"); console.log("STARTED worklet-processor.js");
} }
@@ -39,7 +39,7 @@ class WorkletProcessor extends AudioWorkletProcessor {
} else { } else {
samplesToProcess = outputs[0][0].length; samplesToProcess = outputs[0][0].length;
audioWorklet.nl.astraeus.processor.WorkletProcessor.process( audioWorklet.WorkletProcessor.process(
samplesToProcess, samplesToProcess,
outputs[0][0], outputs[0][0],
outputs[0][1] outputs[0][1]

View File

@@ -1,7 +1,5 @@
package nl.astraeus
import kotlinx.browser.document import kotlinx.browser.document
import nl.astraeus.handler.AudioWorkletHandler import handler.AudioWorkletHandler
fun main() { fun main() {
AudioWorkletHandler.loadCode() AudioWorkletHandler.loadCode()

View File

@@ -1,4 +1,4 @@
package nl.astraeus.handler package handler
import kotlinx.browser.window import kotlinx.browser.window
import org.w3c.dom.MessageEvent import org.w3c.dom.MessageEvent

View File

@@ -1,5 +1,3 @@
package nl.astraeus.application
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer