This commit is contained in:
りき萌 2022-11-20 23:06:55 +01:00
parent f8612db6ef
commit 6aba8d98ff
64 changed files with 1264 additions and 207 deletions

View file

@ -17,7 +17,11 @@ dependencies {
minecraft("com.mojang", "minecraft", project.extra["minecraft_version"] as String)
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-api", "fabric-api", project.extra["fabric_version"] as String)
modImplementation(
"net.fabricmc.fabric-api",
"fabric-api",
project.extra["fabric_version"] as String
)
modImplementation(
"net.fabricmc",
"fabric-language-kotlin",
@ -36,6 +40,27 @@ rustImport {
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 {
val javaVersion = JavaVersion.toVersion((project.extra["java_version"] as String).toInt())

7
d3r/Cargo.lock generated
View file

@ -220,6 +220,7 @@ dependencies = [
"bitvec",
"cpal",
"env_logger",
"hound",
"jni 0.20.0",
"log",
]
@ -264,6 +265,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "humantime"
version = "2.1.0"

View file

@ -12,5 +12,6 @@ crate-type = ["cdylib"]
bitvec = "1.0.1"
cpal = "0.14.1"
env_logger = "0.9.3"
hound = "3.5.0"
jni = "0.20.0"
log = "0.4.17"

View file

@ -1,8 +1,10 @@
use std::cell::RefCell;
use std::fmt::Display;
use std::fs::File;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{BufferSize, Device, Host, SampleRate, Stream, StreamConfig};
use hound::{SampleFormat, WavSpec};
use jni::objects::{JClass, JMethodID, JObject};
use jni::signature::ReturnType;
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 jvm = env.get_java_vm().map_err(|e| e.to_string())?;
let mut initialized = false;
let stream = device
.build_output_stream(
&config.clone(),

Binary file not shown.

Binary file not shown.

BIN
proj/block/speaker_side.ase Normal file

Binary file not shown.

BIN
proj/device/port.ase Normal file

Binary file not shown.

View file

@ -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

View 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
}
}
}

View 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"
}
}

View file

@ -1,7 +1,7 @@
package net.liquidev.d3r;
public interface AudioOutputStream {
float[] getOutputBuffer(int sampleCount, int channels);
float[] getOutputBuffer(int sampleCount, int channelCount);
void error(String message);
}

View file

@ -9,7 +9,7 @@ import java.nio.file.Path;
import java.util.Comparator;
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;
public static void load() throws D3rException, IOException {

View file

@ -7,8 +7,8 @@ abstract class D3Registry<T> {
var registered = arrayListOf<Registered<T>>()
fun add(id: String, item: T): Registered<T> {
val entry = Registered(Identifier(Mod.id, id), item)
fun add(id: Identifier, item: T): Registered<T> {
val entry = Registered(id, item)
registered.add(entry)
return entry
}

View file

@ -4,33 +4,45 @@ import net.fabricmc.api.ClientModInitializer
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.liquidev.d3r.D3r
import net.liquidev.dawd3.audio.Audio
import net.liquidev.dawd3.block.Blocks
import net.liquidev.dawd3.block.entity.registerBlockEntityEvents
import net.liquidev.dawd3.item.Items
import net.liquidev.dawd3.sound.Sound
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@Suppress("UNUSED")
object Mod : ModInitializer, ClientModInitializer {
const val id = "dawd3"
private val logger = LoggerFactory.getLogger("dawd³")
val logger = logger(null)
override fun onInitialize() {
logger.info("hello, sound traveler! welcome to the dawd³ experience")
Blocks.blockRegistry.registerAll()
Blocks.initialize()
Items.registry.registerAll()
}
override fun onInitializeClient() {
logger.info("booting up sound engine")
D3r.load()
Sound.forceInitializationNow()
Audio.forceInitializationNow()
ClientLifecycleEvents.CLIENT_STOPPING.register {
logger.info("shutting down sound engine")
Sound.deinitialize()
Audio.deinitialize()
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)
}

View 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)
}
}

View 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!!
}
}

