Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subtle timing issue with PScaleLinLin #26

Open
dtenenba opened this issue Mar 17, 2022 · 2 comments
Open

subtle timing issue with PScaleLinLin #26

dtenenba opened this issue Mar 17, 2022 · 2 comments

Comments

@dtenenba
Copy link
Contributor

dtenenba commented Mar 17, 2022

Below is a script that is meant to do something simple:

  • Create a finite PSequence (pat) from 10 hardcoded notes with repeats=1
  • Create another pattern (dur) using PScaleLinLin to scale the notes in pat to the range 0.2 to 1.2
  • Create reversed copies of each (rpat and rdur)
  • Schedule pat notes to play for duration dur
  • Schedule rpat notes to play for duration rdur
  • Run timeline.

You would expect both tracks to finish at the same time, because the sums of dur and rdur are the same forwards or backwards. But the first track finishes well before the second, as you can hear and see from the logging messages:

[2022-03-17 14:37:24,183] Timeline: Scheduled new track (total tracks: 1)
[2022-03-17 14:37:24,183] Timeline: Scheduled new track (total tracks: 2)
[2022-03-17 14:37:24,183] Timeline: Starting
[2022-03-17 14:37:27,624] Timeline: Track finished, removing from scheduler (total tracks: 1)
[2022-03-17 14:37:35,014] Timeline: Track finished, removing from scheduler (total tracks: 0)
[2022-03-17 14:37:35,014] Timeline: Finished

I found a workaround which is to pre-iterate through dur:

dur = iso.PSequence(dur.all(), 1) # <-- workaround!

When I uncomment that line, the tracks finish in the same millisecond:

[2022-03-17 14:52:35,214] Timeline: Scheduled new track (total tracks: 1)
[2022-03-17 14:52:35,214] Timeline: Scheduled new track (total tracks: 2)
[2022-03-17 14:52:35,214] Timeline: Starting
[2022-03-17 14:52:46,046] Timeline: Track finished, removing from scheduler (total tracks: 1)
[2022-03-17 14:52:46,046] Timeline: Track finished, removing from scheduler (total tracks: 0)
[2022-03-17 14:52:46,046] Timeline: Finished

But I am wondering why this happens. I looked at the code for PScaleLinLin and PMap which it calls, but nothing jumps out at me. It seems that when the PScaleLinLin pattern is iterated "live" (while scheduled on the timeline) something happens and it finishes early.

Not a huge deal since I have a workaround but thought it was worth reporting, and I'm curious what this turns out to be.

I'm on an M1 Mac Mini running macOS 12.3/Monterey and Python 3.10.2 (native ARM build) and the latest version of isobar from PyPI (0.1.1) (but it also happens when I install the latest version from GitHub).

Here is the full script:

#!/usr/bin/env python3

import isobar as iso

import logging

logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(message)s")

patnotes = [74, 1, 76, 76, 52, 30, 83, 13, 97, 9]
pat = iso.PSequence(patnotes, 1)

dur = iso.PScaleLinLin(pat, min(patnotes), max(patnotes), 0.2, 1.2)

# dur = iso.PSequence(dur.all(), 1) # <-- workaround!

rpat = iso.PReverse(pat.copy())
rdur = iso.PReverse(dur.copy())

# all have the same length:
print(len(pat))
print(len(dur))
print(len(rpat))
print(len(rdur))


timeline = iso.Timeline(40)
timeline.stop_when_done = True

timeline.schedule(dict(
    note=pat,
    duration=dur,
    amplitude=100,
    gate=1,
    channel=0
))

timeline.schedule(dict(
    note=rpat,
    duration=rdur,
    amplitude=100,
    gate=1,
    channel=0
))


try:
    timeline.run()
except KeyboardInterrupt:
    timeline.output_device.all_notes_off()
@loparcog
Copy link
Collaborator

This seems like a weird interaction between the PReverse class and using a PScaleLinLin object as input. It's also interesting to note that putting dur in both schedule functions, they still don't match up unless you copy the object, so it could also be an issue with scheduling non-constant durations

@loparcog
Copy link
Collaborator

loparcog commented Feb 11, 2023

Late response but found the issue, when making the dur variable, it pulls from pat by reference instead of by value, the easiest way to see this is getting the length of dur, running nextn on pat, and getting the length again:

print(len(dur))
print(pat.nextn(5))
print(len(dur))

Gives the output:

10
[74, 1, 76, 76, 52]
5

The workaround you listed seems to work because PSequence copies the input list. Another way to solve this issue would be to use iso.PScaleLinLin(pat.copy(), ..., but I'll put in a push request to copy the list by value for PMap and any other needed functions

(Addressed in #31)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants