vishnubob / python-midi

Python MIDI library
MIT License
1.51k stars 369 forks source link

Best way to deepcopy a track #60

Open tleyden opened 9 years ago

tleyden commented 9 years ago

I'm trying to get a deep copy of a track via:

copy.deepcopy(track)

but I'm getting this error:

  File "/Users/tleyden/Development/climate_music/src/transformer.py", line 52, in transform
    track_copy = copy.deepcopy(track)
  File "/Users/tleyden/DevLibraries/anaconda/lib/python2.7/copy.py", line 190, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  File "/Users/tleyden/DevLibraries/anaconda/lib/python2.7/copy.py", line 351, in _reconstruct
    item = deepcopy(item, memo)
  File "/Users/tleyden/DevLibraries/anaconda/lib/python2.7/copy.py", line 190, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  File "/Users/tleyden/DevLibraries/anaconda/lib/python2.7/copy.py", line 346, in _reconstruct
    setattr(y, key, value)
  File "/Users/tleyden/DevLibraries/anaconda/lib/python2.7/site-packages/midi/events.py", line 125, in set_velocity
    self.data[1] = val
AttributeError: data

As an alternative, I'm writing my own kludge and just propagating the events I care about:

def copy_track(track):
    """
    copy.deepcopy() didn't work, so I hand rolled this as a workaround
    """
    track_copy = midi.Track()
    for event in track:
        if isinstance(event, midi.NoteOnEvent):
            on = midi.NoteOnEvent(tick=event.tick, velocity=event.velocity, pitch=event.pitch)
            track_copy.append(on)
        if isinstance(event, midi.NoteOffEvent):
            off = midi.NoteOffEvent(tick=event.tick, velocity=event.velocity, pitch=event.pitch)
            track_copy.append(off)
     return track_copy

If it's not feasible to make copy.deepcopy() work, is there a more elegant way of rolling my own?

tdhsmith commented 9 years ago

I never really looked into this fully. From the discussion last time (#40), it seems I had tracked it to a known interaction between deepcopy using property/custom getters/setters, but I also seem to recall something about how __slots__ worked affecting it. Hrm...

So I think fixing it is feasible, but no one ever got around to figuring it out.

In the meantime, I know you can simply provide custom functions for __copy__/__deepcopy__ which will be used by them automatically. Here's an SO thread on it.

cgloeckner commented 8 years ago

I also tried to deepcopy, because I was removing single notes (note on & off) from a track and so I had to update the next note's tick to produce a suitable gap between both (the last and the next remaining note). Because I accessed the same source track multiple times (to split several drum components like bass drum or snare in one session without reloading), I used the event's constructor to recreate the event when appending to the new track (so to say: manually deep copy it). So it looked like this:

kwargs = {
    "tick": event.tick,
    "velocity": event.velocity,
    "pitch": event.pitch,
    "channel": event.channel
}
target.append(type(event)(**kwargs))

But I'm not sure whether I fetched all necessary ctor-args .. but it seems to work for me ^^ Doing this on your own isn't perfect but maybe a suitable temporary solution to you :-)