Wednesday, July 26, 2006

Comments are Working Again

It seems that for some indeterminate amount of time comments have not been working here. I don't know what was going on over at HaloScan but it looks like it is resolved now. If you post a comment it will actually show up, and the COMMENTS link at the bottom of each posting will reflect the number of comments for that post. I know I have had a few visitors, but I have no idea if anyone has tried to post anything. Haloscan also claims I'll get e-mail notifications if someone posts here, and it's worked once, so with any luck it will continue to function and I can find out if I have any actual readers out there or not.

Labels:

Sunday, July 16, 2006

While I'm on the Subject of Tabs...

When Firefox came out with tabs implemented, one thing was immediately obvious: this would save real estate on my screen. Even at work, where I have two monitors, I don't have enough space to have multiple browser instances open. I have a hex editor and a text editor (Crimson Editor) that both use tabs to handle multiple files, and I've never wanted them to work differently.

What I don't understand is why more applications aren't making use of this new mechanism. Actually, just about every modern spreadsheet does, except that the tabs are pages in the same spreadsheet, rather than different documents. Adobe Acrobat almost gets it right - all documents load into the same window, but you have to use the "Window" menu to switch betweeen them. I'm sure there are many other examples of apps that are halfway there, but what I'm really intested in are the apps that are all the way there. GAIM, for example. I would love to see tabs in OpenOffice.org.

Yesterday I had the functionality to support tabs in wmamp working, but the visuals weren't there. Instead only the name of the active tab was shown. I spent some time today implementing some general-purpose tab routines, and I think I spent almost as much time getting them to look right today as I spent making them work yesterday. This is probably why I don't like UI programming very much. What I considered to be the "interesting" code was already done and working, having "pretty tabs" (okay, pretty is relative here) is just so much chrome. But it's done now, and I likely won't look at it again unless someone decides to make me some nice graphical buttons to replace the text. And I would appreciate said graphics. In terms of audio playback functionality I believe that wmamp now at least matches (and possibly exceeds) the functionality of the original Linksys player. It does not, however, look as nice. A fellow by the name of Ben Frost (for whom I have no contact information) did the original wmamp logo, and I quite like it. I'd love to have tab graphics with a similar look, but until someone can hand them to me implementing something like that will be a low priority task.

In the mean time, I'm going to rework the listview code. I've been able to determine that in almost every case the items fed into the listview are already sorted the way they should be. listview jumps through all kinds of hoops to do things like moving to the previous or next element (never mind paging by 10). This code would be so much cleaner (not to mention leaner) if it used arrays, so I figure I'll take a stab at that. The only other outstanding features I can think of then are "play once/loop" and a shuffle feature. Okay, so Linksys' software has those features and we don't, so I guess we really don't have feature parity yet.

Once those are done, I think that iTunes playback is essentially complete. After that is the next project I'm keen to work on: enabling SHOUTcast and icecast playback.

Labels:

Saturday, July 15, 2006

Who is the Muse of Programming?

I believe I was visited by the Muse of Programming today. I've been adding all these cool new functions to wmamp but I couldn't think of a good way to make them available without requiring the user to do even more navigation (at least in some cases). I've been struggling with this for days now because I had this nagging feeling in the back of my head that there was a way to do this and make the "action tree" shallower(?) instead of deeper. I know this feeling - it's very similar to the feeling I get when I am looking for a bug and know I should be able to see it but can't. The thing to do in those situations is walk away and do something else for a while. I have been hoping that eventually the answer would come to me, and it looks like maybe it has.

It's so simple (now that I have thought of it), and I can't believe it didn't occur to me earlier. After selecting a server, the next screen has four "tabs" at the top: Playlists, Artists, Albums, and Genres. The left and right arrows move between these, and the rest of the screen is populated with items based on the active tab. If you have UI experience, you can start laughing now, but this is a real breakthrough for me.

Besides, I was right: this does make the tree shallower. The tabs populate quickly, even though I'm dumping the contents of the old tab and loading the new tab every time left or right is pressed. If this becomes an issue with large song collections, it should be possible to keep the lists in memory to further speed up the process. The typical scenario is now to select a server, select a category, then confirm your songs. That's only three screens, instead of the four it used to be, and a far cry from the five screens I had anticipated might be required in some cases. I figure that the worst case would be if you selected an artist, and then an album by that artist, and then confirmed the songs. This would be four screens, but that selection method isn't available right now (selecting an artist shows all their songs immediately, there's no capability to do album selection by artist), and it's probably not necessary anyway given that album selection can be done directly. I can only envision this being desirable if the user wants a specific album but only knows the artist and not the name of the album they're interested in. Is this a scenario that's likely to occur often (or at all)?

