Generate sound
This commit is contained in:
@@ -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"))
|
||||||
}
|
}
|
||||||
162
src/commonMain/kotlin/Note.kt
Normal file
162
src/commonMain/kotlin/Note.kt
Normal 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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
81
src/jsAudioWorkletMain/kotlin/WorkletProcessor.kt
Normal file
81
src/jsAudioWorkletMain/kotlin/WorkletProcessor.kt
Normal 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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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]
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user