diff --git a/README.md b/README.md index 4e36f0a..0c61cef 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,8 @@ New features since fork the first thing actually implemented was a set of regression tests to avoid breaking everything beyond any hope of repair. Done, using CTest. - **New units**. Bit-crusher, gain, inverse gain, clip, modulate bpm - (proper triplets!)... As always, if you don't use them, they won't be - compiled into the code. + (proper triplets!), compressor (can be used for side-chaining)... As + always, if you don't use them, they won't be compiled into the code. - **Pattern length does not have to be a power of 2**. Future goals @@ -68,8 +68,11 @@ Future goals on CMake and compiles on Windows. Cross-platform NASM/YASM macros have been drafted and remain to be tested. Once the project is more mature, I will try compiling on other platforms. - - **Even more opcodes**. At least: compressor (with side-chaining). Maybe - also equalizer. + - **Find a more general solution for skipping opcodes / early outs**. It's + probably a new opcode "skip" that skips from the opcode to the next out in + case the signal entering skip and the signal leaving out are both close to + zero. + - **Even more opcodes**. Maybe an equalizer? DC-offset removal? - **Support for 64-bit targets**. - **Browser-based GUI and MIDI instrument**. Modern browsers support WebMIDI, WebAudio and, most importantly, they are cross-platform and come installed diff --git a/src/opcodes/effects.asm b/src/opcodes/effects.asm index 0c27b72..768c588 100644 --- a/src/opcodes/effects.asm +++ b/src/opcodes/effects.asm @@ -419,3 +419,56 @@ SECT_DATA(suconst) %endif %endif ; DELAY_ID > -1 + +;------------------------------------------------------------------------------- +; Compressor Tick +;------------------------------------------------------------------------------- +%if COMPRES_ID > -1 + +SECT_TEXT(sucompr) + +EXPORT MANGLE_FUNC(su_op_compressor,0) + fld st0 ; x x + fmul st0, st0 ; x^2 x +%ifdef INCLUDE_STEREO_COMPRES + jnc su_op_compressor_mono + fld st2 ; r l^2 l r + fmul st0, st0 ; r^2 l^2 l r + faddp st1, st0 ; r^2+l^2 l r + call su_op_compressor_mono ; So, for stereo, we square both left & right and add them up + fld st0 ; and return the computed gain two times, ready for MULP STEREO + ret +su_op_compressor_mono: +%endif + fld dword [WRK+su_compres_wrk.level] ; l x^2 x + mov al,1 ; high bits are zero so this is ok. eax = 1 => release + fucomi st0, st1 ; if l > x^2 // we're releasing + jnb su_op_compressor_releasing + xor eax, eax ; eax = 0 => attacking +su_op_compressor_releasing: + fsub st1, st0 ; l x^2-l x + call su_env_map ; c l x^2-l x, c is either attack or release parameter mapped in a nonlinear way + fmulp st2, st0 ; l c*(x^2-l) x + faddp st1, st0 ; l+c*(x^2-l) x + fst dword [WRK+su_compres_wrk.level] ; l'=l+c*(x^2-l), l' x + fld dword [edx+su_compres_ports.threshold] ; t l' x + fmul st0, st0 ; t*t + fucomi st0, st1 ; if threshold < l' + jb su_op_compressor_compress ; then we actually do compression + fstp st0 ; l' x + fstp st0 ; x + fld1 ; 1 x + ret ; return unity gain when we are below threshold +su_op_compressor_compress: ; l' x + fdivrp st1, st0 ; t*t/l' x + fld dword [edx+su_compres_ports.ratio] ; r t*t/l' x + fmul dword [c_0_5] ; p=r/2 t*t/l' x + fxch ; t*t/l' p x + fyl2x ; p*log2(t*t/l') x + jmp MANGLE_FUNC(su_power,0) ; 2^(p*log2(t*t/l')) x + ; tail call ; Equal to: + ; (t*t/l')^p x + ; if ratio is at minimum => p=0 => 1 x + ; if ratio is at maximum => p=0.5 => t/x => t/x*x=t + +%endif ; COMPRES_ID > -1 \ No newline at end of file diff --git a/src/opcodes/effects.inc b/src/opcodes/effects.inc index 784f51e..66b3d3e 100644 --- a/src/opcodes/effects.inc +++ b/src/opcodes/effects.inc @@ -344,3 +344,47 @@ struc su_delayline_wrk .buffer resd MAX_DELAY .size endstruc + + +;------------------------------------------------------------------------------- +; COMPRES effect related defines +;------------------------------------------------------------------------------- +%assign COMPRES_ID -1 + +%macro USE_COMPRES 0 + %if COMPRES_ID == -1 + %assign COMPRES_ID CUR_ID + %assign CUR_ID CUR_ID + 2 + %xdefine OPCODES OPCODES MANGLE_FUNC(su_op_compressor,0), + %xdefine NUMPARAMS NUMPARAMS 4, + %endif +%endmacro + +%macro SU_COMPRES 5 + db %2 + db %3 + db %4 + db %5 + USE_COMPRES + %xdefine CMDS CMDS COMPRES_ID + %1, + %if %1 == STEREO + %define INCLUDE_STEREO_COMPRES + %endif +%endmacro + +%define ATTAC(val) val +%define RELEASE(val) val +%define THRESHOLD(val) val +%define RATIO(val) val + +struc su_compres_ports + .attack resd 1 + .release resd 1 + .threshold resd 1 + .ratio resd 1 + .ports +endstruc + +struc su_compres_wrk + .level resd 1 +endstruc \ No newline at end of file diff --git a/src/sointu.asm b/src/sointu.asm index c501547..d1c27a2 100644 --- a/src/sointu.asm +++ b/src/sointu.asm @@ -152,7 +152,7 @@ su_transform_values_out: ;------------------------------------------------------------------------------- SECT_TEXT(supower) -%if ENVELOPE_ID > -1 +%if ENVELOPE_ID > -1 ; TODO: compressor also uses this, so should be compiled if either su_env_map: fld dword [edx+eax*4] ; x, where x is the parameter in the range 0-1 fimul dword [c_24] ; 24*x diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab65b8a..33119a3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -100,6 +100,9 @@ regression_test(test_clip_stereo CLIP) regression_test(test_crush "VCO_SINE;ENVELOPE;FOP_MULP;INVGAIN" CRUSH) regression_test(test_crush_stereo CRUSH) +regression_test(test_compressor "" COMPRESSOR) +regression_test(test_compressor_stereo COMPRESSOR) + regression_test(test_filter_band "VCO_SINE;ENVELOPE;FOP_MULP") regression_test(test_filter_low "VCO_SINE;ENVELOPE;FOP_MULP") regression_test(test_filter_high "VCO_SINE;ENVELOPE;FOP_MULP") diff --git a/tests/expected_output/test_compressor.raw b/tests/expected_output/test_compressor.raw new file mode 100644 index 0000000..a9fe6ce Binary files /dev/null and b/tests/expected_output/test_compressor.raw differ diff --git a/tests/expected_output/test_compressor_stereo.raw b/tests/expected_output/test_compressor_stereo.raw new file mode 100644 index 0000000..797a6f3 Binary files /dev/null and b/tests/expected_output/test_compressor_stereo.raw differ diff --git a/tests/test_compressor.asm b/tests/test_compressor.asm new file mode 100644 index 0000000..749764d --- /dev/null +++ b/tests/test_compressor.asm @@ -0,0 +1,43 @@ +%define BPM 100 +%define USE_SECTIONS + +%include "../src/sointu.inc" + +SU_BEGIN_PATTERNS + PATTERN 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, + PATTERN 76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, +SU_END_PATTERNS + +SU_BEGIN_TRACKS + TRACK VOICES(1),0 ; a very silent voice + TRACK VOICES(1),1 ; a loud one +SU_END_TRACKS + +SU_BEGIN_PATCH + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(16) + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(16) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(TRISAW) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(TRISAW) + SU_MULP STEREO + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,left) + SEND_POP + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,right) + SEND_POP + SU_END_INSTRUMENT + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(SINE) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(SINE) + SU_MULP STEREO + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,left) + SEND_POP + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,right) + SEND_POP + SU_END_INSTRUMENT + SU_BEGIN_INSTRUMENT VOICES(1) ; Global compressor effect + SU_RECEIVE STEREO + SU_COMPRES MONO,ATTAC(32),RELEASE(80),THRESHOLD(32),RATIO(64) + SU_MULP MONO + SU_OUT STEREO, GAIN(128) + SU_END_INSTRUMENT +SU_END_PATCH + +%include "../src/sointu.asm" diff --git a/tests/test_compressor_stereo.asm b/tests/test_compressor_stereo.asm new file mode 100644 index 0000000..fda1b76 --- /dev/null +++ b/tests/test_compressor_stereo.asm @@ -0,0 +1,43 @@ +%define BPM 100 +%define USE_SECTIONS + +%include "../src/sointu.inc" + +SU_BEGIN_PATTERNS + PATTERN 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, + PATTERN 76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, +SU_END_PATTERNS + +SU_BEGIN_TRACKS + TRACK VOICES(1),0 ; a very silent voice + TRACK VOICES(1),1 ; a loud one +SU_END_TRACKS + +SU_BEGIN_PATCH + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(16) + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(16) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(TRISAW) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(TRISAW) + SU_MULP STEREO + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,left) + SEND_POP + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,right) + SEND_POP + SU_END_INSTRUMENT + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_ENVELOPE MONO,ATTAC(64),DECAY(64),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(SINE) + SU_OSCILLAT MONO,TRANSPOSE(88),DETUNE(64),PHASE(0),COLOR(128),SHAPE(64),GAIN(128),FLAGS(SINE) + SU_MULP STEREO + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,left) + SEND_POP + SU_SEND MONO,AMOUNT(128),GLOBALPORT(2,0,receive,right) + SEND_POP + SU_END_INSTRUMENT + SU_BEGIN_INSTRUMENT VOICES(1) ; Global compressor effect + SU_RECEIVE STEREO + SU_COMPRES STEREO,ATTAC(32),RELEASE(80),THRESHOLD(32),RATIO(64) + SU_MULP STEREO + SU_OUT STEREO, GAIN(128) + SU_END_INSTRUMENT +SU_END_PATCH + +%include "../src/sointu.asm"