Skip to content
This repository was archived by the owner on Nov 23, 2023. It is now read-only.

Commit f7c831b

Browse files
tidal scheduler, query_feed method
1 parent 74576d6 commit f7c831b

4 files changed

Lines changed: 81 additions & 181 deletions

File tree

examples/notochord/server.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from notochord import Notochord
99
from iipyper import OSC, run
10+
import numpy as np
1011

1112
def main(host="127.0.0.1", receive_port=9999, send_port=None, checkpoint=None):
1213
osc = OSC(host, receive_port)
@@ -17,10 +18,10 @@ def main(host="127.0.0.1", receive_port=9999, send_port=None, checkpoint=None):
1718
else:
1819
predictor = None
1920

20-
@osc.kwargs('/predictor/*', return_port=send_port)
21+
@osc.kwargs('/notochord/*', return_port=send_port)
2122
def _(address, **kw):
2223
"""
23-
Handle OSC messages to Predictor
24+
Handle OSC messages to Notochord
2425
"""
2526
print(f"{address} {kw}")
2627

@@ -40,6 +41,15 @@ def _(address, **kw):
4041
else:
4142
r = predictor.feed(**kw)
4243

44+
elif cmd=="query_feed":
45+
# print(kw)
46+
if predictor is None:
47+
print('no model loaded')
48+
else:
49+
r = predictor.query_feed(**kw)
50+
return ('/notochord/query_return',
51+
*np.ravel(list(r.items())))
52+
4353
elif cmd=="predict":
4454
if predictor is None:
4555
print('no model loaded')
Lines changed: 23 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,181 +1,37 @@
1-
{-
1+
-- Notochord OSC target, OSC specs and Tidal parameters
22

3-
Notochord OSC target, OSC specs and Tidal parameters
4-
5-
See github.com/jarmitage/jarmlib for reference of
6-
how to incorporate this file into your Tidal setup
7-
8-
-}
3+
-- See github.com/jarmitage/jarmlib for reference of
4+
-- how to incorporate this file into your Tidal setup
95

