diff --git a/build.gradle.kts b/build.gradle.kts index bb09ff8..100cbf8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ 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.IR +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("multiplatform") version "1.7.20" @@ -73,4 +74,4 @@ tasks.named("jvmProcessResources") { tasks.named("run") { dependsOn(tasks.named("jvmJar")) classpath(tasks.named("jvmJar")) -} +} \ No newline at end of file diff --git a/src/commonMain/kotlin/Note.kt b/src/commonMain/kotlin/Note.kt new file mode 100644 index 0000000..9f10629 --- /dev/null +++ b/src/commonMain/kotlin/Note.kt @@ -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 + } +*/ + +} diff --git a/src/jsAudioWorkletMain/kotlin/WorkletProcessor.kt b/src/jsAudioWorkletMain/kotlin/WorkletProcessor.kt new file mode 100644 index 0000000..7aef3ce --- /dev/null +++ b/src/jsAudioWorkletMain/kotlin/WorkletProcessor.kt @@ -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++ + } + } + +} diff --git a/src/jsAudioWorkletMain/kotlin/nl/astraeus/processor/WorkletProcessor.kt b/src/jsAudioWorkletMain/kotlin/nl/astraeus/processor/WorkletProcessor.kt deleted file mode 100644 index 85a3c7d..0000000 --- a/src/jsAudioWorkletMain/kotlin/nl/astraeus/processor/WorkletProcessor.kt +++ /dev/null @@ -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) -// } - } - -} diff --git a/src/jsAudioWorkletMain/resources/worklet-processor.js b/src/jsAudioWorkletMain/resources/worklet-processor.js index 2acbe2d..478c12c 100644 --- a/src/jsAudioWorkletMain/resources/worklet-processor.js +++ b/src/jsAudioWorkletMain/resources/worklet-processor.js @@ -15,7 +15,7 @@ class WorkletProcessor extends AudioWorkletProcessor { 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"); } @@ -39,7 +39,7 @@ class WorkletProcessor extends AudioWorkletProcessor { } else { samplesToProcess = outputs[0][0].length; - audioWorklet.nl.astraeus.processor.WorkletProcessor.process( + audioWorklet.WorkletProcessor.process( samplesToProcess, outputs[0][0], outputs[0][1] diff --git a/src/jsMain/kotlin/nl/astraeus/Main.kt b/src/jsMain/kotlin/Main.kt similarity index 82% rename from src/jsMain/kotlin/nl/astraeus/Main.kt rename to src/jsMain/kotlin/Main.kt index f9741eb..cd2f067 100644 --- a/src/jsMain/kotlin/nl/astraeus/Main.kt +++ b/src/jsMain/kotlin/Main.kt @@ -1,7 +1,5 @@ -package nl.astraeus - import kotlinx.browser.document -import nl.astraeus.handler.AudioWorkletHandler +import handler.AudioWorkletHandler fun main() { AudioWorkletHandler.loadCode() diff --git a/src/jsMain/kotlin/nl/astraeus/handler/AudioWorkletHandler.kt b/src/jsMain/kotlin/handler/AudioWorkletHandler.kt similarity index 99% rename from src/jsMain/kotlin/nl/astraeus/handler/AudioWorkletHandler.kt rename to src/jsMain/kotlin/handler/AudioWorkletHandler.kt index af5c0f5..f7bac36 100644 --- a/src/jsMain/kotlin/nl/astraeus/handler/AudioWorkletHandler.kt +++ b/src/jsMain/kotlin/handler/AudioWorkletHandler.kt @@ -1,4 +1,4 @@ -package nl.astraeus.handler +package handler import kotlinx.browser.window import org.w3c.dom.MessageEvent diff --git a/src/jvmMain/kotlin/nl.astraeus.application/Server.kt b/src/jvmMain/kotlin/Server.kt similarity index 96% rename from src/jvmMain/kotlin/nl.astraeus.application/Server.kt rename to src/jvmMain/kotlin/Server.kt index af5d69d..21ca6b6 100644 --- a/src/jvmMain/kotlin/nl.astraeus.application/Server.kt +++ b/src/jvmMain/kotlin/Server.kt @@ -1,5 +1,3 @@ -package nl.astraeus.application - import io.ktor.http.HttpStatusCode import io.ktor.server.application.* import io.ktor.server.engine.embeddedServer