pulse duty cycle and a bunch of other fixes

This commit is contained in:
りき萌 2023-05-03 22:46:46 +02:00
parent c72730f1a2
commit c682d69a3e
18 changed files with 350 additions and 215 deletions

View file

@ -1,5 +1,5 @@
// 1.19.2 2023-05-03T17:34:34.425318453 Models
ff88cf4c4547ef552e4ed2f859978e7b875c0b56 assets/dawd3/models/block/amplifier.json
// 1.19.2 2023-05-03T22:15:16.62168443 Models
5919f5e0f79620a682f481a8e02995f95724b2ce assets/dawd3/models/block/amplifier.json
c3ea3e310d8fe7796b5b055d34db712cb8c7ac5a assets/dawd3/blockstates/triangle_oscillator.json
97f0b7f5e19cd8c92915c4dfdedcee0e0b2a4b7f assets/dawd3/models/item/amplifier.json
0fb5cd695c2a82a2353809529dc8b5086ee2c87d assets/dawd3/blockstates/phase.json
@ -7,24 +7,24 @@ c3ea3e310d8fe7796b5b055d34db712cb8c7ac5a assets/dawd3/blockstates/triangle_oscil
a4e8bc89d39021eb8d56ad7735216cb851d67287 assets/dawd3/models/item/light_blue_patch_cable.json
aa1a1807d2c46f1f25e3d0c507952fefb5b3cd9f assets/dawd3/models/item/knob.json
d65fd7b21da2adf55a0b076103103da7e7bb453a assets/dawd3/models/block/speaker.json
df1b924ee7f1a4f9a6496f52a27d74b449ef05fc assets/dawd3/models/block/triangle_oscillator.json
fe4987cffd1253462cc8440c90ff2341ac3026a4 assets/dawd3/models/block/triangle_oscillator.json
d1e691559156bd4bf0ec0d488d1a28ac193f4967 assets/dawd3/blockstates/adsr.json
123ed8aa83a77b4714cee4dfc6747832cf72c75c assets/dawd3/blockstates/mixer.json
becb814a10bcf2d3e071a6479e9b2289165d0059 assets/dawd3/models/item/modulator.json
f69a4acfdf715c64f64830ce0a79aa452ad4760a assets/dawd3/models/item/saw_oscillator.json
e289094bba4daa5de7873e6af154a25f4defc16b assets/dawd3/models/block/modulator.json
851149820d465dc203cc75e8808a5387e8eb5426 assets/dawd3/models/block/modulator.json
32bb0e6e3bf75b9005602e8fb1042ad5d41286ad assets/dawd3/models/item/lime_patch_cable.json
bb8b84a5a98c77aaf9ee25760dfc352b760fc2d3 assets/dawd3/models/item/keyboard.json
b5cc6f4b4af952380a1539489f2e406bc6ebe5fa assets/dawd3/blockstates/amplifier.json
2a6b9e841be2ba74ff159488c86d5a569f28798b assets/dawd3/models/block/phase.json
019bcf77479bfc64a04555f9864e2cbac2055b9f assets/dawd3/models/block/phase.json
292685c025f28911bca9252da3e4ac72998b3c7a assets/dawd3/blockstates/pulse_oscillator.json
63c9e9521285c0a1041709a42b2232517debee38 assets/dawd3/blockstates/keyboard.json
956d8f117df95cf62c8cac375cff853df96840d6 assets/dawd3/models/item/pink_patch_cable.json
9c18a8292a0c9990cd23bebf5c6191c2114ccc6d assets/dawd3/models/item/black_patch_cable.json
96135c420e211d0cfee4aff9e1ae98484b4603c4 assets/dawd3/models/block/saw_oscillator.json
0afd7ba8d87444961a33cb87bba122fca5ebef8e assets/dawd3/models/block/saw_oscillator.json
67ff4ae22c5f716f1aa2b6e2dac31d12be2336d1 assets/dawd3/blockstates/modulator.json
64c9ff6a94721f17a823c9d253559e5b9e73662a assets/dawd3/models/block/sine_oscillator.json
03377aa722945c78e2746f199d558f8338f61c96 assets/dawd3/models/block/mixer.json
012ae6bec3490e1e4f4b8a4677ce46600e1a501c assets/dawd3/models/block/mixer.json
0812a674d14cfc6fbb7c0e2ac1b473bf2afe1965 assets/dawd3/models/item/brown_patch_cable.json
6e01a1aa07f3a36d7950a1b00d1bc6e9045b9995 assets/dawd3/blockstates/knob.json
bf0e322e33123cb6873c2da4e8c6ab85688deb4e assets/dawd3/models/item/gray_patch_cable.json
@ -40,12 +40,12 @@ f7b47538f17992177e97e06842c0039ae5096b2b assets/dawd3/blockstates/speaker.json
0d899dc2bd693c95d582231a00faf40bf1d67a47 assets/dawd3/blockstates/saw_oscillator.json
8c4b8147bfea2bdb8c1d348bc332a60d2cd82d68 assets/dawd3/models/item/phase.json
8aa966337109315240614d5257eb72f959eba5d8 assets/dawd3/models/item/orange_patch_cable.json
46e362f3f96c54438c29132f76441e6cddb94564 assets/dawd3/models/block/pulse_oscillator.json
0b8f2a8c456a65c5dd415fc6ad3459dd8b43f954 assets/dawd3/models/block/pulse_oscillator.json
12c4bfd825b2476955afcd3bb23c1f736ce68caa assets/dawd3/models/item/yellow_patch_cable.json
aab1bce7ec4e7c7dccd3d33d4242de12b63a981d assets/dawd3/models/item/white_patch_cable.json
6cdda36e539e23acf3db70d761338e88932a6ebb assets/dawd3/models/item/purple_patch_cable.json
e3c6aacd49a6395f37047d3df31f91a18a411267 assets/dawd3/models/item/speaker.json
49d732a11ba0d66d716dcb6d86718075b6ccf5aa assets/dawd3/models/block/keyboard.json
4be49e87d036188e4c9d5285d7fcebbb25954dfe assets/dawd3/models/block/keyboard.json
2adb4f854a9f13090dffaf8ab9dfe0553cc59a80 assets/dawd3/models/item/triangle_oscillator.json
3b1811bab3ba394ba03b67ac2efc72cb35316dc8 assets/dawd3/blockstates/sine_oscillator.json
8688de3eadb6579cac89522683c394f18733e614 assets/dawd3/models/block/knob.json

