Monday, July 18, 2011

fixed lists

I had an unsafe function:

map_signals :: [TrackLang.Control] -> [TrackLang.PitchControl]
    -> ([Signal.Y] -> [Pitch.Degree] -> state -> Score.Event

        -> Derive.Deriver ([Score.Event], state))
    -> state -> Derive.Events -> Derive.Deriver ([Derive.Events], state)

It's unsafe because it looks up each Control and each PitchControl and passes the values back to the given function, and promises the length of [Signal.Y] will be the same as [TrackLang.Control] and likewise for Degree and PitchControl.  But it's just a promise, not a contract, so ghc started complaining about incomplete pattern matches when called like map_signals [a, b, c] [d, e] $ \[a, b, c] [d, e] -> ....  So the fixed-list package fixes that:

map_signals :: (FixedList.FixedList cs, FixedList.FixedList ps) =>
    cs TrackLang.Control -> ps TrackLang.PitchControl
    -> (cs Signal.Y -> ps Pitch.Degree -> state -> Score.Event
        -> Derive.Deriver ([Score.Event], state))
    -> state -> Derive.Events -> Derive.Deriver ([Derive.Events], state)

Now 'ps' and 'cs' are specified to have the same lengths, and no more warnings.  They get called almost the same:

map_signals (a :. b :. c :. Nil) (d :. e :. Nil) $
    \(a :. b :. c :. Nil) (d :. e :. Nil) -> ...

I wasn't too worried about type errors since the lists and patterns are adjacent, but it's a neat trick anyway.

step play complete

Finally after spending too much time on it, it's done.  I wound up implementing both forwards and backwards stepping, and simplified things in the process.  Previously I had a stream of MIDI messages and would step along them by finding the step position, calculating the next step, finding the resultant RealTime, and playing that many MIDI messages.

I changed it to produce all the step points along with the MIDI state at each point, right off the bat, and store that.  Then stepping is just moving forward one state, diffing it with the previous one to generate a set of MIDI messages, and playing those.  Now backwards is as easy as forwards.  And the StepState is much simplified, and the stepping functions don't have to know anything about the steps or about RealTime, or anything like that.

In a strict language, this approach wouldn't work because it would immediately want to realize the entire score just to play a single note.  Sometimes I'm on the fence about whether laziness is worth it in the long run, but this is another point for laziness.

I'm still not sure whether backwards scrubbing is really worth all that craziness, but it's kind of fun to mess around with.

Friday, July 15, 2011

rats are ideal

When rats wash a lot there's a sort of a sweetish wet fur smell.  I like it, and it brings up all kinds of associations with late night hacking, or gaming, or whatever else, since they hang out on my lap all night grooming themselves.

This is why rats are pretty much the ideal hacking pet.  A dog smells bad and usually doesn't fit on your lap.  A cat may or may not deign to sit on your lap, and may or may not decide to claw you if it's feeling particularly content, but will certainly be shedding hair into your face and decide the keyboard is a better place to relax anyway.

Rats hang out and do their thing while you hang out and do your thing.

context sensitive bugs

I spent a little while trying to track down a text entry bug that happened when I was distracted IMing with  惠茹.  But when I came back to fix it, I couldn't get it to happen again.  Figures that it vanishes as soon as I can pay attention.

Well, what if there actually is a connection?  And then I had it: the bug only shows up when the Chinese IME is activated.

Monday, July 11, 2011

What haskell doesn't have

(but it does have fun!)

At one point, someone on buzz was advocating haskell, and he pulled out the usual "look how elegant the fibonacci function looks!" thing.  Someone else was doing the "the point must be productivity, and you can't prove it adds more productivity" thing.  I think the point is fun, not productivity.  I wrote a big long response, but then felt like getting involved in a big language discussion on work time was kind of a waste of time, so didn't bother posting it.  And it was mostly going to be a self-indulgent preaching to the choir thing anyway.  But then I thought, well, as long as I've written it I might as well put it somewhere.  And I don't believe I've noticed anyone saying it in exactly this way before.  Anyway, where it is:

I know people like to pull out fibonacci and quicksort, but they don't seem all that compelling to me.


Think about the whole thing with iterators or generators or whatever else that so many imperative languages have.  That's all gone.

Select vs. threads vs. coroutines is also gone, but that's just a ghc feature.  All that flap about asynchronous events and event based programming?  Gone.

Think about that whole null pointer thing that all these imperative languages have.  Null pointer exceptions?  Gone!

Think about that whole thing with reference vs. values.  That's gone.  Or think about the thing with identity vs. equality vs. deep equality, that's gone too.

