Tuesday, October 13, 2009

event selection

Things are always more complicated than you first think.

That was the theme for today. I was fixing a pair of functions for inserting time or deleting time. They just either nudge events forward or pull them back. They nudge everything after the selection for the size of the selection, and for convenience if the selection is a point they nudge by the current time step.

Ok, but what if the selection is overlapped by an event? Well, I should shorten or lengthen that event too, since I'm inserting or deleting time in its middle. But wait, if I'm shortening the event and it I can't shorten it more than its total duration, it should at most shorten down to the point where I'm nudging. But wait again, what if the time I'm deleting overlaps with the beginning of an event? Well, I shouldn't delete the whole event, I should only clip off the beginning before shifting it back. Of course, if that means clipping the whole note, then it gets deleted after all. So "simple" event nudging is not so simple after all.

All of this slicing and shifting exposes a weakness in the interface for modifying events in Ui.State, so I wound up rethinking that. The problem is that the traditional definition for a range is half-open, which means that its everything greater or equal than the start but less than the end. However, the nudge commands, along with a fair amount of others, should affect an event directly on the selection even when it's a point, which in a traditional half-open range will never select anything. So I wind up with a function for a range, a function for a point, and a lot of code that checks start==end and tries one or the other. This seemed error-prone so I wanted to have the range functions handle that, but baking a nonstandard exception like that into a primitive function seemed like a bad idea, so I wind up with three versions of each function: one for points, one for ranges, and one point/range hybrid.

So even deleting a range of events is not so simple after all.

Hopefully I can put these somewhat complicated but convenient behaviours into standard utilities. Then commands will behave more uniformly and it will be easier to implement them. So while three versions of each ranged function sounds excessive, I think it's probably best is the long run.

Sunday, October 11, 2009

merged tracks

Today I finally completed the merged tracks implementation. Since the merged track is hidden, I had to implement hidden tracks too, and it was more complicated than I thought. Simply pretending to the UI that the track was removed means that tracknums coming from the UI are wrong. I could insert a layer to correct incoming updates with tracknums based on the current hidden tracks, but it just seemed too complicated, so I decided to implement track collapse in the c++ layer. I actually implemented it as collapsing to a divider, since I was never happy with there being both hidden and collapsed tracks.

It meant a bit of hackery because c++ has to remember the state of the collapsed track so it can restore it. Keeping state in c++ is a bit sketchy because it's duplicated information and because it's internal operations instead of the normal diff -> update -> sync avenue. For example, what happens if a collapsed track is resized in haskell? When it's expanded, it won't get the new size. Mabybe the tracknum translation would have been cleaner? Oh well, I suppose I can switch back if need be.

Along the way I fixed a few long-standing bugs in how tracks are resized.