View file

@ -40,6 +40,30 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
3.0,
-0.01
],
"to": [
7.0,
7.0,
0.01
]
},
{
"faces": {
"north": {
@ -87,30 +111,6 @@
7.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
3.0,
-0.01
],
"to": [
7.0,
7.0,
0.01
]
}
],
"textures": {

View file

@ -40,6 +40,30 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
9.0,
9.0,
-0.01
],
"to": [
13.0,
13.0,
0.01
]
},
{
"faces": {
"north": {
@ -87,30 +111,6 @@
7.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
9.0,
9.0,
-0.01
],
"to": [
13.0,
13.0,
0.01
]
}
],
"textures": {

View file

@ -40,30 +40,6 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
0.0,
0.0,
4.0,
4.0
]
}
},
"from": [
9.0,
3.0,
-0.01
],
"to": [
13.0,
7.0,
0.01
]
},
{
"faces": {
"north": {
@ -88,6 +64,30 @@
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
0.0,
0.0,
4.0,
4.0
]
}
},
"from": [
9.0,
3.0,
-0.01
],
"to": [
13.0,
7.0,
0.01
]
},
{
"faces": {
"north": {

View file

@ -40,30 +40,6 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
},
{
"faces": {
"north": {
@ -87,6 +63,30 @@
10.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
}
],
"textures": {

View file

@ -64,30 +64,6 @@
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
0.0,
0.0,
4.0,
4.0
]
}
},
"from": [
9.0,
3.0,
-0.01
],
"to": [
13.0,
7.0,
0.01
]
},
{
"faces": {
"north": {
@ -111,6 +87,30 @@
10.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
0.0,
0.0,
4.0,
4.0
]
}
},
"from": [
9.0,
3.0,
-0.01
],
"to": [
13.0,
7.0,
0.01
]
}
],
"textures": {

View file

@ -55,12 +55,12 @@
},
"from": [
9.0,
3.0,
9.0,
-0.01
],
"to": [
13.0,
7.0,
13.0,
0.01
]
},
@ -79,11 +79,35 @@
},
"from": [
3.0,
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
0.0,
0.0,
4.0,
4.0
]
}
},
"from": [
9.0,
3.0,
-0.01
],
"to": [
13.0,
7.0,
0.01
]

