2019-07-02: this page is deprecated. Go here.
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.
.git as of 2018-09-21.
git.tar.gz.sig: signature of git.tar.gz.
See here to know how to use this signature.
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 --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.
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.
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.
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.
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...
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?
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.
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.
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!
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.
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.
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...
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.
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.
Contact: sed@free.fr
Created:
Tue, 12 May 2015 20:00:54 +0200
Last update:
Fri, 21 Sep 2018 20:10:50 +0200