
General notes

I started off writing code of the form;

a_tdb%!((n%*t_size%)+offset%)

But this gets very clunky, particularly if n% in the above is replaced by another lookup to get something from another table;

a_tdb%!(((a_bdb%!((p%*a_size%)+offseta%))*t_size%)+offsetb%)

While some code like this remains (because I haven't gone back to change it), most of AMPlay isn't written that way, but instead uses functions to read from the tables, and procedures to write new values. So, instead of;

a_tdb%!((n%*t_size%)+offset%)

I do;

FNdb_track(n%,offset%)

And the second example becomes;

FNdb_track(FNdb_album(p%,offseta%),offsetb%)

which is a bit more readable.

To write a new value, change FN to PROC, and pass the new value as the third parameter;

PROCdb_track(n%,offset%,newvalue%)

All this is somewhat slower than the direct approach, but not too bad. Adds around 10% to really intensive db stuff.



Tracks database.

a_tdb% = pointer to block of memory. This stores per track information, 48 bytes per track


offset  var             desc       size    notes
     0  o_tflags%       flags     (4bytes)
     4  o_talbum%       album     (4bytes)
     8  o_tartist%      artist    (4bytes)
    12  o_tfilter_lin%  filters   (4bytes) 
    16  o_tfilter_iso%  filters   (4bytes)
    20  o_tfilter_both% filters   (4bytes)
    24  o_tinlist%      tracklist (4bytes) (position within post-filtered list)
    28  o_tdur%         duration  (4bytes)
    32  o_tdata%        data      (4bytes) (relative volume, weighting etc)
    36  o_tnamecache%   namecache (4bytes)
    40  o_textname%     extname   (4bytes) (pointer to extended name block)
    44  o_tpath%        path      (4bytes) (pointer to path to file)
        t_size%                   =48bytes

Track n% is accessible via the pointer stored in a_tdb%!((n%*t_size%)+offset%), or by FNdb_track(n%,offset%), where offset is one of the variables listed above.

path to file is a simple string stored in a block of its own.

if playlist naming is used, the namecache word stores cached name information. If some other naming scheme (tag or custom name) is being used for that track, then the ext_name word is a pointer to another block containing that information. Which to use is set in the flags word in tdb.

tinlist is 0 if the track does not pass through the current filter.

Note that there is no track 0. Therefore, the first 48 bytes of the block do not contain track information. The contain the following;

offset
0 size of entire block (4 bytes)
4 block format version (currently 1) (4 bytes)
8 block format flags (currently 0) (4 bytes)
12  The string AMPlay_TrackDB_ (16 bytes)
remaining bytes unused, should normally be 0.


Name Blocks

In the name block, whether it is a tag or custom block is set in the flags word of the track block.

name blocks would be structured as follows:

0     o_n_size%         size          (4 bytes) (of entire block)
4     o_n_flags%        flags         (4 bytes) 
8     o_n_genre%        genre         (4 bytes)
12    o_n_year%         year          (4 bytes)
16    o_n_trnum%        track number  (4 bytes) (within album)
20    o_n_albxoffset%   album offset  (4 bytes) (from start of block
24    o_n_artxoffset%   artist offset (4 bytes) ''
28    o_n_cmtxoffset%   comment offset(4 bytes) ''
      n_size%                          =32 bytes 
32    o_n_troffset%     track         (however many)
!20                     album         (however many)
!24                     artist        (however many)
!28                     comment       (however many)

If it's a tag block, then track, album artist and comment fields are limited to 30 characters (28 for comments). If it's a custom name block, then the normal BASIC string limits apply.

cached tags do not have trailing spaces - however strings are stored CR terminated, so may take up to 31 bytes (=32 when rounding to nearest word).

Also note that the album, artist and comment fields start at word-aligned boundaries (i.e. round up to next 4 bytes).

A tag block is a cached copy of tag information actually present on the file itself (it shouldn't differ from the file unless the file has been modified subsequently). A custom name block is entirely present within the amplay database, and may have no presence in the mp3 on disk, although it may have originally been populated from tag information.

The flags word in the name block indicates the origins of the information;

bit   meaning
0     Derived from playlist name
1     Derived from tag read from file
2     Editted
3     Written back to file

Bits 0 and 1 are mutually exclusive, other bits reserved.
Either bit 0 or bit 1 must be set, but see note below;
(The function that returns the flags word of an extended name block will return 0 if the specified track doesn't have a name block - calling routines must check for this if it is possible that the track doesn't have one)
(The written back to file status isn't currently used for anything, but might be used in future for a 'only update tracks that haven't been updated before' option)

Minimum size of an extname block is where all the strings are single characters. These still round up to 4 bytes, so 48 bytes total.
Max size is all strings at 240 chars = (240*4)+32 = 992 



Sort blocks

When sorting, a_sortdb% would need to be created. This would comprise pointers to structs, each struct being composed as follows;

-4 trackno (4bytes)
 0 string (up to 256)

The pointer is to the string, not the start of the block. Trackno would be determined using a negative offset.

On running os_heapsort32 against a_sortdb%, this could then be analysed to workout how to shuffle the data around in a_tdb%.

All the sort structs and db could then be freed. Might be worth storing this in a separate DA, so that it can be genuinely freed on completion rather than fragmenting the main DA.


Album and Artist blocks

For albums and artists, it is much the same except that not all the per-track information needs to be stored. in particular, no strings need to be pointed to.

a_bdb% ;

0    o_aflags%       flags             (4 bytes)
4    o_afirst%       first track       (4 bytes)
8    o_alast%        last track        (4 bytes)
12   o_afilter_lin%  filters           (4 bytes)
16   o_afilter_iso%  filters           (4 bytes)
20   o_afilter_both% filters           (4 bytes)
24   o_ainlist%      position in list  (4 bytes)
28   o_afiltdur%     filtered duration (4 bytes)
32   o_adur%         duration          (4 bytes)
36   o_adata%        data              (4 bytes)
40   o_anamecache%   namecache         (4 bytes)
                                      =44 bytes  a_size%


NOTE: For internal optimisation reasons, o_afilter_lin,iso,both must always be the same offsets within the albums/artists as o_tfilter_lin,iso,both are within tracks.

Album N would be accessed via a_bdb%!(a_size%*N) (starting from album 0.
duration of album N would be; a_bdb%!(a_size%*N+o_adur%)

Again, there is no album or artist 0. Therefore, the first 32 bytes of the block do not contain album/artist information. The contain the following;

offset
0  size of entire block (4bytes)
4 block format version (currently 1) (4 bytes)
8 block format flags (currently 0) (4 bytes)
then either
12  The string AMPlay_AlbumDB_ (16 bytes)
or
12  The string AMPlay_ArtistDB (16 bytes)

remaining bytes unused, should normally be 0.


Other structures.

Similar structures would exist for history (and histmenu), but would contain track numbers as currently, not pointers or real data.

As there is no 0th item in the history, the first 4 bytes of the history block contain the size of the block.

Size in the first 4 bytes is only done for blocks that are saved to disk. Ones that only ever reside in memory may have junk in unused areas.

Also for the dynamic menus.

Also for the playlist arrays, again a block containing track/album/artist numbers would be needed.

See Search/txt for details of search blocks.



Tdb words:

Filters:

bits 0-19 correspond to filters 1-20
Other bits currently unused.

Which filter should be used depends on the ordermethod selected.

Note that in some cases both filters should be taken into account. There is a function for returning the correct filter information which handles this. Don't deal directly with the filter words unless necessary.


Flags:

 0: reserved
 1: album word is valid
 2: artist word is valid
 3: filter words are valid
 4: duration is valid
 5: data word is valid
 6: namecache information is present and valid
 7: nameblock pointer is present and valid
 8: path to file pointer is present and valid
 9: track type is valid [not implemented]
10: reserved
11: reserved
12: Track has ignore uniqueness flag set.
13: Track is present in track history.
14: Track is present multiple times in track history (implies bit 13 set)
15: Track has Link to Next set.
16: Track has relative volume set in data word. (implies bit 5 set)
17: Track has a weighting set in data word. (implies bit 5 set)
18: Track has volume fade in set [not implemented]
19: Track has volume fade out set [not implemented]
20: reserved
21: Track is transient [not implemented]
22: Track is in the default directory.
23: Uses extended name block (implies bit 7 set)
24: Uses pathname naming, uneditted
25: reserved
26: reserved
27: track is a stream [not implemented]
28: reserved
29: reserved
30: reserved
31: reserved

By default, either bit 22 or 24 is set (based on default naming options).
One of bits 22,23 and 24 must be set.
The filter words are either all valid or none are. There is no indication of partial status here.

Editting a pathname based name implicitly transfers it to an extended name block, at which point it differs from a tag only in where the information came from. The only way to tell them apart is by looking at the flags word of the name block in question.


Data word:

0-6  relative track volume
8-11 track weighting
16-23 track type [not implemented]
other bits reserved.

Intent is for track type to indicate the playback method to use, in cases where we support playback by methods other than the amplayer module, or in cases where the track is really a stream rather than a file on disk. Bits are reserved for this use, but this is not implemented in 2.0x. (Intended for 2.2x)


For albums and artists, the following applies;

Filters:

Bits 0-19 set if the corresponding bit is set in the filters word of any track within that artist or album (i.e. the artist or album filter comprises all the track filters ORd together)

Flags:

 0: first track word is valid
 1: last track word is valid
 2: filtered duration is valid
 3: filter words are valid
 4: duration is valid
 5: data word is valid
 6: namecache information is present and valid
 7: reserved
 8: reserved
 9: reserved
10: reserved
11: reserved
12: ignore uniqueness set. (local setting) 
13: reserved 
14: reserved 
15: album/artist is linked to next. (local setting)
16: album/artist has a rel vol set. (implies bit 5 set)
17: album/artist has a weighting set. (implies bit 5 set)  (local setting)
18+ reserved

local setting = setting that is set on the artist/album itself, and not one cached or calculated from the tracks on that album/artist.

(Note that albums/artists always have a rel vol, even if none of the tracks within them do. In the case where no tracks have one set, the album and artist are set, but contain the default value that is also assumed for tracks that don't have a specific value).

Also note that the namecache information only exists if the first track in the album/artist uses pathname based naming. 

0 by default

Data word:

0-6  relative track volume album/artist average.
8-11 album/artist weighting



Saved states

Album and artist blocks can be dumped directly to file and read back in to memory. Likewise history.

The trackdb is also be dumped directly to a file, and read back in again. However, on being read back in, the pointer to filepath and pointer to name (if any)  need to be zeroed and re-linked to the correct places.

The lists, history etc are also simple dumps of the memory block.

For track, album and artist blocks, the block in memory may be larger (due to having been created with room to expand). Only the used portion of the block is saved to disk.

Paths are not stored raw, but the strings extracted and written to the output, producing a text readable list of paths.

The two more interesting ones are the search history, and the extended name blocks.

These are both cases where we have multiple blocks to save (up to 16 search blocks, and up to 1 name block per track), but the block size may vary.

We therefore save two files;

[thing]i: contains information describing file2
[thing]:  contains all the blocks wadded together.

i.e. an index file and the real data.

For search history, the index just contains one word per search block, giving the size of the block.

On loading the state, it takes the number stored at offset 0 in the index to be the number of bytes to read from the real data file. This reads the first search block. Then the word at +4 in the index is used, and so on until we run out of blocks to load.


For the extended name blocks, we have two words of information in the index, per extended name block in the real data.

The index file comprises;

Offset
0:  Track number in DB with which the 1st name block is associated.
4:  Size of 1st name block
8:  Track number in DB with which the 2nd name block is associated.
12: Size of 2nd name block
etc.

This allows the state loader to read the index, and then read chunks from the real data file, allocate memory and store these chunks, putting the pointers to those chunks in the relevant attribute of the correct track.

Note that the extended name block information in the saved state can't be interpreted except as part of that state. The track numbers don't make sense otherwise.


________________________________________________________________________
Copyright  2008 Mike Sandells, mike@mikejs.com
Last Modified: 30.05.2008



