ports
This commit is contained in:
parent
f8612db6ef
commit
6aba8d98ff
64 changed files with 1264 additions and 207 deletions
|
|
@ -17,7 +17,11 @@ dependencies {
|
||||||
minecraft("com.mojang", "minecraft", project.extra["minecraft_version"] as String)
|
minecraft("com.mojang", "minecraft", project.extra["minecraft_version"] as String)
|
||||||
mappings("net.fabricmc", "yarn", project.extra["yarn_mappings"] as String, null, "v2")
|
mappings("net.fabricmc", "yarn", project.extra["yarn_mappings"] as String, null, "v2")
|
||||||
modImplementation("net.fabricmc", "fabric-loader", project.extra["loader_version"] as String)
|
modImplementation("net.fabricmc", "fabric-loader", project.extra["loader_version"] as String)
|
||||||
modImplementation("net.fabricmc.fabric-api", "fabric-api", project.extra["fabric_version"] as String)
|
modImplementation(
|
||||||
|
"net.fabricmc.fabric-api",
|
||||||
|
"fabric-api",
|
||||||
|
project.extra["fabric_version"] as String
|
||||||
|
)
|
||||||
modImplementation(
|
modImplementation(
|
||||||
"net.fabricmc",
|
"net.fabricmc",
|
||||||
"fabric-language-kotlin",
|
"fabric-language-kotlin",
|
||||||
|
|
@ -36,6 +40,27 @@ rustImport {
|
||||||
layout.set("flat")
|
layout.set("flat")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mainSourceSet = sourceSets.main.get()
|
||||||
|
|
||||||
|
val datagenDir = layout.projectDirectory.dir("src").dir(mainSourceSet.name).dir("generated")
|
||||||
|
loom {
|
||||||
|
runs {
|
||||||
|
create("datagenClient") {
|
||||||
|
inherit(runConfigs["client"])
|
||||||
|
name("Data Generation")
|
||||||
|
vmArg("-Dfabric-api.datagen")
|
||||||
|
vmArg("-Dfabric-api.datagen.output-dir=$datagenDir")
|
||||||
|
vmArg("-Dfabric-api.datagen.modid=dawd3")
|
||||||
|
ideConfigGenerated(true)
|
||||||
|
runDir("build/datagen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.named(mainSourceSet.name) {
|
||||||
|
resources.srcDir(datagenDir)
|
||||||
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
val javaVersion = JavaVersion.toVersion((project.extra["java_version"] as String).toInt())
|
val javaVersion = JavaVersion.toVersion((project.extra["java_version"] as String).toInt())
|
||||||
|
|
||||||
|
|
|
||||||
7
d3r/Cargo.lock
generated
7
d3r/Cargo.lock
generated
|
|
@ -220,6 +220,7 @@ dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"cpal",
|
"cpal",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"hound",
|
||||||
"jni 0.20.0",
|
"jni 0.20.0",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
@ -264,6 +265,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hound"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,6 @@ crate-type = ["cdylib"]
|
||||||
bitvec = "1.0.1"
|
bitvec = "1.0.1"
|
||||||
cpal = "0.14.1"
|
cpal = "0.14.1"
|
||||||
env_logger = "0.9.3"
|
env_logger = "0.9.3"
|
||||||
|
hound = "3.5.0"
|
||||||
jni = "0.20.0"
|
jni = "0.20.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use cpal::{BufferSize, Device, Host, SampleRate, Stream, StreamConfig};
|
use cpal::{BufferSize, Device, Host, SampleRate, Stream, StreamConfig};
|
||||||
|
use hound::{SampleFormat, WavSpec};
|
||||||
use jni::objects::{JClass, JMethodID, JObject};
|
use jni::objects::{JClass, JMethodID, JObject};
|
||||||
use jni::signature::ReturnType;
|
use jni::signature::ReturnType;
|
||||||
use jni::sys::jvalue;
|
use jni::sys::jvalue;
|
||||||
|
|
@ -157,6 +159,7 @@ pub extern "system" fn Java_net_liquidev_d3r_D3r_openOutputStream(
|
||||||
let generator_ref = env.new_global_ref(generator).map_err(|e| e.to_string())?;
|
let generator_ref = env.new_global_ref(generator).map_err(|e| e.to_string())?;
|
||||||
let jvm = env.get_java_vm().map_err(|e| e.to_string())?;
|
let jvm = env.get_java_vm().map_err(|e| e.to_string())?;
|
||||||
let mut initialized = false;
|
let mut initialized = false;
|
||||||
|
|
||||||
let stream = device
|
let stream = device
|
||||||
.build_output_stream(
|
.build_output_stream(
|
||||||
&config.clone(),
|
&config.clone(),
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
proj/block/speaker_side.ase
Normal file
BIN
proj/block/speaker_side.ase
Normal file
Binary file not shown.
BIN
proj/device/port.ase
Normal file
BIN
proj/device/port.ase
Normal file
Binary file not shown.
|
|
@ -0,0 +1,4 @@
|
||||||
|
// 1.19.2 2022-11-20T23:04:16.520652716 Models
|
||||||
|
e3c6aacd49a6395f37047d3df31f91a18a411267 assets/dawd3/models/item/speaker.json
|
||||||
|
f7b47538f17992177e97e06842c0039ae5096b2b assets/dawd3/blockstates/speaker.json
|
||||||
|
bd0adfc8b3dc271042dd4b19a8cace4e0fffedfe assets/dawd3/models/block/speaker.json
|
||||||
19
src/main/generated/assets/dawd3/blockstates/speaker.json
Normal file
19
src/main/generated/assets/dawd3/blockstates/speaker.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"facing=east": {
|
||||||
|
"model": "dawd3:block/speaker",
|
||||||
|
"y": 90
|
||||||
|
},
|
||||||
|
"facing=north": {
|
||||||
|
"model": "dawd3:block/speaker"
|
||||||
|
},
|
||||||
|
"facing=south": {
|
||||||
|
"model": "dawd3:block/speaker",
|
||||||
|
"y": 180
|
||||||
|
},
|
||||||
|
"facing=west": {
|
||||||
|
"model": "dawd3:block/speaker",
|
||||||
|
"y": 270
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/main/generated/assets/dawd3/models/block/speaker.json
Normal file
73
src/main/generated/assets/dawd3/models/block/speaker.json
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"parent": "block/block",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"cullface": "down",
|
||||||
|
"texture": "#side"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"cullface": "east",
|
||||||
|
"texture": "#side"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"cullface": "north",
|
||||||
|
"texture": "#front"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"cullface": "south",
|
||||||
|
"texture": "#side"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"cullface": "up",
|
||||||
|
"texture": "#side"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"cullface": "west",
|
||||||
|
"texture": "#side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"from": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
16.0,
|
||||||
|
16.0,
|
||||||
|
16.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"faces": {
|
||||||
|
"south": {
|
||||||
|
"cullface": "south",
|
||||||
|
"texture": "#port",
|
||||||
|
"uv": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
4.0,
|
||||||
|
4.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"from": [
|
||||||
|
6.5,
|
||||||
|
6.5,
|
||||||
|
15.99
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
9.5,
|
||||||
|
9.5,
|
||||||
|
16.01
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures": {
|
||||||
|
"front": "dawd3:block/speaker_front",
|
||||||
|
"particle": "#side",
|
||||||
|
"port": "dawd3:device/port",
|
||||||
|
"side": "dawd3:block/speaker_side"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package net.liquidev.d3r;
|
package net.liquidev.d3r;
|
||||||
|
|
||||||
public interface AudioOutputStream {
|
public interface AudioOutputStream {
|
||||||
float[] getOutputBuffer(int sampleCount, int channels);
|
float[] getOutputBuffer(int sampleCount, int channelCount);
|
||||||
|
|
||||||
void error(String message);
|
void error(String message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import java.nio.file.Path;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class D3r {
|
public class D3r {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("dawd³/d3r");
|
private static final Logger LOGGER = LoggerFactory.getLogger("dawd3/d3r");
|
||||||
private static Path tempDir = null;
|
private static Path tempDir = null;
|
||||||
|
|
||||||
public static void load() throws D3rException, IOException {
|
public static void load() throws D3rException, IOException {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ abstract class D3Registry<T> {
|
||||||
|
|
||||||
var registered = arrayListOf<Registered<T>>()
|
var registered = arrayListOf<Registered<T>>()
|
||||||
|
|
||||||
fun add(id: String, item: T): Registered<T> {
|
fun add(id: Identifier, item: T): Registered<T> {
|
||||||
val entry = Registered(Identifier(Mod.id, id), item)
|
val entry = Registered(id, item)
|
||||||
registered.add(entry)
|
registered.add(entry)
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,45 @@ import net.fabricmc.api.ClientModInitializer
|
||||||
import net.fabricmc.api.ModInitializer
|
import net.fabricmc.api.ModInitializer
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
|
||||||
import net.liquidev.d3r.D3r
|
import net.liquidev.d3r.D3r
|
||||||
|
import net.liquidev.dawd3.audio.Audio
|
||||||
import net.liquidev.dawd3.block.Blocks
|
import net.liquidev.dawd3.block.Blocks
|
||||||
|
import net.liquidev.dawd3.block.entity.registerBlockEntityEvents
|
||||||
import net.liquidev.dawd3.item.Items
|
import net.liquidev.dawd3.item.Items
|
||||||
import net.liquidev.dawd3.sound.Sound
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
object Mod : ModInitializer, ClientModInitializer {
|
object Mod : ModInitializer, ClientModInitializer {
|
||||||
const val id = "dawd3"
|
const val id = "dawd3"
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger("dawd³")
|
val logger = logger(null)
|
||||||
|
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
logger.info("hello, sound traveler! welcome to the dawd³ experience")
|
logger.info("hello, sound traveler! welcome to the dawd³ experience")
|
||||||
|
Blocks.initialize()
|
||||||
Blocks.blockRegistry.registerAll()
|
|
||||||
Items.registry.registerAll()
|
Items.registry.registerAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInitializeClient() {
|
override fun onInitializeClient() {
|
||||||
logger.info("booting up sound engine")
|
logger.info("booting up sound engine")
|
||||||
D3r.load()
|
D3r.load()
|
||||||
Sound.forceInitializationNow()
|
Audio.forceInitializationNow()
|
||||||
|
|
||||||
ClientLifecycleEvents.CLIENT_STOPPING.register {
|
ClientLifecycleEvents.CLIENT_STOPPING.register {
|
||||||
logger.info("shutting down sound engine")
|
logger.info("shutting down sound engine")
|
||||||
Sound.deinitialize()
|
Audio.deinitialize()
|
||||||
D3r.unload()
|
D3r.unload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerBlockEntityEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loggerName(name: String?): String =
|
||||||
|
if (name != null) "$id/$name" else id
|
||||||
|
|
||||||
|
fun logger(name: String?): Logger =
|
||||||
|
LoggerFactory.getLogger(loggerName(name))
|
||||||
|
|
||||||
|
inline fun <reified T> logger(): Logger =
|
||||||
|
logger(T::class.simpleName)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
src/main/kotlin/net/liquidev/dawd3/audio/Audio.kt
Normal file
37
src/main/kotlin/net/liquidev/dawd3/audio/Audio.kt
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package net.liquidev.dawd3.audio
|
||||||
|
|
||||||
|
import net.liquidev.d3r.D3r
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.audio.generator.MixGenerator
|
||||||
|
import net.liquidev.dawd3.audio.unit.Decibels
|
||||||
|
|
||||||
|
/** Audio system and common settings. */
|
||||||
|
object Audio {
|
||||||
|
val logger = Mod.logger<Audio>()
|
||||||
|
|
||||||
|
const val sampleRate = 48000
|
||||||
|
const val sampleRateF = sampleRate.toFloat()
|
||||||
|
const val sampleRateFInv = 1.0f / sampleRateF
|
||||||
|
private const val bufferSize = 256
|
||||||
|
|
||||||
|
private val outputDeviceId: Int
|
||||||
|
private val outputStreamId: Int
|
||||||
|
|
||||||
|
val mixer = MixGenerator()
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger.info("initializing")
|
||||||
|
logger.info("${Decibels(-3.0f).toAmplitude()}")
|
||||||
|
D3r.openDefaultHost()
|
||||||
|
outputDeviceId = D3r.openDefaultOutputDevice()
|
||||||
|
outputStreamId = D3r.openOutputStream(outputDeviceId, sampleRate, 1, bufferSize, mixer)
|
||||||
|
D3r.startPlayback(outputStreamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceInitializationNow() {}
|
||||||
|
|
||||||
|
fun deinitialize() {
|
||||||
|
D3r.closeOutputStream(outputStreamId)
|
||||||
|
D3r.closeOutputDevice(outputDeviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/kotlin/net/liquidev/dawd3/audio/AudioBuffer.kt
Normal file
23
src/main/kotlin/net/liquidev/dawd3/audio/AudioBuffer.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package net.liquidev.dawd3.audio
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
|
||||||
|
/** Audio buffer that reallocates itself when needed. */
|
||||||
|
class AudioBuffer {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<AudioBuffer>()
|
||||||
|
}
|
||||||
|
|
||||||
|
var array: FloatArray? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun getOrReallocate(minSampleCount: Int): FloatArray {
|
||||||
|
val inArray = array
|
||||||
|
if (inArray == null || inArray.size < minSampleCount) {
|
||||||
|
logger.info("reallocating output buffer for $this; this might cause stuttering")
|
||||||
|
// Allocate a little more so that reallocations don't trigger too often.
|
||||||
|
array = FloatArray(minSampleCount)
|
||||||
|
}
|
||||||
|
return array!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
/** Device that can process audio. */
|
||||||
|
interface Device {
|
||||||
|
fun process(sampleCount: Int, channels: Int)
|
||||||
|
|
||||||
|
fun visitInputPorts(visit: (PortName, InputPort) -> Unit) {}
|
||||||
|
fun visitOutputPorts(visit: (PortName, OutputPort) -> Unit) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
class DeviceInstance(val state: Device) {
|
||||||
|
val inputPortsByName = hashMapOf<PortName, InputPort>()
|
||||||
|
val outputPortsByName = hashMapOf<PortName, OutputPort>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
state.visitInputPorts { portName, inputPort ->
|
||||||
|
ensureUniquePort(portName)
|
||||||
|
inputPortsByName[portName] = inputPort
|
||||||
|
inputPort.owningDevice = this
|
||||||
|
}
|
||||||
|
state.visitOutputPorts { portName, outputPort ->
|
||||||
|
ensureUniquePort(portName)
|
||||||
|
outputPortsByName[portName] = outputPort
|
||||||
|
outputPort.owningDevice = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureUniquePort(name: PortName) {
|
||||||
|
if (name in inputPortsByName || name in outputPortsByName) {
|
||||||
|
throw PortAlreadyExistsException("port $name is already registered in device $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun process(sampleCount: Int, channels: Int) {
|
||||||
|
state.process(sampleCount, channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DeviceInstance($state)"
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/main/kotlin/net/liquidev/dawd3/audio/device/Devices.kt
Normal file
53
src/main/kotlin/net/liquidev/dawd3/audio/device/Devices.kt
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
/** Device utility functions. */
|
||||||
|
object Devices {
|
||||||
|
fun makeConnection(
|
||||||
|
from: DeviceInstance,
|
||||||
|
outputPortName: PortName,
|
||||||
|
to: DeviceInstance,
|
||||||
|
inputPortName: PortName
|
||||||
|
) {
|
||||||
|
val outputPort = from.outputPortsByName[outputPortName]
|
||||||
|
val inputPort = to.inputPortsByName[inputPortName]
|
||||||
|
|
||||||
|
if (outputPort == null) {
|
||||||
|
throw noSuchPort(from, outputPortName)
|
||||||
|
}
|
||||||
|
if (inputPort == null) {
|
||||||
|
throw noSuchPort(to, inputPortName)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPort.connectedOutput = outputPort
|
||||||
|
outputPort.connectedInputs.add(inputPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnects everything from the given port. Returns the number of ports disconnected. */
|
||||||
|
fun severAllConnections(device: DeviceInstance, portName: PortName): Int {
|
||||||
|
var total = 0
|
||||||
|
|
||||||
|
val inputPort = device.inputPortsByName[portName]
|
||||||
|
if (inputPort != null) {
|
||||||
|
val connectedOutput = inputPort.connectedOutput
|
||||||
|
if (connectedOutput != null) {
|
||||||
|
connectedOutput.connectedInputs.remove(inputPort)
|
||||||
|
inputPort.connectedOutput = null
|
||||||
|
total += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputPort = device.outputPortsByName[portName]
|
||||||
|
if (outputPort != null) {
|
||||||
|
for (port in outputPort.connectedInputs) {
|
||||||
|
port.connectedOutput = null
|
||||||
|
}
|
||||||
|
total += outputPort.connectedInputs.size
|
||||||
|
outputPort.connectedInputs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun noSuchPort(device: DeviceInstance, name: PortName): NoSuchPortException =
|
||||||
|
NoSuchPortException("device $device does not have port with name $name")
|
||||||
|
}
|
||||||
45
src/main/kotlin/net/liquidev/dawd3/audio/device/Sim.kt
Normal file
45
src/main/kotlin/net/liquidev/dawd3/audio/device/Sim.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sim is the simulator for device graphs.
|
||||||
|
*/
|
||||||
|
class Sim {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<Sim>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stack = arrayListOf<DeviceInstance>()
|
||||||
|
private val evalList = arrayListOf<DeviceInstance>()
|
||||||
|
private val visited = arrayListOf<DeviceInstance>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a simulation sequence: establishes the order in which devices in a graph have to be
|
||||||
|
* processed so that we arrive at the input signals fed into the terminal.
|
||||||
|
*/
|
||||||
|
fun rebuild(terminal: DeviceInstance) {
|
||||||
|
stack.add(terminal)
|
||||||
|
|
||||||
|
evalList.clear()
|
||||||
|
while (stack.isNotEmpty()) {
|
||||||
|
val device = stack.removeAt(stack.size - 1)
|
||||||
|
evalList.add(device)
|
||||||
|
for ((_, inputPort) in device.inputPortsByName) {
|
||||||
|
val connectedOutputPort = inputPort.connectedOutput
|
||||||
|
if (connectedOutputPort != null) {
|
||||||
|
stack.add(connectedOutputPort.owningDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("rebuilt device graph; new evaluation order (reversed): $evalList")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Process all devices in the built sequence. */
|
||||||
|
fun simulate(sampleCount: Int, channels: Int) {
|
||||||
|
for (device in evalList.asReversed()) {
|
||||||
|
device.process(sampleCount, channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
class NoSuchPortException(what: String) : Exception(what)
|
||||||
|
|
||||||
|
class PortAlreadyExistsException(what: String) : Exception(what)
|
||||||
38
src/main/kotlin/net/liquidev/dawd3/audio/device/ports.kt
Normal file
38
src/main/kotlin/net/liquidev/dawd3/audio/device/ports.kt
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package net.liquidev.dawd3.audio.device
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.AudioBuffer
|
||||||
|
|
||||||
|
class InputPort {
|
||||||
|
private companion object {
|
||||||
|
val emptyBuffer = AudioBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectedOutput: OutputPort? = null
|
||||||
|
lateinit var owningDevice: DeviceInstance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function that returns an empty buffer if there is no connected port, or the
|
||||||
|
* connected port doesn't have a buffer with the given index.
|
||||||
|
*/
|
||||||
|
fun getConnectedOutputBuffer(index: Int, sampleCount: Int): FloatArray {
|
||||||
|
val connectedOutput = connectedOutput
|
||||||
|
return if (connectedOutput != null && index < connectedOutput.buffers.size) {
|
||||||
|
connectedOutput.buffers[index].array ?: emptyBuffer.getOrReallocate(sampleCount)
|
||||||
|
} else {
|
||||||
|
emptyBuffer.getOrReallocate(sampleCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OutputPort(bufferCount: Int) {
|
||||||
|
init {
|
||||||
|
require(bufferCount >= 1) { "output port must have at least one buffer" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val buffers = Array(bufferCount) { AudioBuffer() }
|
||||||
|
val connectedInputs = hashSetOf<InputPort>()
|
||||||
|
lateinit var owningDevice: DeviceInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marker class that port name markers should inherit from. */
|
||||||
|
abstract class PortName
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.liquidev.dawd3.audio.devices
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.device.Device
|
||||||
|
import net.liquidev.dawd3.audio.device.OutputPort
|
||||||
|
import net.liquidev.dawd3.audio.device.PortName
|
||||||
|
|
||||||
|
class ConstantDevice(var value: Float) : Device {
|
||||||
|
object Output : PortName()
|
||||||
|
|
||||||
|
val output = OutputPort(bufferCount = 1)
|
||||||
|
|
||||||
|
override fun process(sampleCount: Int, channels: Int) {
|
||||||
|
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
outputBuffer[i] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitOutputPorts(visit: (PortName, OutputPort) -> Unit) {
|
||||||
|
visit(Output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package net.liquidev.dawd3.audio.devices
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.Audio
|
||||||
|
import net.liquidev.dawd3.audio.device.Device
|
||||||
|
import net.liquidev.dawd3.audio.device.InputPort
|
||||||
|
import net.liquidev.dawd3.audio.device.OutputPort
|
||||||
|
import net.liquidev.dawd3.audio.device.PortName
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
private const val twoPi = 2.0f * kotlin.math.PI.toFloat()
|
||||||
|
|
||||||
|
class SineOscillatorDevice : Device {
|
||||||
|
object FrequencyCV : PortName()
|
||||||
|
object Output : PortName()
|
||||||
|
|
||||||
|
val frequencyCV = InputPort()
|
||||||
|
val output = OutputPort(bufferCount = 1)
|
||||||
|
|
||||||
|
private var phase = 0.0f
|
||||||
|
|
||||||
|
override fun process(sampleCount: Int, channels: Int) {
|
||||||
|
val frequencyBuffer = frequencyCV.getConnectedOutputBuffer(0, sampleCount)
|
||||||
|
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
val phaseStep = Audio.sampleRateFInv * frequencyBuffer[i]
|
||||||
|
phase += phaseStep
|
||||||
|
phase %= 1.0f
|
||||||
|
outputBuffer[i] = sin(phase * twoPi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInputPorts(visit: (PortName, InputPort) -> Unit) {
|
||||||
|
visit(FrequencyCV, frequencyCV)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitOutputPorts(visit: (PortName, OutputPort) -> Unit) {
|
||||||
|
visit(Output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package net.liquidev.dawd3.audio.devices
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.device.Device
|
||||||
|
import net.liquidev.dawd3.audio.device.InputPort
|
||||||
|
import net.liquidev.dawd3.audio.device.PortName
|
||||||
|
|
||||||
|
class TerminalDevice : Device {
|
||||||
|
object Input : PortName()
|
||||||
|
|
||||||
|
val input = InputPort()
|
||||||
|
|
||||||
|
override fun process(sampleCount: Int, channels: Int) {
|
||||||
|
// Terminals don't do any audio processing.
|
||||||
|
// The output port connected to `input` is instead used by terminal block entities like
|
||||||
|
// speakers.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInputPorts(visit: (PortName, InputPort) -> Unit) {
|
||||||
|
visit(Input, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package net.liquidev.dawd3.audio.generator
|
||||||
|
|
||||||
|
import net.liquidev.d3r.AudioOutputStream
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.audio.AudioBuffer
|
||||||
|
|
||||||
|
abstract class AudioGenerator : AudioOutputStream {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<AudioGenerator>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var outputBuffer = AudioBuffer()
|
||||||
|
|
||||||
|
abstract fun generate(output: FloatArray, sampleCount: Int, channelCount: Int)
|
||||||
|
|
||||||
|
override fun getOutputBuffer(sampleCount: Int, channelCount: Int): FloatArray {
|
||||||
|
val outputArray = outputBuffer.getOrReallocate(sampleCount)
|
||||||
|
try {
|
||||||
|
generate(outputArray, sampleCount, channelCount)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("exception occured in audio generator")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return outputArray
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(message: String) {
|
||||||
|
println("Error reported by audio stream: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package net.liquidev.dawd3.audio.generator
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||||
|
import net.liquidev.dawd3.audio.device.Sim
|
||||||
|
import net.liquidev.dawd3.audio.devices.TerminalDevice
|
||||||
|
|
||||||
|
/** Audio generator that evaluates a device graph. */
|
||||||
|
class DeviceGraphGenerator : AudioGenerator() {
|
||||||
|
private val sim = Sim()
|
||||||
|
private val terminalDeviceState = TerminalDevice()
|
||||||
|
val terminalDevice = DeviceInstance(terminalDeviceState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be called whenever the device graph changes to reestablish what order devices
|
||||||
|
* should be processed in.
|
||||||
|
*/
|
||||||
|
fun rebuildSim() {
|
||||||
|
sim.rebuild(terminalDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generate(output: FloatArray, sampleCount: Int, channelCount: Int) {
|
||||||
|
sim.simulate(sampleCount, channelCount)
|
||||||
|
val buffer = terminalDeviceState.input.getConnectedOutputBuffer(0, sampleCount)
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
output[i] = buffer[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package net.liquidev.dawd3.audio.generator
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.audio.unit.Decibels
|
||||||
|
import net.liquidev.dawd3.common.TaskQueue
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
/** Audio mixer. Mixes multiple channels into a single output stream. */
|
||||||
|
class MixGenerator : AudioGenerator() {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<MixGenerator>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val taskQueue = TaskQueue()
|
||||||
|
private val channels = arrayListOf<WeakReference<Channel<AudioGenerator>>>()
|
||||||
|
|
||||||
|
fun <T : AudioGenerator> createChannel(generator: T): Channel<T> {
|
||||||
|
val channel = Channel(taskQueue, generator)
|
||||||
|
val weak = WeakReference(channel as Channel<AudioGenerator>)
|
||||||
|
taskQueue.execute { channels.add(weak) }
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generate(output: FloatArray, sampleCount: Int, channelCount: Int) {
|
||||||
|
// Flush task queue as soon as possible to reduce latency.
|
||||||
|
taskQueue.flush()
|
||||||
|
reapStoppedChannels()
|
||||||
|
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
output[i] = 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: These loops are separated for better efficiency.
|
||||||
|
// The processor is better at performing the same task many times, and interleaving types of tasks
|
||||||
|
// is slower than doing them in batches.
|
||||||
|
for (weak in channels) {
|
||||||
|
val channel = weak.get()!!
|
||||||
|
channel.audioBuffer = channel.generator.getOutputBuffer(sampleCount, channelCount)
|
||||||
|
}
|
||||||
|
for (weak in channels) {
|
||||||
|
val channel = weak.get()!!
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
output[i] += channel.audioBuffer[i] * channel.volume.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reapStoppedChannels() {
|
||||||
|
var i = 0
|
||||||
|
while (i < channels.size) {
|
||||||
|
val channel = channels[i].get()
|
||||||
|
val reap = channel == null || !channel.playing
|
||||||
|
if (reap) {
|
||||||
|
logger.info("reaping channel at index $i in queue $channels")
|
||||||
|
channels[i] = channels[channels.size - 1]
|
||||||
|
channels.removeAt(channels.size - 1)
|
||||||
|
} else {
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Channel<out T : AudioGenerator>(
|
||||||
|
private val taskQueue: TaskQueue,
|
||||||
|
val generator: T,
|
||||||
|
) {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<Channel<AudioGenerator>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
var volume = Decibels(-12.0f).toAmplitude()
|
||||||
|
var playing = true
|
||||||
|
private set
|
||||||
|
lateinit var audioBuffer: FloatArray
|
||||||
|
|
||||||
|
/** Shuts down the channel on the next audio generation request. */
|
||||||
|
fun stop() {
|
||||||
|
taskQueue.execute { playing = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalize() {
|
||||||
|
if (playing) {
|
||||||
|
logger.warn("mixer channel stopped in finalizer; this might cause a ConcurrentModificationException")
|
||||||
|
}
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
package net.liquidev.dawd3.sound
|
package net.liquidev.dawd3.audio.generator
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.Audio
|
||||||
|
import net.liquidev.dawd3.audio.unit.Amplitude
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
class SineOscGenerator(frequency: Float, private val amplitude: Float) : AudioGenerator() {
|
class SineOscGenerator(frequency: Float, private val amplitude: Amplitude) : AudioGenerator() {
|
||||||
private val phaseStep = (1.0f / Sound.sampleRate.toFloat()) * frequency
|
private val phaseStep = (1.0f / Audio.sampleRate.toFloat()) * frequency
|
||||||
private var phase = 0.0f
|
private var phase = 0.0f
|
||||||
|
|
||||||
private fun synthesize(): Float {
|
private fun synthesize(): Float {
|
||||||
phase += phaseStep
|
phase += phaseStep
|
||||||
phase %= 1.0f
|
phase %= 1.0f
|
||||||
return sin(phase * 2.0f * kotlin.math.PI.toFloat()) * amplitude
|
return sin(phase * 2.0f * kotlin.math.PI.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generate(output: FloatArray, sampleCount: Int, channels: Int) {
|
override fun generate(output: FloatArray, sampleCount: Int, channelCount: Int) {
|
||||||
for (i in 0 until sampleCount) {
|
for (i in 0 until sampleCount) {
|
||||||
val sample = synthesize()
|
val sample = synthesize()
|
||||||
output[i] = sample
|
output[i] = sample
|
||||||
14
src/main/kotlin/net/liquidev/dawd3/audio/unit/volume.kt
Normal file
14
src/main/kotlin/net/liquidev/dawd3/audio/unit/volume.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.liquidev.dawd3.audio.unit
|
||||||
|
|
||||||
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Amplitude(val value: Float) {
|
||||||
|
fun toDecibels(): Decibels = Decibels(20.0f * log10(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Decibels(val value: Float) {
|
||||||
|
fun toAmplitude(): Amplitude = Amplitude(10.0f.pow(value / 20.0f))
|
||||||
|
}
|
||||||
|
|
@ -1,38 +1,50 @@
|
||||||
package net.liquidev.dawd3.block
|
package net.liquidev.dawd3.block
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings
|
import net.fabricmc.fabric.api.item.v1.FabricItemSettings
|
||||||
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
|
||||||
import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder
|
import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder
|
||||||
import net.liquidev.dawd3.D3Registry
|
import net.liquidev.dawd3.D3Registry
|
||||||
import net.liquidev.dawd3.Mod
|
import net.liquidev.dawd3.block.device.AnyDeviceBlockDescriptor
|
||||||
|
import net.liquidev.dawd3.block.device.DeviceBlock
|
||||||
|
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||||
|
import net.liquidev.dawd3.block.devices.SpeakerBlockDescriptor
|
||||||
import net.liquidev.dawd3.item.Items
|
import net.liquidev.dawd3.item.Items
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.Block
|
||||||
import net.minecraft.block.Material
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
import net.minecraft.item.BlockItem
|
import net.minecraft.item.BlockItem
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.minecraft.util.registry.Registry
|
import net.minecraft.util.registry.Registry
|
||||||
|
|
||||||
object Blocks {
|
object Blocks {
|
||||||
var blockRegistry = object : D3Registry<Block>() {
|
data class RegisteredDeviceBlock(
|
||||||
override fun doRegister(identifier: Identifier, item: Block) {
|
val block: Block,
|
||||||
Registry.register(Registry.BLOCK, identifier, item)
|
val item: D3Registry.Registered<Items.RegisteredItem>,
|
||||||
}
|
val blockEntity: BlockEntityType<DeviceBlockEntity>,
|
||||||
}
|
val descriptor: AnyDeviceBlockDescriptor,
|
||||||
|
|
||||||
val speaker = add("speaker", SpeakerBlock(moduleBlockSettings()))
|
|
||||||
val speakerEntity = Registry.register(
|
|
||||||
Registry.BLOCK_ENTITY_TYPE,
|
|
||||||
Identifier(Mod.id, "speaker"),
|
|
||||||
FabricBlockEntityTypeBuilder.create(::SpeakerBlockEntity, speaker.item).build(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun moduleBlockSettings() = FabricBlockSettings
|
val deviceBlocks = hashMapOf<Identifier, RegisteredDeviceBlock>()
|
||||||
.of(Material.METAL)
|
|
||||||
.hardness(5.0f)
|
|
||||||
.resistance(6.0f)
|
|
||||||
|
|
||||||
private fun add(name: String, block: Block): D3Registry.Registered<Block> {
|
fun registerDeviceBlock(descriptor: AnyDeviceBlockDescriptor): RegisteredDeviceBlock {
|
||||||
Items.addItem(name, BlockItem(block, FabricItemSettings().group(Items.creativeTab)))
|
val block =
|
||||||
return blockRegistry.add(name, block)
|
Registry.register(Registry.BLOCK, descriptor.id, DeviceBlock(descriptor))
|
||||||
|
val item = Items.addItem(
|
||||||
|
descriptor.id,
|
||||||
|
BlockItem(block, FabricItemSettings().group(Items.creativeTab))
|
||||||
|
)
|
||||||
|
val blockEntity = Registry.register(
|
||||||
|
Registry.BLOCK_ENTITY_TYPE,
|
||||||
|
descriptor.id,
|
||||||
|
FabricBlockEntityTypeBuilder.create(
|
||||||
|
DeviceBlockEntity.factory(descriptor), block
|
||||||
|
).build(),
|
||||||
|
)
|
||||||
|
val registeredDeviceBlock = RegisteredDeviceBlock(block, item, blockEntity, descriptor)
|
||||||
|
deviceBlocks[descriptor.id] = registeredDeviceBlock
|
||||||
|
return registeredDeviceBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device blocks
|
||||||
|
val speaker = registerDeviceBlock(SpeakerBlockDescriptor)
|
||||||
|
|
||||||
|
fun initialize() {}
|
||||||
}
|
}
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package net.liquidev.dawd3.block
|
|
||||||
|
|
||||||
import net.minecraft.block.*
|
|
||||||
import net.minecraft.block.entity.BlockEntity
|
|
||||||
import net.minecraft.entity.LivingEntity
|
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
|
||||||
import net.minecraft.item.ItemPlacementContext
|
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.state.StateManager
|
|
||||||
import net.minecraft.state.property.Properties
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.World
|
|
||||||
|
|
||||||
class SpeakerBlock(settings: Settings) : BlockWithEntity(settings), BlockEntityProvider {
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
|
||||||
builder.add(Properties.HORIZONTAL_FACING)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPlacementState(ctx: ItemPlacementContext): BlockState {
|
|
||||||
return defaultState.with(Properties.HORIZONTAL_FACING, ctx.playerFacing.opposite)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
|
|
||||||
return SpeakerBlockEntity(pos, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRenderType(state: BlockState): BlockRenderType {
|
|
||||||
return BlockRenderType.MODEL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaced(
|
|
||||||
world: World,
|
|
||||||
pos: BlockPos,
|
|
||||||
state: BlockState,
|
|
||||||
placer: LivingEntity?,
|
|
||||||
itemStack: ItemStack,
|
|
||||||
) {
|
|
||||||
println("Speaker placed")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
|
|
||||||
println("Speaker broken, deinitializing block entity")
|
|
||||||
val blockEntity = world.getBlockEntity(pos) as SpeakerBlockEntity
|
|
||||||
blockEntity.deinit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package net.liquidev.dawd3.block
|
|
||||||
|
|
||||||
import net.minecraft.block.BlockState
|
|
||||||
import net.minecraft.block.entity.BlockEntity
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
|
|
||||||
class SpeakerBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Blocks.speakerEntity, pos, state) {
|
|
||||||
init {
|
|
||||||
println("Speaker block entity created")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deinit() {
|
|
||||||
println("Speaker block entity deinitialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finalize() {
|
|
||||||
deinit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package net.liquidev.dawd3.block.device
|
||||||
|
|
||||||
|
import net.minecraft.block.*
|
||||||
|
import net.minecraft.block.entity.BlockEntity
|
||||||
|
import net.minecraft.item.ItemPlacementContext
|
||||||
|
import net.minecraft.state.StateManager
|
||||||
|
import net.minecraft.state.property.Properties
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
|
class DeviceBlock(private val descriptor: AnyDeviceBlockDescriptor) :
|
||||||
|
BlockWithEntity(descriptor.blockSettings),
|
||||||
|
BlockEntityProvider {
|
||||||
|
|
||||||
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
|
builder.add(Properties.HORIZONTAL_FACING)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPlacementState(context: ItemPlacementContext): BlockState =
|
||||||
|
defaultState.with(Properties.HORIZONTAL_FACING, context.playerFacing.opposite)
|
||||||
|
|
||||||
|
@Deprecated("do not call directly")
|
||||||
|
override fun getRenderType(state: BlockState): BlockRenderType =
|
||||||
|
BlockRenderType.MODEL
|
||||||
|
|
||||||
|
override fun createBlockEntity(pos: BlockPos, state: BlockState): BlockEntity =
|
||||||
|
DeviceBlockEntity.factory(descriptor).create(pos, state)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package net.liquidev.dawd3.block.device
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
||||||
|
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||||
|
import net.minecraft.block.AbstractBlock
|
||||||
|
import net.minecraft.block.Material
|
||||||
|
import net.minecraft.client.world.ClientWorld
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
|
||||||
|
typealias AnyDeviceBlockDescriptor = DeviceBlockDescriptor<DeviceBlockDescriptor.ClientState, Any>
|
||||||
|
|
||||||
|
interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out ServerState> {
|
||||||
|
val id: Identifier
|
||||||
|
val blockSettings: AbstractBlock.Settings
|
||||||
|
// The default block settings are used for metal-enclosed modules.
|
||||||
|
get() = FabricBlockSettings
|
||||||
|
.of(Material.METAL)
|
||||||
|
.hardness(5.0f)
|
||||||
|
.resistance(6.0f)
|
||||||
|
|
||||||
|
val portLayout: Array<PhysicalPort>
|
||||||
|
|
||||||
|
fun onClientLoad(world: ClientWorld): CS
|
||||||
|
fun onClientUnload(state: @UnsafeVariance CS, world: ClientWorld) {}
|
||||||
|
|
||||||
|
interface ClientState {
|
||||||
|
val logicalDevice: DeviceInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package net.liquidev.dawd3.block.device
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder
|
||||||
|
import net.liquidev.dawd3.block.Blocks
|
||||||
|
import net.liquidev.dawd3.block.entity.D3BlockEntity
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
|
import net.minecraft.client.world.ClientWorld
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
|
private typealias DeviceBlockFactory = FabricBlockEntityTypeBuilder.Factory<DeviceBlockEntity>
|
||||||
|
|
||||||
|
class DeviceBlockEntity(
|
||||||
|
type: BlockEntityType<DeviceBlockEntity>,
|
||||||
|
blockPos: BlockPos,
|
||||||
|
blockState: BlockState,
|
||||||
|
private val descriptor: AnyDeviceBlockDescriptor,
|
||||||
|
) : D3BlockEntity(type, blockPos, blockState) {
|
||||||
|
companion object {
|
||||||
|
fun factory(descriptor: AnyDeviceBlockDescriptor): DeviceBlockFactory =
|
||||||
|
DeviceBlockFactory { blockPos, blockState ->
|
||||||
|
val type by lazy { Blocks.deviceBlocks[descriptor.id]!!.blockEntity }
|
||||||
|
DeviceBlockEntity(type, blockPos, blockState, descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var clientState: DeviceBlockDescriptor.ClientState? = null
|
||||||
|
private var serverState: Any? = null
|
||||||
|
|
||||||
|
override fun onClientLoad(world: ClientWorld) {
|
||||||
|
clientState = descriptor.onClientLoad(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClientUnload(world: ClientWorld) {
|
||||||
|
val clientState = clientState
|
||||||
|
if (clientState != null) {
|
||||||
|
descriptor.onClientUnload(clientState, world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package net.liquidev.dawd3.block.device
|
||||||
|
|
||||||
|
import net.liquidev.dawd3.audio.device.PortName
|
||||||
|
import net.liquidev.dawd3.common.Affine2x2
|
||||||
|
import net.minecraft.util.math.Direction
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
|
||||||
|
/** The physical appearance of a port. */
|
||||||
|
data class PhysicalPort(
|
||||||
|
val port: PortName,
|
||||||
|
/** The coordinates are relative, in range [0.0, 1.0] where 0 is top-left and 1 is bottom-right. */
|
||||||
|
val position: Vec2f,
|
||||||
|
val side: Side,
|
||||||
|
) {
|
||||||
|
/** Where to place the port on the model. */
|
||||||
|
enum class Side(
|
||||||
|
/** The transform matrix to apply to ports' coordinates in the model. */
|
||||||
|
val transform: Affine2x2,
|
||||||
|
/** The model face on which the port is placed. */
|
||||||
|
val face: Direction,
|
||||||
|
) {
|
||||||
|
Front(
|
||||||
|
transform = Affine2x2(1f, 0f, 0f, 1f),
|
||||||
|
face = Direction.NORTH,
|
||||||
|
),
|
||||||
|
Back(
|
||||||
|
transform = Affine2x2(-1f, 0f, 0f, -1f, translateX = 16f, translateY = 16f),
|
||||||
|
face = Direction.SOUTH,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package net.liquidev.dawd3.block.devices
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.audio.Audio
|
||||||
|
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||||
|
import net.liquidev.dawd3.audio.devices.TerminalDevice
|
||||||
|
import net.liquidev.dawd3.audio.generator.DeviceGraphGenerator
|
||||||
|
import net.liquidev.dawd3.audio.generator.MixGenerator
|
||||||
|
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
|
||||||
|
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||||
|
import net.minecraft.block.Material
|
||||||
|
import net.minecraft.client.world.ClientWorld
|
||||||
|
import net.minecraft.sound.BlockSoundGroup
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
|
||||||
|
object SpeakerBlockDescriptor : DeviceBlockDescriptor<SpeakerBlockDescriptor.ClientState, Unit> {
|
||||||
|
override val id = Identifier(Mod.id, "speaker")
|
||||||
|
override val blockSettings = FabricBlockSettings
|
||||||
|
.of(Material.WOOD)
|
||||||
|
.hardness(2.0f).resistance(6.0f)
|
||||||
|
.sounds(BlockSoundGroup.WOOD)!!
|
||||||
|
|
||||||
|
override val portLayout = arrayOf(
|
||||||
|
PhysicalPort(TerminalDevice.Input, Vec2f(0.5f, 0.5f), PhysicalPort.Side.Back)
|
||||||
|
)
|
||||||
|
|
||||||
|
class ClientState : DeviceBlockDescriptor.ClientState {
|
||||||
|
internal val channel: MixGenerator.Channel<DeviceGraphGenerator>
|
||||||
|
override val logicalDevice: DeviceInstance
|
||||||
|
|
||||||
|
init {
|
||||||
|
val generator = DeviceGraphGenerator()
|
||||||
|
channel = Audio.mixer.createChannel(generator)
|
||||||
|
|
||||||
|
val terminal = generator.terminalDevice
|
||||||
|
logicalDevice = terminal
|
||||||
|
|
||||||
|
generator.rebuildSim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClientLoad(world: ClientWorld): ClientState = ClientState()
|
||||||
|
|
||||||
|
override fun onClientUnload(state: ClientState, world: ClientWorld) {
|
||||||
|
state.channel.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package net.liquidev.dawd3.block.entity
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.block.entity.BlockEntity
|
||||||
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
|
import net.minecraft.client.world.ClientWorld
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
|
abstract class D3BlockEntity(
|
||||||
|
type: BlockEntityType<out D3BlockEntity>,
|
||||||
|
pos: BlockPos,
|
||||||
|
state: BlockState,
|
||||||
|
) :
|
||||||
|
BlockEntity(type, pos, state) {
|
||||||
|
|
||||||
|
open fun onClientLoad(world: ClientWorld) {}
|
||||||
|
open fun onClientUnload(world: ClientWorld) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package net.liquidev.dawd3.block.entity
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents
|
||||||
|
|
||||||
|
fun registerBlockEntityEvents() {
|
||||||
|
ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.register { blockEntity, world ->
|
||||||
|
if (blockEntity is D3BlockEntity) {
|
||||||
|
blockEntity.onClientLoad(world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register { blockEntity, world ->
|
||||||
|
if (blockEntity is D3BlockEntity) {
|
||||||
|
blockEntity.onClientUnload(world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/kotlin/net/liquidev/dawd3/common/Affine2x2.kt
Normal file
23
src/main/kotlin/net/liquidev/dawd3/common/Affine2x2.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package net.liquidev.dawd3.common
|
||||||
|
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
import net.minecraft.util.math.Vec3f
|
||||||
|
|
||||||
|
/** 2x2 matrix. */
|
||||||
|
data class Affine2x2(
|
||||||
|
val xx: Float,
|
||||||
|
val xy: Float,
|
||||||
|
val yx: Float,
|
||||||
|
val yy: Float,
|
||||||
|
val translateX: Float = 0f,
|
||||||
|
val translateY: Float = 0f,
|
||||||
|
) {
|
||||||
|
operator fun times(vec: Vec2f): Vec2f =
|
||||||
|
Vec2f(vec.x * xx + vec.y * xy + translateX, vec.y * yx + vec.y * yy + translateY)
|
||||||
|
|
||||||
|
fun timesXZ(vec: Vec3f): Vec3f {
|
||||||
|
val vecXZ = Vec2f(vec.x, vec.z)
|
||||||
|
val u = times(vecXZ)
|
||||||
|
return Vec3f(u.x, vec.y, u.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/kotlin/net/liquidev/dawd3/common/TaskQueue.kt
Normal file
18
src/main/kotlin/net/liquidev/dawd3/common/TaskQueue.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package net.liquidev.dawd3.common
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
class TaskQueue : Executor {
|
||||||
|
private val tasks = arrayListOf<Runnable>()
|
||||||
|
|
||||||
|
override fun execute(task: Runnable) {
|
||||||
|
tasks.add(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flush() {
|
||||||
|
for (task in tasks) {
|
||||||
|
task.run()
|
||||||
|
}
|
||||||
|
tasks.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/kotlin/net/liquidev/dawd3/common/math.kt
Normal file
14
src/main/kotlin/net/liquidev/dawd3/common/math.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.liquidev.dawd3.common
|
||||||
|
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
import net.minecraft.util.math.Vec3f
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
operator fun Vec2f.plus(other: Vec2f): Vec2f = Vec2f(x + other.x, y + other.y)
|
||||||
|
operator fun Vec2f.minus(other: Vec2f): Vec2f = Vec2f(x - other.x, y - other.y)
|
||||||
|
operator fun Vec2f.times(other: Vec2f): Vec2f = Vec2f(x * other.x, y * other.y)
|
||||||
|
operator fun Vec2f.div(other: Vec2f): Vec2f = Vec2f(x / other.x, y / other.y)
|
||||||
|
|
||||||
|
fun Vec3f.min(other: Vec3f): Vec3f = Vec3f(min(x, other.x), min(y, other.y), min(z, other.z))
|
||||||
|
fun Vec3f.max(other: Vec3f): Vec3f = Vec3f(max(x, other.x), max(y, other.y), max(z, other.z))
|
||||||
14
src/main/kotlin/net/liquidev/dawd3/datagen/Datagen.kt
Normal file
14
src/main/kotlin/net/liquidev/dawd3/datagen/Datagen.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.liquidev.dawd3.datagen
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint
|
||||||
|
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
|
||||||
|
object Datagen : DataGeneratorEntrypoint {
|
||||||
|
private val logger = Mod.logger<Datagen>()
|
||||||
|
|
||||||
|
override fun onInitializeDataGenerator(datagen: FabricDataGenerator) {
|
||||||
|
logger.info("initializing data generator")
|
||||||
|
datagen.addProvider(::ModelDatagen)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/main/kotlin/net/liquidev/dawd3/datagen/ModelDatagen.kt
Normal file
49
src/main/kotlin/net/liquidev/dawd3/datagen/ModelDatagen.kt
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package net.liquidev.dawd3.datagen
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
|
||||||
|
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.block.Blocks
|
||||||
|
import net.liquidev.dawd3.datagen.device.DeviceBlockModel
|
||||||
|
import net.minecraft.data.client.*
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ModelDatagen(generator: FabricDataGenerator) : FabricModelProvider(generator) {
|
||||||
|
private companion object {
|
||||||
|
val logger = Mod.logger<ModelDatagen>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateBlockStateModels(generator: BlockStateModelGenerator) {
|
||||||
|
logger.info("generating block state models")
|
||||||
|
|
||||||
|
for ((id, deviceBlock) in Blocks.deviceBlocks) {
|
||||||
|
val modelId = Identifier(Mod.id, "block/${id.path}")
|
||||||
|
generator.modelCollector.accept(modelId) { DeviceBlockModel.generate(id, deviceBlock) }
|
||||||
|
generator.blockStateCollector.accept(horizontallyRotatableBlockState(id, deviceBlock))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun horizontallyRotatableBlockState(
|
||||||
|
id: Identifier,
|
||||||
|
deviceBlock: Blocks.RegisteredDeviceBlock,
|
||||||
|
): BlockStateSupplier {
|
||||||
|
val modelId = Identifier(Mod.id, "block/${id.path}")
|
||||||
|
return VariantsBlockStateSupplier.create(
|
||||||
|
deviceBlock.block,
|
||||||
|
BlockStateVariant.create().put(VariantSettings.MODEL, modelId)
|
||||||
|
)
|
||||||
|
.coordinate(BlockStateModelGenerator.createNorthDefaultHorizontalRotationStates())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun generateItemModels(generator: ItemModelGenerator) {
|
||||||
|
logger.info("generating item models")
|
||||||
|
for ((id, deviceBlock) in Blocks.deviceBlocks) {
|
||||||
|
generator.register(
|
||||||
|
deviceBlock.item.item.item,
|
||||||
|
Model(Optional.of(Identifier(Mod.id, "block/${id.path}")), Optional.empty())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package net.liquidev.dawd3.datagen.device
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import net.liquidev.dawd3.Mod
|
||||||
|
import net.liquidev.dawd3.block.Blocks
|
||||||
|
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||||
|
import net.liquidev.dawd3.common.*
|
||||||
|
import net.liquidev.dawd3.datagen.json.element
|
||||||
|
import net.liquidev.dawd3.datagen.json.face
|
||||||
|
import net.liquidev.dawd3.datagen.json.faces
|
||||||
|
import net.liquidev.dawd3.datagen.json.uvRect
|
||||||
|
import net.liquidev.dawd3.datagen.jsonArray
|
||||||
|
import net.liquidev.dawd3.datagen.jsonObject
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.math.Vec2f
|
||||||
|
import net.minecraft.util.math.Vec3f
|
||||||
|
|
||||||
|
object DeviceBlockModel {
|
||||||
|
private val portSize = Vec2f(3f, 3f)
|
||||||
|
private const val portTexture = "${Mod.id}:device/port"
|
||||||
|
|
||||||
|
private fun getSideTexture(id: Identifier) =
|
||||||
|
"${Mod.id}:block/${id.path}_side"
|
||||||
|
|
||||||
|
private fun getFrontTexture(id: Identifier) =
|
||||||
|
"${Mod.id}:block/${id.path}_front"
|
||||||
|
|
||||||
|
fun generate(
|
||||||
|
id: Identifier,
|
||||||
|
deviceBlock: Blocks.RegisteredDeviceBlock,
|
||||||
|
): JsonObject {
|
||||||
|
return jsonObject {
|
||||||
|
add("parent", "block/block")
|
||||||
|
|
||||||
|
add("textures", jsonObject {
|
||||||
|
add("side", getSideTexture(id))
|
||||||
|
add("front", getFrontTexture(id))
|
||||||
|
add("port", portTexture)
|
||||||
|
add("particle", "#side")
|
||||||
|
})
|
||||||
|
|
||||||
|
add("elements", jsonArray {
|
||||||
|
add(blockElement)
|
||||||
|
|
||||||
|
val descriptor = deviceBlock.descriptor
|
||||||
|
for (port in descriptor.portLayout) {
|
||||||
|
add(getPortElement(port))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val blockElement = element(
|
||||||
|
from = Vec3f(0f, 0f, 0f),
|
||||||
|
to = Vec3f(16f, 16f, 16f),
|
||||||
|
faces = faces(
|
||||||
|
north = face(texture = "#front", cullface = "north"),
|
||||||
|
east = face(texture = "#side", cullface = "east"),
|
||||||
|
south = face(texture = "#side", cullface = "south"),
|
||||||
|
west = face(texture = "#side", cullface = "west"),
|
||||||
|
up = face(texture = "#side", cullface = "up"),
|
||||||
|
down = face(texture = "#side", cullface = "down"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val portPlayArea = Vec2f(16f, 16f) - portSize
|
||||||
|
private fun relativeToAbsolutePortPosition(position: Vec2f): Vec2f {
|
||||||
|
val inv = Vec2f(1f - position.x, 1f - position.y)
|
||||||
|
return inv * portPlayArea
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPortElement(port: PhysicalPort): JsonObject {
|
||||||
|
val bottomLeft2D = relativeToAbsolutePortPosition(port.position)
|
||||||
|
val topRight2D = bottomLeft2D + portSize
|
||||||
|
val bottomLeft = Vec3f(bottomLeft2D.x, bottomLeft2D.y, -0.01f)
|
||||||
|
val topRight = Vec3f(topRight2D.x, topRight2D.y, 0.01f)
|
||||||
|
|
||||||
|
val rotatedBottomLeft = port.side.transform.timesXZ(bottomLeft)
|
||||||
|
val rotatedTopRight = port.side.transform.timesXZ(topRight)
|
||||||
|
|
||||||
|
val min = rotatedBottomLeft.min(rotatedTopRight)
|
||||||
|
val max = rotatedBottomLeft.max(rotatedTopRight)
|
||||||
|
|
||||||
|
return element(
|
||||||
|
from = min,
|
||||||
|
to = max,
|
||||||
|
faces = jsonObject {
|
||||||
|
val faceName = port.side.face.getName()
|
||||||
|
add(
|
||||||
|
faceName, face(
|
||||||
|
texture = "#port",
|
||||||
|
cullface = faceName,
|
||||||
|
uv = uvRect(0f, 0f, 4f, 4f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/kotlin/net/liquidev/dawd3/datagen/json/json.kt
Normal file
74
src/main/kotlin/net/liquidev/dawd3/datagen/json/json.kt
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package net.liquidev.dawd3.datagen
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import net.minecraft.util.math.Vec3f
|
||||||
|
|
||||||
|
class JsonObjectBuilder {
|
||||||
|
private val obj = JsonObject()
|
||||||
|
|
||||||
|
fun add(key: String, value: Boolean) =
|
||||||
|
obj.add(key, JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(key: String, value: Int) =
|
||||||
|
obj.add(key, JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(key: String, value: Float) =
|
||||||
|
obj.add(key, JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(key: String, value: String) =
|
||||||
|
obj.add(key, JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(key: String, value: JsonObject) =
|
||||||
|
obj.add(key, value)
|
||||||
|
|
||||||
|
fun add(key: String, value: JsonArray) =
|
||||||
|
obj.add(key, value)
|
||||||
|
|
||||||
|
fun build(): JsonObject = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonArrayBuilder {
|
||||||
|
private val array = JsonArray()
|
||||||
|
|
||||||
|
fun add(value: Boolean) =
|
||||||
|
array.add(JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(value: Int) =
|
||||||
|
array.add(JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(value: Float) =
|
||||||
|
array.add(JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(value: String) =
|
||||||
|
array.add(JsonPrimitive(value))
|
||||||
|
|
||||||
|
fun add(value: JsonObject) =
|
||||||
|
array.add(value)
|
||||||
|
|
||||||
|
fun add(value: JsonArray) =
|
||||||
|
array.add(value)
|
||||||
|
|
||||||
|
fun build(): JsonArray = array
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jsonObject(build: JsonObjectBuilder.() -> Unit): JsonObject {
|
||||||
|
val builder = JsonObjectBuilder()
|
||||||
|
with(builder, build)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jsonArray(build: JsonArrayBuilder.() -> Unit): JsonArray {
|
||||||
|
val builder = JsonArrayBuilder()
|
||||||
|
with(builder, build)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Vec3f.toJson(): JsonArray {
|
||||||
|
return jsonArray {
|
||||||
|
add(x)
|
||||||
|
add(y)
|
||||||
|
add(z)
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/kotlin/net/liquidev/dawd3/datagen/json/model.kt
Normal file
61
src/main/kotlin/net/liquidev/dawd3/datagen/json/model.kt
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package net.liquidev.dawd3.datagen.json
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import net.liquidev.dawd3.datagen.jsonArray
|
||||||
|
import net.liquidev.dawd3.datagen.jsonObject
|
||||||
|
import net.liquidev.dawd3.datagen.toJson
|
||||||
|
import net.minecraft.util.math.Vec3f
|
||||||
|
|
||||||
|
fun element(
|
||||||
|
from: Vec3f,
|
||||||
|
to: Vec3f,
|
||||||
|
faces: JsonObject,
|
||||||
|
): JsonObject {
|
||||||
|
return jsonObject {
|
||||||
|
add("from", from.toJson())
|
||||||
|
add("to", to.toJson())
|
||||||
|
add("faces", faces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun faces(
|
||||||
|
north: JsonObject? = null,
|
||||||
|
east: JsonObject? = null,
|
||||||
|
south: JsonObject? = null,
|
||||||
|
west: JsonObject? = null,
|
||||||
|
up: JsonObject? = null,
|
||||||
|
down: JsonObject? = null,
|
||||||
|
): JsonObject {
|
||||||
|
return jsonObject {
|
||||||
|
if (north != null) add("north", north)
|
||||||
|
if (east != null) add("east", east)
|
||||||
|
if (south != null) add("south", south)
|
||||||
|
if (west != null) add("west", west)
|
||||||
|
if (up != null) add("up", up)
|
||||||
|
if (down != null) add("down", down)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uvRect(x1: Float, y1: Float, x2: Float, y2: Float): JsonArray {
|
||||||
|
return jsonArray {
|
||||||
|
add(x1)
|
||||||
|
add(y1)
|
||||||
|
add(x2)
|
||||||
|
add(y2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun face(
|
||||||
|
uv: JsonArray? = null,
|
||||||
|
texture: String,
|
||||||
|
cullface: String? = null,
|
||||||
|
rotation: Int? = null,
|
||||||
|
): JsonObject {
|
||||||
|
return jsonObject {
|
||||||
|
if (uv != null) add("uv", uv)
|
||||||
|
add("texture", texture)
|
||||||
|
if (cullface != null) add("cullface", cullface)
|
||||||
|
if (rotation != null) add("rotation", rotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,14 +30,20 @@ object Items {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Icon for creative tab
|
// Icon for creative tab
|
||||||
val dawd3 = registry.add("dawd3", RegisteredItem(Item(FabricItemSettings())).hiddenFromCreativeTab())
|
val dawd3 = registry.add(
|
||||||
|
Identifier(Mod.id, "dawd3"),
|
||||||
|
RegisteredItem(Item(FabricItemSettings())).hiddenFromCreativeTab()
|
||||||
|
)
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
val patchCable = addItem("patch_cable", PatchCable(FabricItemSettings().group(ItemGroup.REDSTONE)))
|
val patchCable =
|
||||||
|
addItem("patch_cable", PatchCable(FabricItemSettings().group(ItemGroup.REDSTONE)))
|
||||||
|
|
||||||
fun addItem(name: String, item: Item) {
|
fun addItem(name: Identifier, item: Item): D3Registry.Registered<RegisteredItem> =
|
||||||
registry.add(name, RegisteredItem(item))
|
registry.add(name, RegisteredItem(item))
|
||||||
}
|
|
||||||
|
fun addItem(name: String, item: Item) =
|
||||||
|
addItem(Identifier(Mod.id, name), item)
|
||||||
|
|
||||||
data class RegisteredItem(
|
data class RegisteredItem(
|
||||||
val item: Item,
|
val item: Item,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package net.liquidev.dawd3.mixin
|
|
||||||
|
|
||||||
import net.minecraft.client.sound.SoundManager
|
|
||||||
import net.minecraft.client.sound.SoundSystem
|
|
||||||
import org.spongepowered.asm.mixin.Mixin
|
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor
|
|
||||||
|
|
||||||
@Mixin(SoundManager::class)
|
|
||||||
interface SoundManagerAccessor {
|
|
||||||
@Accessor
|
|
||||||
fun getSoundSystem(): SoundSystem
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package net.liquidev.dawd3.mixin
|
|
||||||
|
|
||||||
import net.minecraft.client.sound.Channel
|
|
||||||
import net.minecraft.client.sound.SoundSystem
|
|
||||||
import org.spongepowered.asm.mixin.Mixin
|
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor
|
|
||||||
|
|
||||||
@Mixin(SoundSystem::class)
|
|
||||||
interface SoundSystemAccessor {
|
|
||||||
@Accessor
|
|
||||||
fun getChannel(): Channel
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package net.liquidev.dawd3.sound
|
|
||||||
|
|
||||||
import net.liquidev.d3r.AudioOutputStream
|
|
||||||
|
|
||||||
abstract class AudioGenerator : AudioOutputStream {
|
|
||||||
private var outputBuffer: FloatArray? = null
|
|
||||||
|
|
||||||
private fun allocateOutputBuffer(sampleCount: Int): FloatArray {
|
|
||||||
val inOutputBuffer = outputBuffer
|
|
||||||
if (inOutputBuffer == null || inOutputBuffer.size < sampleCount) {
|
|
||||||
outputBuffer = FloatArray(sampleCount)
|
|
||||||
}
|
|
||||||
return outputBuffer!!
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun generate(output: FloatArray, sampleCount: Int, channels: Int)
|
|
||||||
|
|
||||||
override fun getOutputBuffer(sampleCount: Int, channels: Int): FloatArray {
|
|
||||||
val outputBuffer = allocateOutputBuffer(sampleCount)
|
|
||||||
generate(outputBuffer, sampleCount, channels)
|
|
||||||
return outputBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(message: String) {
|
|
||||||
println("Error reported by audio stream: $message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package net.liquidev.dawd3.sound
|
|
||||||
|
|
||||||
import net.liquidev.d3r.D3r
|
|
||||||
|
|
||||||
/** Common sound utilities. */
|
|
||||||
object Sound {
|
|
||||||
const val sampleRate = 48000
|
|
||||||
private const val bufferSize = 256
|
|
||||||
|
|
||||||
val outputDeviceId: Int
|
|
||||||
val outputStreamId: Int
|
|
||||||
|
|
||||||
init {
|
|
||||||
D3r.openDefaultHost()
|
|
||||||
outputDeviceId = D3r.openDefaultOutputDevice()
|
|
||||||
outputStreamId = D3r.openOutputStream(
|
|
||||||
outputDeviceId,
|
|
||||||
sampleRate,
|
|
||||||
1,
|
|
||||||
bufferSize,
|
|
||||||
SineOscGenerator(frequency = 440.0f, amplitude = 0.5f)
|
|
||||||
)
|
|
||||||
D3r.startPlayback(outputStreamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun forceInitializationNow() {}
|
|
||||||
|
|
||||||
fun deinitialize() {
|
|
||||||
D3r.closeOutputStream(outputStreamId)
|
|
||||||
D3r.closeOutputDevice(outputDeviceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"variants": {
|
|
||||||
"facing=north": { "model": "dawd3:block/speaker" },
|
|
||||||
"facing=east": { "model": "dawd3:block/speaker", "y": 90 },
|
|
||||||
"facing=south": { "model": "dawd3:block/speaker", "y": 180 },
|
|
||||||
"facing=west": { "model": "dawd3:block/speaker", "y": 270 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
{
|
{
|
||||||
"item.dawd3.patch_cable": "Patch Cable"
|
"itemGroup.dawd3.main": "dawd³",
|
||||||
|
"item.dawd3.patch_cable": "Patch Cable",
|
||||||
|
"block.dawd3.speaker": "Speaker"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"parent": "dawd3:block/module_with_front",
|
|
||||||
"textures": {
|
|
||||||
"front": "dawd3:block/speaker_front"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 125 B |
Binary file not shown.
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 297 B |
BIN
src/main/resources/assets/dawd3/textures/block/speaker_side.png
Normal file
BIN
src/main/resources/assets/dawd3/textures/block/speaker_side.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 290 B |
BIN
src/main/resources/assets/dawd3/textures/device/port.png
Normal file
BIN
src/main/resources/assets/dawd3/textures/device/port.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 B |
|
|
@ -6,7 +6,5 @@
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"SoundManagerAccessor",
|
|
||||||
"SoundSystemAccessor"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@
|
||||||
"adapter": "kotlin",
|
"adapter": "kotlin",
|
||||||
"value": "net.liquidev.dawd3.Mod"
|
"value": "net.liquidev.dawd3.Mod"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"fabric-datagen": [
|
||||||
|
{
|
||||||
|
"adapter": "kotlin",
|
||||||
|
"value": "net.liquidev.dawd3.datagen.Datagen"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
|
|
|
||||||
Reference in a new issue