ocarina/DESIGN

1364 lines
36 KiB
Plaintext

===============================================================================
= =
= Ocarina 6.1 =
= =
===============================================================================
Ocarina 6.1 is the 6th implementation of the Ocarina music player - a
lightweight, GTK+ based music player with all the features that I want.
Improvements over the 5.x series will include the existence of both a design
document (this file) and unit tests. This should make maintenance easier and
help me stay focused.
Ocarina 6.1 will use Gstreamer 1.0 for audio playback, GTK-MM 3 for user
interface development, and Taglib for extracting tags.
Install:
Ocarina will be compiled into a single executable placed under
/usr/bin/. Any extra files needed to run will be placed under
/usr/share/ocarina/.
Version:
This file contains a simple function for returning a string stating
the current version. The current version will be set by the build
system and passed using the CONFIG_VERSION macro.
API:
const char *get_version();
Returns a string describing the current version.
Errors:
This file contains an enum defining error codes used throughout the
codebase. Errors will be reported using "throw" and "catch".
Error Codes:
enum error_t {
E_AUDIO = 1, /* Audio error */
E_EXIST, /* Requested object does not exist */
E_INVAL, /* Invalid operation requested */
};
Printing:
Sometimes text needs to be printed to the screen so users (or debuggers)
can trace what is going on.
API:
void print(string fmt, ...);
void dprint(string fmt, ...);
Print text to the screen. The dprint() option will only only
be implemented when when CONFIG_DEBUG or CONFIG_TEST is enabled,
and will be an empty function otherwise.
Callbacks:
Callbacks are used to notify a unit test or the gui that something in
the backend has changed. The callbacks structure should be initialized
with no-op default values and filled in by the user through the
get_callbacks() function.
- Callback functions:
struct Callbacks {
void (*on_library_add)(unsigned int, library :: Library *);
void (*on_library_update)(unsigned int, library :: Library *);
void (*on_library_track_add)();
};
static struct Callbacks callbacks;
- API:
struct Callbacks *get_callbacks();
Return the Callbacks structure;
On-disk files:
Data will be stored in the user's home directory according to the
XDG / freedesktop.org specification. This means data will be stored
in a subdirectory of $XDG_DATA_HOME.
The filse class will support reading and writing files in the users
$XDG_CONFIG_HOME/ocarina{-debug|test}.
Items should be written to a file with either a space or new line
separating multiple values.
- Notation:
"File << aaaaa << bbbbb << endl" is translated into "aaaaa bbbbb\n"
- File version:
#define FILE_VERSION 0
- Open mode:
enum OpenMode {
OPEN_READ,
OPEN_WRITE,
NOT_OPEN,
}
- File:
class File : public std::fstream {
private:
unsigned int version;
OpenMode mode;
string filename;
public:
File(const std::string &);
~File();
const std::string get_filepath();
const unsigned int get_version();
bool exists();
void open(OpenMode);
void close();
string getline();
}
- File format:
File << FILE_VERSION << endl;
File << <OTHER_DATA>;
- API:
File :: File(const std::string &filename);
Store the name of the file.
File :: ~File();
Close the file stream if it is open.
const std::string File :: get_filepath();
If filename == "":
return ""
Otherwise, resolve filepath to one of:
XDG_DATA_HOME/ocarina/filepath
XDG_DATA_HOME/ocarina-debug/filepath
XDG_DATA_HOME/ocarina-test/filepath
and return the result.
const unsigned int File :: get_version();
Return the file version number.
bool File :: exists();
Return true if the file exists in the filesystem.
Return false otherwise.
bool File :: open(OpenMode mode);
Return false if:
- filename == ""
- mode == NOT_OPEN
- The file is already open
When opening a file for reading (mode == OPEN_READ),
- Return false if the file does not exist
- Open the file
- Read in version from the start of the file
When opening a file for writing (mode == OPEN_WRITE),
- Create missing directories as needed
- Write version information to the start of the file
Return true on success.
void File :: close();
Close a file after IO.
string File :: getline();
Read an entire line from the file and return it to the caller.
In theory a return value optimization will occur so returning
a string by value won't be a problem.
Database Entry:
The database entry class is a base class used for storing data inside
a database (below). The valid flag and id counter will be managed by
the database itself, and should be initialized to false and 0.
- DatabaseEntry:
class DatabaseEntry {
public:
unsigned int id;
DatabaseEntry();
virtual void ~DatabaseEntry() = 0;
virtual const std::string primary_key() const = 0;
virtual void write(File &) = 0;
virtual void read(File &) = 0;
};
- API:
DatabaseEntry :: DatabaseEntry():
Set id = 0.
const std::string DatabaseEntry :: primary_key() const;
This function should return a unique string representing this
DatabaseEntry instance, which will be used to prevent
duplicates in a database. This string is not expected to
change once a DatabaseEntry has been initialized.
void DatabaseEntry :: write(File &);
This function is called to write a specific DatabaseEntry to
file.
void DatabaseEntry :: read(File &);
This function is called to read a DatabaseEntry from a file.
Database:
Databases are a generic store for information used by Ocarina. Users
need to inherit from a DatabaseEntry class (above) to properly use a
database.
The Database class is a templated class, so code could potentially
get messy. Normal class declarations can still exist in the file
include/database.h and member functions are written in the file
include/database.hpp, which will be included by database.h. Any
function not relying on a template can be written in lib/database.cpp.
- Automatic saving:
Databases can save automatically whenever a new value is inserted or
deleted. This will be more efficient for Databases that do not change
often but will be a huge performance hit for Databases that have many
changes at once. All databases will be loaded automatically from disk.
- Primary keys:
Databases use the primary_key() function of a DatabaseEntry to enforce
uniqueness. This key is used when inserting a new value into the
Database, and will not be updated after.
- Id:
The "id" field of a DatabaseEntry is also managed by the entry's
Database container It will be set to the entry's ID when the entry is
inserted into the database.
- Database:
template <class T>
class Database {
private:
std::vector<T *> _db;
std::map<const std::string, unsigned int> _keys;
unsigned int _size; /* Number of valid rows */
bool _autosave;
File _file;
public:
typedef std::vector<T *>::iterator iterator;
typedef std::vector<T *>::const_iterator const_iterator;
Database(std::string, bool);
~Database();
void save();
void load();
T *insert(const T &);
void remove(unsigned int);
unsigned int size();
unsigned int actual_size();
iterator begin();
iterator end();
iterator next(iterator &);
T *at(unsigned int);
T *find(const std::string &);
};
- File format:
File << db.size() << endl
File << INDEX_0 << db[INDEX_0].valid << db[INDEX_0] << endl;
File << INDEX_1 << db[INDEX_1].valid << db[INDEX_1] << endl;
...
- API:
Database :: Database(std::string filepath, bool autosave);
Initialize a database using "filepath" as a location to store
data on disk. If the file already exists, read the data into
the backing vector.
Database :: ~Database();
Delete all entries remaining in the database.
void Database :: save();
Save the database to disk.
void Database :: load();
Load the database from disk.
T *Database :: insert(const T &item);
Look up the item in the _keys map.
If we find an item with the same key:
- Return NULL.
Otherwise:
- Use new to allocate memory for a new item.
- Add the new item to the end of the _db.
- Add the new item to _keys.
- Set new item.id to the index of the new item.
_ Increment _size.
- If autosave == true: save().
- Return a pointer to the new item.
unsigned int Database :: remove(unsigned int id);
- Remove the item from the _keys map.
- Delete _db[id]
- Set _db[id] = NULL
- If autosave == true: save().
- Decrement _size.
unsigned int Database :: size();
return _size;
unsigned int Database :: actual_size();
return _db.size();
iterator Database :: begin();
Return _db.end() if there are no valid entries
If the first entry is valid:
- return _db.begin();
Otherwise:
- return an iterator to the first valid entry.
iterator Database :: end();
return _db.end();
iterator Database :: next(iterator &cur);
Return the next DatabaseEntry with valid == true or _db.end()
if there are no valid entries left.
T *Database :: at(unsigned int i);
If i is beyond the end of the container:
Return NULL
Otherwise:
Return _db[i];
T *Database :: find(const std::string &key);
If key is in the _keys map:
Return a pointer to the corresponding entry.
Otherwise:
Return NULL;
Index:
An index is a special database used to map a std::string key to
multiple integer values.
- IndexEntry:
class IndexEntry : public DatabaseEntry {
public:
const std::string key;
set<unsigned int> values;
IndexEntry(const std::string &);
const std::string primary_key() const;
void insert(unsigned int);
void remove(unsigned int);
void write(File &);
void read(File &);
};
File << key << endl;
File << values.size() << values[0] << .. << values[N] << endl;
- IndexEntry API:
IndexEntry :: IndexEntry();
Creat an empty IndexEntry.
std::string IndexEntry :: primary_key() const;
return key;
void IndexEntry :: insert(unsigned int value);
Add value to the values set.
void IndexEntry :: remove(unsigned int value);
Remove value from the values set.
void IndexEntry :: write(File &f);
Write the values set to a file.
void IndexEntry :: read(File &f);
Read values from a file.
- Index:
class Index : public Database<IndexEntry> {
public:
Index(const std::string &, bool);
void insert(const std::string &, unsigned int);
void remove(const std::string &, unsigned int);
};
- Index API:
Index :: Index(const std::string &filepath, bool autosave);
Pass values on to the Database constructor.
void Index :: insert(const std::string &key, unsigned int value);
Create an IndexEntry for key if one does not exist yet.
Insert value into the IndexEntry corresponding to key.
If autosave is enabled, save().
void Index :: remove(const std::string &key, unsigned int value);
Remove value from the IndexEntry corresponding to key. Do not
remove the IndexEntry, even if it is empty.
If autosave is enabled, save().
Filter:
Filtering is used to generate a subset of songs displayed by the UI to
that users can choose from. The inverted index is generated at startup
so there is no need for a remove() function, since it will be wiped
the next time the application starts.
- Parsing:
- Scan over the input text to create a list of words using the following
characters as delimiters: \/,;()_-~+"
- While scanning, convert the string to lowercase and strip out any
other special characters.
- API:
std::string filter :: add(const std::string &key, unsigned int track_id);
Parse the key into words following the "Parsing" section above.
Generate substrings for each word and add each (substring,
track_id) pair to the index. Return the lowercased text to the
caller.
To generate substrings, iterate over the word starting from
the front. For example: "goron" would contain the substrings
{g, go, gor, goro, goron}.
std::string filter :: lowercase(const std::string &text);
Parse the text into lowercased words following the "Parsing"
section above. Return the lowercased string to the caller.
void filter :: search(const std::string &text, std::set<track_id> &res);
This function finds all track_ids matching the input text.
Parse the string into substrings and take the intersection of
all sets returned by the index for each substring.
Idle queue:
The idle queue is used to schedule function calls that run at a later
time.
A single queue instance needs to be able to run functions that take
different types of arguments. This is done by creating an IdleBase
base class that the templated IdleTask class inherits from.
- IdleBase:
class IdleBase {
private:
schedule();
public:
IdleBase();
~IdleBase();
virtual void run() = 0;
};
- IdleTask:
template <class T>
class IdleTask : IdleBase {
private:
void (*func)(T &);
T &data;
public:
IdleTask(void (*)(T &), T);
void run();
};
- Queue:
queue<IdleBase *> idle_queue;
float queued = 0.0
float serviced = 0.0
- API:
void IdleBase :: schedule();
Add the idle task to the idle queue. This should be called
by the IdleTask constructor.
queued++;
template <class T>
static inline void idle :: schedule(void (*)(T &), T);
Create a new IdleTask to run the function later.
bool idle :: run_task();
If there are tasks on the queue:
run the next task
scheduled++
If there are still tasks on the queue:
return true
else:
queued = 0
scheduled = 0
return false
float idle :: get_progress();
Return (serviced / queued) to the caller. If there are no
tasks, return 1.0 to indicate that the queue is finished (and
to avoid a divide-by-zero error).
Tag Database:
The tag database is actually several databases that describe every
track added by the user. I do not expect the artist_db, album_db,
genre_db and library_db to change often, so they can be created as
autosaving databases that will write to disk whenever they are changed.
The track_db can have several additions and removals in a row, so the
commit() function is used to control when this db is written to disk,
avoiding the huge performance hit that would come with saving on EVERY
change.
Tags are defined in the sections below.
- Databases:
Database<Artist> artist_db;
Database<Album> album_db;
Database<Genre> genre_db;
Database<Library> library_db;
Database<Track> track_db;
- API:
void tagdb :: init();
Load all databases from disk.
void tagdb :: commit();
Write track_db to disk.
Track *tagdb :: add_track(const std::string &filepath, Library *library);
Add a new track to the track_db and return a pointer to it.
Return NULL if this track is already in the database.
Library *tagdb :: add_library(const std::string &filepath);
Add a new path to library_db. Return a pointer to the new path
or return NULL if the path is already in the database.
void tagdb :: remove_track(unsigned int track_id);
Remove the track with id equal to track_id from the track_db.
void tagdb :: remove_library(unsigned int library_id);
Remove all tracks associated with this library from the
track_db, then remove this library from the library_db.
Track *tagdb :: lookup(unsigned int track_id);
Look up the track_id in the track database. Return NULL if
there is no matching track.
Database<Track> &tagdb :: get_track_db();
Return a reference to the track_db.
Database<Library> &tagdb :: get_library_db();
Return a reference to the library_db.
Artist Tag:
The arist tag is used to collect basic information about the various
artists that have been added to the library.
- Artist:
class Artist : public DatabaseEntry {
public:
std::string name;
std::string lower;
Artist();
Artist(const std::string &);
const std::string primary_key() const;
void read(File &);
void write(File &);
};
- File Format:
File << name;
- API:
Artist();
Initialize an invalid Artist instance.
Artist(const std::string &artist_name);
Set artist_name and find the lowercase form.
const std::string Artist :: primary_key() const;
Use artist name as primary key.
void Artist :: read(File &f);
Read artist name from file and find the lowercase form.
void Artist :: write(File &f);
Write artist name to file.
Album Tag:
The album tag is used to collect information about each artist's albums.
- Album:
class Album : public DatabaseEntry {
public:
std::string name;
std::string lower;
unsigned int year;
Album();
Album(const std::string &, unsigned int);
const std::string primary_key() const;
void read(File &);
void write(File &);
};
- File Format:
File << year << name;
- API:
Album();
Initialize an invalid Album instance.
Album(const std::string &album_name, unsigned int album_year);
Set name and year from album name and album_year. Find the
lowercase form of the album name.
const std::string Album :: primary_key() const;
Return the string: "$year.$name"
void Album :: read(File &f);
Read year, and name from file. Then set the lowercase form
of the album name.
void Artist :: write(File &f);
Write album information to file.
Genre Tag:
The genre tag is used to collect basic information about the various
genres of songs in the library.
- Genre:
class Genre : public DatabaseEntry {
public:
std::string genre;
std::string lower;
Genre();
Genre(const std::string &);
const std::string primary_key() const;
void read(File &);
void write(File &);
};
- File Format:
File << name;
- API:
Genre();
Initialize an invalid Genre instance.
Genre(const std::string &genre_name);
Set genre from genre name and find the lowercase form.
const std::string Genre :: primary_key() const;
Use genre as primary key.
void Genre :: read(File &f);
Read genre from file and find the lowercase form.
void Genre :: write(File &f);
Write genre to file.
Library Tag:
The library tag is used to store a single directory added to Ocarina
by the user. It is not an ID3 tag, and is instead something I use
internally to keep track of paths added by the user. The count field
will be managed by the Track tag class.
- Library:
class Library : public DatabaseEntry {
public:
std::string root_path;
unsigned int count;
bool enabled;
Library();
Library(const std::string &);
const std::string primary_key() const;
void read(File &);
void write(File &);
};
- File Format:
File << enabled << root_path
- API:
Library();
Initialize an invalid Library instance.
Set count = 0.
Set enabled = false.
Library(const std::string &path);
Set root_path from the provided path.
Set count = 0.
Set enabled = true.
const std::string Library :: primary_key() const;
Use root_path as the primary key,
void read(File &f);
Read a library path from file.
void write(File &f);
Write a library path to file.
Track Tag:
The track tag is used to store information about a single track in the
user's music collection.
- Sorting:
enum sort_t {
SORT_ARTIST,
SORT_ALBUM,
SORT_COUNT,
SORT_GENRE,
SORT_LENGTH,
SORT_PLAYED,
SORT_TITLE,
SORT_TRACK,
SORT_YEAR,
};
- Track:
class Track : public DatabaseEntry {
public:
Library *library;
Artist *artist;
Album *album;
Genre *genre;
unsigned int track;
unsigned int length;
unsigned int play_count;
unsigned int last_year;
unsigned int last_month;
unsigned int last_day;
std :: string title;
std :: string title_lower;
std :: string filepath;
std :: string length_str;
Track();
Track(const std::string &, Library *);
const std::string primary_key() const;
void read(File &);
void write(File &);
void tag();
const std::string path();
void played();
bool less_than(Track *, sort_t);
};
- File Format:
File << library->id << artist->id << album->id << genre->id << track;
File << last_year << last_month << last_day << play_count << length;
File << title << endl;
File << filepath << endl;
- API:
Track();
Initialize an invalid Track instance.
Track(const std::string &full_path, Library *lib);
This function will only set up the primary key, and the tag()
function must be called to find audio tags.
- Strip library path from the beginning of full_path and use
the result to set filepath.
- Set library = lib.
const std::string Track :: primary_key() const;
return path();
void read(File &f);
Read track information from file. Look up the corresponding
Library, Artist, Album and Genre pointers. Add title to the
filter and find the string version of length.
void write(File &f);
Write track information to file.
void Track :: tag();
Use TagLib to find tags and audio properties for this file.
- Insert Artist, Album, and Genre into their databases and
set the corresponding pointers.
- Find title, track number, and length.
- Set play_count, last_year, last_month and last_day = 0.
- Set lowercase title and find the string form of length.
const std::string Track :: path();
Combine library->path and filepath to find the full path to
the audio file.
void Track :: played();
Called when a track has been played. Increment the play count
and set last_day, last_month, and last_year to today's date.
Call tagdb :: commit() to save the track database.
int Track :: less_than(Track *rhs, sort_t field);
Compare the requested field for this Track instance to the same
field in the provided track.
Return -1 if this < rhs.
Return 0 if this == rhs.
Return 1 if this > rhs.
Random:
The random number generator is used by the Queue to pick a song as
randomly as possible. I use two different RNG implementations,
depending on if Ocarina is compiled with CONFIG_TEST or not.
When CONFIG_TEST is enabled, the RNG is a simple counter. Every time
_pick_random() is called, the counter is incremented and returned.
When compiled without CONFIG_TEST we will return values using the
rand() function from stdlib.h.
- API:
void random_seed(unsigned int n);
Seed the random number generator based on n.
void random(unsigned int min, unsigned int max);
Return a random number between min and max, inclusive.
If min >= max: return min.
Queue:
Queues are lists of songs that the user has requested to play. They
are the main interface for all music played by Ocarina.
- Flags:
enum queue_flag {
Q_ENABLED (1 << 0),
Q_RANDOM (1 << 1),
Q_REPEAT (1 << 2),
Q_NO_SORT (1 << 3),
};
- Sort info:
struct sort_info {
sort_t field;
bool ascending;
};
- Sorting:
Sorting is done using std::stable_sort() to make sure that orders won't
change unexpectedly.
- Queue:
class Queue {
protected:
vector<Track *> _tracks;
list<sort_t> _sort_order;
unsigned int _cur;
unsigned int _flags;
unsigned int _length;
public:
Queue();
Queue(unsigned int);
void read(File &);
void write(File &);
void set_flag(queue_flag);
void unset_flag(queue_flag);
bool has_flag(queue_flag);
unsigned int add(Track *);
void del(Track *);
void del(unsigned int);
void updated(Track *);
Track *next();
unsigned int size();
const std::string size_str();
const std::string length_str();
void sort(sort_t, bool);
Track *operator[](unsigned int);
void track_selected(unsigned int);
};
File Format:
File << flags << tracks.size() << tracks[0] << tracks[1] << ... << tracks[N];
- API
Queue :: Queue();
Initialize _flags = 0, _cur = -1, _length = 0, and empty sort
order.
Queue :: Queue(unsigned int flags);
Initialize _flags = flags, _cur = -1, _length = 0, and empty
sort order.
void Queue :: read(File &f);
Read queue from file.
void Queue :: write(File &f);
Write queue to file.
void Queue :: set_flag(queue_flag f);
Set the appropriate flag.
void Queue :: unset_flag(queue_flag f);
Unset the appropriate flag.
bool Queue :: has_flag(queue_flag f);
Return true if the queue has the flag enabled and false
otherwise.
unsigned int Queue :: add(Track *track);
Add a new track to the tracks vector and return the index.
Increase length by the length of the track.
void Queue :: del(Track *track);
Remove all instances of the requested track from the queue.
void Queue :: del(unsigned int queue_id);
Remove the track at the given index from the queue.
void Queue :: updated(Track *track);
Find all indexes of the updated track and notify the UI that
it has changed.
Track *Queue :: next();
Return the next track to play.
if (tracks.size() == 0)
return NULL;
if (flags & PL_RANDOM):
_cur += rand() % tracks.size();
else:
_cur += 1;
if (_cur >= tracks.size())
_cur -= tracks.size();
track = tracks[_cur];
if (!(flags & PL_REPEAT)):
del(_cur);
return track;
unsigned int Queue :: size();
Return the number of tracks currently on the queue.
const std::string Queue :: size_str();
Return the number of tracks currently on the queue, in string
form.
const std::string Queue :: length_str();
Return the remaining length of the queue in a human-readable
format.
void Queue :: sort(sort_t field, bool reset);
If the field is already in the sort order, toggle its
ascending value. Otherwise, add a new sort field to the end
of the sort order with ascending set to true. If reset is set
to true, clear the sorting list before appending.
Track *Queue :: operator[](unsigned int i);
Return the track and index i.
void Queue :: track_selected(unsigned int queue_id);
Set _cur to queue_id. If PQ_REPEAT is not set, remove the
track from the queue.
Library:
The library is in charge of scanning and updating library paths added
to the tag database. In addition, the library layer is also in charge
of managing a library queue used by the UI. This queue has a special
file format, and will be saved to the file "library.q".
- Queue:
class LibraryQueue : public Queue {
public:
LibraryQueue();
save();
load();
set_flag(queue_flag);
unset_flag(queue_flag);
sort(sort_t, bool);
};
File << flags << _sort_order.size()
File << _sort_order[N].field << _sort_order[N].ascending << ...
- Validation:
Use a single idle function to loop over each track in the track
database. Check if the track still exists in the filesystem and remove
it from the tagdb if not.
- Updating:
Scan over all files in the current directory directory.
For each encountered directory:
Use the idle queue to call the update function with the new
directory as the "current" directory.
For each encountered file:
Attempt to add the file to the track_db.
Commit the database if at least one new file has been added.
- Testing:
The script tests/library/gen_library.sh will create a sample library
in the /tmp/ directory for testing purposes. All the track files are
complete silence, but the script will fake up tags for each file.
To test importing, create several mock library files and copy them to
~/.ocarina-test/library/ and attempt to read them in.
- LibraryQueue API:
LibraryQueue :: LibraryQueue();
Initialize a Queue with the flags Q_ENABLED and Q_REPEAT. The
default sorting order should be artist, year, track.
LibraryQueue :: save();
Write a library queue to disk.
LibraryQueue :: load();
Read a library queue from disk.
LibraryQueue :: set_flag(queue_flag f);
LibraryQueue :: unset_flag(queue_flag f);
LibraryQueue :: sort(sort_t field, bool reset);
These functions are wrappers around the default Queue
implementation. First call the original function, then use
save() to store the changes.
- API
void library :: init();
Scan the tagdb track list, and add each track to the library
queue.
Library *library :: add(string dir);
If dir is not a directory:
return NULL
Add a new path to the tag database, trigger an update, and
then return the corresponding Library tag to the caller.
void library :: remove(Library *library);
Invalidate a library_db row and all tracks owned by that path.
Do not use the library pointer after calling this function.
void library :: update(Library *library);
First, validate all tracks in the given library.
Next, trigger an update on the given library.
void library :: update_all();
Update all valid library paths.
void library :: set_enabled(Library *library, bool enabled);
Toggle if a library path is enabled or not. A disabled
library path will have its tracks removed from the
LibraryQueue.
void library :: get_queue();
Return the LibraryQueue to the caller.
Playlist:
Playlists are a new feature in Ocarina 6 and are modeled after Gmail
labels. Ocarina 6.1 will support two different playlists that the
user can add tracks to: banned and favorites.
The playlist layer will maintain a queue that is used by the UI to
display tracks in a given playlist. This queue is inherited from
the base Queue class to provide extra features.
Future releases will add support for more playlists.
- Index:
Index playlist_db("playlist.db", true);
- Queue:
class PlaylistQueue : public Queue {
public:
PlaylistQueue();
fill(IndexEntry *);
};
- Default playlists:
Favorites:
The user will add music they really like to this playlist.
Banned:
The user should add music they do not like to this playlist.
Tracks should be removed from the Library playqueue when they
are banned and added back to the playqueue when they are
un-banned.
- PlaylistQueue API:
PlaylistQueue :: PlaylistQueue();
Initialize a Queue with the flags Q_ENABLED, Q_REPEAT, and
Q_NO_SORT set. Default sorting order should be artist, year,
track.
PlaylistQueue :: fill(IndexEntry *ent);
Remove all tracks in the queue and repopulate using ent.
- API
void playlist :: init():
Load the playlist index from file.
void playlist :: add(Track *track, const std::string &name);
Add track->id to the playlist named "name" and return true.
Return false if the playlist does not exist.
If "name" is the currently selected playlist, add the track
to the PlaylistQueue.
void playlist :: del(Track *track, const std::string &name);
Remove track->id from the playlist named "name" and return true.
Return false if the playlist does not exist or if the track
is not in the playlist.
If "name" is the currently selected playlist, remove the track
from the PlaylistQueue.
bool playlist :: has(Track *track, const std::string &name);
Return true if the chosen playlist has the given track.
Return false otherwise.
void playlist :: select(const std::string &name);
Change the currently displayed playlist to "name".
const IndexEntry *playlist :: get_tracks(const std::string &name);
Return the IndexEntry represeting the requested playlist.
Return NULL if the requested playlist does not exist.
Queue *playlist :: get_queue();
Return the PlaylistQueue to the caller.
Deck: (lib/deck.cpp)
The playqueue deck is used to hold the temporary playqueues created by
the user.
This module also controls the library playqueue, which should be updated
using the on_library_track_add() and on_library_track_del() callback
functions. The library playqueue will always have PQ_ENABLED and
PQ_REPEAT set. This playlist will default to PQ_RANDOM unset.
- Deck:
list<Playqueue> deck;
Playqueue library_pq;
File << library_pq.random << deck.size() << endl;
File << deck[0] << endl;
File << deck[N] << endl;
- API
void deck :: init();
Set up callbacks used by the library.
void deck :: read(File &);
void deck :: write(File &);
Read or write the playqueue file. This will be called
from the audio layer to store state.
Playqueue *deck :: create();
Adds a new playqueue to the end of the deck and returns a
pointer to it.
void deck :: remove(N);
Remove playqueue N from the deck.
Playqueue *deck :: get(N);
Return playqueue N from the deck.
void deck :: move(M, N);
Move playqueue at index M to index N.
unsigned int deck :: next();
Iterate through the deck until you find a playqueue with the
flag PQ_ENABLED set. Call next() on this playqueue and return
the result.
If the playqueue is empty after calling next(), remove it from
the deck.
If there are no playqueues on the deck, play a song from the
library playqueue.
If there are no playable IDs, throw -EEXIST.
void deck :: reset();
This function only exists if CONFIG_TEST is enabled. Erase
all the playqueue information and reset the deck list.
void deck :: print_info();
This function only exists if CONFIG_TEST is enabled. Print
out helpful stats about the current state of the playqueue deck.
Playqueue *deck :: get_library_pq();
Return the library playqueue.
Audio: (lib/audio.cpp)
This file will introduce an "audio" namespace containing all of the
functions interacting with gstreamer. This will create a wrapper
namespace that will be easier to work with than using raw gstreamer
functions.
The audio layer is meant to be an interface used by the front end to
control most features of the backend library. This means implementing
next track, previous track, and so on here.
Gstreamer options passed to audio :: init() can be found by running
`gst-inspect-1.0 --help-gst` on the command line.
- Internal:
Set up a message bus to look for end-of-stream and error messages so
the next song can be played. This function should call the play
function after loading a track and after checking the "pause after N"
count.
The audio layer will also create an internal playqueue for tracking
recently played songs.
- State:
The audio layer will store the current trackid to disk, and then save
the playqueue deck.
File << current_track << endl
deck.write(File);
- API:
void audio :: init(argc, argv);
Initialize the gstreamer layer and reload the track that was
last loaded before shutdown. Gstreamer is supposed to remove
options from the argv array as they are processed, so pass
pointers to argc and argv to this function.
Read in the state file.
void audio :: quit();
Clean up memory allocated by gstreamer.
Write out the state file.
void audio :: play();
void audio :: pause();
Change the gstreamer state to either GST_STATE_PLAYING or
GST_STATE_PAUSED. Do nothing if there is not a track loaded
in the pipeline. Throw -EAUDIO if there is an error changing
state.
void audio :: seek_to(long);
Seek to a position X nanoseconds into the track. Throw -EAUDIO
if there is an error seeking to the requested position. Do
nothing if there is not a track loaded in the pipeline.
Seconds can be converted to nanoseconds by multiplying with
GST_SECOND.
void audio :: stop();
pause()
seek_to(0)
void audio :: next();
Call the deck :: next() function to get the next trackid,
and load that file into the gstreamer pipeline. Do not change
the state of the pipeline (if nothing is playing yet, don't
call play()). Throw -EEXIST if there is no track to load
into the pipeline.
When a track is loaded:
Is it already in the recently played playqueue?
If yes, remove it from the playqueue.
Add to the front of the recently played playqueue.
Reset the current pointer in the playqueue to 0.
Write out the state file.
void audio :: previous();
Call the playlist :: previous() function to iterate backwards
through the recently played playqueue. Load the returned trackid
without changing the pipeline state.
trackid audio :: current_trackid();
Return the trackid of the currently playing song. If no track
is loaded throw -EEXIST;
unsigned int audio :: position();
Return the number of seconds that the song has played.
unsigned int audio :: duration();
Return the duration of the current song in seconds.
void audio :: pause_after(bool enabled, unsigned int N);
Pause after N tracks. The first parameter is a bool
representing if this feature is enabled or not (true == enabled,
false == disabled).
The count will only be decremented when an end-of-stream message
is received by the gstreamer pipeline, and not when calling
next().
bool audio :: pause_enabled();
Return true if pausing is enabled, and false if pausing is
disabled.
unsigned int audio :: pause_count();
Return the number of tracks that will be played before
playback pauses.
Gui: (ocarina/*)
The GUI will be written in C++ using gtkmm3 for (hopefully) cleaner code.
- Design requirements:
- gtkmm3
- Front-end for the library to add, remove and modify paths
- This can be hidden since it's not a common task
- Double-click to play songs
- Front-end for groups to add and remove tracks
- This is a common task and should not be hidden
- Don't save / restore window size
- Some window managers already do this automatically
- Set reasonable defaults