mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
taud: panning law toggle
This commit is contained in:
@@ -749,6 +749,27 @@ NOTE: **`3.00` — is No-op**
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Effects That Modifies Global Behaviour
|
||||||
|
|
||||||
|
Effects in this section modifies the behaviour of the mixer. Primary intention of the commands is to provide switches for legacy tracker and modern DAW behaviours.
|
||||||
|
|
||||||
|
## 1 $01xx — Set stereo panning law
|
||||||
|
|
||||||
|
**Plain.** Sets how the mixer should treat the panning. Available modes are:
|
||||||
|
|
||||||
|
- 0: Linear panning mode (tracker-accurate). Centre panning gets 3 dB boost. Default setting.
|
||||||
|
- 1: Equal-power panning mode. L/R amplitude is at 0.707 when centre-panned.
|
||||||
|
|
||||||
|
**Implementation.**
|
||||||
|
- Mode 0:
|
||||||
|
- L_gain = if (pan < 0x80) 1.0 else 1.0 - (pan - 128.0) / 128.0
|
||||||
|
- R_gain = if (pan < 0x80) pan / 128.0 else 1.0
|
||||||
|
- Mode 1:
|
||||||
|
- L_gain = cos(pi*x / 512.0)
|
||||||
|
- R_gain = sin(pi*x / 512.0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# ProTracker to Taud conversion table
|
# ProTracker to Taud conversion table
|
||||||
|
|
||||||
This table maps each PT effect to its Taud equivalent. Arguments follow PT's two-nibble form and expand to Taud's 16-bit form as shown.
|
This table maps each PT effect to its Taud equivalent. Arguments follow PT's two-nibble form and expand to Taud's 16-bit form as shown.
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ play:"\u008422u\u008423u",
|
|||||||
|
|
||||||
const fxNames = {
|
const fxNames = {
|
||||||
'0':"No effect ",
|
'0':"No effect ",
|
||||||
'1':"UNIMPLEMENTED",
|
'1':"Mixer config ", // Taud: 1 01xx: set stereo panning law
|
||||||
'2':"UNIMPLEMENTED",
|
'2':"UNIMPLEMENTED",
|
||||||
'3':"UNIMPLEMENTED",
|
'3':"UNIMPLEMENTED",
|
||||||
'4':"UNIMPLEMENTED",
|
'4':"UNIMPLEMENTED",
|
||||||
@@ -94,26 +94,26 @@ G:"Portamento ",
|
|||||||
H:"Vibrato ",
|
H:"Vibrato ",
|
||||||
I:"Tremor ",
|
I:"Tremor ",
|
||||||
J:"Arpeggio ",
|
J:"Arpeggio ",
|
||||||
K:"UNIMPLEMENTED",
|
K:"UNIMPLEMENTED", // Volume slide+Vibrato. Use H0000 and VolEff instead
|
||||||
L:"UNIMPLEMENTED",
|
L:"UNIMPLEMENTED", // Volume slide+Portamento. Use G0000 and VolEff instead
|
||||||
M:"UNIMPLEMENTED",
|
M:"UNIMPLEMENTED", // IT: Set channel volume. Use VolEff instead
|
||||||
N:"UNIMPLEMENTED",
|
N:"UNIMPLEMENTED", // IT: Channel volume slide. Use VolEff instead
|
||||||
O:"Sample offset",
|
O:"Sample offset",
|
||||||
P:"UNIMPLEMENTED",
|
P:"UNIMPLEMENTED", // IT: panning slide. Use PanEff instead
|
||||||
Q:"Retrigger ",
|
Q:"Retrigger ",
|
||||||
R:"Tremolo ",
|
R:"Tremolo ",
|
||||||
S:"Special ",
|
S:"Special ",
|
||||||
S0:"UNIMPLEMENTED",
|
S0:"UNIMPLEMENTED", // PT: Set audio filter.
|
||||||
S1:"Gliss. ctrl ",
|
S1:"Gliss. ctrl ",
|
||||||
S2:"Sample tune ",
|
S2:"Sample tune ",
|
||||||
S3:"Vibrato LFO ",
|
S3:"Vibrato LFO ",
|
||||||
S4:"Tremolo LFO ",
|
S4:"Tremolo LFO ",
|
||||||
S5:"Panbrello LFO",
|
S5:"Panbrello LFO",
|
||||||
S6:"UNIMPLEMENTED",
|
S6:"UNIMPLEMENTED", // IT: Fine pattern delay.
|
||||||
S7:"UNIMPLEMENTED",
|
S7:"UNIMPLEMENTED", // IT: misc. functions
|
||||||
S8:"Channel pan ",
|
S8:"Channel pan ", // Taud: 8-bit channel panning.
|
||||||
S9:"UNIMPLEMENTED",
|
S9:"UNIMPLEMENTED", // IT: Sound control.
|
||||||
SA:"UNIMPLEMENTED",
|
SA:"UNIMPLEMENTED", // SC3: Stereo control. IT: Sample offset high twobyte.
|
||||||
SB:"Pattern loop ",
|
SB:"Pattern loop ",
|
||||||
SC:"Note cut ",
|
SC:"Note cut ",
|
||||||
SD:"Note delay ",
|
SD:"Note delay ",
|
||||||
@@ -122,10 +122,10 @@ SF:"Funk it ",
|
|||||||
T:"Tempo ",
|
T:"Tempo ",
|
||||||
U:"Fine vibrato ",
|
U:"Fine vibrato ",
|
||||||
V:"Global volume",
|
V:"Global volume",
|
||||||
W:"UNIMPLEMENTED",
|
W:"UNIMPLEMENTED", // IT: Global volume slide.
|
||||||
X:"UNIMPLEMENTED",
|
X:"UNIMPLEMENTED", // IT: 8-bit channel panning. Use PanEff or S80xx instead
|
||||||
Y:"Panbrello ",
|
Y:"Panbrello ",
|
||||||
Z:"UNIMPLEMENTED",
|
Z:"UNIMPLEMENTED", // IT: MIDI macro.
|
||||||
}
|
}
|
||||||
const panFxNames = {
|
const panFxNames = {
|
||||||
0:"Set to",
|
0:"Set to",
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import net.torvald.tsvm.ThreeFiveMiniUfloat
|
|||||||
import net.torvald.tsvm.VM
|
import net.torvald.tsvm.VM
|
||||||
import net.torvald.tsvm.toInt
|
import net.torvald.tsvm.toInt
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import kotlin.math.cos
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sin
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
||||||
private fun printdbg(msg: Any) {
|
private fun printdbg(msg: Any) {
|
||||||
@@ -1133,6 +1136,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
// Letters A..Z map to 0x0A..0x23 (digit value 10..35).
|
// Letters A..Z map to 0x0A..0x23 (digit value 10..35).
|
||||||
private object EffectOp {
|
private object EffectOp {
|
||||||
const val OP_NONE = 0x00
|
const val OP_NONE = 0x00
|
||||||
|
const val OP_1 = 0x01
|
||||||
const val OP_A = 0x0A
|
const val OP_A = 0x0A
|
||||||
const val OP_B = 0x0B
|
const val OP_B = 0x0B
|
||||||
const val OP_C = 0x0C
|
const val OP_C = 0x0C
|
||||||
@@ -1352,6 +1356,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
private fun applyEffectRow(ts: TrackerState, playhead: Playhead, voice: Voice, vi: Int, op: Int, rawArg: Int) {
|
private fun applyEffectRow(ts: TrackerState, playhead: Playhead, voice: Voice, vi: Int, op: Int, rawArg: Int) {
|
||||||
when (op) {
|
when (op) {
|
||||||
EffectOp.OP_NONE -> {}
|
EffectOp.OP_NONE -> {}
|
||||||
|
EffectOp.OP_1 -> {
|
||||||
|
// 1 $01xx — Set stereo panning law. High byte selects subcommand; only $01 is defined.
|
||||||
|
if ((rawArg ushr 8) == 0x01) ts.panLaw = rawArg and 0xFF
|
||||||
|
}
|
||||||
EffectOp.OP_A -> {
|
EffectOp.OP_A -> {
|
||||||
val tr = (rawArg ushr 8) and 0xFF
|
val tr = (rawArg ushr 8) and 0xFF
|
||||||
if (tr != 0) playhead.tickRate = tr
|
if (tr != 0) playhead.tickRate = tr
|
||||||
@@ -1733,8 +1741,21 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
if (!voice.active || voice.muted) continue
|
if (!voice.active || voice.muted) continue
|
||||||
val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
|
val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
|
||||||
val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0
|
val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0
|
||||||
mixL += s * vol * (63 - voice.rowPan) / 63.0
|
val pan = voice.channelPan
|
||||||
mixR += s * vol * voice.rowPan / 63.0
|
val lGain: Double
|
||||||
|
val rGain: Double
|
||||||
|
when (ts.panLaw) {
|
||||||
|
1 -> { // equal-power: constant loudness at centre (0.707 each)
|
||||||
|
lGain = cos(PI * pan / 512.0)
|
||||||
|
rGain = sin(PI * pan / 512.0)
|
||||||
|
}
|
||||||
|
else -> { // linear balance (tracker default): centre gives 0 dB on both channels
|
||||||
|
lGain = if (pan < 0x80) 1.0 else 1.0 - (pan - 128.0) / 128.0
|
||||||
|
rGain = if (pan < 0x80) pan / 128.0 else 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mixL += s * vol * lGain
|
||||||
|
mixR += s * vol * rGain
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.mixLeft[n] = mixL.toFloat().coerceIn(-1.0f, 1.0f)
|
ts.mixLeft[n] = mixL.toFloat().coerceIn(-1.0f, 1.0f)
|
||||||
@@ -1988,6 +2009,9 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
var firstRow = true
|
var firstRow = true
|
||||||
val voices = Array(20) { Voice() }
|
val voices = Array(20) { Voice() }
|
||||||
|
|
||||||
|
// Global mixer config (effect 1).
|
||||||
|
var panLaw = 0 // 0 = linear balance (default), 1 = equal-power
|
||||||
|
|
||||||
// Pending row-end events (set during a row by B/C; consumed at row end).
|
// Pending row-end events (set during a row by B/C; consumed at row end).
|
||||||
var pendingOrderJump = -1 // -1 = none; otherwise the order index to jump to
|
var pendingOrderJump = -1 // -1 = none; otherwise the order index to jump to
|
||||||
var pendingRowJump = -1 // -1 = none; otherwise the row index for the next pattern
|
var pendingRowJump = -1 // -1 = none; otherwise the row index for the next pattern
|
||||||
@@ -2109,6 +2133,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
ts.pendingOrderJump = -1; ts.pendingRowJump = -1
|
ts.pendingOrderJump = -1; ts.pendingRowJump = -1
|
||||||
ts.patternDelayRemaining = 0; ts.patternDelayActive = false
|
ts.patternDelayRemaining = 0; ts.patternDelayActive = false
|
||||||
ts.sexWinningChannel = -1
|
ts.sexWinningChannel = -1
|
||||||
|
ts.panLaw = 0
|
||||||
ts.voices.forEach {
|
ts.voices.forEach {
|
||||||
it.active = false
|
it.active = false
|
||||||
it.channelVolume = 0x3F
|
it.channelVolume = 0x3F
|
||||||
|
|||||||
Reference in New Issue
Block a user