View file

@ -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) {}
}

View file

@ -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)"
}
}

View 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")
}

View 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)
}
}
}

View file

@ -0,0 +1,5 @@
package net.liquidev.dawd3.audio.device
class NoSuchPortException(what: String) : Exception(what)
class PortAlreadyExistsException(what: String) : Exception(what)

View 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

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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")
}
}

View file

@ -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]
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
class SineOscGenerator(frequency: Float, private val amplitude: Float) : AudioGenerator() {
private val phaseStep = (1.0f / Sound.sampleRate.toFloat()) * frequency
class SineOscGenerator(frequency: Float, private val amplitude: Amplitude) : AudioGenerator() {
private val phaseStep = (1.0f / Audio.sampleRate.toFloat()) * frequency
private var phase = 0.0f
private fun synthesize(): Float {
phase += phaseStep
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) {
val sample = synthesize()
output[i] = sample

View 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))
}

View file

@ -1,38 +1,50 @@
package net.liquidev.dawd3.block
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.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.minecraft.block.Block
import net.minecraft.block.Material
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.item.BlockItem
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
object Blocks {
var blockRegistry = object : D3Registry<Block>() {
override fun doRegister(identifier: Identifier, item: Block) {
Registry.register(Registry.BLOCK, identifier, item)
}
}
val speaker = add("speaker", SpeakerBlock(moduleBlockSettings()))
val speakerEntity = Registry.register(
Registry.BLOCK_ENTITY_TYPE,
Identifier(Mod.id, "speaker"),
FabricBlockEntityTypeBuilder.create(::SpeakerBlockEntity, speaker.item).build(),
data class RegisteredDeviceBlock(
val block: Block,
val item: D3Registry.Registered<Items.RegisteredItem>,
val blockEntity: BlockEntityType<DeviceBlockEntity>,
val descriptor: AnyDeviceBlockDescriptor,
)
private fun moduleBlockSettings() = FabricBlockSettings
.of(Material.METAL)
.hardness(5.0f)
.resistance(6.0f)
val deviceBlocks = hashMapOf<Identifier, RegisteredDeviceBlock>()
private fun add(name: String, block: Block): D3Registry.Registered<Block> {
Items.addItem(name, BlockItem(block, FabricItemSettings().group(Items.creativeTab)))
return blockRegistry.add(name, block)
fun registerDeviceBlock(descriptor: AnyDeviceBlockDescriptor): RegisteredDeviceBlock {
val 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() {}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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)
}
}
}

View file

@ -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,
),
}
}

View file

@ -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()
}
}

View file

@ -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) {}
}

View file

@ -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)
}
}
}

View 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)
}
}

View 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()
}
}

View 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))

View 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)
}
}

View 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())
)
}
}
}

View file

@ -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)
)
)
}
)
}
}

View 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)
}
}

View 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)
}
}

View file

@ -30,14 +30,20 @@ object Items {
.build()
// 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
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))
}
fun addItem(name: String, item: Item) =
addItem(Identifier(Mod.id, name), item)
data class RegisteredItem(
val item: Item,

View file

@ -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
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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)
}
}

View file

@ -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 }
}
}

View file

@ -1,3 +1,5 @@
{
"item.dawd3.patch_cable": "Patch Cable"
"itemGroup.dawd3.main": "dawd³",
"item.dawd3.patch_cable": "Patch Cable",
"block.dawd3.speaker": "Speaker"
}

View file

@ -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

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

View file

@ -6,7 +6,5 @@
"defaultRequire": 1
},
"mixins": [
"SoundManagerAccessor",
"SoundSystemAccessor"
]
}

View file

@ -27,6 +27,12 @@
"adapter": "kotlin",
"value": "net.liquidev.dawd3.Mod"
}
],
"fabric-datagen": [
{
"adapter": "kotlin",
"value": "net.liquidev.dawd3.datagen.Datagen"
}
]
},
"mixins": [