compose

This program is a very simple tool to study tonal classical music.

It will never be "finished".

It has some kind of graphical user interface (basic X Window) and does audio via ALSA MIDI.

Download

.git as of 2016-08-18.

To use the source, do something like:

mkdir /tmp/compose
cd /tmp/compose
wget http://sed.free.fr/compose/git.tar.gz
tar xf git.tar.gz
rm git.tar.gz
git reset
git checkout-index -u -a
(maybe those last two can be replaced by "git reset --hard")

And you're up. To see the code for a given day, do:

git checkout day_001

And replace "001" by the day you want.

I made a copy on a popular (as of 2015-10-17) website. I try to keep it synchronized but I'm lazy. (The thing may disappear at some unspecified point of time in the future.)

History

Let me repeat what's written above: to see the code as described in each entry below, do "git checkout day_XXX".

Here comes some sort of diary about this program.

2015-07-17: I'm bored with the history thing. Get the sources and "git log". Too much work. Too useless. I don't have enough time for this blabla stuff.

Day 032 [2015-07-02]

Undo/redo? Undo/redo.

Git log.

    day 032 - undo/redo
    
    Well well well...
    Less than four hundred lines of code (history.c and serializer.c)
    for a generic undo/redo mechanism.
    
    What do you think?
    
    Sure it's very inefficient.
    
    But so what? That works. I just need to call "new_history" whenever
    I modify the document and I'm done.
    
    It will be hard to do simpler...
    
    Good day today too!
    
    Sleep? Sleep.

Day 031 [2015-07-01]

Take a document, make a string of it. Take a string, make a document of it.

And use that to save the document (ctrl+s).

And that's it.

Day 030 [2015-06-30]

Git log.

    day 030 - initial import of libxdiff for undo/redo
    
    Why bother?
    
    You transform the current document into a textual representation.
    You diff with previous version and keep the diffs in a list.
    That gives you an history of edits.
    
    Then you patch/unpatch to get to next/previous version and
    convert the textual representation back into a document.
    
    I will also use the same document / text conversion to
    save and load documents. You minimize code. That's nice.
    
    In gcomposer I had a very complex undo/redo mechanism. This
    time I want something generic that does not care of what is
    going on in an action (an "action" is something that modifies
    the document). Just tell the undo/redo module that the document
    has changed and it will deal with it using a textual
    representation. You write the undo/redo mechanism once and then
    you forget about it for ever. Peace of mind.
    
    I stole libxdiff from http://www.xmailserver.org/xdiff.html
    I will delete some files that I don't need. I wanted to do it
    before the initial import, but, well it's late. Sleep. Git
    history pollution...

Day 029 [2015-06-25]

Several user commands today:

Nothing much to add. Easy work. A bit boring but has to be done.

One commit per new feature, for why not?

Day 028 [2015-06-24]

Git log follows.

    day 028 - audio playback improvements
    
    Audio playback starts at the cursor. There is also visual feedback.
    So some modifications in plot.c. More to come to make it pretty.
    Modifications in play.c too to associate a NOTE ON event with a position
    in the document.
    
    I have a new file user_playback.c to deal with the user when she
    plays audio.
    
    I modified the cursor's handling to switch it off when in playback
    mode.
    
    That's funny how easy it is to go from default mode to playback
    mode. Simply modify the "do_command" in user_default.c so that
    when the command is "play" you call "user_playback" and let that
    new state open audio and wait for it to finish or for the user
    to stop it (see the function user_playback).
    
    Another funny thing is that at first the "space" key was not handled
    anymore in user_playback.c so pressing it would do nothing. I
    absolutely wanted to keep the previous behavior (pressing it
    continuously speeds up the playback). So what was an accident had
    to be added in again. And I had to think to do that.
    
    Funny.
    
    Late, sleep.

Day 027 [2015-06-23]

