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://sedcore.eu.org/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: see here
Created:
Tue, 12 May 2015 20:00:54 +0200
Last update:
Fri, 21 Sep 2018 20:10:50 +0200