fix(vm): change crush resolution to bits (closes #79)

BREAKING CHANGE: The problem with crush was that it had very few usable values. This changes the crush to map the value nonlinearly, so the crush resolution is bits. Still the upper portion of the values is not very usable (bits 12-24 i.e. hardly any crushing), but at least the lower portion is usable. But now crush resolution has slightly different meaning.
This commit is contained in:
5684185+vsariola@users.noreply.github.com
2023-09-23 21:07:35 +03:00
parent 1ac2ad3c75
commit 7df8103bf9
10 changed files with 24 additions and 10 deletions

View File

@ -22,6 +22,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- In the WebAssembly core, $WRK was messed after stereo oscillators, - In the WebAssembly core, $WRK was messed after stereo oscillators,
making modulations not work making modulations not work
### Changed
- The crush resolution is now in bits instead of linear range; this is a
breaking change and changes the meaning of the resolution values. But
now there are more usable values in the resolution.
## v0.1.0 ## v0.1.0
### Added ### Added
- An instrument (set of opcodes & accompanying values) can have any number of voices. - An instrument (set of opcodes & accompanying values) can have any number of voices.

View File

@ -233,6 +233,11 @@ func (p Patch) ParamHintString(instrIndex, unitIndex int, param string) string {
return "aux3 right" return "aux3 right"
} }
} }
case "crush":
switch param {
case "resolution":
return fmt.Sprintf("%v bits", 24*float32(value)/128)
}
} }
return "" return ""
} }

Binary file not shown.

View File

@ -25,6 +25,6 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: crush - type: crush
parameters: {resolution: 64, stereo: 0} parameters: {resolution: 32, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}

View File

@ -23,6 +23,6 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: crush - type: crush
parameters: {resolution: 32, stereo: 1} parameters: {resolution: 8, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}

View File

@ -63,7 +63,7 @@ if (process.argv.length <= 3) {
return 1 return 1
} }
let margin = 1e-2 * (instance.exports.t.value ? 32767 : 1); let margin = 2e-2 * (instance.exports.t.value ? 32767 : 1);
var firstError = true, firstErrorPos, errorCount = 0 var firstError = true, firstErrorPos, errorCount = 0
// we still have occasional sample wrong here or there. We only consider this a true error // we still have occasional sample wrong here or there. We only consider this a true error

View File

@ -34,16 +34,19 @@ su_op_hold_holding:
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; CRUSH opcode: quantize the signal to finite number of levels ; CRUSH opcode: quantize the signal to finite number of levels
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; Mono: x -> e*int(x/e) ; Mono: x -> e*int(x/e) where e=2**(-24*resolution)
; Stereo: l r -> e*int(l/e) e*int(r/e) ; Stereo: l r -> e*int(l/e) e*int(r/e)
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Func "su_op_crush" "Opcode"}} {{.Func "su_op_crush" "Opcode"}}
{{- if .Stereo "crush"}} {{- if .Stereo "crush"}}
{{.Call "su_effects_stereohelper"}} {{.Call "su_effects_stereohelper"}}
{{- end}} {{- end}}
fdiv dword [{{.Input "crush" "resolution"}}] xor eax, eax
{{.Call "su_nonlinear_map"}}
fxch st0, st1
fdiv st0, st1
frndint frndint
fmul dword [{{.Input "crush" "resolution"}}] fmulp st1, st0
ret ret
{{end}} {{end}}

View File

@ -54,14 +54,14 @@
;; Mono: x -> e*int(x/e) ;; Mono: x -> e*int(x/e)
;; Stereo: l r -> e*int(l/e) e*int(r/e) ;; Stereo: l r -> e*int(l/e) e*int(r/e)
;;------------------------------------------------------------------------------- ;;-------------------------------------------------------------------------------
(func $su_op_crush (param $stereo i32) (func $su_op_crush (param $stereo i32) (local $e f32)
{{- if .Stereo "crush"}} {{- if .Stereo "crush"}}
(call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "crush") 2}})) (call $stereoHelper (local.get $stereo) (i32.const {{div (.GetOp "crush") 2}}))
{{- end}} {{- end}}
call $pop call $pop
(f32.div (call $input (i32.const {{.InputNumber "crush" "resolution"}}))) (f32.div (local.tee $e (call $nonLinearMap (i32.const {{.InputNumber "crush" "resolution"}}))))
f32.nearest f32.nearest
(f32.mul (call $input (i32.const {{.InputNumber "crush" "resolution"}}))) (f32.mul (local.get $e))
call $push call $push
) )
{{end}} {{end}}

View File

@ -611,7 +611,8 @@ func clip(value float32) float32 {
} }
func crush(value, amount float32) float32 { func crush(value, amount float32) float32 {
return float32(math.Round(float64(value/amount)) * float64(amount)) n := nonLinearMap(amount)
return float32(math.Round(float64(value/n)) * float64(n))
} }
func waveshape(value, amount float32) float32 { func waveshape(value, amount float32) float32 {