106
-- Notochord Target
117
:{
12-
ncTarget = Target {oName = "Notochord",
13-
oAddress = "127.0.0.1",
14-
oHandshake = False,
15-
oPort = 6789,
16-
oBusPort = Nothing,
17-
oLatency = 0.1,
18-
oSchedule = Pre BundleStamp,
19-
oWindow = Nothing
20-
}
21-
:}
22-
23-
{-
24-
Notochord.feed() consumes an event and advance hidden state
25-
26-
inst: int. instrument of current note.
27-
0 is start token
28-
1-128 are General MIDI instruments
29-
129-256 are drumkits (MIDI 1-128 on channel 13)
30-
257-264 are 'anonymous' melodic instruments
31-
265-272 are 'anonymous' drumkits
32-
pitch: int. MIDI pitch of current note.
33-
0-127 are MIDI pitches / drums
34-
128 is start token
35-
time: float. elapsed time in seconds since previous event.
36-
vel: float. (possibly dequantized) MIDI velocity from 0-127 inclusive.
37-
0 indicates a note-off event
38-
-}
39-
40-
-- Notochord Feed OSC Specs
41-
:{
42-
ncFeedOSCSpecs = [OSC "/notochord/feed/inst" $ ArgList [("ncfeedI", Nothing)],
43-
OSC "/notochord/feed/pitch" $ ArgList [("ncfeedP", Nothing)],
44-
OSC "/notochord/feed/time" $ ArgList [("ncfeedT", Nothing)],
45-
OSC "/notochord/feed/vel" $ ArgList [("ncfeedV", Nothing)]]
46-
:}
47-
48-
-- Notochord Feed Parameters
49-
:{
50-
let ncfeedI = pI "ncfeedI"
51-
ncfeedP = pI "ncfeedP"
52-
ncfeedT = pF "ncfeedT"
53-
ncfeedV = pF "ncfeedV"
54-
ncfeed i p t v = ncfeedI i # ncfeedP p # ncfeedT t # ncfeedV v
55-
:}
56-
57-
{-
58-
Notochord.query() returns a prediction for the next note.
59-
various constraints on the the next note can be requested.
60-
61-
# hard constraints
62-
fix_*: same as the arguments to feed, but to fix a value for the predicted note.
63-
sampled values will always condition on fixed values, so passing
64-
`fix_instrument=1`, for example, will make the event appropriate
65-
for the piano (instrument 1) to play.
66-
67-
# partial constraints
68-
allow_end: if False, zero probability of sampling the end marker
69-
min_time, max_time: if not None, truncate the time distribution
70-
include_instrument: instrument id(s) to include in sampling.
71-
(if not None, all others will be excluded)
72-
exclude_instrument: instrument id(s) to exclude from sampling.
73-
include_pitch: pitch(es) to include in sampling.
74-
(if not None, all others will be excluded)
75-
exclude_pitch: pitch(es) to exclude from sampling.
76-
min_vel, max_vel: if not None, truncate the velocity distribution
77-
78-
# sampling strategies
79-
instrument_temp: if not None, apply top_p sampling to instrument. 0 is
80-
deterministic, 1 is 'natural' according to the model
81-
pitch_temp: if not None, apply top_p sampling to pitch. 0 is
82-
deterministic, 1 is 'natural' according to the model
83-
velocity_temp: if not None, apply temperature sampling to the velocity
84-
component.
85-
rhythm_temp: if not None, apply top_p sampling to the weighting
86-
of mixture components. this affects coarse rhythmic patterns;
87-
0 is deterministic, 1 is 'natural' according to the model
88-
timing_temp: if not None, apply temperature sampling to the time
89-
component. this affects fine timing; 0 is deterministic and
90-
precise, 1 is 'natural' according to the model.
91-
index_pitch: Optional[int]. if not None, deterministically take the
92-
nth most likely pitch instead of sampling.
93-
94-
# multiple predictions
95-
pitch_topk: Optional[int]. if not None, instead of sampling pitch,
96-
stack the top k most likely pitches along the batch dimension
97-
sweep_time: if True, instead of sampling time, choose a diverse set of
98-
times and stack along the batch dimension
99-
100-
Returns: dict of
101-
'end': int. value of 1 indicates the *current* event (the one
102-
passed as arguments to `predict`) was the last event, and the
103-
predicted event should *not* be played. if `allow end` is false,
104-
this will always be 0.
105-
'step': int. number of steps since calling `reset`.
106-
'instrument': int. id of predicted instrument.
107-
1-128 are General MIDI standard melodic instruments
108-
129-256 are drumkits for MIDI programs 1-128
109-
257-264 are 'anonymous' melodic instruments
110-
265-272 are 'anonymous' drums
111-
'pitch': int. predicted MIDI number of next note, 0-128.
112-
'time': float. predicted time to next note in seconds.
113-
'velocity': float. unquantized predicted velocity of next note.
114-
0-127; hard 0 indicates a note-off event.
115-
'*_params': tensor. distribution parameters for visualization
116-
purposes.
117-
-}
118-
119-
-- Notochord Query OSC Specs
120-
:{
121-
ncQueryOSCSpecs = [OSC "/notochord/query/fix/inst" $ ArgList [("ncqueryfixI", Nothing)],
122-
OSC "/notochord/query/fix/pitch" $ ArgList [("ncqueryfixP", Nothing)],
123-
OSC "/notochord/query/fix/time" $ ArgList [("ncqueryfixT", Nothing)],
124-
OSC "/notochord/query/fix/vel" $ ArgList [("ncqueryfixV", Nothing)],
125-
OSC "/notochord/query/constrain/excludei" $ ArgList [("ncqueryexcludeI", Nothing)],
126-
OSC "/notochord/query/constrain/includep" $ ArgList [("ncqueryincludeP", Nothing)],
127-
OSC "/notochord/query/constrain/excludep" $ ArgList [("ncqueryexcludeP", Nothing)],
128-
OSC "/notochord/query/constrain/mint" $ ArgList [("ncqueryminT", Nothing)],
129-
OSC "/notochord/query/constrain/maxt" $ ArgList [("ncquerymaxT", Nothing)],
130-
OSC "/notochord/query/constrain/minv" $ ArgList [("ncqueryminV", Nothing)],
131-
OSC "/notochord/query/constrain/maxv" $ ArgList [("ncquerymaxV", Nothing)],
132-
OSC "/notochord/query/sample/ncquerytempi" $ ArgList [("ncquerytempI", Nothing)],
133-
OSC "/notochord/query/sample/ncquerytempp" $ ArgList [("ncquerytempP", Nothing)],
134-
OSC "/notochord/query/sample/ncquerytempt" $ ArgList [("ncquerytempT", Nothing)],
135-
OSC "/notochord/query/sample/ncquerytempr" $ ArgList [("ncquerytempR", Nothing)],
136-
OSC "/notochord/query/sample/ncquerytempv" $ ArgList [("ncquerytempV", Nothing)],
137-
OSC "/notochord/query/sample/ncqueryindexp" $ ArgList [("ncqueryindexP", Nothing)],
138-
OSC "/notochord/query/multi/ncquerytopp" $ ArgList [("ncquerytopP", Nothing)],
139-
OSC "/notochord/query/multi/ncquerysweept" $ ArgList [("ncquerysweepT", Nothing)]
140-
]
141-
:}
142-
143-
-- Notochord Query: fix
144-
:{
145-
let ncqueryfixI = pI "ncqueryfixI"
146-
ncqueryfixP = pI "ncqueryfixP"
147-
ncqueryfixT = pF "ncqueryfixT"
148-
ncqueryfixV = pF "ncqueryfixV"
149-
:}
150-
151-
-- Notochord Query: constraints
152-
:{
153-
let ncqueryexcludeI = pI "ncqueryexcludeI"
154-
ncqueryincludeP = pI "ncqueryincludeP"
155-
ncqueryexcludeP = pI "ncqueryexcludeP"
156-
ncqueryminT = pF "ncqueryminT"
157-
ncquerymaxT = pF "ncquerymaxT"
158-
ncqueryminV = pF "ncqueryminV"
159-
ncquerymaxV = pF "ncquerymaxV"
8+
ncTarget = Target {
9+
oName = "Notochord",
10+
oAddress = "127.0.0.1",
11+
oHandshake = False,
12+
oPort = 6789,
13+
oBusPort = Nothing,
14+
oLatency = 0.1,
15+
oSchedule = Pre BundleStamp,
16+
oWindow = Nothing
17+
}
16018
:}
16119

