<jupyter_start><jupyter_text>Parameter Mapping Sonification using mesonic Let's see how we can use the Synth concept for Parameter Mapping Sonification (PMson) and use the Playback filtering feature to explore our PMson.It is recommended to use headphones for the examples as they use panning.<jupyter_code>import mesonic<jupyter_output><empty_output><jupyter_text>Preparation of Synths Let's start by preparing our Context and Synths.<jupyter_code>context = mesonic.create_context()<jupyter_output><empty_output><jupyter_text>Let's create two Synths- A discrete (s1) immutable Synth named s1i- and a continuous (s2) mutable Synth named s2<jupyter_code>s1i = context.synths.create("s1", mutable=False)
s2 = context.synths.create("s2", track=1)<jupyter_output><empty_output><jupyter_text>And we also create a Playback for later usage.<jupyter_code>pb = context.create_playback()<jupyter_output><empty_output><jupyter_text>Data Preparation We also prepare the data for the sonification.We will use the [Palmer penguins dataset](https://allisonhorst.github.io/palmerpenguins/) for the examples.<jupyter_code>import seaborn as sns
df = sns.load_dataset("penguins")
df = df.dropna(subset=["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"])
df
sns.pairplot(data=df, hue="species")<jupyter_output><empty_output><jupyter_text>Mapping Preparation We use the mapping functions from sc3nb<jupyter_code>from sc3nb import linlin, midicps, cpsmidi<jupyter_output><empty_output><jupyter_text>And we also define a small helper class that allows us to map the values linearly<jupyter_code>class DFMapper:
    # We define a mapping helper class that gets a dataframe
    # and allows to provide a index and column of a value in the dataframe
    # to map the corresponding value from the source (smin,smax)
    # to the target (tmin,tmax) linearly.
    # If the source range is None we get the min / max of the column
    
    def __init__(self, df):
        self.df = df
        
    def linear(self, idx, col, tmin, tmax, smin=None, smax=None):
        if smin is None:
            smin = getattr(self.df, col).min()
        if smax is None:
            smax = getattr(self.df, col).max()
        # get the value using the idx and column name
        value = getattr(self.df, col)[idx]
        return linlin(value, smin, smax, tmin, tmax)<jupyter_output><empty_output><jupyter_text>Create a instance for later usage<jupyter_code>m = DFMapper(df)<jupyter_output><empty_output><jupyter_text>Example of a discrete PMSonFor a discrete PMSon a immutable Synth is often very practical as we do not need to worry about* overlapping Synth playbacks and* the Synth is expected to end itself. Let's create the PMSon<jupyter_code>context.reset()

# we iterate over the penguins dataset
for idx in df.index:
    # longer flipper -> later onset
    onset = m.linear(idx, "flipper_length_mm", 0, 5)
    # use the context manager to provide time and add the species as info.
    with context.at(onset, info={"species": df.loc[idx, "species"]}):
        s1i.start(
            # heavier -> lower frequency
            freq = midicps(m.linear(idx, "body_mass_g", 96, 50)),        
            # longer bill -> longer sound
            dur = m.linear(idx, "bill_length_mm", 0, 0.3),
            # pan the sounds according to sex
            pan = {"Female": 1, "Male": -1}.get(df.loc[idx, "sex"], 0),  
            amp = 0.15
        )

dpmson = context.timeline.to_dict()  # we can save the times with the Events as dict and insert them later again
context.timeline.plot()
context.timeline
pb.start()<jupyter_output><empty_output><jupyter_text>Example of a continuous PMSonFor a continuous PMSon a mutable Synths is the obvious choice.* The Synth can be changed by setting the Parameters or using `Synth.set` and* it allows stopping the Synth playback in a controlled way and thus allows using continuous (not self ending) Synths<jupyter_code>context.reset()

# the continuous Synth must be started at the beginning
with context.at(0):
    s2.start()


# we use a similiar mapping here but set the Parameters
# of the continuous Synth instead of starting the Synth.
for idx in df.index:
    onset = m.linear(idx, "flipper_length_mm", 0, 5)
    with context.at(onset, info={"species": df.loc[idx, "species"]}) as tp:
        s2.freq = midicps(m.linear(idx, "body_mass_g", 96, 50))
        s2.amp = m.linear(idx, "bill_length_mm", 0, 0.2)
        s2.pan = {"Female": 1, "Male": -1}.get(df.loc[idx, "sex"], 0)


# and also don't forget to stop the continuous Synth
with context.at(context.timeline.last_timestamp + 0.1):
    s2.stop()

cpmson = context.timeline.to_dict()
context.timeline.plot()
context.timeline
pb.start()<jupyter_output><empty_output><jupyter_text>Example of a mixed PMSon To mix the both PMSon types we can simply combine the two code snippets from above.<jupyter_code>context.reset()
# Here we simply extend the timeline with the stored Timelines.
context.timeline.extend(dpmson)
context.timeline.extend(cpmson)<jupyter_output><empty_output><jupyter_text>The Timeline is now updated accordingly.<jupyter_code>context.timeline.plot()
context.timeline<jupyter_output><empty_output><jupyter_text>Note that only one more entry now exists as we use the same onset as in the discrete mapping. We can now hear both Synths playing.<jupyter_code>pb.start()<jupyter_output><empty_output><jupyter_text>* Using the processor we can select the single tracks.* We used the tracks here to divide the different PMSon types.* so if we select just one track we can listen to the different PMSon types separately<jupyter_code>pb.processor.selected_tracks = [1]
pb.start()<jupyter_output><empty_output><jupyter_text>You can adjust this while the Playback is running.<jupyter_code>pb.processor.selected_tracks = [0, 1]<jupyter_output><empty_output><jupyter_text>However this can quickly lead to inconsistencies in the timeline* a continuous Synths might not be stopped or started which will then result in errors in the backend.<jupyter_code>pb.processor.selected_tracks = [0]<jupyter_output><empty_output><jupyter_text>A `Context.stop` will stop all Playbacks created by the Context aswell as trigger `Backend.stop` of the backend that is used. This should stop all Events from being executed and also stop all sounds that have become stuck in the backend.<jupyter_code>context.stop()<jupyter_output><empty_output><jupyter_text>Filtering and Transforming the Sonification The Playback can be filtered by tracks as shown above.Another way to filter the Playback is to using the `event_filter` of BundleProcessor.Let's do it with the discrete PMSon<jupyter_code>context.reset()
context.timeline.extend(dpmson)
context.timeline.plot()<jupyter_output><empty_output><jupyter_text>We can filter f.e by species<jupyter_code>df.species.unique()
def create_filter(selected_species):
    def filter_fun(event):
        if event.info.get("species", None) == selected_species:
            return event
        else:
            return None
    return filter_fun<jupyter_output><empty_output><jupyter_text>This allows us to listen just to the single species.<jupyter_code>pb.processor.event_filter = create_filter("Adelie")

pb.start()
pb.processor.event_filter = create_filter("Chinstrap")

pb.start()<jupyter_output><empty_output><jupyter_text>The Gentoo penguins are the ones with the greatest body mass and the longest flippers. Therefore they will only sound quite late in the timeline.<jupyter_code>pb.processor.event_filter = create_filter("Gentoo")

pb.start()<jupyter_output><empty_output><jupyter_text>It is also possible to use the `event_filter` to transform the Events.Here we create a slider that allows us to change the panning of the events during Playback.<jupyter_code>import ipywidgets as widgets
pan_slider = widgets.FloatSlider(value=0, min=-1, max=1)
pan_slider
def custom_transform(event):
    if event.data.get("freq", None):
        event.data["pan"] = pan_slider.value
    if event.data.get("name", None) == "pan":
        event.data["pan"] = pan_slider.value
    return event

pb.processor.event_filter = custom_transform
pb.start()
# set the Event processing back to normal.
pb.processor.event_filter = None   
context.close()<jupyter_output>Quitting SCServer... Done.
Exiting sclang... Done.
{'fraction_non_alphanumeric': 0.09284939105269505, 'fraction_numerical': 0.006511515736163029, 'mean_word_length': 4.755725190839694, 'pattern_counts': {'":': 7, '<': 88, '<?xml version=': 0, '>': 91, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '154113', 'license': 'MIT', 'n_tokens_mistral': 2687, 'n_tokens_neox': 2589, 'n_words': 843, 'path': 'notebooks/mesonic-pmson.ipynb', 'repo_name': 'dreinsch/mesonic', 'size': '8293'}