Uxnseq

Uxnseq

Overview

uxnseq is a proof-of-concept sequencer for sndkit, controlled via the Uxn Virtual Machine.

Uxnseq works by loading an instance of the Uxn VM and ROM inside of sndkit. One or more readers could then concurrently make calls to the Uxn VM at different starting memory positions. Clocking is done via an audio-rate clock, such as metro. Evaluation happens until the next BRK statement is reached. Uxn programs can then use virtual devices and DEO to communicate to the sequencing components, which are in turn read by sndkit.

The purpose of building this was to assess the viability of using Uxn for creating generative music and procedurally generated musical control structures.

The findings here have lead to the construction of gestvm, a VM controlled gesture synthesizer similar to gest.

Code

Sourcehut: https://git.sr.ht/~pbatch/uxnseq.

Github: https://github.com/paulbatchelor/uxnseq.

Examples

The initial examples below have been written to test out specific mechanics of procedurally generated music.

Each example has two components: a TAL file with the Uxn program, and sndkit program written in LILthat load the Uxn program into the synthesizer patch and then render a WAV file.

No sound file previews (maybe later?). Sorry!

Sequencing

A small looping sequence that takes advantage of Uxn to make sequencing look a bit more readable.

TAL file:

<<seq.tal>>=
%PITCH { #30 ADD #20 DEO }
%DUR { #21 DEO }
%NOTE { DUR PITCH BRK }

|0100

@pos 00
@seq 00 02 03 0a 0c 07 03 02

@top
;seq ;pos LDA DUP INC ;pos STA ADD LDA
;pos LDA #08 LTH ,&skip JCN
#00 ;pos STA
&skip
#01 NOTE
,top JMP

LIL file:

<<seq.lil>>=
uxnseqnew uxn
uxnseqload [grab uxn] seq.rom

regset [hold [metro 10]] 0

uxnseqnode [grab uxn] [uxnsym seq.rom top] [regget 0]
tsmoother zz [tick] [trand [regget 0] 0.001 0.01]
mtof zz
blsaw zz
butlp zz [smoother [trand [regget 0] 500 2000] 0.001]
mul zz 0.4
unhold [regget 0]
wavout zz "seq.wav"

computes 15

Parallel Sequences

This slightly over-engineered example shows how two sequences can be played at once.

<<sing.tal>>=
%PITCH { #3f ADD #20 DEO }
%DUR { #21 DEO }
%NOTE { DUR PITCH BRK }

|0100
BRK

|0108
&top
#00 #02 NOTE
#02 #01 NOTE
#0b #02 NOTE
#09 #01 NOTE
#00 #01 SUB #02 NOTE
#02 #01 NOTE
#09 #02 NOTE
#07 #01 NOTE
,&top JMP

|0200
&melb
#07 #01 NOTE
#06 #02 NOTE

#07 #01 NOTE
#09 #01 NOTE
#06 #01 NOTE

#02 #01 NOTE
#06 #02 NOTE

#04 #01 NOTE
#00 #01 NOTE
#00 #01 SUB #01 NOTE
;&melb JMP2

<<sing.lil>>=
uxnseqnew uxn
uxnseqload [grab uxn] sing.rom

uxnseqnode [grab uxn] [expr 256 + 8] [metro 1.6]
uxnseqlast [grab uxn]
regset zz 1
tsmoother zz [tick] [rline 0.1 0.15 1]
sine [rline 6 6.6 1] 0.5
add zz zz
mtof zz
glottis zz [rline 0.85 0.9 0.3]

uxnseqtk [regget 1]
dup
trand zz 0.1 0.9
smoother zz 0.01
swap
trand zz 0.5 0.9
smoother zz 0.01
tractxy zz zz zz
mul zz 0.4

uxnseqnode [grab uxn] 512 [metro 1.6]
uxnseqlast [grab uxn]
regset zz 1
tsmoother zz [tick] [param 0.04]
sine [rline 6 6.6 0.5] 0.3
add zz zz
mtof zz
glottis zz [rline 0.7 0.8 0.4]
uxnseqtk [regget 1]
dup
trand zz 0.1 0.9
smoother zz 0.01
swap
trand zz 0.5 0.9
smoother zz 0.01
tractxy zz zz zz
mul zz 0.2

add zz zz

butlp zz 4000
buthp zz 60

dup
dup
verbity zz zz 0.3 0.9 0.1
drop
mul zz [dblin -12]
dcblocker zz
add zz zz

wavout zz "sing.wav"

computes 25

Randomness

This program produces a sequence that can randomly select pre-composed melodic fragments. The PRNG algorithm used for randomness is created inside of Uxn and is part of the program

<<rng.tal>>=
(
Random sequencing test in uxnseq.
)

%PITCH { #20 DEO }
%DUR { #21 DEO }
%NOTE { DUR PITCH BRK }
%SEQ-GET { ;pos LDA ADD LDA }
%UPDATE-POS { ;pos LDA INC ;pos STA ;pos LDA }
%BACK-TO-TOP { #00 ;pos STA ;top JMP2 }
%SEQ-PLAY { ;&seq SEQ-GET #01 NOTE UPDATE-POS }
%MOD2 { DIV2k MUL2 SUB2 }

|0100
@init
#12 #34 #56 #78 ;prng-init JSR2
BRK

@sequences :a :b :c :d

@pos 00

@top
;sequences
;prng JSR2 #00 #04 MOD2 SWP POP
#02 MUL ADD LDA2 JMP2

@a SEQ-PLAY #04 LTH ;a JCN2 BACK-TO-TOP
&seq 00 02 03 07

@b SEQ-PLAY #04 LTH ;b JCN2 BACK-TO-TOP
&seq 00 08 07 03

@c SEQ-PLAY #03 LTH ;c JCN2 BACK-TO-TOP
&seq 00 0a 02

@d SEQ-PLAY #08 LTH ;d JCN2 BACK-TO-TOP
&seq 00 03 02 07 0a 09 05 02

( https://wiki.xxiivv.com/site/uxntal_macros.html )

@prng-init ( -- ) ,prng/y STR2 ,prng/x STR2 JMP2r

@prng ( -- number* )
	LIT2 &x $2
	DUP2 #50 SFT2 EOR2
	DUP2 #03 SFT2 EOR2
	LIT2 &y $2 DUP2 ,&x STR2
	DUP2 #01 SFT2 EOR2 EOR2
	,&y STR2k POP
JMP2r

<<rng.lil>>=
uxnseqnew uxn
uxnseqload [grab uxn] rng.rom

# init
uxnseqeval [grab uxn] [uxnsym rng.rom init]

regset [hold [metro 10]] 0

uxnseqnode [grab uxn] [uxnsym rng.rom top] [regget 0]
add zz 48
tsmoother zz [tick] [rline 0.001 0.008 1]
mtof zz
blsaw zz
butlp zz [rline [param 400] [param 1800] [rline 1 4 1]]
mul zz 0.4
unhold [regget 0]
wavout zz rng.wav

computes 15

Coordination

Perhaps one of the most unique things a VM can do is create sequences a that have an awareness of other sequences.

This program features two sequences. The first sequence randomly chooses melodic fragments. The second chooses sequences based on what the first melody is currently playing.

<<coord.tal>>=
%PITCH { #20 DEO }
%DUR { #21 DEO }
%NOTE { DUR PITCH }
%CHECK-RESET {
    LDA ;&pos LDA SWP
    LTH ;&skip JCN2 #00 ;&pos STA
}
%SEQ-NOTE {
;&pos LDA ;SEQ-GET JSR2 BRK
}

|0100

@init
#1234 #5678 ;prng-init JSR2
;seqA1 #00 STZ2
;seqB1 #02 STZ2
BRK

%MOD2 { DIV2k MUL2 SUB2 }
@melA
#00 LDZ2 SEQ-NOTE
;&pos LDA INC ;&pos STA
#00 LDZ2 CHECK-RESET

;seqA ;prng JSR2 #0003 MOD2
#0002 MUL2 ADD2 LDA2
#00 STZ2

&skip ;melA JMP2
&pos 00



@melB
#02 LDZ2 SEQ-NOTE
;&pos LDA INC ;&pos STA
#02 LDZ2 CHECK-RESET

#00 LDZ2 ;seqA2 NEQ2 ;&choose-B1 JCN2
;seqB2 #02 STZ2 ,&skip JMP
&choose-B1
;seqB1 #02 STZ2
&skip ;melB JMP2
&pos 00

@SEQ-GET
#02 MUL ADD #01 ADD LDA2 NOTE
JMP2r

@seqA :seqA1 :seqA2 :seqA3

@seqA1 04
00 02
02 02
07 02
0c 01

@seqA2 04
01 02
06 02
08 02
0b 01

@seqA3 05
00 01
05 01
07 01
00 02
0c 02

@seqB1 01
18 18

@seqB2 02
1d 06
1f 16

( https://wiki.xxiivv.com/site/uxntal_macros.html )

@prng-init ( -- ) ,prng/y STR2 ,prng/x STR2 JMP2r

@prng ( -- number* )
	LIT2 &x $2
	DUP2 #50 SFT2 EOR2
	DUP2 #03 SFT2 EOR2
	LIT2 &y $2 DUP2 ,&x STR2
	DUP2 #01 SFT2 EOR2 EOR2
	,&y STR2k POP
JMP2r

<<coord.lil>>=
uxnseqnew uxn
uxnseqload [grab uxn] coord.rom

regset [hold [metro 8]] 0

uxnseqeval [grab uxn] [uxnsym coord.rom init]

hold [zero]
regset zz 2

uxnseqnode [grab uxn] [uxnsym coord.rom melA] [regget 0]
regset [uxnseqlast [grab uxn]] 1
dup
add zz 36
tsmoother zz [tick] 0.005
mtof zz
gensine [tabnew 8192]
swap
fmpair zz zz \
    [param 1] \
    [param 1] \
    [rline 1 4 0.5] \
    [param 0.3]
swap
add zz 24
mtof zz
blsaw zz
butlp zz 300
add zz zz

softclip zz 4.8
mul zz 0.1
uxnseqtk [regget 1]
env zz 0.003 0.1 0.08
mul zz zz
dup
buthp zz 300
mix zz [regget 2] 0.5

gensine [tabnew 8192]
uxnseqnode [grab uxn] [uxnsym coord.rom melB] [regget 0]
regset [uxnseqlast [grab uxn]] 1
add zz 48
tsmoother zz [tick] 0.01
mtof zz
param 1
param 1
uxnseqtk [regget 1]
env zz 0.001 0.001 0.02
scale zz 0 5
param 0.1
fmpair zz zz zz zz zz zz
mul zz 0.3
buthp zz 200


uxnseqtk [regget 1]

env zz 0.001 0.05 0.2
mul zz zz

dup
mix zz [regget 2] 0.5

add zz zz

regget 2
vardelay zz 0.0 0.1 0.2
dup
bigverb zz zz 0.97 10000
drop
dcblocker zz
mul zz [dblin -12]
add zz zz

unhold [regget 0]
unhold [regget 2]
wavout zz coord.wav

computes 30

home | index