View file

@ -40,30 +40,6 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
},
{
"faces": {
"north": {
@ -87,6 +63,30 @@
10.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
}
],
"textures": {

View file

@ -40,30 +40,6 @@
16.0
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
},
{
"faces": {
"north": {
@ -87,6 +63,30 @@
10.0,
0.01
]
},
{
"faces": {
"north": {
"cullface": "north",
"texture": "#port",
"uv": [
4.0,
0.0,
8.0,
4.0
]
}
},
"from": [
3.0,
6.0,
-0.01
],
"to": [
7.0,
10.0,
0.01
]
}
],
"textures": {

View file

@ -7,37 +7,56 @@ import net.liquidev.dawd3.audio.math.oversample
import net.liquidev.dawd3.audio.math.polyBLEP
import net.minecraft.util.Identifier
class PulseOscillatorDevice : Device<NoControls> {
class PulseOscillatorDevice : Device<PulseOscillatorDevice.Controls> {
companion object : DeviceDescriptor {
override val id = Identifier(Mod.id, "pulse_oscillator")
val dutyCycleControl = ControlDescriptor(id, "duty_cycle", 0.5f)
val dutyCycleCVControl = ControlDescriptor(id, "duty_cycle_cv", 0.5f)
val phasePort = InputPortName(id, "phase")
val dutyCyclePort = InputPortName(id, "duty_cycle")
val outputPort = OutputPortName(id, "output")
// TODO: Make this into a couple controls and put it into a nice UI
const val usePolyBLEP = true
const val oversampling = 4
}
// Especially this should be a knob+CV controlled thing.
const val dutyCycle = 0.5f
class Controls : ControlSet {
val dutyCycle = FloatControl(dutyCycleControl)
val dutyCycleCV = FloatControl(dutyCycleCVControl)
override fun visitControls(visit: (ControlName, Control) -> Unit) {
visit(dutyCycleControl.name, dutyCycle)
visit(dutyCycleCVControl.name, dutyCycleCV)
}
}
private val phase = InputPort()
private val dutyCycle = InputPort()
private val output = OutputPort(bufferCount = 1)
private val phaseDerivative = PhaseDerivative()
override fun process(sampleCount: Int, controls: NoControls) {
override fun process(sampleCount: Int, controls: Controls) {
val phaseBuffer = phase.getConnectedOutputBuffer(0, sampleCount)
val dutyCycleBuffer = dutyCycle.getConnectedOutputBuffer(0, sampleCount)
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
val constantDutyCycle = controls.dutyCycle.value
val dutyCycleCV = controls.dutyCycleCV.value
for (i in 0 until sampleCount) {
val phase = phaseBuffer[i]
val deltaPhase = phaseDerivative.stepNextDerivative(phase)
outputBuffer[i] = oversample(phase, deltaPhase, oversampling, ::pulse)
val dutyCycle = constantDutyCycle + dutyCycleBuffer[i] * dutyCycleCV
outputBuffer[i] =
oversample(phase, deltaPhase, oversampling) { t, dt -> pulse(t, dt, dutyCycle) }
}
}
private fun pulse(t: Float, dt: Float): Float {
private fun pulse(t: Float, dt: Float, dutyCycle: Float): Float {
val naivePulse = if (t > 1f - dutyCycle) 1f else -1f
return if (usePolyBLEP) {
naivePulse + polyBLEP((t + dutyCycle) % 1f, dt) - polyBLEP(t, dt)
@ -48,6 +67,7 @@ class PulseOscillatorDevice : Device<NoControls> {
override fun visitInputPorts(visit: (InputPortName, InputPort) -> Unit) {
visit(phasePort, phase)
visit(dutyCyclePort, dutyCycle)
}
override fun visitOutputPorts(visit: (OutputPortName, OutputPort) -> Unit) {

View file

@ -200,8 +200,7 @@ class DeviceBlockEntityRenderer(context: BlockEntityRendererFactory.Context) : B
val startAbsolute = blockEntity.pos.toVec3d()
val endAbsolute = connection.blockPosition.toVec3d()
// TODO: Null checks here?
val inputPort = blockEntity.descriptor.portLayout[inputPortName]!!
val inputPort = blockEntity.descriptor.portLayout[inputPortName] ?: continue
val outputBlockState = world.getBlockState(connection.blockPosition)
// TODO: The line below will crash if the block is destroyed (becomes air)
// Though it never crashed for me during testing?
@ -280,14 +279,27 @@ class DeviceBlockEntityRenderer(context: BlockEntityRendererFactory.Context) : B
colorIndex: Float,
) {
val forward = to - from
val right = forward.copy()
right.cross(Vec3f.POSITIVE_Y)
right.normalize()
right.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
val up = right.copy()
up.cross(forward)
up.normalize()
up.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
if (forward.x == 0f && forward.y != 0f && forward.z == 0f) {
// Special case: the cross product fails if the cable is going straight up, so we have
// to set the vectors ourself.
right.set(Vec3f.POSITIVE_X)
right.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
up.set(Vec3f.POSITIVE_Z)
up.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
} else {
right.cross(Vec3f.POSITIVE_Y)
right.normalize()
right.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
up.set(right)
up.cross(forward)
up.normalize()
up.multiplyComponentwise(cableThickness, cableThickness, cableThickness)
}
renderCableWithThicknessVector(
world,
matrixStack,

View file

@ -2,35 +2,78 @@ package net.liquidev.dawd3.block.devices.oscillator
import net.liquidev.dawd3.Mod
import net.liquidev.dawd3.audio.device.DeviceInstance
import net.liquidev.dawd3.audio.device.NoControls
import net.liquidev.dawd3.audio.devices.oscillator.PulseOscillatorDevice
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
import net.liquidev.dawd3.block.device.PhysicalPort
import net.liquidev.dawd3.ui.units.PercentageValue
import net.liquidev.dawd3.ui.widget.Knob
import net.liquidev.dawd3.ui.widget.Window
import net.minecraft.client.world.ClientWorld
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec2f
object PulseOscillatorBlockDescriptor : DeviceBlockDescriptor<PulseOscillatorBlockDescriptor.ClientState, NoControls> {
object PulseOscillatorBlockDescriptor : DeviceBlockDescriptor<PulseOscillatorBlockDescriptor.ClientState, PulseOscillatorDevice.Controls> {
override val id = Identifier(Mod.id, "pulse_oscillator")
override val portLayout = PhysicalPort.layout {
port(
PulseOscillatorDevice.phasePort,
position = Vec2f(0.25f, 0.25f),
PhysicalPort.Side.Front
)
port(
PulseOscillatorDevice.dutyCyclePort,
position = Vec2f(0.25f, 0.75f),
side = PhysicalPort.Side.Front,
PhysicalPort.Side.Front
)
port(
PulseOscillatorDevice.outputPort,
position = Vec2f(0.75f, 0.75f),
side = PhysicalPort.Side.Front
position = Vec2f(0.75f, 0.5f),
PhysicalPort.Side.Front
)
}
class ClientState(controls: NoControls) : DeviceBlockDescriptor.ClientState {
class ClientState(controls: PulseOscillatorDevice.Controls) : DeviceBlockDescriptor.ClientState {
override val logicalDevice = DeviceInstance.create(PulseOscillatorDevice(), controls)
}
override fun initControls() = NoControls
override fun initControls() = PulseOscillatorDevice.Controls()
override fun onClientLoad(controls: NoControls, world: ClientWorld) = ClientState(controls)
override fun onClientLoad(controls: PulseOscillatorDevice.Controls, world: ClientWorld) =
ClientState(controls)
override val ui = object : DeviceBlockDescriptor.UI<PulseOscillatorDevice.Controls> {
override fun open(controls: PulseOscillatorDevice.Controls, x: Int, y: Int) =
Window(
x,
y,
width = 78,
height = 48,
Text.translatable("block.dawd3.pulse_oscillator")
).apply {
children.add(
Knob(
x = 14,
y = 18,
control = controls.dutyCycle,
min = 0f,
max = 1f,
color = Knob.Color.Blue,
unit = PercentageValue,
)
)
children.add(
Knob(
x = 42,
y = 18,
control = controls.dutyCycleCV,
min = 0f,
max = 1f,
color = Knob.Color.Green,
unit = PercentageValue,
)
)
}
}
}

View file

@ -6,6 +6,7 @@ import net.liquidev.dawd3.render.Atlas
import net.liquidev.dawd3.render.Render
import net.liquidev.dawd3.render.Sprite
import net.liquidev.dawd3.ui.widget.Widget
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.client.world.ClientWorld
@ -14,6 +15,7 @@ import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import kotlin.math.max
class Rack(
val world: ClientWorld,
@ -24,20 +26,38 @@ class Rack(
val widget: Widget,
)
private var openWidgets = run {
var windowX = 32
private var openWidgets =
ArrayList(shownDevices.mapNotNull { blockPosition ->
val blockEntity = world.getBlockEntity(blockPosition) as DeviceBlockEntity
blockEntity.descriptor.ui
?.open(blockEntity.controls, windowX, 32)
?.let { widget ->
windowX += widget.width + 8
OpenWidget(blockPosition, widget)
}
?.open(blockEntity.controls, 0, 0)
?.let { widget -> OpenWidget(blockPosition, widget) }
})
private fun layoutWindows() {
var x = 16
var y = 16
var rowHeight = 0
for (openWidget in openWidgets) {
if (x + openWidget.widget.width >= width - 16) {
x = 16
y += rowHeight + 8
rowHeight = 0
}
openWidget.widget.x = x
openWidget.widget.y = y
x += openWidget.widget.width + 8
rowHeight = max(rowHeight, openWidget.widget.height)
}
}
fun hasOpenWindows(): Boolean = openWidgets.isNotEmpty()
override fun init() {
layoutWindows()
}
override fun resize(client: MinecraftClient?, width: Int, height: Int) {
layoutWindows()
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrices)
@ -52,7 +72,15 @@ class Rack(
openWidget.widget.draw(matrices, mouseX, mouseY, delta)
}
Render.sprite(matrices, 8, 8, badge.width * 2, badge.height * 2, atlas, badge)
Render.sprite(
matrices,
8,
height - badge.height - 8,
badge.width * 2,
badge.height * 2,
atlas,
badge
)
}
private fun propagateEvent(event: Event): Boolean {

View file

@ -161,6 +161,9 @@ class Knob(
alterValue(control.value, by = deltaY.toFloat())
)
draggingInfo.previousMouseY = event.absoluteMouseY
// Consume the events so that other controls don't get triggered while the knob
// is being dragged.
return true
}
}
}

View file

@ -33,13 +33,16 @@ class Keyboard(
}
override val width = children.maxOf { it.x + it.width }
override val height = 24
override val height = children.maxOf { it.y + it.height }
private var pressedKey: Key? = null
override fun event(context: EventContext, event: Event): Boolean {
if (event is MouseButton && event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
mouseIsDown = event.action == Action.Down
mouseIsDown = event.action == Action.Down && containsRelativePoint(
event.mouseX.toInt(),
event.mouseY.toInt()
)
if (mouseIsDown) {
setPressedKey(context, findPressedKey(event.mouseX.toInt(), event.mouseY.toInt()))
} else {

View file

@ -39,5 +39,7 @@
"dawd3.control.dawd3.fma.add": "ADD",
"dawd3.control.dawd3.fma.multiply": "MUL",
"dawd3.control.dawd3.mix.a_amplitude": "A",
"dawd3.control.dawd3.mix.b_amplitude": "B"
"dawd3.control.dawd3.mix.b_amplitude": "B",
"dawd3.control.dawd3.pulse_oscillator.duty_cycle": "%",
"dawd3.control.dawd3.pulse_oscillator.duty_cycle_cv": "CV"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 350 B

Before After
Before After