I'm pretty happy with how this turned out. It was remarkably easy to put together and it doesn't break from the old interface too badly. The one thing that has changed is that on the "tab" screen the left button is no longer a synonym for the "previous" button, and right isn't a synonym for "select". I think the added functionality is worth it, but I should make the behaviour consistent across all screens as much as possible.

Labels:

Thursday, July 13, 2006

Browsing is Nifty

Genre browsing works now, well, at least as well as it can work when most files don't contain any genre information, or worse, contain mis-spelled genre information. I did manage to find a rather handy tool: mp3tag, which thankfully can tag more than mp3s (eg. oggs). Check out the website to see all the different formats it can handle.

I discovered that my "new" deadlock that I'd found yesterday isn't actually new - an old deadlock bug just had new symptoms because I'd fixed it incorrectly. I think I finally have it fixed now (at least, until the next time).

I really like the way that the functionality is shaping up, the problem now is that there's no good way to access all these new functions. I could insert a new screen after server selection, but this means that the worst-case scenario involves traversing five screens before you make a music selection. There has to be a way to flatten this out.

Labels:

Wednesday, July 12, 2006

I can Browse Artists. Cool.

Browsing by artist actually works, and appears to work with both mt-daapd and iTunes. It should now be trivial to enable browsing by album and genre. "Browsing" used to be done the BFI way (ie. get a list of all songs and throw out the ones we're not interested in) which is perfectly valid, especially when more sophisticated mechanisms are not well (or at all) documented. For large collections of audio files sending browse requests to the DAAP server pushes all the "heavy lifting" over to the server, which probably runs faster and just generally has more resources than the WMA11b. Between browsing and smart playlists it should be possible to cut down on network traffic in many cases. What I have noticed though, is that iTunes sends a sorted list of, say, artists, which is not great for our binary tree sorting code. Sorting by adding items to a binary tree can be very cheap, but it presumes that the data being added is unsorted. Adding already-sorted items to the list is a worst-case scenario that causes the code to do the maximum amount of work.

It remains to be seen how much of the mt-daapd documentation I found yesterday can be used with the DAAP protocol. Basic browsing appears to be supported, but mt-daapd also allows for browsing with filtering (ie. browse all albums by a specified artist). It would be nice to be able to use these as well, but if they do not work with iTunes or other DAAP servers then there's a diminishing reward for implementing them.

For now I think I will content myself with implementing the browsing that I am confident will work across platforms, and then addressing issues like stability and performance. I saw tonight another deadlock of some sort, where I stopped the song that was playing and then tried to cue up a new set of songs, but nothing happens. Playback appeared to be impossible at this point. I thought I had found all this problems, but apparently not.

Labels:

Tuesday, July 11, 2006

Playlists are Working, Browsing is Next

I've been able to debug and prove out my playlist code by temporarily replacing the artist selection code with playlist selection, so that's pretty cool. The DAAP server I'm running - mt-daapd - has all kinds of support for iTunes-style smartlists, which are composed on-the-fly based on criteria you provide. If your mp3s (or oggs) have appropriate meta-data, like the year the song was released or the genre, you can include songs in smartlists based on these attributes. There also seems to be a top-level playlist created with the server's name that includes all songs, so that makes some of the work I did earlier redundant. Not that I'm going to complain about that, especially if it means that I get to rip out some code.

Also, I've found some kind of documentation of the "browse" capabilities here, although this seems to be documentation on how to get XML responses from mt-daapd, not actual DAAP output. I'm hoping that similar requests can be made over DAAP. I can't believe they only showed up on the second page of my google search. With any luck this will mean that it should be possible to do album and artist browsing (and other kinds too) without having to suck in details about every song that the DAAP server knows about. If it works wmamp is going to become way more usable.

Labels:

Monday, July 10, 2006

Finding Bugs is Hard

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. -- Brian Kernighan

With thorough abuse comes success. After finding and fixing one deadlock condition I was able to find a second: start a song playing, press stop, then pause. After you've done this, you can't play songs any more. Easy enough to fix once it's reproducible.

I've done some more work on libmdnsd also. Initially all I wanted to do was make a shared object (the Unix version of a dll) out of it, but I found that some of its output was annoying me. For some reason the original mdnsd code would return network addresses and ports in the processor's native format (typically little-endian). I'm not sure why this was done as it makes displaying an IP address into a programming exercise (albeit a simple one) rather than a function call (inet_ntoa - found in any decent libc implementation). The other thing is that this makes results returned by the original mdnsd different from most other name resolution services, so I decided to fix it. IP addresses are returned in a proper structure and ports are big-endian. Other data returned should probably also be big-endian, but I'll deal with that when I (or someone) actually tries to use them.

"Fixing" libmdnsd naturally required changes to be made to wmamp, mostly for the better I think. Server information is stored a little more compactly (though I think it could still be improved - it's not just every cycle that counts - every byte does too) and on the whole I think it is a little more readable. So lots of changes under the hood there, but no real change in functionality. I didn't break anything (for long) so I can't complain.

I've been meaning to get playlists working, so I took a stab at it last night and I'm most of the way there. I can query the server for all playlists, and I think I can query the songs in the playlist, but I haven't been able to try it yet because I've run into an interface problem. wmamp's interface was originally strictly hierarchical: select a server, an artist, an album, then a song. I've since changed the interface a little so that the process can be short-circuited: you can select the server in such a way that it immediately shows all songs on the server, or you can select an artist such that it shows all their songs, irrespective of the album the belong to (if any). This was, IMHO, better than the previous mechanism - it let me listen to a variety of songs and also leave the player running for an extended period of time without driving me nuts.

My first thought was to show both artists and playlists after a server was selected, and while this is possible wmamp's penchant for sorting will cause artists and playlists to be intermixed and it's not obvious which is which and I feel that is bad. The other thing I want to keep in mind is that some work has already been done to provide a web interface to wmamp with an eye towards controlling music selection when no television is connected.

Implementing playlist support has been a very valuable experience though. I had suspected that memory was still leaking somewhere, and it was, but not at all where I had expected. Entries are sorted by inserting them into a binary tree. It turns out that the code for sorting made two mistakes - it would not always detect duplicate entries (which should not be added to the tree) and when it did detect a duplicate (rarely) it had no way of communicating to its caller that it had not been added to the tree and needed to be free()'d (as data being added to the tree was generally malloc()'d). Both of these have been corrected, and if there is a memory leak now it is much more subtle than what I was seeing previously. I have another suspicion that I haven't had time to check out yet: when any DAAP request is processed it's added into a tree (as above) which sorts the entries based on some simple key. For display, the contents of this tree are moved into a different tree, also keyed, but the key may be more elaborate. If the keys are identical (as in most cases) then the second tree actually becomes a simple linked-list and and not a tree at all. If the additional sorting is not important then it may be faster and more memory efficient to count the items in the tree and malloc() an array to hold the listview items.

Oh, and a note to other wmamp hackers who want to keep the code small: libhttpd appears to be compiled by default with -g (add debugging information) and -O (some optimisation). Unless you are trying to debug this code, rebuild it without -g and use -Os instead of -O. The resulting code is much smaller, and probably faster too. I think this knocked another 10-12kB off the size of wmamp. As I recall, my current builds of wmamp are now around 64kB.

Labels:

Thursday, July 06, 2006

Closer and Closer to Something I Can Use

Only minor improvements to wmamp in the last couple of days. I tracked down a longstanding deadlock issue: pausing a song then returning to the song list and pressing SELECT on any song would kill playback until wmamp will killed and restarted. It turns out that when paused it was looking only for a return to play, and so wasn't handling the situation when you then tried to kill the current play list and load a new one. It was an interesting bug to track down, and I wasn't even in front of the computer while I did it. I've had this happen to me before, it's like I soak up all the information and the while I'm doing something else (like watching Starship Operators) my subconscious finds the answer for me. When the source of the bug popped into my head I just knew it was right, I knew exactly where to look for it and I knew exactly how to fix it. The first time it ever happened I was driving home from work and I almost turned around to go and prove to myself that I've solved the problem.

I also tracked down a new problem I'd only started seeing once I was able to access all the songs on my server - sometimes I'd select a song from the list and it would play, and sometimes the wrong song (but always the same wrong song) would play. It turns out that wmamp was limited to handling 30 songs in a playlist. That's not unreasonable when they're all coming from the same album (as they would have in the past), but it's no longer true with my changes. For now I've upped the limit to 100, but in the long term I have to figure out how to do "browsing", which I'm hoping means querying the DAAP server for songs based on some provided criteria. I wasn't sure if mt-daapd supported it, but from what I've been reading on the project's page it does. Now I just have to figure out how to browse. Thus far I've not been having a lot of luck getting low-level protocol details except through reading source code. Not that I have problem reading source code - working source ultimately tells you what's really happening, but it's not always the easiest to understand.

Labels:

Tuesday, July 04, 2006

Usability. Meh.

"My Sharona" by The Kinks was just playing, and now it's Echo and the Bunnymen's cover of "People are Strange", and it's wmamp that's playing them. This shouldn't be a big deal, but it wasn't something that could be done before. I can only assume that the original interface for music selection was some sort of proof-of-concept or something, because I can't imagine using it myself on a day-to-day basis. Music selection in wmamp has apparently been very simple right from day one: pick a DAAP server, select an artist, select an album, then select a song to start playing. If the song screen lists more than one song it will play all songs from the one selected to the end.

I have only a small selection of songs on my linux server (remember, only 4GB of space on the hard drives), and so I have at most a handful of songs from any one artist, and those are likely from a couple of different albums. Until now this has meant that any time I want to do some testing I end up listening to a handful of songs over and over. Since I'd finally had some luck tuning uClibc and busybox (more on this later) I decided to keep the hacking light for the evening (ha!).

My idea to improve the interface was to provide two methods of selection at the server and artist level: method 1 selects all songs available at that level and method 2 follows the original scheme. The concept is simple, but the execution not so much. The code for making DAAP requests was tied fairly tightly to the original selection mechanism. I also learned something else: whenever wmamp makes a request for a list of songs it receives the entire song list from the server and extracts the ones it wants. I don't know if this is an artifact of the way that the requests are constructed or a limit of DAAP, but it sure would be nice if a specific query could be sent to the server and it returned only matching results. This may actually be possible - I haven't looked at DAAP that closely yet but it looks like I may have to. Anyway, wmamp has a routine that reads in the song descriptions and keeps only those that match the artist and album requested (remember, this is the original code I'm talking about here). This was where the bulk of my work was done - certain search parameters might not be available (or any, if you played all the songs on the server), and it would discard valid songs as non-matches. That wasn't too hard to fix but it was only half the job. All my ogg files have Song Title, Artist and Album Name information, but a lot of the mp3s contain only the name in the id3tag. I've even seen mp3s with no meta-data - only the filename of the mp3 told you what it was. Some songs were being thrown out because of missing album or artist information, and so I had to code up something to deal with crappy mp3 encoders.

There was other work too, but that was the heavy lifting. No point in boring people with all the minutia.

The other thing I managed to do today was pare down the size of busybox and uClibc. Neither got a lot smaller, especially since I'm statically linking busybox, but even so, the image I'm downloading is around 1.1MB whereas the original 0.6 image weighs in at 1.6MB. If I didn't have to deal with the original uClibc it would be even smaller. I really need to replace the disk image that's flashed into the WMA11b itself. If I could get some reasonably modern code in there I'd have a lot more RAM to work with. I'm going to need it unless I can start making smarter DAAP requests.

Labels:

Sunday, July 02, 2006

It's alive. Alive!

Buildroot failed to compile last night, but at least I know why. Debian testing apparently has three different versions of gcc installed: 3.3, 3.4 and 4.0. 4.0 is the default and apparently trying to build gcc 3.4.2 and its support libraries with a new gcc is not something that works. Once I had forced buildroot to use gcc 3.4 I was able to complete the buildroot process. I have to give the uClibc guys credit - the process works pretty well. Once you're done you have uClibc for your target, a complete suite of cross-compiler tools, a working BusyBox and potentially a lot of other goodies if you've enabled them. I did a quick check and out-of-the-box I get a libc that's about 300kB, which is pretty good compared to the 1.1MB of glibc. It's not as good as the one in flash however, which is a measly 189kB. The difference is likely due in part to unnecessary features in the default build and in part to the fact that I built uClibc 0.9.27 while the one in flash is 0.9.12. The same is true for BusyBox - they have 0.60.2 and I've got 1.1.3 (and all kinds of things enabled).

At least I am making progress. All the libraries that wmamp needs recompiled without any problems, and there is only one issue with wmamp itself: it uses strverscmp(), which does some very cool things, but is a GNU extension to glibc. It looks like uClibc should/can support it but so far I haven't been able to link wmamp unless I substitute strcmp() , and for now that is okay.

I've been able to create an image with all the uClibc stuff, but getting it all to work has been tricky. The two versions of uClibc are in no way compatible, and so once I start trying to symlink the new libc in place programs (like busybox) start failing. Since I can't replace the files in flash (yet), I need a way to be able to create all the symlinks to the new libc files without the application doing the symlinking failing. The solution for now is a statically-linked BusyBox. When the image loads the new BusyBox replaces the old one and creates all the necessary symlinks. Once those are in place a new process can be forked that uses dynamically linked programs and they will work as they should. This is a bad hack - the resulting BusyBox comes in at over 500kB, and so just about any uClibc savings are lost. This could be handled better with a purpose-built application, but since this is just a work-around until more of the boot enviroment can be replaced, it hardly seems worth doing. Right now I think the more productive route is to prune BusyBox and uClibc down a little. I know that the default builds for both include many features that have no use on this device.

Labels:

Saturday, July 01, 2006

Taking the Evening Off

On the off-chance that there's been a sudden in-flux of readers from mytinker, there may not be a wmamp/WMA11b update tonight. I'm getting together with a friend for an evening of junk-food, video games and with any luck Final Fantasy VII: Advent Children. Yes, I do in fact have other interests besides hacking - hacking the WMA11b just so happens to be the most blog-able.

I'm hoping that while I am out uClibc buildroot will run to completion and I will have a cross-compiling uClibc environment when I get back. If I do, I'm skeptical that I will be able to wait until tomorrow before I start trying to rebuild wmamp's support libraries and ultimately wmamp itself. Note to others who may try to follow this path: make sure that bash is your login shell, or the build process will fail. I could probably have had this working days ago if I wasn't so enamored of tcsh (no link, because as much as I love it, I don't want to hose someone else).

Labels:

This is How I Measure Success

It's three a.m. and Lee Aaron is playing.

This is a big deal, not because its Lee Aaron, but because something is playing at all. I have successfully ripped Apple's mDNSResponder out of wmamp and replaced it with a version of mdnsd (itself ripped from libopendaap). wmamp is just a little more Free. Not like I'm all militant about Free Software or anything, but I got to kill two birds with one stone here. Not only is mdnsd available under the GPL but it was also designed with embedded systems in mind (so I guess there are still some of us left). Here's how I measure success tonight: wmamp is running, and appears to be working as well as it ever did. (I have to give credit to those who worked on wmamp before me - all its multi-cast DNS code is contained in a single source file, so I wasn't hacking on half a dozen files all evening.) But more than not breaking it, wmamp is now smaller. I have an archived version of wmamp that's timestamped 6/29 19:29 - that would be the one that plays ogg files, but doesn't walk the album's playlist. The stripped binary is 218076 bytes. The canonical version of wmamp 0.6 builds to 213508 bytes. So I added a couple of kB to the program itself, and then dropped a 100kB decoder for oggvorbis in the image as well. The good news tonight is that after all my work wmamp weighs in at a measly 115600 bytes. Switching to mdnsd has reclaimed 102476 bytes. The net result is that wmamp just got ogg playback for free. That's a clear win in my books.

And speaking of walking the playlist, I was pondering the correct behaviour yesterday, thinking that I had probably introduced a bug that caused that last song to play ad nauseum. Today I read in a README or something (damned if I can find it just now) that it's always been like this. I should really get on that. All this other stuff is more interesting to me, but at some point the program's got to be usable as well. My personal goal is that it should be a better application than the original. This is no mean task, the stock software (for the WMA11b itself) is pretty good.

One other thing is starting to drive me nuts as well: I can't make the video look good on my widescreen TV. The output is standard 640x480, and my TV knows all kinds of tricks for stretching a 4x3 image to 16x9, but none of it looks any good. I really want to take a closer look at the video controller and see if I can't make it emit something more appropriate. I've worked with this processor before and I'm pretty sure it can accomodate me.

Labels: