Problem
When NoteGeneratorThread follows an Ableton Link session, _set_generator_tempos() walks the generator tree and updates .tempo on every rhythm-notetype Itemstream found in generator.streams. However, mapping streams stored in generator.context are invisible to this walk — their .tempo is never updated.
This is a common pattern in csound-pieces (especially index-based granular pieces): a mapping Itemstream with mapping_keys=[keys.rhythm, keys.index] lives in context['tuplestream'], and a post_process callable pulls from it to set note.rhythm, note.pfields[keys.duration], and other pfields. The mapping stream's .tempo controls the actual rhythmic timing, not the generator's own rhythm stream (which is effectively a dummy).
Example from thuja-ep/olallan/jams4.py:
g.context['tuplestream'] = Itemstream(
mapping_keys=[keys.rhythm, keys.index],
mapping_lists=[rhythms, indexes],
tempo=160,
streammode=streammodes.random
)
def post_process(note, context):
item = context['tuplestream'].get_next_value()
note.rhythm = utils.rhythm_to_duration(item[keys.rhythm], context['tuplestream'].tempo)
note.pfields[keys.index] = item[keys.index]
note.pfields[keys.duration] = note.rhythm
When Link changes tempo to 100 bpm, _set_generator_tempos updates the generator's rhythm stream to 100, but context['tuplestream'].tempo stays at 160. The audible rhythm doesn't change.
Scope
This pattern appears in many pieces in csound-pieces, particularly:
thuja-ep/books-style/ — index-based granular pieces
thuja-ep/olallan/ — live-coding pieces with mapping streams
thuja-ep/live_coding/ — similar patterns
The freq_to_file post_process (copy-pasted across 30+ files) is a related pattern that also relies on context-stored state.
Options to consider
-
Convention-based: _set_generator_tempos also walks generator.context looking for Itemstream values and updates their .tempo. Simple but implicit — any Itemstream in context gets its tempo changed, which may not always be desired.
-
Opt-in registration: Add a method like generator.register_tempo_stream(stream) that adds the stream to a list. _set_generator_tempos updates both streams and the registered list. Explicit but requires user action.
-
Callback hook: Fire a user-defined callback on tempo change (e.g., generator.on_tempo_change(new_bpm)), letting the post_process author decide what to update. Most flexible, least magic.
-
Tempo indirection: Instead of storing a tempo value on each stream, streams could reference a shared tempo source (the generator's rhythm stream tempo, or the Link BPM directly). The mapping stream's .tempo would be a property that reads from the source. Cleanest long-term but larger refactor.
Interaction with #46 (tempo ratios)
If per-generator tempo ratios are implemented, the mapping stream's tempo would need to respect the same ratio. Options 1 and 2 handle this naturally (the walk applies the ratio). Options 3 and 4 would need the ratio passed to the callback or shared source.
Problem
When
NoteGeneratorThreadfollows an Ableton Link session,_set_generator_tempos()walks the generator tree and updates.tempoon every rhythm-notetypeItemstreamfound ingenerator.streams. However, mapping streams stored ingenerator.contextare invisible to this walk — their.tempois never updated.This is a common pattern in
csound-pieces(especially index-based granular pieces): a mappingItemstreamwithmapping_keys=[keys.rhythm, keys.index]lives incontext['tuplestream'], and apost_processcallable pulls from it to setnote.rhythm,note.pfields[keys.duration], and other pfields. The mapping stream's.tempocontrols the actual rhythmic timing, not the generator's own rhythm stream (which is effectively a dummy).Example from
thuja-ep/olallan/jams4.py:When Link changes tempo to 100 bpm,
_set_generator_temposupdates the generator's rhythm stream to 100, butcontext['tuplestream'].tempostays at 160. The audible rhythm doesn't change.Scope
This pattern appears in many pieces in
csound-pieces, particularly:thuja-ep/books-style/— index-based granular piecesthuja-ep/olallan/— live-coding pieces with mapping streamsthuja-ep/live_coding/— similar patternsThe
freq_to_filepost_process (copy-pasted across 30+ files) is a related pattern that also relies on context-stored state.Options to consider
Convention-based:
_set_generator_temposalso walksgenerator.contextlooking for Itemstream values and updates their.tempo. Simple but implicit — any Itemstream in context gets its tempo changed, which may not always be desired.Opt-in registration: Add a method like
generator.register_tempo_stream(stream)that adds the stream to a list._set_generator_temposupdates bothstreamsand the registered list. Explicit but requires user action.Callback hook: Fire a user-defined callback on tempo change (e.g.,
generator.on_tempo_change(new_bpm)), letting the post_process author decide what to update. Most flexible, least magic.Tempo indirection: Instead of storing a tempo value on each stream, streams could reference a shared tempo source (the generator's rhythm stream tempo, or the Link BPM directly). The mapping stream's
.tempowould be a property that reads from the source. Cleanest long-term but larger refactor.Interaction with #46 (tempo ratios)
If per-generator tempo ratios are implemented, the mapping stream's tempo would need to respect the same ratio. Options 1 and 2 handle this naturally (the walk applies the ratio). Options 3 and 4 would need the ratio passed to the callback or shared source.