All that stuff about copy constructors or equals methods or all the 'this.x = x' constructor garbage, all that boring boilerplate is gone.  No more writing toString()s or __str__()s by hand, you can get a useful one for free.

Or how about all that stuff about "const" or "final" or "const correctness" and separate const and non-const methods, that all just goes away and good riddance.

Type casts are gone too.

All that hairy generics stuff is vastly simplified without subtyping.  Contravariance vs. covariance, sub/super constraints on generics, multiple vs.  single inheritance, the whole is-a vs. has-a thing is gone.  Yes it means there's no dynamic dispatch, but I don't miss it much.  If you want to parameterize on an operation, just pass the operation as a parameter, instead of passing an object that dynamically dispatches on one of several operations.

Think about all the mandatory type declarations those not-python languages have.  That's gone.  Or maybe think about how slow and single-threaded and hard to deploy and runtime-error-loving python is, that's gone too.

That whole thing about control structures being built-in syntax and different from functions?  That's gone.  It's hard to quantify the difference it makes because you stop thinking about the difference between them and just structure your code as feels most natural.  Like the whole order of definition thing (that's gone too), it's just one more restriction removed.  The whole statement vs. expression thing is gone too.

That lengthy edit, compile, wait, debug cycle is mostly gone too.  It usually takes less than a second to reload a module and start running the code you wrote one second ago.

And of course, this is just a list of the things that are gone.  There's an equally large list of new things that are there, but it might be hard to see how those are useful from the outside.

I have a project with more than 200 source files, each one defines from 5 to 20-ish data types.  If it were java or c++, with their heavyweight class declarations, each one would be a directory with 5 to 20 files in it, about one per data type.  No one is going to go to all that work, so the result is that you create types for the big abstractions and pass the rest around as ints and doubles and strings, or Maps and Lists.  The result is undescriptive signatures, search and replace expeditions when a type changes, reusing "almost right" types that results in extra "impossible" error cases to deal with, mixed up values, and of course runtime type errors.

Similarly, when defining a new function requires minimum 3 lines of boilerplate and some redundant type declarations that need to be updated whenever your types change and maybe defined half a screen away, you create a lot fewer functions, and factor less.  If it's too annoying to do, no one will do it.  And if they do, they will do it because they feel obligated to do the right thing, even if it's tedious.  Not because they're having a good time.

To me, the main compelling thing is *fun*.  All of that stuff which is gone is mostly boring bookkeeping crap.  I never get excited thinking about putting in new 'const' declarations or equals() or toString() methods.  I don't look forward to all the TypeErrors and NameErrors and ValueErrors I might face today, or waiting for the application to relink.  Programming is more fun without that stuff.

Sunday, July 10, 2011

step play

So here's what I put in my TODO file that finally got me to start the blog.  This is what I've been working on for the last week or so:

Adding a simple feature: play back score step by step instead of in realtime.  It's useful to hear how each note gets added to the mix and listen for as long as I like, so I want a feature where I can press a key to advance from note to note.

Simple, right?  Well, first I need to step note by note, so that means adding "event edge" to the time step system.  No wait, it's actually note beginnings, and that triggers a rewrite of time steps to support merged steps and multiple steps (e.g. since I want to start a little while before the insertion point, and "start 4 notes back" seems like a useful thing to be able to say).  Wait, and then it needs an additional mode to stop as soon as can no longer step instead of aborting, because if you say "start from 4 notes back" it would be better to start from 3 notes back if the fourth doesn't exist.

Ok, then I need an efficient way to seek around in the note stream.  So add a simple kind of zipper ([reverse a], [a]) and some tricky functions to zip forward and backward.  All of this prompts some additions and then reorganization of the State record.  I've found that it's nice to divide state up along its access pattern: a constant bit, a monoidal "collecting" bit, and a true stateful bit that must be threaded since previous computations may rely on the modifications of previous ones.  It makes it easier to understand what should be reverted and what should be retained after a computation aborts, and clear what the troublesome bits are likely to be (it's the true stateful part).

Now, how to display the current position of the step play?  Well, another selection like the realtime playback selection would be logical, but then I run into the problem that a user selection is a single contiguous block, but playback can be at different times simultaneously due to multiple tempi.  The realtime playback gets around that by going directly to the GUI instead of being stored in the song state... which makes sense since you don't want to save the realtime playback position with the piece.  So now I need a separate non-contiguous selection concept, or a way to extend the realtime playback hack to work with step time playback.  This involves somewhat deep changes so I punt for the moment on correctly displaying multiple tempi.

Now the actual playing of the notes... forward works fine, but what about backward?  MIDI is extremely stateful so you can't just play it backward.  So, record the state at each point on the way forward, and then when going backward emit the messages that take the synthesizer state from the current one to the previous one.  Fortunately I already have a "synthesizer simulator" used for tests that I can repurpose for this.  I punt again and only play forward.  I'll do backward later.

Ok, and then for the tests... the existing test framework is based around running a single command and examining the results, I need to extend it to support chaining of several commands and inspecting the state in between.  After a couple of false starts here along with a general refactoring it's good enough to do what I want, and turns up a few bugs, but there are still more that only come up when testing by hand.

One of these bugs leads to another bug-fixing session in the time step code for some cases that didn't get tested.  Another bug reveals that converting from RealTime to ScoreTime to RealTime isn't identity, there's a tiny bit of error that I never noticed before because nothing else tries to do a round-trip (and it does the round trip because I need to step forward by a certain amount relative to a certain track, then find out the real time difference to play events, and then find out the subsequent score position on *all* tracks).  On further thought, it's apparent that it's because RealTime is fixed point and ScoreTime is a Double, so of course you can't convert without losing precision.  RealTime is fixed point so that floating imprecision doesn't cause things to no longer be lined up right when it comes time to performance (you can't hear the difference, but if a pitch doesn't line up with its note then it screws up channel allocation).

So... either switch ScoreTime to fixed as well, or switch RealTime to Double and have a separate fixed Timestamp?  My knee-jerk reaction is to want to switch ScoreTime to fixed, but that's the reaction that turns these simple feature additions into epic polar expeditions.  Maybe I don't need to solve the problem, and can fake it by not bothering to display the position correctly in the presence of multiple tempi.

I always had a bad feeling about using Double for time, all the way from the beginning.  In fact, originally the time was integral, but then I switched it to floating since it seemed like it's more convenient to work with and of such high precision that any built up inaccuracy should be well below the 5ms or so I require to be able to hear the difference.  It's convenient to be able to work with arbitrarily divisible units, especially since the convention was that all time is normalized from 0--1 and then stretched to its final size.  But I had a bad feeling about it even back then.  Well, those chickens may be coming home to roost, I guess.  Unfortunately it's a hassle of a change since it goes all the way down to the C++ layer.

Anyway, the main point was that every seemingly simple little thing gets complicated in a hurry.  I keep thinking some day I should have enough bugs worked out and enough test utilities and enough support in place that this won't happen any more.  But now I'm thinking... maybe that never happens.  Maybe it's unavoidable that every 5 minutes of adding something new is preceded by 2 hours of fiddling behind the scenes.

work continues

I'm working on a music sequencer, and this is a development blog for it.

As I work, I find myself taking notes to myself.  Usually they're just fragmentary bits planning for some feature or bug fix, but sometimes I write more, and it's occurred to me that maybe sometime in the future it will be interesting to have a more organized record of my thought process.  And rather than it going in a bunch of fragmentary files in some directory I'll lose track of, maybe I can put it on the web.  So this is mostly a record for myself, but maybe someone else will find it interesting too.

I've been thinking about the need for a new music composition program and how I could do it ever since highschool, so that's about 15 years now.  I went through some more organized planning in college but wasn't confident enough in my programming skill to know where to start.  After that I got wrapped up in performance and haven't seriously written much music for a long time.  Around 6 years ago I was back in the US and didn't have a job and thought it was a good time to start on a project, so I spent about a month and got most of the UI up---the UI is very simple so that's not saying much.  Then I got a job at Google and got distracted for a long time (though I do remember I discussed some parts of it in the interviews).  Then, about 3 years ago I got serious about working on it again, rewrote the UI stuff and got started on the rest.

So the first darcs checkin is Jan 2008.  I thought it might take a few years, but it's been three so far and will probably be a while yet.  In the meanwhile, I've learned another language, lived in another country, and gotten married.  This is by far the largest project I have ever attempted.  I'm wary of the endless developing and never completing trap, but it really has turned out to be much more complicated than I thought.  Time will tell.

I'd like to think I've learned a lot, about application design (it's also the first GUI-owning program I've written), about dealing with larger programs, and about haskell, which is the main implementation language.  But it's hard to judge about these things.  All I can say now is that I've done things that I hadn't previously done, but not whether I could do them quicker or better than I might have previously, or even if I did them particularly well in the first place.  Maybe if I keeping notes will help with the long term perspective.

Here's the current stats on line count, as measured by wc:

   39775 *.hs     268 files
    2381 *.h      34 files
    5589 *.cc      28 files
     496 *.py       7 files
   48241 total     337

Oh, and the blog title is a bit of a pun.  "Karya" means "work" and can also mean work in the sense of a musical composition, so it seemed an appropriate name for the sequencer.