162-
-- Notochord Query: sampling
20+
-- need to use Named instead of ArgList to get
21+
-- time from pattern structure (delta, cps, cycle)
16322
:{
164-
let ncquerytempI = pF "ncquerytempI"
165-
ncquerytempP = pF "ncquerytempP"
166-
ncquerytempT = pF "ncquerytempT"
167-
ncquerytempR = pF "ncquerytempR"
168-
ncquerytempV = pF "ncquerytempV"
169-
ncqueryindexP = pI "ncqueryindexP"
23+
ncOSCSpecs = [
24+
OSC "/notochord/tidal_feed" $ Named {requiredArgs = []}
25+
]
17026
:}
17127

172-
-- Notochord Query: multiple predictions
28+
-- control patterns
17329
:{
174-
let ncquerytopP = pI "ncquerytopP"
175-
ncquerysweepT = pI "ncquerysweepT"
30+
let ncinst = pI "ncinst"
31+
ncpitch = pI "ncpitch"
32+
ncvel = pF "ncvel"
33+
-- ncfeed i p t v = ncfeedI i # ncfeedP p # ncfeedT t # ncfeedV v
17634
:}
17735

178-
let ncOSCSpecs = ncFeedOSCSpecs++ncQueryOSCSpecs
179-
18036
-- Notochord OSC Map
18137
ncOscMap = (ncTarget, ncOSCSpecs)

