Compare commits
3 Commits
ff8a4dbf92
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7110188d33 | |||
| 2cfc8a8201 | |||
| ce353d3113 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -42,6 +42,10 @@ bin/
|
||||
.DS_Store
|
||||
|
||||
/web
|
||||
/web1
|
||||
/web2
|
||||
|
||||
/data/*.db*
|
||||
**/kotlin-js-store/*
|
||||
.kotlin
|
||||
.idea
|
||||
|
||||
105
.junie/guidelines.md
Normal file
105
.junie/guidelines.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# VST Chip Synthesizer Development Guidelines
|
||||
|
||||
## Project Overview
|
||||
|
||||
VST Chip is a Kotlin multiplatform project that implements a chip-style synthesizer with a web interface. The project consists of:
|
||||
|
||||
- JVM backend that serves the web application
|
||||
- JS frontend that runs in the browser
|
||||
- Audio worklet processor for real-time audio processing
|
||||
|
||||
## Build/Configuration Instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- JDK 11 or higher
|
||||
- Gradle 7.x or higher
|
||||
- Node.js and npm (for JS development)
|
||||
|
||||
### Building the Project
|
||||
|
||||
1. **Full Build**:
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
2. **JS Build Only**:
|
||||
```bash
|
||||
./gradlew buildJS
|
||||
```
|
||||
|
||||
3. **Deployment**:
|
||||
```bash
|
||||
./gradlew deploy
|
||||
```
|
||||
This will build the project, copy web assets, and deploy to the configured server location.
|
||||
|
||||
### Configuration
|
||||
|
||||
- Server port: 9005 (configured in `Main.kt`)
|
||||
- JDBC stats port: 6005
|
||||
- Deployment directory: configured in `build.gradle.kts` as `vst-chip.midi-vst.com`
|
||||
|
||||
## Testing Information
|
||||
|
||||
- **Do not generate tests** for this project. The audio processing code is highly specialized and requires manual testing with audio
|
||||
equipment.
|
||||
- Manual testing should be performed using MIDI controllers and audio monitoring tools.
|
||||
|
||||
## Development Information
|
||||
|
||||
### Project Structure
|
||||
|
||||
- **src/commonMain**: Shared code between JS and JVM
|
||||
- **src/jsMain**: Browser-specific code
|
||||
- **src/jvmMain**: Server-specific code
|
||||
- **audio-worklet**: Audio processing code that runs in a separate thread in the browser
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **JVM Server** (`src/jvmMain/kotlin/nl/astraeus/vst/chip/Main.kt`):
|
||||
- Undertow server that serves the web application
|
||||
- Database initialization
|
||||
- Logging setup
|
||||
|
||||
2. **JS Frontend** (`src/jsMain/kotlin/nl/astraeus/vst/chip/Main.kt`):
|
||||
- UI implementation using Komponent library
|
||||
- MIDI handling
|
||||
- WebSocket client for server communication
|
||||
|
||||
3. **Audio Processor** (`audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt`):
|
||||
- Real-time audio synthesis
|
||||
- MIDI message handling
|
||||
- Sound generation with multiple waveforms (sine, square, triangle, sawtooth)
|
||||
- Effects processing (FM, AM, ADSR envelope, delay, feedback)
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. Make changes to the code
|
||||
2. Run `./gradlew buildJS` to build the JS part
|
||||
3. Run the JVM application to test locally
|
||||
4. Use `./gradlew deploy` to deploy to the server
|
||||
|
||||
### Audio Worklet Development
|
||||
|
||||
The audio worklet runs in a separate thread in the browser and handles real-time audio processing. When modifying the audio worklet code:
|
||||
|
||||
1. Understand that it runs in a separate context from the main JS code
|
||||
2. Communication happens via message passing
|
||||
3. Performance is critical - avoid garbage collection and heavy operations in the audio processing loop
|
||||
|
||||
### MIDI Implementation
|
||||
|
||||
The synthesizer responds to standard MIDI messages:
|
||||
|
||||
- Note On/Off (0x90/0x80)
|
||||
- Control Change (0xb0) for various parameters
|
||||
- Program Change (0xc9) for waveform selection
|
||||
|
||||
## Deployment
|
||||
|
||||
The project is configured to deploy to a specific server location. The deployment process:
|
||||
|
||||
1. Builds the project
|
||||
2. Copies web assets
|
||||
3. Creates a symbolic link for the latest version
|
||||
@@ -31,7 +31,7 @@ kotlin {
|
||||
}
|
||||
|
||||
distribution {
|
||||
outputDirectory.set(File("$projectDir/../web/"))
|
||||
outputDirectory.set(File("$projectDir/../web2/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package nl.astraeus.vst.midi
|
||||
|
||||
typealias MidiHandler = (Byte, Byte, Byte) -> Unit
|
||||
|
||||
val Byte.channel: Byte
|
||||
get() {
|
||||
return (this.toInt() and 0x0F).toByte()
|
||||
}
|
||||
|
||||
val Byte.command: Byte
|
||||
get() {
|
||||
return ((this.toInt() and 0xF0) shr 4).toByte()
|
||||
}
|
||||
|
||||
val Byte.channelCommand: Boolean
|
||||
get() {
|
||||
return this.command >= 8 && this.command <= 13
|
||||
}
|
||||
|
||||
class MidiMessageHandler(
|
||||
var channel: Byte = -1
|
||||
) {
|
||||
val singleByteHandlers = mutableMapOf<Byte, MidiHandler>()
|
||||
val doubleByteHandlers = mutableMapOf<Byte, MutableMap<Byte, MidiHandler>>()
|
||||
|
||||
fun addHandler(
|
||||
byte1: Byte,
|
||||
byte2: Byte = 0,
|
||||
handler: MidiHandler
|
||||
) {
|
||||
val b1 = if (byte1.channelCommand) {
|
||||
(byte1.toInt() and 0xF0).toByte()
|
||||
} else {
|
||||
byte1
|
||||
}
|
||||
if (byte2 == 0.toByte()) {
|
||||
singleByteHandlers[b1] = handler
|
||||
} else {
|
||||
val map = doubleByteHandlers.getOrPut(b1) {
|
||||
mutableMapOf()
|
||||
}
|
||||
map[byte2] = handler
|
||||
}
|
||||
}
|
||||
|
||||
fun addHandler(
|
||||
byte1: Int,
|
||||
byte2: Int = 0,
|
||||
handler: MidiHandler
|
||||
) = addHandler(
|
||||
byte1.toByte(),
|
||||
byte2.toByte(),
|
||||
handler
|
||||
)
|
||||
|
||||
fun handle(
|
||||
byte1: Byte,
|
||||
byte2: Byte,
|
||||
byte3: Byte
|
||||
) {
|
||||
if (
|
||||
channel < 0 ||
|
||||
!byte1.channelCommand ||
|
||||
(byte1.channelCommand && channel == byte1.channel)
|
||||
) {
|
||||
val b1 = if (byte1.channelCommand) {
|
||||
(byte1.toInt() and 0xF0).toByte()
|
||||
} else {
|
||||
byte1
|
||||
}
|
||||
|
||||
if (doubleByteHandlers.containsKey(b1)) {
|
||||
doubleByteHandlers[b1]?.get(byte2)?.invoke(byte1, byte2, byte3)
|
||||
} else if (singleByteHandlers.containsKey(b1)) {
|
||||
singleByteHandlers[b1]?.invoke(byte1, byte2, byte3)
|
||||
} else {
|
||||
println("Unhandled message: $byte1 $byte2 $byte3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ kotlin {
|
||||
}
|
||||
|
||||
distribution {
|
||||
outputDirectory.set(File("$projectDir/web/"))
|
||||
outputDirectory.set(File("$projectDir/web1/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,18 @@ application {
|
||||
mainClass.set("nl.astraeus.vst.chip.MainKt")
|
||||
}
|
||||
|
||||
tasks.register<Copy>("buildJS") {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
dependsOn("audio-worklet:jsBrowserDevelopmentExecutableDistribution")
|
||||
dependsOn("jsBrowserDevelopmentExecutableDistribution")
|
||||
|
||||
from(layout.projectDirectory.dir("web1"))
|
||||
into(layout.projectDirectory.dir("web"))
|
||||
|
||||
from(layout.projectDirectory.dir("web2"))
|
||||
into(layout.projectDirectory.dir("web"))
|
||||
}
|
||||
|
||||
/* Hardcoded deploy configuration */
|
||||
|
||||
val deployDirectory = "vst-chip.midi-vst.com"
|
||||
|
||||
1
data/readme.md
Normal file
1
data/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
Data directory for the db
|
||||
@@ -1,6 +1,6 @@
|
||||
pluginManagement {
|
||||
plugins {
|
||||
kotlin("multiplatform") version "2.1.10"
|
||||
kotlin("multiplatform") version "2.1.20"
|
||||
}
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
|
||||
Reference in New Issue
Block a user