Re: Implementing Better String Storage
[ Is the #freeamp hidden? It was not on the list, but I could join it... ] Theme for this whole thread of discussion: [chrisk@kuklewicz ~/www]$ fortune Nothing endures but change. -- Heraclitus An update on my progress, Yesterday I replaced how metadata is stored internally and much of how it is accessed of outside (e.g. PeekAlbum() not Album().c_str() and Album_length() not Album().size() and so on). Before I submit I will temporarily disable the const string Album() style access methods and follow the compiler errors to audit how the returned strings are used. The strings are now *temporary* objects, instead of "const string" references to MetaData fields. With help from Isaac I managed to get it compiling and it ran loaded the playlist just find, but editing a comment segfaulted. I have it cleaner today, with a slightly more evolved design and it could edit metadata okay, but opening the streams part of the tree eventually segfaulted. It seems I did need to be thread safe. I observed the two [New Thread xxx (runnable)] notices, and the names of the functions in the back trace (at end of message). So I now use a Mutex in the HashStore to protect everything. Also, thank you to whomever already added the AutoMutex class! I also added a static const char empty[1]=""; to the HashStore for caching (merge duplicates) of all zero length strings. I also went back and changes the HashStore methods to use const everywhere again. Old example output from "cerr *m_store" after it loaded my database on startup: HashStore report Logical Strings held: 2317 Strings in HSet: 1009 Strings in HMap: 146 So very soon I will have a whopper of a patch. Any special handling requests? Currently: I did make clean make, and now I sleep. Most recent crash as seen by gdb, as evidence that thread safety is required: @ ui/musicbrowser/unix/src/browsertree.cpp : 1370 : void GTKMusicBrowser::StreamTimer() LWP 15638 exited. @ ui/musicbrowser/unix/src/browsertree.cpp : 1535 : void GTKMusicBrowser::FillRelatable(bool = false) [New Thread 28701 (runnable)] Delayed SIGSTOP caught for LWP 15639. @ ui/musicbrowser/unix/src/browsertree.cpp : 1331 : static void GTKMusicBrowser::stream_timer_func(void *) @ lib/http/src/Http.cpp : 92 : enum Error Http::DownloadToString(const class string , class string ) args: url=http://www.freeamp.org/streams.xml, page=, m_bytesInBuffer=0, m_bufferSize=0, (void *)m_buffer=(nil) @ lib/http/src/Http.cpp : 181 : enum Error Http::Download(const class string , bool) args: url=http://www.freeamp.org/streams.xml, fileDownload=0 @ lib/http/src/Http.cpp : 182 : enum Error Http::Download(const class string , bool) args: m_bufferSize=0, m_bytesInBuffer=0, (void*)m_buffer=(nil) @ ui/musicbrowser/unix/src/browsertree.cpp : 1535 : void GTKMusicBrowser::FillRelatable(bool = false) [New Thread 29726 (runnable)] Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 29726 (runnable)] 0x80d4043 in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffaa4) at /usr/include/g++-2/stl_hashtable.h:777 777 node* next = cur-next; (gdb) bt #0 0x80d4043 in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffaa4) at /usr/include/g++-2/stl_hashtable.h:777 #1 0x80d5ddd in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffacc) at /usr/include/g++-2/stl_hashtable.h:829 #2 0x80d5da7 in hash_setchar *, hashchar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it={cur = 0x81ef448, ht = 0x813aa50}) at /usr/include/g++-2/stl_hash_set.h:160 #3 0x809ecf2 in HashStore::unref (this=0x813aa50, s=0x81eb4a8 "\030\e\b\036@0's (Rob's Pick) ") at base/src/charstore.cpp:204 #4 0x80e48fc in MetaData::setentry (dest=@0xbbfffcf4, src=0x40e3b7c0 "Upbeat 80's (Rob's Pick)") at base/include/metadata.h:166 #5 0x80e4319 in MetaData::SetTitle (this=0xbbfffcec, title=0x40e3b7c0 "Upbeat 80's (Rob's Pick)") at base/include/metadata.h:83 #6 0x4096f063 in Misc::ReadMetaData (this=0x8148ff0, url=0x40e3b730 "http://216.32.166.94:8076/\"", metadata=0xbbfffcec) at plm/metadata/misc/misc.cpp:227 #7 0x80b2721 in PlaylistManager::MetaDataThreadFunction (this=0x81452f8, list=0x81e0f28) at base/src/playlist.cpp:2472 #8 0x80b292b in PlaylistManager::metadata_thread_function (arg=0x81f34f0) at base/src/playlist.cpp:2519 ---Type return to continue, or q return to quit--- #9 0x80c4b4d in pthreadThread::InternalThreadFunction (this=0x8b93788) at base/unix/src/pthreadthread.cpp:73 #10 0x80c4b1d in pthreadThread::internalThreadFunction (arg=0x8b93788) at base/unix/src/pthreadthread.cpp:62 #11 0x40028cd5 in pthread_start_thread (arg=0xbbfffe40) at manager.c:241 #12
Re: Implementing Better String Storage
So I am interested in hearing how much memory savings we are seeing from all this. Can you run freeamp on your machine pre-patch and post-patch with the same playlists, etc. and let us know the memory footprints? elrod Chris Kuklewicz wrote: [ Is the #freeamp hidden? It was not on the list, but I could join it... ] Theme for this whole thread of discussion: [chrisk@kuklewicz ~/www]$ fortune Nothing endures but change. -- Heraclitus An update on my progress, Yesterday I replaced how metadata is stored internally and much of how it is accessed of outside (e.g. PeekAlbum() not Album().c_str() and Album_length() not Album().size() and so on). Before I submit I will temporarily disable the const string Album() style access methods and follow the compiler errors to audit how the returned strings are used. The strings are now *temporary* objects, instead of "const string" references to MetaData fields. With help from Isaac I managed to get it compiling and it ran loaded the playlist just find, but editing a comment segfaulted. I have it cleaner today, with a slightly more evolved design and it could edit metadata okay, but opening the streams part of the tree eventually segfaulted. It seems I did need to be thread safe. I observed the two [New Thread xxx (runnable)] notices, and the names of the functions in the back trace (at end of message). So I now use a Mutex in the HashStore to protect everything. Also, thank you to whomever already added the AutoMutex class! I also added a static const char empty[1]=""; to the HashStore for caching (merge duplicates) of all zero length strings. I also went back and changes the HashStore methods to use const everywhere again. Old example output from "cerr *m_store" after it loaded my database on startup: HashStore report Logical Strings held: 2317 Strings in HSet: 1009 Strings in HMap: 146 So very soon I will have a whopper of a patch. Any special handling requests? Currently: I did make clean make, and now I sleep. Most recent crash as seen by gdb, as evidence that thread safety is required: @ ui/musicbrowser/unix/src/browsertree.cpp : 1370 : void GTKMusicBrowser::StreamTimer() LWP 15638 exited. @ ui/musicbrowser/unix/src/browsertree.cpp : 1535 : void GTKMusicBrowser::FillRelatable(bool = false) [New Thread 28701 (runnable)] Delayed SIGSTOP caught for LWP 15639. @ ui/musicbrowser/unix/src/browsertree.cpp : 1331 : static void GTKMusicBrowser::stream_timer_func(void *) @ lib/http/src/Http.cpp : 92 : enum Error Http::DownloadToString(const class string , class string ) args: url=http://www.freeamp.org/streams.xml, page=, m_bytesInBuffer=0, m_bufferSize=0, (void *)m_buffer=(nil) @ lib/http/src/Http.cpp : 181 : enum Error Http::Download(const class string , bool) args: url=http://www.freeamp.org/streams.xml, fileDownload=0 @ lib/http/src/Http.cpp : 182 : enum Error Http::Download(const class string , bool) args: m_bufferSize=0, m_bytesInBuffer=0, (void*)m_buffer=(nil) @ ui/musicbrowser/unix/src/browsertree.cpp : 1535 : void GTKMusicBrowser::FillRelatable(bool = false) [New Thread 29726 (runnable)] Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 29726 (runnable)] 0x80d4043 in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffaa4) at /usr/include/g++-2/stl_hashtable.h:777 777 node* next = cur-next; (gdb) bt #0 0x80d4043 in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffaa4) at /usr/include/g++-2/stl_hashtable.h:777 #1 0x80d5ddd in hashtablechar *, char *, hashchar *, identitychar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it=@0xbbfffacc) at /usr/include/g++-2/stl_hashtable.h:829 #2 0x80d5da7 in hash_setchar *, hashchar *, EqualString, __default_alloc_templatetrue, 0 ::erase (this=0x813aa50, it={cur = 0x81ef448, ht = 0x813aa50}) at /usr/include/g++-2/stl_hash_set.h:160 #3 0x809ecf2 in HashStore::unref (this=0x813aa50, s=0x81eb4a8 "\030\e\b\036@0's (Rob's Pick) ") at base/src/charstore.cpp:204 #4 0x80e48fc in MetaData::setentry (dest=@0xbbfffcf4, src=0x40e3b7c0 "Upbeat 80's (Rob's Pick)") at base/include/metadata.h:166 #5 0x80e4319 in MetaData::SetTitle (this=0xbbfffcec, title=0x40e3b7c0 "Upbeat 80's (Rob's Pick)") at base/include/metadata.h:83 #6 0x4096f063 in Misc::ReadMetaData (this=0x8148ff0, url=0x40e3b730 "http://216.32.166.94:8076/\"", metadata=0xbbfffcec) at plm/metadata/misc/misc.cpp:227 #7 0x80b2721 in PlaylistManager::MetaDataThreadFunction (this=0x81452f8, list=0x81e0f28) at base/src/playlist.cpp:2472 #8 0x80b292b in PlaylistManager::metadata_thread_function (arg=0x81f34f0) at base/src/playlist.cpp:2519 ---Type return to continue, or q return to
Re: Implementing Better String Storage
On Tue, Sep 26, 2000 at 08:13:08AM -0700, Mark B. Elrod wrote: So I am interested in hearing how much memory savings we are seeing from all this. Can you run freeamp on your machine pre-patch and post-patch with the same playlists, etc. and let us know the memory footprints? elrod Patience. The new compile segfaulted before the GUI came up. Once I see it stop segfaulting from my patch I will compile clean cvs and my tree with the same optimizations. Right now I expect only minor memory improvements, with the removal of many string objects with no data being the main benefit. In the future the mapstring,string for GUID-(string URL) for GetFilename will be replaced with GUID-pairchar* Path, char* File mapping to allow refcounting of duplicate paths. Unless your collection of songs is really strangely organized, this will be a benefit. [ If you need to scale to 10^5 + pathname then you ought to check out how the locate command caches the data ] Right now I am using "g++ -O0 -ggdb3" and tarball gdb from cvs snapshot on 9/20/2000. It seems to mostly work, but some things still cannot be examined with "print". The install directory /opt/freeamp-mine is 61 MB. The nirvana of g++ 3.0 is still elusive. -- Chris Kuklewicz ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Re: Implementing Better String Storage
On Mon, Sep 25, 2000 at 01:30:32AM -0400, Isaac Richards wrote: On 25-Sep-2000 Chris Kuklewicz wrote: What is the difference between a .o and a .lo object? Nothing, really. The .lo is just an extra extension so that I can compile the few object files that are shared in the main executible and plugins with or without -fPIC as needed. Stuff in the main executible gets .o, and objects that need to be compiled again for a plugin get .lo. Arg. I attached the short error message to this message. You forgot to initialize m_store.. Stick: HashStore *MetaData::m_store = NULL; in player.cpp or something.. Isaac Doh! Thank you very very much. It should go into base/src/metadata.cpp, which already has the initializer for MetaData::empty. I added two static items, but only one initializer! While waiting for that fix, I have created another right click option menu item that I wanted last time I used the browsertree. I'll send in the patch for that when I'm done. It is essentially the same as "add to playlist and play now" but it will not add items that are already in the list. I just ran cvs update on my hacking tree. The make has a small glitch: gcc -I. -I. -I./config -DUNIX_LIBDIR=\"/opt/freeamp-mine//lib\" -Dlinux -I. -I./lib/gdbm -I./base/include -I./config -I./io/include -I./ui/include -I./lmc/include -I./base/unix/include -I./base/unix/linux/include -I./io/esound/include -I./ui/musicbrowser/unix/include -I./ui/freeamp/include -I./ui/freeamp/unix/include -I./ui/download/unix/include -I./ui/musicbrowser/include -I./ftc/kjofol -I./io/alsa/unix/linux/include -I./io/soundcard/unix/linux/include -I./lmc/xingmp3/include -I./lmc/cd/include -I./plm/portable/pmp300/sba -I./lib/xml/include -I./lib/zlib/include -I./lib/unzip/include -I./io/cd/unix/include -I./base/aps -I./io/wavout/include -I./ui/lcd/include -I./ui/irman/include -I./lib/http/include -I./io/signature/include -I./lmc/vorbis/include -Wall -ggdb3 -O0 -fno-inline -fno-inline-functions -I/usr/lib/glib/include -I/usr/X11R6/include -I/usr/lib/glib/include -I/usr/X11R6/include -D_REENTRANT -fPIC -c lmc/vorbis/src/lib/psy.c -o lmc/vorbis/src/lib/psy.o lmc/vorbis/src/lib/psy.c: In function `_vp_psy_init': lmc/vorbis/src/lib/psy.c:175: structure has no member named `ath_att' lmc/vorbis/src/lib/psy.c:272: structure has no member named `toneatt_125Hz' lmc/vorbis/src/lib/psy.c:273: structure has no member named `toneatt_250Hz' lmc/vorbis/src/lib/psy.c:274: structure has no member named `toneatt_500Hz' lmc/vorbis/src/lib/psy.c:275: structure has no member named `toneatt_1000Hz' lmc/vorbis/src/lib/psy.c:276: structure has no member named `toneatt_2000Hz' lmc/vorbis/src/lib/psy.c:277: structure has no member named `toneatt_4000Hz' lmc/vorbis/src/lib/psy.c:278: structure has no member named `toneatt_8000Hz' lmc/vorbis/src/lib/psy.c:280: structure has no member named `noiseatt_125Hz' lmc/vorbis/src/lib/psy.c:281: structure has no member named `noiseatt_250Hz' lmc/vorbis/src/lib/psy.c:282: structure has no member named `noiseatt_500Hz' lmc/vorbis/src/lib/psy.c:283: structure has no member named `noiseatt_1000Hz' lmc/vorbis/src/lib/psy.c:284: structure has no member named `noiseatt_2000Hz' lmc/vorbis/src/lib/psy.c:285: structure has no member named `noiseatt_4000Hz' lmc/vorbis/src/lib/psy.c:286: structure has no member named `noiseatt_8000Hz' lmc/vorbis/src/lib/psy.c:298: structure has no member named `peakatt_125Hz' lmc/vorbis/src/lib/psy.c:299: structure has no member named `peakatt_250Hz' lmc/vorbis/src/lib/psy.c:300: structure has no member named `peakatt_500Hz' lmc/vorbis/src/lib/psy.c:301: structure has no member named `peakatt_1000Hz' lmc/vorbis/src/lib/psy.c:302: structure has no member named `peakatt_2000Hz' lmc/vorbis/src/lib/psy.c:303: structure has no member named `peakatt_4000Hz' lmc/vorbis/src/lib/psy.c:304: structure has no member named `peakatt_8000Hz' lmc/vorbis/src/lib/psy.c: In function `_vp_apply_floor': lmc/vorbis/src/lib/psy.c:623: structure has no member named `noisefit_threshdB' lmc/vorbis/src/lib/psy.c:650: structure has no member named `noisefitp' lmc/vorbis/src/lib/psy.c:651: structure has no member named `noisefit_subblock' lmc/vorbis/src/lib/psy.c:665: structure has no member named `noisefit_subblock' make[1]: *** [lmc/vorbis/src/lib/psy.o] Error 1 When did this happen? Or have I messed up the build tree? Anyway..time to sleep while the compile continues. Again: THANKS FOR THE FIX ! :) -- [chrisk@kuklewicz ~/www]$ fortune Conway's Law: In any organization there will always be one person who knows what is going on. This person must be fired. And that person certainly is not me! ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Implementing Better String Storage
First: I have written/compiled charstore.h and charstore.cpp which implement the API for storage of reference counted char* string. The overall idea is to reduce storage size even though operations may take longer. There is an abstract interface and two actual classes: AbstractStore which defines the api methods, and documents the semantics. HashStore which uses both a hash_set and a hash_map so it is space and time efficient for large string collections. The hash set of refcount==1 strings is accessed first so it is fairly optimised for strings that are never duplicated (e.g. GUID). StringStore which uses a single map to keep the strings in a sorted order. It is less space efficient than the hash_set in HashStore if there are few or no duplicated strings. Keeping it always sorted is slower than using a hash based container. The advantage is only for items that need to be read out in a sorted order. (e.g. ???) The API works best for dynamically allocated strings, but it is kludged to handle "static" strings that are never freed. The API is not for storing "mutable" strings. If you want to change the title, you have to unref the old title and store the new one. Is this a problem anywhere? Second: Now how to perform the radical surgery? Following the "data path"...the Read/WriteMetadataFromDatabase use the MetaData get/set functions to store the strings. So the MetaData class is the primary target: keep the api but change the storage to the new charstore.h classes. So how does the lightweight metadata class lean about the concrete storage classes? Simple: Declare the storage classes during program initialization and put pointers into the FAContext. Then make a class static variable m_context in MetaData that points to FAContext and set it during program initialization. This also seems to solve telling the windows dll's where the freestore is. So the Meta class has storage for Artist, Album, Title, Comment, Genre, FormatExtension (?), Year, GUID. Do any of those need to be pulled out in sorted order? Artist: we need a sorted artist list for the musicbrowser tree. Title: Is the "all songs" tree list in sorted order Also, what is FormatExtension? At first, I will simply store all of the fields in one huge HashStore. If the hash is reasonably efficient the large size will not affect performance. Note: Splitting the url into pathname and filename for efficiency will not be attemped till much later. I have not studied the m_guidTable closely yet. Third: Thread safety. How is the current sting storage protected? I can easily add a mutex to protect the stores, but I ought to understand the current system. Help? ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
RE: Implementing Better String Storage
On 24-Sep-2000 Chris Kuklewicz wrote: First: I have written/compiled charstore.h and charstore.cpp which implement the API for storage of reference counted char* string. The overall idea is to reduce storage size even though operations may take longer. There is an abstract interface and two actual classes: AbstractStore which defines the api methods, and documents the semantics. HashStore which uses both a hash_set and a hash_map so it is space and time efficient for large string collections. The hash set of refcount==1 strings is accessed first so it is fairly optimised for strings that are never duplicated (e.g. GUID). Just fyi, GUIDs can be duplicated -- I have a few tracks in my collection that are identical, just on separate albums. StringStore which uses a single map to keep the strings in a sorted order. It is less space efficient than the hash_set in HashStore if there are few or no duplicated strings. Keeping it always sorted is slower than using a hash based container. The advantage is only for items that need to be read out in a sorted order. (e.g. ???) I don't see any reason to keep it sorted. The API works best for dynamically allocated strings, but it is kludged to handle "static" strings that are never freed. The API is not for storing "mutable" strings. If you want to change the title, you have to unref the old title and store the new one. Is this a problem anywhere? Shouldn't be a problem.. The access methods of the Metadata class are all const, so if anyone was playing w/ them, there should be compiler warnings, and I haven't seen any of those from that.. Second: Now how to perform the radical surgery? Following the "data path"...the Read/WriteMetadataFromDatabase use the MetaData get/set functions to store the strings. So the MetaData class is the primary target: keep the api but change the storage to the new charstore.h classes. Right -- should be able to just change the access/set methods of the MetaData class, and it _should_ work. So how does the lightweight metadata class lean about the concrete storage classes? Simple: Declare the storage classes during program initialization and put pointers into the FAContext. Then make a class static variable m_context in MetaData that points to FAContext and set it during program initialization. This also seems to solve telling the windows dll's where the freestore is. So the Meta class has storage for Artist, Album, Title, Comment, Genre, FormatExtension (?), Year, GUID. Do any of those need to be pulled out in sorted order? Artist: we need a sorted artist list for the musicbrowser tree. Title: Is the "all songs" tree list in sorted order Don't need any sorting -- see base/src/musiccatalog.cpp, it handles storing things sorted, and the GUI code in the musicbrowsers can handle sorting as well. Also, what is FormatExtension? The extension of the file associated w/ the metadata -- I wasn't aware anything used this. At first, I will simply store all of the fields in one huge HashStore. If the hash is reasonably efficient the large size will not affect performance. Note: Splitting the url into pathname and filename for efficiency will not be attemped till much later. I have not studied the m_guidTable closely yet. Third: Thread safety. How is the current sting storage protected? I can easily add a mutex to protect the stores, but I ought to understand the current system. Help? Isn't really any protection currently, except what's inherent in the STL. Isaac ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Re: Implementing Better String Storage
On Sun, Sep 24, 2000 at 07:18:12PM -0400, Isaac Richards wrote: On 24-Sep-2000 Chris Kuklewicz wrote: Also, what is FormatExtension? The extension of the file associated w/ the metadata -- I wasn't aware anything used this. So..there are current lots of copies of the string "mp3" or "ogg" being stored? Wow... ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Re: Implementing Better String Storage
On 24-Sep-2000 Chris Kuklewicz wrote: So..there are current lots of copies of the string "mp3" or "ogg" being stored? Wow... Actually, there's not. That field's not used, afaik. Isaac ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Re: Implementing Better String Storage
On Sun, Sep 24, 2000 at 06:31:05PM -0400, Chris Kuklewicz wrote: First: I have written/compiled charstore.h and charstore.cpp which implement the API for storage of reference counted char* string. The overall idea is to reduce storage size even though operations may take longer. I have not tried compiling everything yet. I have finished retrofitting metadata.h using an expanded API like: Error SetTitle(const char* title){ set(m_title,title); return kError_NoErr; } Error GetTitle(char* buf, uint32* len) const { return SetBuffer(buf, m_title, len); } const char* PeekTitle() const { return m_title; } const string Title() const { return m_title; } size_t Title_length() const { return (m_title?strlen(m_title):0); } The const string return value is now just const string. Beware of the fact this is now a temporary. The previous use of Title().size() or Title().length() is now simply Title_length() The very common previous Title().c_str() is now PeekTitle() Alot of search/replace has replaces all uses of c_str() and size() or length() with the new functions for Artist,Album,etc... Yes, that took a while. Now to correct a large number of compile errors -- Chris Kuklewicz The party adjourned to a hot tub, yes. Fully clothed, I might add. -- IBM employee, testifying in California State Supreme Court ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev
Re: Implementing Better String Storage
On 25-Sep-2000 Chris Kuklewicz wrote: What is the difference between a .o and a .lo object? Nothing, really. The .lo is just an extra extension so that I can compile the few object files that are shared in the main executible and plugins with or without -fPIC as needed. Stuff in the main executible gets .o, and objects that need to be compiled again for a plugin get .lo. Arg. I attached the short error message to this message. You forgot to initialize m_store.. Stick: HashStore *MetaData::m_store = NULL; in player.cpp or something.. Isaac ___ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev