@@ -428,9 +428,15 @@ def _check_consistency(self, duration_key="dur"):
428428 def _create_segments (self , offset_key = "freq" , width_key = "amp" ):
429429 splitted_synth_events = self ._check_consistency ()
430430 # for quiver X, U, Y (V=0), arrowwidths
431- segments = {"begins" : [], "ends" : [], "offsets" : [], "widths" : []}
432- # for scatter tuples (x,y)
433- markers = {"start" : [], "stop" : [], "set" : []}
431+ segments = {
432+ "begins" : [],
433+ "ends" : [],
434+ "offsets" : [],
435+ "widths" : [],
436+ "connections" : [],
437+ }
438+ # start & stop tuples (x,y) for scatter, set events: (x,y,x,y2)
439+ markers = {"starts" : [], "stops" : [], "sets" : []}
434440 synth_plot_info = {
435441 synth : {
436442 "events" : events ,
@@ -444,6 +450,7 @@ def _create_segments(self, offset_key="freq", width_key="amp"):
444450 ends = synth_plot_info [synth ]["segments" ]["ends" ]
445451 offsets = synth_plot_info [synth ]["segments" ]["offsets" ]
446452 widths = synth_plot_info [synth ]["segments" ]["widths" ]
453+ connections = synth_plot_info [synth ]["segments" ]["connections" ]
447454
448455 for time , event in events [SynthEventType .START ]:
449456 begins .append (time )
@@ -458,9 +465,7 @@ def _create_segments(self, offset_key="freq", width_key="amp"):
458465 for set_time , set_event in events [SynthEventType .SET ]:
459466 combined_set_events [set_time ].append (set_event )
460467 insert_idx = 0
461- # begins = begins
462468 for set_time , set_events in combined_set_events .items ():
463- # set search index back by one
464469 while insert_idx < len (begins ) and begins [insert_idx ] < set_time :
465470 insert_idx += 1
466471 # get properties for the event data
@@ -471,6 +476,12 @@ def _create_segments(self, offset_key="freq", width_key="amp"):
471476 offset = set_event .data ["new_value" ]
472477 offsets .insert (insert_idx , offset )
473478 found_offset = True
479+ connections .append (
480+ [
481+ (set_time , set_event .data ["old_value" ]),
482+ (set_time , offset ),
483+ ]
484+ )
474485 if set_event .data ["name" ] == width_key :
475486 width = set_event .data ["new_value" ]
476487 widths .insert (insert_idx , width )
@@ -493,9 +504,7 @@ def _create_segments(self, offset_key="freq", width_key="amp"):
493504 assert len (begins ) == len (widths )
494505 return synth_plot_info
495506
496- def plot_new (
497- self , offset = "freq" , width = "amp" , duration_key = "dur" , default_duration = 0.1
498- ):
507+ def plot_new (self , offset = "freq" , width = "amp" , scale = None ):
499508 """Plot the Timeline.
500509
501510 Parameters
@@ -516,7 +525,11 @@ def plot_new(
516525 If matplotlib cannot be imported.
517526 """
518527 try :
528+ import matplotlib .patches as mpatches
519529 import matplotlib .pyplot as plt
530+ import matplotlib .ticker as ticker
531+ from matplotlib .collections import LineCollection
532+
520533 except ImportError as err :
521534 raise ImportError (
522535 "plotting the Timeline is only possible when matplotlib is installed."
@@ -560,14 +573,25 @@ def plot_new(
560573 # "color": None,
561574 # "y_offset": 0,
562575
563- fig = plt .figure ()
564- axes = fig .add_subplot (1 , 1 , 1 )
565- from matplotlib . collections import LineCollection
576+ fig = plt .figure (figsize = ( 8 , 2 ) )
577+ ax = fig .add_subplot (1 , 1 , 1 )
578+ cmap = plt . get_cmap ( "Set1" )
566579
567580 x_min , x_max = 0 , 0
568581 y_min , y_max = 0 , 0
569582
570- for synth in synth_plot_info .keys ():
583+ patches = []
584+ synth_colors = {}
585+ synth_labels = {}
586+ for synth_idx , synth in enumerate (synth_plot_info .keys ()):
587+ synth_colors [synth ] = cmap (synth_idx )
588+ synth_labels [synth ] = f"{ synth .name } " + (
589+ " (mutable)" if synth .mutable else " (immutable)"
590+ )
591+ patches .append (
592+ mpatches .Patch (color = synth_colors [synth ], label = synth_labels [synth ])
593+ )
594+
571595 segments = synth_plot_info [synth ]["segments" ]
572596 begins = np .array (segments ["begins" ])
573597 ends = np .array (segments ["ends" ])
@@ -597,22 +621,66 @@ def warp_width(value, scale=None, width_min=2, width_max=4):
597621 )
598622 )
599623 )
600- lc = LineCollection (
601- lines ,
602- linewidth = widths ,
624+ synth_segments_collection = LineCollection (
625+ lines , linewidth = widths , color = synth_colors [synth ]
603626 )
604- axes .add_collection (lc )
627+ ax .add_collection (synth_segments_collection )
628+ if synth .mutable :
629+ connections = synth_plot_info [synth ]["segments" ]["connections" ]
630+ synth_connections_collection = LineCollection (
631+ connections , linewidth = 0.5 , color = synth_colors [synth ]
632+ )
633+ ax .add_collection (synth_connections_collection )
605634
606635 x_min = min (begins .min (), x_min )
607636 x_max = max (ends .max (), x_max )
608637 y_min = min (offsets .min (), y_min )
609638 y_max = max (offsets .max (), y_max )
610639
611- x_lim = (x_max - x_min ) * 0.1
612- axes .set_xlim (x_min - x_lim , x_max + x_lim )
613- y_lim = (y_max - y_min ) * 0.1
614- axes .set_ylim (y_min - y_lim , y_max + y_lim )
615- axes .grid ()
640+ x_lim = (x_max - x_min ) * 0.01
641+ ax .set_xlim (x_min - x_lim , x_max + x_lim )
642+ # y_lim = (y_max - y_min) * 0.1
643+ # axes.set_ylim(y_min - y_lim, y_max + y_lim)
644+
645+ # test
646+ ax .legend (handles = patches , loc = "best" )
647+ ax .grid ()
648+ ax .set_title ("Timeline" )
649+ ax .set_xlabel ("time [s]" )
650+
651+ if offset == "freq" :
652+ ax .set_yscale ("log" )
653+ ax .set_ylabel ("frequency [Hz]" )
654+ secax = ax .secondary_yaxis (
655+ location = "right" , functions = (pam .cps_to_midi , pam .midi_to_cps )
656+ )
657+ secax .set_ylabel ("MIDI Note" )
658+ secax .yaxis .set_major_locator (
659+ ticker .MaxNLocator (nbins = "auto" , steps = [1 , 2 , 4 , 5 , 10 ], integer = True )
660+ )
661+ secax .yaxis .set_major_formatter (lambda x , pos : str (int (x )))
662+ elif offset == "amp" :
663+ ax .set_yscale ("log" )
664+ ax .set_ylabel ("amplitude" )
665+
666+ def safe_amp_to_db (amp ):
667+ return pam .amp_to_db (amp ) if amp > 0 else - 90
668+
669+ secax = ax .secondary_yaxis (
670+ location = "right" , functions = (safe_amp_to_db , pam .db_to_amp )
671+ )
672+ secax .set_ylabel ("level [dB]" )
673+ secax .yaxis .set_major_locator (
674+ ticker .MaxNLocator (nbins = "auto" , steps = [1 , 2 , 4 , 5 , 10 ], integer = True )
675+ )
676+ secax .yaxis .set_major_formatter (lambda x , pos : str (int (x )))
677+ else :
678+ ax .set_ylabel (offset )
679+
680+ ax .autoscale (enable = True , axis = True , tight = True )
681+ # axes.margins(0.05)
682+
683+ fig .tight_layout ()
616684
617685 # TODO Idea: convention to order the Synth Params by most probalbe usage
618686
0 commit comments