diff --git a/README.md b/README.md index 7b7e8be..3c99947 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ New features since fork spare. See [this example](tests/test_oscillat_sample.asm), and this Python [script](scripts/parse_gmdls.py) parses the gm.dls file and dumps the sample offsets from it. + - **Unison oscillators**. Multiple copies of the oscillator running sligthly + detuned and added up to together. Great for trance leads (supersaw). Unison + of up to 4, or 8 if you make stereo unison oscillator and add up both left + and right channels. See [this example](tests/test_oscillat_unison.asm). Future goals ------------ diff --git a/src/opcodes/sources.asm b/src/opcodes/sources.asm index 5f66714..ffca345 100644 --- a/src/opcodes/sources.asm +++ b/src/opcodes/sources.asm @@ -101,24 +101,46 @@ su_op_noise_mono: SECT_TEXT(suoscill) EXPORT MANGLE_FUNC(su_op_oscillat,0) - lodsb ; load the flags + lodsb ; load the flags + fld dword [edx+su_osc_ports.detune] ; e, where e is the detune [0,1] + fsub dword [c_0_5] ; e-.5 + fadd st0, st0 ; d=2*e-.5, where d is the detune [-1,1] %ifdef INCLUDE_STEREO_OSCILLAT jnc su_op_oscillat_mono - add WRK, 4 - call su_op_oscillat_mono - fld1 ; invert the detune for second run for some stereo separation - fld dword [edx+su_osc_ports.detune] - fsubp st1 - fstp dword [edx+su_osc_ports.detune] - sub WRK, 4 -su_op_oscillat_mono: + fld st0 ; d d + call su_op_oscillat_mono ; r d + add WRK, 4 ; state vars: r1 l1 r2 l2 r3 l3 r4 l4, for the unison osc phases + fxch ; d r + fchs ; -d r, negate the detune for second round + su_op_oscillat_mono: +%endif +%ifdef INCLUDE_UNISONS + pushad ; push eax, WRK, WRK would suffice but this is shorter + fldz ; 0 d + fxch ; d a=0, "accumulated signal" +su_op_oscillat_unison_loop: + fst dword [esp] ; save the current detune, d. We could keep it in fpu stack but it was getting big. + call su_op_oscillat_single ; s a + faddp st1, st0 ; a+=s + test al, UNISON4 + je su_op_oscillat_unison_out + add WRK, 8 + fld dword [edx+su_osc_ports.phaseofs] ; p s + fadd dword [c_i12] ; p s, add some little phase offset to unison oscillators so they don't start in sync + fstp dword [edx+su_osc_ports.phaseofs] ; s note that this changes the phase for second, possible stereo run. That's probably ok + fld dword [esp] ; d s + fmul dword [c_0_5] ; .5*d s // negate and halve the detune of each oscillator + fchs ; -.5*d s // negate and halve the detune of each oscillator + dec eax + jmp short su_op_oscillat_unison_loop +su_op_oscillat_unison_out: + popad ; similarly, pop WRK, WRK, eax would suffice + ret +su_op_oscillat_single: %endif fld dword [edx+su_osc_ports.transpose] fsub dword [c_0_5] fdiv dword [c_i128] - fld dword [edx+su_osc_ports.detune] - fsub dword [c_0_5] - fadd st0 faddp st1 test al, byte LFO jnz su_op_oscillat_skipnote diff --git a/src/opcodes/sources.inc b/src/opcodes/sources.inc index f75b017..813d031 100644 --- a/src/opcodes/sources.inc +++ b/src/opcodes/sources.inc @@ -64,12 +64,15 @@ endstruc %endif %endmacro -%define SAMPLE 0x80 ; in this case, all the rest of the bits is the sample index +%define SAMPLE 0x80 %define SINE 0x40 %define TRISAW 0x20 %define PULSE 0x10 %define LFO 0x08 %define GATE 0x04 +%define UNISON2 0x01 +%define UNISON3 0x02 ; Warning, UNISON3 and UNISON4 do not work with gate at the moment, as they use the same state variable +%define UNISON4 0x03 %macro SU_OSCILLAT 8 db %2 @@ -99,6 +102,9 @@ endstruc %if (%8) & SAMPLE == SAMPLE %define INCLUDE_SAMPLES %endif + %if (%8) & UNISON4 > 0 + %define INCLUDE_UNISONS + %endif %endmacro struc su_osc_ports @@ -113,7 +119,7 @@ endstruc struc su_osc_wrk .phase resd 1 - .gatestate resd 1 + .gatestate equ 16 ; we put is late so only UNISON3 and UNISON4 are unusable with gate .size endstruc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fe4c268..509ab3e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -80,6 +80,8 @@ regression_test(test_oscillat_gate ENVELOPE) regression_test(test_oscillat_stereo ENVELOPE) regression_test(test_oscillat_sample ENVELOPE) regression_test(test_oscillat_sample_stereo ENVELOPE) +regression_test(test_oscillat_unison ENVELOPE) +regression_test(test_oscillat_unison_stereo ENVELOPE) regression_test(test_oscillat_lfo "ENVELOPE;VCO_SINE;VCO_PULSE;FOP_MULP2") regression_test(test_oscillat_transposemod "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") regression_test(test_oscillat_detunemod "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") diff --git a/tests/expected_output/test_oscillat_unison.raw b/tests/expected_output/test_oscillat_unison.raw new file mode 100644 index 0000000..a02edfb Binary files /dev/null and b/tests/expected_output/test_oscillat_unison.raw differ diff --git a/tests/expected_output/test_oscillat_unison_stereo.raw b/tests/expected_output/test_oscillat_unison_stereo.raw new file mode 100644 index 0000000..05f658e Binary files /dev/null and b/tests/expected_output/test_oscillat_unison_stereo.raw differ diff --git a/tests/test_oscillat_unison.asm b/tests/test_oscillat_unison.asm new file mode 100644 index 0000000..61cfd02 --- /dev/null +++ b/tests/test_oscillat_unison.asm @@ -0,0 +1,24 @@ +%define BPM 100 +%define USE_SECTIONS + +%include "../src/sointu.inc" + +SU_BEGIN_PATTERNS + PATTERN 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0, +SU_END_PATTERNS + +SU_BEGIN_TRACKS + TRACK VOICES(1),0 +SU_END_TRACKS + +SU_BEGIN_PATCH + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE MONO, ATTAC(32),DECAY(32),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_OSCILLAT MONO, TRANSPOSE(64),DETUNE(0),PHASE(64),COLOR(128),SHAPE(64),GAIN(32), FLAGS(TRISAW + UNISON4) + SU_MULP MONO + SU_PUSH MONO + SU_OUT STEREO, GAIN(128) + SU_END_INSTRUMENT +SU_END_PATCH + +%include "../src/sointu.asm" diff --git a/tests/test_oscillat_unison_stereo.asm b/tests/test_oscillat_unison_stereo.asm new file mode 100644 index 0000000..c7f80bf --- /dev/null +++ b/tests/test_oscillat_unison_stereo.asm @@ -0,0 +1,23 @@ +%define BPM 100 +%define USE_SECTIONS + +%include "../src/sointu.inc" + +SU_BEGIN_PATTERNS + PATTERN 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0, +SU_END_PATTERNS + +SU_BEGIN_TRACKS + TRACK VOICES(1),0 +SU_END_TRACKS + +SU_BEGIN_PATCH + SU_BEGIN_INSTRUMENT VOICES(1) ; Instrument0 + SU_ENVELOPE STEREO, ATTAC(32),DECAY(32),SUSTAIN(64),RELEASE(64),GAIN(128) + SU_OSCILLAT STEREO, TRANSPOSE(64),DETUNE(0),PHASE(64),COLOR(128),SHAPE(64),GAIN(32), FLAGS(TRISAW + UNISON4) + SU_MULP STEREO + SU_OUT STEREO, GAIN(128) + SU_END_INSTRUMENT +SU_END_PATCH + +%include "../src/sointu.asm"