Git log.

    day 027 - back del (unfinished but works)
    
    Backdel if you are after something deletes what is before
    the cursor.
    
    If you backdel on an empty line (not the first) it smashes that
    line.
    
    More user interactions (well not much, but it's fast to write,
    that's nice).
    
    I slowly recover from the big lock of death.
    
    You know what's wrong with that lock? Locality is lost.
    You need to think about the program as a whole. This is
    very error-prone. It's no big deal in my case. But still,
    global thinking.
    
    No big deal because the thinking is simple: lock/unlock around
    all modification of the document. The lock/unlock for plot
    and size is already done and seems to work, so no need to
    worry there. And it's easy to spot places where the document
    is modified. So more or less clean to think. Bbut still, it's
    a global thing. So well...
    
    It's reasonably late, let's get some sleep. I have a hard time
    with sleeping these days.

Day 026 [2015-06-22]

Shame on me. Maybe I should abandon this project before I jump in the darkness of the bloat?

Git log.

    day 026 - the big lock of death
    
    There it comes. Putting its fat in the middle of the beauty.
    
    I feel European today.
    
    I don't like it.
    
    The crash has gone, smashed by the little tail. The little tail
    of the bloat. Now I need to keep an eye on that tail. Does it
    move? Where does it go? Will it invade the beauty?
    
    I feel ashamed.
    
    Bad days these days.
    
    I knew it. User interactions are evil.
    
    Shit! shit! shit!

Day 025 [2015-06-21]

Shit day today.

Git log.

    day 025 - insert note (bugged version)
    
    Not much time. So buggy.
    
    When you type 'a' there is a 'la' inserted
    (no check done on current clef, sol clef supposed).
    
    Repeat that a few times. It works nice!
    
    And then it crashes, in the plotter, at random times. It is
    a strong sign of a race condition. If you read the new function
    "command_new_note" you see that the document is modified
    (new_item followed by new_vertical_item). The X thread may well
    call plot at the same time (eg. the cursor thread orders it at some
    times).
    
    I don't think it's good at all.
    
    Next time I'll introduce a lock to protect the document.
    
    And the mess begins... Yes, that will be a total mess.
    Locks are a pain in the brain.
    
    Or maybe I'll find another idea, but I really don't see how
    to avoid the big lock.
    
    Ah shit.

Day 024 [2015-06-16]

Some structuring. To fight the bloat. To feel better when the brain malaxes the program at lost times of the day.

I want to write a program that works. I also want to write a program I feel good to think about.

Git log follows.

    day 024 - structuring
    
    Nothing new in the land of features.
    
    I don't want things to get crazy and insane, so here is some
    structuring in there.
    
    A 'user' thread has popped up (file user.c). Its role is to deal
    with commands. For now its mission is just to call 'user_default'
    (file user_default.c) which is the default state of the user when
    starting the program. All current commands have been put there.
    
    Other states will be:
      - playback mode
      - selection mode
      - chord edit mode
    
    Maybe others. We'll see.
    
    The system throws a command to 'user'. Based on its current state
    'user' will do something (or nothing) with the command. For example
    in playback mode, you won't be able to edit the document. It will
    also change its state based on the command if applicable.
    
    That's the idea of 'state'.
    
    One important point in all of this: a user (you, human being) is the
    most unreliable part of a program (yes, you are just a /part/ of all
    this, just parameters I have to deal with). She may type anything at
    anytime. She may move the mouse at some totally unexpected times. And
    other more hardcore stuff my marvelous brain doesn't want to hear about.
    Dealing with that means to deal with complexity.
    
    Yes, humans are complex.
    
    I've been thinking hard those last days about all this. The current
    situation may change. It's not fully satisfying. Very verbose. Still
    too much maybe. It's better than yesterday because with the structuring
    of today I introduce isolation. I can think of the mess a user is by
    little independant pieces. I hope that will help me manage the mess
    (that's you).
    
    One thing I really don't like in the current situtation: too much
    malloc and free. A command is let's say "key -ctrl q", which is done
    as a string "key" with two parameters (still strings) "-ctrl" and "q".
    These strings are copied (strdup) into a linked list (I hope it's
    done correctly, I do it so rarely). Later on it's taken out of that
    list, processed, and the memory is freed. A lot of overhead. But it's
    easier to think this way. In the end you think less and (hopefully)
    you put less bugs in there. But that feels bloaty. I can't explain
    why. It's a feeling. (Well, I can. Just go get read some "serious"
    software out there, you'll see what messy memory use means.)
    
    We'll see where we go when we'll go. (Shake Spears!)
    
    Hopefully I don't fall in the bloat. Because once in there it's hard
    to realize you're in it and it's impossible to get out of it. You end
    up solving problems you wouldn't have had if you hadn't jumped in there
    in the first place. I don't know if that previous sentence has any
    meaning at all. Let's go get some sleep.

Day 023 [2015-06-15]

Some work on user interaction. I'm not happy. It gets ugly. We'll see.

I had a gcc crash. git checkout 85b5098154f9c9e1fb964002caa2e915e5b6f865 and make to see it if you have gcc (Debian 4.9.2-10) 4.9.2. Maybe other versions too.

Funny.

Git log.

    day 023 - cursor movements and some restructuring
    
    There we are. The cursor moves. Not very pretty (moving to previous/next
    line puts the cursor at beginning of line; it should/could move
    to almost the same horizontal position) but well, you know,
    laziness.
    
    I also restructured the X thread so that it doesn't generate
    complex commands, so that it doesn't have to know much about
    the state of the rest of the system. Not clear? If I press a
    key, maybe the action triggered depends on something the X
    thread does not know about. Imagine you are playing the document,
    at that time inserting a note should not work. That decision should
    not be done by the X thread, thus the change.
    
    I am not happy with what I've done in command.c. Have a look at it.
    It seemed a nice idea when I *thought* of it. It looks too verbose
    now that I *did* it. The idea was that each command is like a small
    program with argc/argv like the "main" function. The command parses
    its arguments and acts upon them. There is a '--help' thing too. Just
    like a mini unix command. It works but I don't know... it doesn't
    feel good. Too verbose, too complicated, too noisy, too everything.
    
    The thing is that I want a unique mechanism to deal with commands
    done via the X window (press a key, click somewhere) and the commands
    the user will type in the terminal. The first case is unambiguous.
    The last one is very ambiguous (the user may, I mean *will* type
    crap at some point, even unintentionaly). Now, mix both to get a unique
    mechanism. What do you get? Complexity wins! We must be "user friendly",
    which means: get ready to parse some shit and report nice messages
    in case of error...
    
    Bah, we'll see how that evolves.
    
    Enough blabla. Time to sleep. It's already too late...

Day 022 [2015-06-13]

Midi playback!

I didn't test much, it may fail to work. Accidentals, clefs, durations, armatures, bars, all this has not been tested! Or so little. But I trust my brain! And I'm wrong. So be it.

Ah, I also changed the cursor handling so that it gets off when out of focus.

I already did a lot. And there still remains so much to do...

Next step will be user interaction. So I can properly test playback. I didn't want to test too much today because entering notes by editing the code is a pain.

See this:

  new_line(&d, 0, 1);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 6, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 5, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 4, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 3, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 2, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 1, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, NOTE, 2, 0, X, 0, 0, STEM_UP);
  new_item(&d.l[0], 0);
  new_vertical_item(&d.l[0].i[0], 0, CLEF, CLEF_FA, 0, 2);

This is what I need to type to insert a fa clef and a few notes (in this case: ré, mi, fa, sol, la, si, do).

My ass is not used to such a pain. Or is it my brain? Some days those two organs get blurred. (Don't try to picture that.)

No screenshot today, nothing new.

Here is the git log.

    day 022 - midi playback
    
    I didn't test much, so there might be bugs in there.
    It took several hours. It was expected. It is a bit complex
    due to the handling of midi channels. There are only 16 of
    them as far as I understand. So at any given point of time
    you can only have 16 notes on. (If I understand the thing
    and I didn't dig into midi, so I might well be wrong.) So
    you must schedule things to end up with something that fits
    into those 16 channels, whatever you have in input. That
    means that you cannot render properly a document into midi
    if you have too much notes at the same time. (Again if I
    understand that midi thing correctly.) But that situation
    is rare I guess.
    
    So what's going on?
    
    The funcion get_note_list works in two passes.
    
    First pass is to take the document and produce a list of
    notes with their start and end times. That decision is easy
    to make on a per note basis. The time is incremented based
    on the shortest note of the current processed chord. (This
    pass is commented as "feed the scheduler" in the code.)
    
    Then you order that list. This might not be necessary because
    the previous processing is done in order. (Then why did I
    include it?)
    
    Then the second pass is to take that list and generate another
    list of midi events (note on, note off) and delays in between.
    And that's it. A list of commands. And only three command:
    NOTE_ON, NOTE_OFF, DELAY. This is where you also deal with the
    16 midi channels. (This pass is commented as "schedule the 16
    midi channels with our notes" in the code.)
    
    After calling that function, the player thread only deals with that
    basic list and acts upon what it gets. The funny behavior (bug?)
    that speeds the plakback up when you continuously press the space
    bar is still there. It will stay there. I like it.
    
    Two things still need to be done and will require some not too minor
    changes:
      - visual feedback of the current played stuff
      - start playback anywhere
    
    For both of those changes I think I will simply add a new event in
    the final 'note_list' thing, that is something like "set the
    line/item to X/Y". I just need to produce that thing from the document
    and adapt the 'schedule_list' to have it somewhere somehow.
    
    As ever, whatever will be will be.

That was a long session! More than four hours and less than eight.
I like precision.

Now, let the brain think a bit by itself about user interaction. You know, I don't think I could compress time. Day 001 to 022 represent something like 20 hours of work. But I could not have done it in 3 days of 7 hours of work because the thinking in between is very important. How important? I can't tell. A lot I guess.

Day 021 [2015-06-09]

Not much today. Press space to start play and escape to stop. A bit of internal "structuring" (that word does not exist, or does it?) too.

Git log message.

    day 021 - play/stop mechanism
    
    Nothing much today. Just putting things in place so that real
    stuff can now be done for the playback. Things will move I think.
    
    I introduced "bugs". If you press continuously the space bar then
    the playback is fasten. Conceptually, this is a bug. The space bar
    should just start the playback. And escape to stop it. But it's
    funny as it is. Try it.
    
    I don't know what to do when all notes have been played. I suppose
    I'll just stop the playback and so be it. But there again it's
    conceptually wrong. The playback should stop when you press escape.
    This is how it is in gcomposer. But I don't know. No more notes
    means stop to play, no? It makes sense.
    
    User interactions are going to be a bit of a pain. User expectations
    about how a program should behave are very very variable. One
    person thinks it should be this and one other thinks it should be
    that. Well, as long as the program does not crash it'll be fine.
    And by the way I doubt it will be used by more than one person on
    this little planet.
    
    Whatever will be will be.
    
    Not too happy today. Not much done...

Next time I hope to feel strong enough to do that get_note_list function, the big one. That function takes a document and produces a list of midi commands (note on/note off) and sleep commands in between that will then be processed in the thread a bit below in play.c, at the play: label.

Do you like gotos? I love them. Some people pretend they look ugly. I think those people never read crazy code full of "if" and "else" all around. THAT is a pain in the brain. Put a few meaningful labels here and there instead and your code is zillion times easier to read. And code is supposed to be easy to read. At least as easy as possible.

Oh my! it's late.

Days 011 to 020

Days 001 to 010


Contact: sed@free.fr

Created: Tue, 12 May 2015 20:00:54 +0200
Last update: Thu, 18 Aug 2016 20:23:33 +0200