examples/notochord/tidalcycles/notochord.tidal

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
-- Notochord feed and query demo patterns
2+
:set
23

3-
d "feed"
4-
$ ncfeedP (scale "<major minor>" $ run 16)
5-
# ncfeedT (range 0 1 $ cos)
6-
# ncfeedV (range 0 1 $ sine)
7-
# ncfeedI "0 1 2 3"
4+
p "feed"
5+
$ ncfeedI "1 9 17 25"
6+
# ncfeedP (run 16 + 48)
7+
# ncfeedV (range 1 127 $ sine)
88

9-
d "query"
9+
p "query"
1010
$ ncqueryfixP (scale "<major minor>" $ run 16)
1111
# ncqueryfixT (range 0 1 $ cos)
1212
# ncqueryfixV (range 0 1 $ sine)
@@ -26,3 +26,5 @@ d "query"
2626
# ncqueryindexP "[0, 1, 2, 3]"
2727
# ncquerytopP "[0, 1, 2, 3]"
2828
# ncquerysweepT "<0 1>"
29+
30+
hush

notochord/notochord/model.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ def query(self,
483483
include_pitch=None, exclude_pitch=None,
484484
instrument_temp=None, pitch_temp=None, velocity_temp=None,
485485
rhythm_temp=None, timing_temp=None,
486-
min_vel=None, max_vel=None):
486+
min_vel=None, max_vel=None,
487+
handle=None, return_params=False):
487488
"""
488489
return a prediction for the next note.
489490
@@ -529,6 +530,11 @@ def query(self,
529530
sweep_time: if True, instead of sampling time, choose a diverse set of
530531
times and stack along the batch dimension
531532
533+
# other
534+
handle: an event ID to be included in the returned
535+
dict, if not None
536+
return_params: if True, return distribution parameters
537+
532538
Returns: dict of
533539
'end': int. value of 1 indicates the *current* event (the one
534540
passed as arguments to `predict`) was the last event, and the
@@ -702,25 +708,51 @@ def query(self,
702708
end = end.item()
703709

704710
self.step += 1
705-
return {
711+
r = {
706712
'end': end,
707713
'step': self.step,
708714
'instrument': pred_inst,
709715
'pitch': pred_pitch,
710716
'time': pred_time,
711717
'velocity': pred_vel,
712-
'inst_params': params[iperm[0]],
713-
'pitch_params': params[iperm[1]],
714-
'time_params': params[iperm[2]],
715-
'vel_params': params[iperm[3]]
716718
}
717719

720+
if handle is not None:
721+
r['handle'] = handle
722+
723+
if return_params:
724+
r |= {
725+
'inst_params': params[iperm[0]],
726+
'pitch_params': params[iperm[1]],
727+
'time_params': params[iperm[2]],
728+
'vel_params': params[iperm[3]]
729+
}
730+
731+
return r
732+
733+
718734
def predict(self, inst, pitch, time, vel, **kw):
735+
"""
736+
DEPRECATED: alias for feed_query
737+
"""
738+
self.feed(inst, pitch, time, vel)
739+
return self.query(**kw)
740+
741+
def feed_query(self, inst, pitch, time, vel, **kw):
719742
"""
720743
call self.feed with *args, then self.query with **kwargs.
721744
"""
722745
self.feed(inst, pitch, time, vel)
723746
return self.query(**kw)
747+
748+
def query_feed(self, *a, **kw):
749+
"""
750+
call self.query with *args **kwargs, then self.feed with result,
751+
and return result
752+
"""
753+
r = self.query(*a, **kw)
754+
self.feed(r['instrument'], r['pitch'], r['time'], r['velocity'])
755+
return r
724756

725757
def reset(self, start=True):
726758
"""

0 commit comments

Comments
 (0)