design: Remove design directory
Keeping these documents updated with the top-level design.txt was annoying. Moving forward, I would like to keep a single document up to date without extra hassle. Signed-off-by: Anna Schumaker <schumaker.anna@gmail.com>
This commit is contained in:
parent
be13c7dd87
commit
2a1f695ea8
|
@ -1,109 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
import os
|
|
||||||
|
|
||||||
created_files = dict()
|
|
||||||
node_names = dict()
|
|
||||||
|
|
||||||
class DesignFile:
|
|
||||||
def read_files(self, file):
|
|
||||||
lastkey = ""
|
|
||||||
for line in file:
|
|
||||||
if line == "\n":
|
|
||||||
return
|
|
||||||
if line[0] == " " and line[1] == " ":
|
|
||||||
created_files[lastkey] += [line.strip()]
|
|
||||||
created_files[lastkey].sort()
|
|
||||||
else:
|
|
||||||
lastkey = line.strip()
|
|
||||||
created_files.setdefault(lastkey, [])
|
|
||||||
|
|
||||||
def read_depends(self, file):
|
|
||||||
for line in file:
|
|
||||||
if line == "\n":
|
|
||||||
return;
|
|
||||||
else:
|
|
||||||
self.depends += line.strip().split()
|
|
||||||
|
|
||||||
def __init__(self, file):
|
|
||||||
self.name = ""
|
|
||||||
self.lines = []
|
|
||||||
self.depends = []
|
|
||||||
self.depend_nodes = set()
|
|
||||||
|
|
||||||
if file == None:
|
|
||||||
return
|
|
||||||
self.name = file.rsplit(".", 1)[0]
|
|
||||||
f = open("design/%s" % file)
|
|
||||||
for line in f:
|
|
||||||
if line == "== Files ==\n":
|
|
||||||
self.read_files(f)
|
|
||||||
elif line == "== Depends ==\n":
|
|
||||||
self.read_depends(f)
|
|
||||||
else:
|
|
||||||
self.lines += [line]
|
|
||||||
node_names[self.name] = self
|
|
||||||
|
|
||||||
|
|
||||||
def pump_depends(nodes):
|
|
||||||
options = []
|
|
||||||
res = None
|
|
||||||
for n in nodes:
|
|
||||||
if len(n.depend_nodes) == 0:
|
|
||||||
options += [ n.name ]
|
|
||||||
options.sort()
|
|
||||||
res = node_names[ options[0] ]
|
|
||||||
nodes.remove(res)
|
|
||||||
for n in nodes:
|
|
||||||
if res in n.depend_nodes:
|
|
||||||
n.depend_nodes.remove(res)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_dependencies():
|
|
||||||
tmp = set()
|
|
||||||
res = []
|
|
||||||
|
|
||||||
for key, node in node_names.items():
|
|
||||||
tmp.add(node)
|
|
||||||
for depend in node.depends:
|
|
||||||
if depend == "*":
|
|
||||||
node.depend_nodes = set(node_names.values())
|
|
||||||
else:
|
|
||||||
node.depend_nodes.add(node_names[depend])
|
|
||||||
node.depend_nodes.discard(node_names[node.name])
|
|
||||||
|
|
||||||
while len(tmp) > 0:
|
|
||||||
res += [ pump_depends(tmp) ]
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def gen_files_list(parsed):
|
|
||||||
design = DesignFile(None)
|
|
||||||
design.lines += [ "Files:\n"]
|
|
||||||
keys = created_files.keys()
|
|
||||||
keys.sort()
|
|
||||||
for key in keys:
|
|
||||||
design.lines += [ " %s\n" % key ]
|
|
||||||
for val in created_files[key]:
|
|
||||||
design.lines += [ " %s\n" % val ]
|
|
||||||
parsed.insert(1, design)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_design(target, source, env):
|
|
||||||
files = os.listdir("design/")
|
|
||||||
files.sort()
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".txt"):
|
|
||||||
DesignFile(file)
|
|
||||||
node_list = resolve_dependencies()
|
|
||||||
gen_files_list(node_list)
|
|
||||||
|
|
||||||
f = open("design.txt", 'w')
|
|
||||||
for index, design in enumerate(node_list):
|
|
||||||
if index != 0:
|
|
||||||
f.write("\n\n\n")
|
|
||||||
for line in design.lines:
|
|
||||||
f.write(line)
|
|
||||||
|
|
||||||
design = Command("design.txt", None, merge_design)
|
|
||||||
Alias("design", design)
|
|
116
design/audio.txt
116
design/audio.txt
|
@ -1,116 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
audio.h
|
|
||||||
ocarina/lib/
|
|
||||||
audio.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
deck library
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,28 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
callback.h
|
|
||||||
ocarina/lib/
|
|
||||||
callback.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
version print
|
|
||||||
|
|
||||||
Callbacks: (lib/callback.cpp)
|
|
||||||
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;
|
|
|
@ -1,169 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
database.h
|
|
||||||
database.hpp
|
|
||||||
ocarina/lib/
|
|
||||||
database.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
file
|
|
||||||
|
|
||||||
Database: (lib/database.cpp)
|
|
||||||
Ocarina 5.x created a different save file format for each type of
|
|
||||||
data that needed to be stored (preferences, library paths, playlists).
|
|
||||||
I intend to unify everything into a generic file format that can be
|
|
||||||
accessed through a generic database interface. The database code will
|
|
||||||
be in charge of printing the "valid" bit for each DatabaseEntry so that
|
|
||||||
child classes do not need to call into the parent class. If valid ==
|
|
||||||
true, the DatabaseEntry will be streamed out followed by a newline. If
|
|
||||||
valid == false the database will print the next entry in the vector.
|
|
||||||
|
|
||||||
Modules should inherit from the DatabasEntry class and implement their
|
|
||||||
own read() and write() functions. The "valid" field will be stored
|
|
||||||
before these functions are called, and the entry will be skipped if
|
|
||||||
valid is set to false.
|
|
||||||
|
|
||||||
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 can be 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.
|
|
||||||
|
|
||||||
- DatabaseEntry:
|
|
||||||
class DatabaseEntry { /* let database modify valid flag */
|
|
||||||
public:
|
|
||||||
const std::string primary_key;
|
|
||||||
bool valid;
|
|
||||||
|
|
||||||
DatabaseEntry(const std::string &);
|
|
||||||
virtual void write(File &) = 0;
|
|
||||||
virtual void read(File &) = 0;
|
|
||||||
virtual void print() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
File << <CHILD_CLASS_DATA>
|
|
||||||
|
|
||||||
- IndexEntry:
|
|
||||||
class IndexEntry : public DatabaseEntry {
|
|
||||||
public:
|
|
||||||
set<unsigned int> values;
|
|
||||||
|
|
||||||
void write(File &);
|
|
||||||
void read(File &);
|
|
||||||
void print();
|
|
||||||
void insert(unsigned int);
|
|
||||||
void remove(unsigned int);
|
|
||||||
};
|
|
||||||
|
|
||||||
File << key << endl;
|
|
||||||
File << values.size() << values[0] << .. << values[N] << endl;
|
|
||||||
|
|
||||||
- Database:
|
|
||||||
template <class T>
|
|
||||||
class Database {
|
|
||||||
private:
|
|
||||||
vector<T> db;
|
|
||||||
map<std::string, unsigned int> keys;
|
|
||||||
unsigned int _size; /* Number of valid rows */
|
|
||||||
File file;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Database::Database(filename, flags);
|
|
||||||
void load();
|
|
||||||
void save();
|
|
||||||
void clear();
|
|
||||||
void print();
|
|
||||||
void print_keys();
|
|
||||||
|
|
||||||
unsigned int insert(T);
|
|
||||||
void remove(unsigned int);
|
|
||||||
unsigned int size();
|
|
||||||
unsigned int num_rows();
|
|
||||||
|
|
||||||
unsigned int first();
|
|
||||||
unsigned int last();
|
|
||||||
unsigned int next(unsigned int &);
|
|
||||||
bool has_key(const std :: string &);
|
|
||||||
unsigned int find_index(const std::string &);
|
|
||||||
T &find(const std::string &);
|
|
||||||
T &operator[](unsigned int);
|
|
||||||
};
|
|
||||||
|
|
||||||
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(filename, flags);
|
|
||||||
Initializes database to use ~/.ocarina{-debug}/filename.
|
|
||||||
Set up flags.
|
|
||||||
|
|
||||||
void Database :: load();
|
|
||||||
Reads a saved database from disk.
|
|
||||||
|
|
||||||
void Database :: save();
|
|
||||||
Saves the database to disk.
|
|
||||||
|
|
||||||
void Database :: clear();
|
|
||||||
This function exists only if CONFIG_TEST is enabled.
|
|
||||||
Clear the database contents in-memory, but do NOT write
|
|
||||||
to disk.
|
|
||||||
|
|
||||||
void Database :: print();
|
|
||||||
This function exists only If CONFIG_TEST is enabled.
|
|
||||||
Following a similar format for writing to disk, print the
|
|
||||||
database to the console in a human-readable format.
|
|
||||||
|
|
||||||
void Database :: print_keys();
|
|
||||||
This function exists only if CONFIG_TEST is enabled.
|
|
||||||
Print out the collected primary keys in the database.
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
unsigned int Database :: insert(T &);
|
|
||||||
Adds a new item to the db and marks it as valid. A reverse
|
|
||||||
mapping is also created into the keys map to map the primary
|
|
||||||
key back to the id of the newly added item.
|
|
||||||
|
|
||||||
void Database :: remove(unsigned int index);
|
|
||||||
Mark db[index] as invalid (quick deletion).
|
|
||||||
|
|
||||||
unsigned int Database :: size();
|
|
||||||
Returns number of valid rows in the database.
|
|
||||||
|
|
||||||
unsigned int Database :: num_rows();
|
|
||||||
Return db.size().
|
|
||||||
|
|
||||||
unsigned int Database :: first();
|
|
||||||
Return the id to the first valid row. Return db.size() if
|
|
||||||
there are no valid rows.
|
|
||||||
|
|
||||||
unsigned int Database :: last();
|
|
||||||
Return the id of the last valid row. Return db.size() if
|
|
||||||
there are no valid rows.
|
|
||||||
|
|
||||||
unsigned int Database :: next(unsigned int &id);
|
|
||||||
Return the id of the next valid row or return db.size() if
|
|
||||||
there are no valid rows.
|
|
||||||
|
|
||||||
bool Database :: has_key(const std::string &key);
|
|
||||||
Return true if an item with primary key "key" exists in the
|
|
||||||
database, and false otherwise.
|
|
||||||
|
|
||||||
unsigned int Database :: find_index(const std::string key);
|
|
||||||
If the key exists in the database, return the index of the
|
|
||||||
database item with that key. Throw -EEXIST if the key is not
|
|
||||||
in the database.
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
T &Database :: find(const std::string &key);
|
|
||||||
Search for primary key "key" in the database. The reverse
|
|
||||||
mapping should be used to make this operation faster.
|
|
||||||
Throw -EEXIST if the key is not found in the mapping.
|
|
||||||
Throw -EINVAL if the key points to an invalid entry.
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
T &Database :: operator[](unsigned int index);
|
|
||||||
Return a reference to db[index].
|
|
||||||
Throw -EEXIST if index is out of range.
|
|
||||||
Throw -EINVAL if index is invalid.
|
|
|
@ -1,73 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
deck.h
|
|
||||||
ocarina/lib/
|
|
||||||
deck.cpp
|
|
||||||
$HOME/.ocarina{-debug}/
|
|
||||||
playqueue.lst
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
playqueue
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,19 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
error.h
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
install
|
|
||||||
|
|
||||||
Errors: (include/error.h
|
|
||||||
This file contains an enum defining error codes used throughout the
|
|
||||||
codebase.
|
|
||||||
|
|
||||||
Error Codes:
|
|
||||||
enum error_t {
|
|
||||||
EAUDIO = 1, /* Audio error */
|
|
||||||
EEXIST, /* Requested object does not exist */
|
|
||||||
EINVAL, /* Invalid operation requested */
|
|
||||||
EIO, /* I/O error */
|
|
||||||
ENOTRACK,
|
|
||||||
};
|
|
118
design/file.txt
118
design/file.txt
|
@ -1,118 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
file.h
|
|
||||||
ocarina/lib/
|
|
||||||
file.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
version print
|
|
||||||
|
|
||||||
On-disk files: (lib/file.cpp)
|
|
||||||
Data will be stored in the user's home directory according to the
|
|
||||||
XDG / freedesktop.org specification. This means storing data in
|
|
||||||
$XDG_DATA_HOME/ocarina{-debug|-test}/ and storing configuration in
|
|
||||||
$XDG_CONFIG_HOME/ocarina{-debug|-test}/. In addition, I will support
|
|
||||||
importing data from Ocarina 5.10 for conversion to the new format.
|
|
||||||
|
|
||||||
In theory my file format will not change often, so it should be
|
|
||||||
possible to use multiple Ocarina versions with the same data. However,
|
|
||||||
should the format need to change I will only support forward
|
|
||||||
compatibility. This means that downgrades will not be possible after
|
|
||||||
a file format change. To keep the code even cleaner, I will only
|
|
||||||
support updating from the previous file format version. This means
|
|
||||||
that legacy support will be removed after the first file format change.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
- Hint where the file is located:
|
|
||||||
enum FileLocHint {
|
|
||||||
FILE_TYPE_CONFIG,
|
|
||||||
FILE_TYPE_DATA,
|
|
||||||
FILE_TYPE_LEGACY,
|
|
||||||
FILE_TYPE_INVALID,
|
|
||||||
}
|
|
||||||
|
|
||||||
- Open mode:
|
|
||||||
enum OpenMode {
|
|
||||||
OPEN_READ,
|
|
||||||
OPEN_WRITE,
|
|
||||||
NOT_OPEN,
|
|
||||||
}
|
|
||||||
|
|
||||||
- File:
|
|
||||||
class File : std::fstream {
|
|
||||||
private:
|
|
||||||
unsigned int version;
|
|
||||||
OpenMode mode;
|
|
||||||
FileLocHint hint;
|
|
||||||
string filepath;
|
|
||||||
|
|
||||||
public:
|
|
||||||
File(string, FileLocHint);
|
|
||||||
~File();
|
|
||||||
const char *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(string filepath, FileLocHint hint);
|
|
||||||
Resolve filepath to one of:
|
|
||||||
XDG_{CONFIG|DATA}_HOME/ocarina/filepath
|
|
||||||
XDG_{CONFIG|DATA}_HOME/ocarina-debug/filepath
|
|
||||||
XDG_{CONFIG|DATA}_HOME/ocarina-test/filepath
|
|
||||||
$HOME/.ocarina/
|
|
||||||
$HOME/.ocarina-debug/
|
|
||||||
$HOME/.ocarina-test/
|
|
||||||
|
|
||||||
If filepath is an empty string, set the file hint to
|
|
||||||
FILE_TYPE_INVALID and do not set the filepath field.
|
|
||||||
|
|
||||||
File :: ~File();
|
|
||||||
Close the file stream if it is open.
|
|
||||||
|
|
||||||
const char *File :: get_filepath();
|
|
||||||
Return the full filepath to the file.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
void File :: open(OpenMode mode);
|
|
||||||
When opening a file for reading (mode == OPEN_READ),
|
|
||||||
- Throw -EEXIST 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),
|
|
||||||
- Throw -ELEGACY if the file has FILE_TYPE_LEGACY set
|
|
||||||
- Create missing directories as needed
|
|
||||||
- Write version information to the start of the file
|
|
||||||
|
|
||||||
Throw -EINVAL if hint == FILE_TYPE_INVALID.
|
|
||||||
Throw -EOPEN if the file is already open.
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,57 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
filter.h
|
|
||||||
ocarina/lib/
|
|
||||||
filter.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
database
|
|
||||||
|
|
||||||
Filter: (lib/filter.cpp)
|
|
||||||
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.
|
|
||||||
|
|
||||||
- Index:
|
|
||||||
Database<database :: IndexEntry> filter_index;
|
|
||||||
map<string, string> lowercase_cache;
|
|
||||||
unsigned int lowercase_cache_hits;
|
|
||||||
|
|
||||||
- Parsing:
|
|
||||||
1) Convert the provided string into a list of words, using whitespace
|
|
||||||
and the following characters as delimiters: \/,;()_-~+"
|
|
||||||
|
|
||||||
For each word:
|
|
||||||
2) Check the lowercase_cache to see if we have seen the word before,
|
|
||||||
a) If we have, return the stored string
|
|
||||||
b) Convert the string to lowercase and strip out remaining
|
|
||||||
special characters. Add the result to the lowercase_cache;
|
|
||||||
|
|
||||||
- API:
|
|
||||||
void filter :: add(string, track_id);
|
|
||||||
Parse the string into substrings following the "Parsing"
|
|
||||||
section (above). Add each (substring, track_id) pair to the
|
|
||||||
filter_index.
|
|
||||||
|
|
||||||
To generate substrings, iterate over the word starting from
|
|
||||||
the front. For example: "goron" would contain the substrings
|
|
||||||
{g, go, gor, goro, goron}.
|
|
||||||
|
|
||||||
void filter :: search(string, set<track_id> &);
|
|
||||||
Parse the string into substrings following the "Parsing"
|
|
||||||
section (above). We want to find track_ids that match ALL
|
|
||||||
substrings, so take the intersection of all sets returned by
|
|
||||||
the filter_index for a given substring.
|
|
||||||
|
|
||||||
std::string filter :: to_lowercase(const std::string &string);
|
|
||||||
Split the string into words following step 1 of "Parsing"
|
|
||||||
(above). Assemble and return a result string using the lower
|
|
||||||
case cache to convert each term to lowercase.
|
|
||||||
|
|
||||||
void filter :: print_cache_stats();
|
|
||||||
Print cache hit and size information.
|
|
||||||
|
|
||||||
void filter :: get_index();
|
|
||||||
This function only exists if CONFIG_TEST is enabled.
|
|
||||||
Return the index storing all the filter data.
|
|
|
@ -1,121 +0,0 @@
|
||||||
== Depends ==
|
|
||||||
*
|
|
||||||
|
|
||||||
Future work:
|
|
||||||
I want to set reasonable expectations for Ocarina 6 so that I don't
|
|
||||||
have to spend a large amount of time coding before releasing something
|
|
||||||
to the wild. This section will be a list of features that I want, but
|
|
||||||
should be deferred to a future release so basic support can be coded.
|
|
||||||
|
|
||||||
Hint: If feature B depends on A, implement A in 6.x and B in 6.x+1
|
|
||||||
|
|
||||||
- New default groups:
|
|
||||||
Unplayed tracks
|
|
||||||
|
|
||||||
- Categories:
|
|
||||||
Use these to make "groups of groups" for better organization.
|
|
||||||
Different categories can include Album, Artist and Genere
|
|
||||||
dynamic groups in addition to user created groups (see below)
|
|
||||||
|
|
||||||
The Artist, Album and Genre "tables" can be used to populate
|
|
||||||
these categories.
|
|
||||||
|
|
||||||
- User created song groups:
|
|
||||||
Basic add and remove features can be implemented using the
|
|
||||||
Library and Banned Songs groups. This will give me a chance
|
|
||||||
to test saving groups on a small scale before letting users
|
|
||||||
create anything they want.
|
|
||||||
|
|
||||||
- Save a user's playlist as a group:
|
|
||||||
|
|
||||||
- Library defragment:
|
|
||||||
Ocarina 6.0 will leave holes in the library when tracks are
|
|
||||||
deleted, potentially leading to fragmentation and larger-than-
|
|
||||||
needed file sizes. A "defragment" utility can be created to
|
|
||||||
clean up unused slots.
|
|
||||||
|
|
||||||
To help with fixing groups, a mapping of (old values) ->
|
|
||||||
(new values) should be kept.
|
|
||||||
|
|
||||||
- Fix track durations:
|
|
||||||
Some tracks in my library are tagged with the wrong duration,
|
|
||||||
so fix them as they are played.
|
|
||||||
|
|
||||||
- Track tag editor:
|
|
||||||
Make a pop-up window for editing the tags of a track. Be sure
|
|
||||||
to update the library information and the on-disk file.
|
|
||||||
|
|
||||||
- Album art:
|
|
||||||
(easy) Start with album art fetching script
|
|
||||||
(hard) Build in to Ocarina
|
|
||||||
|
|
||||||
- Playlist custom sorting:
|
|
||||||
Click column headers to choos sort order
|
|
||||||
Keep a list of fields that the user has selected and place new
|
|
||||||
fields in the front of this list. Use a recursive stable sort
|
|
||||||
to do the sorting.
|
|
||||||
|
|
||||||
- Better design file format:
|
|
||||||
Todo list in each document instead of all at once in the footer.
|
|
||||||
Leave the Todo list out of the official "design document" and
|
|
||||||
keep it in each individual section instead.
|
|
||||||
XML?
|
|
||||||
Code formatting?
|
|
||||||
Checkbox for when features are done? (reference commit)
|
|
||||||
|
|
||||||
- Copy a song group to a different directory:
|
|
||||||
This can be useful for keeping an external device (like an
|
|
||||||
Android phone) updated with the music you want to listen to.
|
|
||||||
Complications: I have an mp3 mirror of all my music, and I
|
|
||||||
want the mp3s to be synced. Perhaps track mirrors in Ocarina?
|
|
||||||
|
|
||||||
- Mirror directory:
|
|
||||||
I rip music to .flac, but keep an mp3 mirror directory to sync
|
|
||||||
to other computers and phones. An Ocarina tool to manage a
|
|
||||||
COMPLETE library mirror might be a good idea so I no longer
|
|
||||||
need to manage it externally. This can still be done with a
|
|
||||||
script, a cron job, and maybe a "mirror this track" option in
|
|
||||||
the library? Perhaps create a mirror group?
|
|
||||||
|
|
||||||
- Extra testing ideas:
|
|
||||||
- Run tests through valgrind to help find memory leaks
|
|
||||||
- Some way to enable / disable tests during development
|
|
||||||
- Run tests based on dependencies
|
|
||||||
- Fix tests that will only work on my computer
|
|
||||||
- Double check that all inputs and outputs are tested
|
|
||||||
- Simple testing library?
|
|
||||||
- Stop tests on failure
|
|
||||||
- Test callbacks
|
|
||||||
|
|
||||||
- Preferences:
|
|
||||||
- Set default sort
|
|
||||||
- Save window size
|
|
||||||
- Set album art size
|
|
||||||
|
|
||||||
- Rip out Ocarina 5.x importing and legacy file support
|
|
||||||
|
|
||||||
- AirPlay / remote audio support
|
|
||||||
|
|
||||||
- Media keys
|
|
||||||
|
|
||||||
- Replaygain support
|
|
||||||
External script to calculate values?
|
|
||||||
Calculate value after first playback?
|
|
||||||
Store in library :: Track structure
|
|
||||||
|
|
||||||
- Autosave databases
|
|
||||||
Artist, album, genere and library path don't change often so
|
|
||||||
saving on every add / remove won't be a huge performance hit
|
|
||||||
and may actually be more efficient in the long run.
|
|
||||||
|
|
||||||
- Scons improvements:
|
|
||||||
Why does everything compile with the same CONFIG_* parameters?
|
|
||||||
Cleanups to tests/Sconscript
|
|
||||||
Global dependency resolution (rather than having multiple ways
|
|
||||||
in lib/ design/ and tests/)
|
|
||||||
|
|
||||||
- Playqueue and database inherit from common class
|
|
||||||
- "About" dialog
|
|
||||||
- Investigate "Bulk insert" callbacks for performance
|
|
||||||
- Initialize PQs with multiple flags
|
|
||||||
- Prefill Song structures in each library :: Track
|
|
|
@ -1,21 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/gui/
|
|
||||||
ocarina6.glade
|
|
||||||
*
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
idle file audio library
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,17 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/
|
|
||||||
design.txt
|
|
||||||
|
|
||||||
===============================================================================
|
|
||||||
= =
|
|
||||||
= Ocarina 6.0 =
|
|
||||||
= =
|
|
||||||
===============================================================================
|
|
||||||
|
|
||||||
Ocarina 6.0 is the 6th (re)writing of the Ocarina music player - a lightweight,
|
|
||||||
GTK+ based music player. Improvements over the 5.x series will include the
|
|
||||||
existence of both a design document and unit tests, two items that will help
|
|
||||||
the main developer stay focused and make maintenance easier.
|
|
||||||
|
|
||||||
Ocarina 6.0 will use Gstreamer 1.0 for audio playback and GTK-MM 3 for user
|
|
||||||
interface development.
|
|
|
@ -1,73 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
idle.h
|
|
||||||
idle.hpp
|
|
||||||
ocarina/lib/
|
|
||||||
idle.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
version print
|
|
||||||
|
|
||||||
Idle queue: (lib/idle.cpp)
|
|
||||||
The idle queue is used to schedule tasks to run at a later time. Idle
|
|
||||||
tasks must inherit from the IdleBase class so that multiple templated
|
|
||||||
IdleTask pointers can be placed on the same idle queue.
|
|
||||||
|
|
||||||
Creating an idle queue in idle.hpp will create a new queue for every
|
|
||||||
file that idle.h is included in. I want to have a single, shared
|
|
||||||
idle queue used by the entire application so to get around this the
|
|
||||||
IdleBase class is used and implemented in lib/idle.cpp.
|
|
||||||
|
|
||||||
- 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).
|
|
|
@ -1,12 +0,0 @@
|
||||||
== Files ==
|
|
||||||
/usr/bin/
|
|
||||||
ocarina
|
|
||||||
/usr/lib/ocarina/
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
header
|
|
||||||
|
|
||||||
Install:
|
|
||||||
Ocarina will be compiled into a single executable placed under
|
|
||||||
/usr/bin/. Any extra files needed to run will be placed under
|
|
||||||
/usr/lib/ocarina/.
|
|
|
@ -1,205 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
library.h
|
|
||||||
ocarina/lib/
|
|
||||||
library.cpp
|
|
||||||
$HOME/.ocarina{-debug}/
|
|
||||||
album.db
|
|
||||||
artist.db
|
|
||||||
genre.db
|
|
||||||
library.db
|
|
||||||
track.db
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
idle database playlist filter
|
|
||||||
|
|
||||||
Library: (lib/library.cpp)
|
|
||||||
The library manages databases containing track information added by the
|
|
||||||
user. Ocarina 6 splits the library into multiple database tables for
|
|
||||||
storing content. The library will exist in a library namespace to
|
|
||||||
to make functions and classes more unique.
|
|
||||||
|
|
||||||
- Databases:
|
|
||||||
enum DB_Type {
|
|
||||||
DB_ALBUM,
|
|
||||||
DB_ARTIST,
|
|
||||||
DB_GENRE,
|
|
||||||
DB_LIBRARY,
|
|
||||||
DB_TRACK,
|
|
||||||
};
|
|
||||||
|
|
||||||
- Album:
|
|
||||||
class library :: Album : public DatabaseEntry {
|
|
||||||
public:
|
|
||||||
/* primary_key = "$name.$year.$artist_id" */
|
|
||||||
string name;
|
|
||||||
string name_lower;
|
|
||||||
unsigned int year;
|
|
||||||
unsigned int artist_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
File << artist_id << year << name
|
|
||||||
|
|
||||||
- Artist and Genre:
|
|
||||||
class library :: AGInfo : public DatabaseEntry {
|
|
||||||
public:
|
|
||||||
string name; /* primary key */
|
|
||||||
string name_lower;
|
|
||||||
};
|
|
||||||
|
|
||||||
File << name
|
|
||||||
|
|
||||||
- Library:
|
|
||||||
class library :: Library : public DatabaseEntry {
|
|
||||||
public:
|
|
||||||
string root_path; /* primary_key */
|
|
||||||
unsigned int count;
|
|
||||||
bool enabled;
|
|
||||||
};
|
|
||||||
|
|
||||||
File << enabled << root_path
|
|
||||||
|
|
||||||
- Track:
|
|
||||||
The primary key for a track is the full filepath (library.root_path +
|
|
||||||
track.filepath)
|
|
||||||
|
|
||||||
class library :: Track : public DatabaseEntry {
|
|
||||||
public:
|
|
||||||
/* primary_key = library.root_path + "/" + filepath */
|
|
||||||
unsigned int library_id;
|
|
||||||
unsigned int artist_id;
|
|
||||||
unsigned int album_id;
|
|
||||||
unsigned int genre_id;
|
|
||||||
|
|
||||||
unsigned int track;
|
|
||||||
unsigned int last_year;
|
|
||||||
unsigned int last_month;
|
|
||||||
unsigned int last_day;
|
|
||||||
unsigned int play_count;
|
|
||||||
unsigned int length;
|
|
||||||
|
|
||||||
string length_str;
|
|
||||||
string title;
|
|
||||||
string title_lower;
|
|
||||||
string filepath;
|
|
||||||
};
|
|
||||||
|
|
||||||
File << library_id << artist_id << album_id << genre_id;
|
|
||||||
File << track << last_year << last_month << last_day << play_count;
|
|
||||||
File << length << length_str << endl
|
|
||||||
File << title << endl;
|
|
||||||
File << filepath << endl;
|
|
||||||
|
|
||||||
- Song:
|
|
||||||
struct Song {
|
|
||||||
library :: Album *album;
|
|
||||||
library :: AGInfo *artist;
|
|
||||||
library :: AGInfo *genre;
|
|
||||||
library :: Library *library;
|
|
||||||
library :: Track *track;
|
|
||||||
};
|
|
||||||
|
|
||||||
- Databases:
|
|
||||||
Database<library :: Album> album_db(album.db);
|
|
||||||
Database<library :: AGInfo> artist_db(artist.db);
|
|
||||||
Database<library :: AGInfo> genre_db(genre.db);
|
|
||||||
Database<library :: Library> library_db(library.db);
|
|
||||||
Database<library :: Track> track_db(track.db);
|
|
||||||
|
|
||||||
- Updating algorithm:
|
|
||||||
1) Use a single IdleTask to loop over each track in the library, check
|
|
||||||
if the track still exists in the filesystem and remove it from
|
|
||||||
library_db if not.
|
|
||||||
2) For each directory in the scan directory, create an IdleTask to
|
|
||||||
scan the next level of directories.
|
|
||||||
3) For each file in the scan directory, check if the file already
|
|
||||||
exists in the track_db and add it to the database if not. Save
|
|
||||||
each database after adding files.
|
|
||||||
|
|
||||||
The taglib library should be used for finding artist, album, etc. tags
|
|
||||||
for each track.
|
|
||||||
|
|
||||||
- Importing
|
|
||||||
Ocarina 5.11 stores library files in ~/.ocarina/library/. Importing
|
|
||||||
involves reading each file and adding them to the database. If the file
|
|
||||||
describes a path already in the database then DO NOT overwrite the
|
|
||||||
current path and instead move on to the next file. If version != 2 then
|
|
||||||
move on to the next file.
|
|
||||||
|
|
||||||
File format:
|
|
||||||
File << version << endl; /* version == 2 */
|
|
||||||
File << path << endl;
|
|
||||||
File << id << enabled << next_track_id << size << endl;
|
|
||||||
File << <track list>
|
|
||||||
|
|
||||||
Track format:
|
|
||||||
File << filepath << endl;
|
|
||||||
File << title << endl;
|
|
||||||
File << artist << endl;
|
|
||||||
File << album << endl;
|
|
||||||
File << comment << endl;
|
|
||||||
File << genre << endl;
|
|
||||||
File << lenstr << endl;
|
|
||||||
File << id << year << track << count;
|
|
||||||
File << last_day << last_month << last_year;
|
|
||||||
File << length << bitrate << sample << channels << banned << endl;
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
- API
|
|
||||||
void library :: init();
|
|
||||||
Initialize databases and read files from disk. While reading
|
|
||||||
the library:
|
|
||||||
- Update the count of tracks in each library path
|
|
||||||
- Find the lowercase text of artist, album, genre, track
|
|
||||||
|
|
||||||
void library :: add_path(string dir);
|
|
||||||
If dir is not a directory:
|
|
||||||
throw -EINVAL
|
|
||||||
|
|
||||||
Trigger the on_library_add() callback on success.
|
|
||||||
|
|
||||||
void library :: del_path(unsigned int lib_id);
|
|
||||||
Invalidate a library_db row and all tracks owned by that path
|
|
||||||
if lib_id is not valid, throw -EEXIST.
|
|
||||||
|
|
||||||
void library :: update_path(lib_id);
|
|
||||||
Update the given library_db row.
|
|
||||||
If lib_id is not valid, throw -EEXIST.
|
|
||||||
|
|
||||||
Trigger the on_library_update() callback.
|
|
||||||
|
|
||||||
void library :: update_all();
|
|
||||||
Update all library paths.
|
|
||||||
Trigger the on_library_update() callback for each path.
|
|
||||||
|
|
||||||
void library :: set_enabled(unsigned int id, bool enabled);
|
|
||||||
Either enable or disable a library path. Trigger
|
|
||||||
on_library_track_del() for each track disabled this way.
|
|
||||||
|
|
||||||
struct Song library :: lookup(track_id);
|
|
||||||
Fill out a Song structure for the provided track_id.
|
|
||||||
Throw -EEXIST if there is no track mapping to track_id.
|
|
||||||
|
|
||||||
struct library :: Library *library :: lookup_path(unsigned int id);
|
|
||||||
Return the library path with index id.
|
|
||||||
Throw -EEXIST if there is no such path.
|
|
||||||
|
|
||||||
void library :: import();
|
|
||||||
Call this function to import an Ocarina 5.11 style library,
|
|
||||||
following the "Importing" section above.
|
|
||||||
|
|
||||||
#ifdef CONFIG_TEST
|
|
||||||
void library :: print_db(DB_Type);
|
|
||||||
Print the database corresponding to DB_Type
|
|
||||||
|
|
||||||
void library :: reset();
|
|
||||||
Clear all databases, returning the library to an empty state.
|
|
||||||
endif /* CONFIG_TEST */
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
playlist.h
|
|
||||||
ocarina/lib/
|
|
||||||
playlist.cpp
|
|
||||||
$HOME/.ocarina{-debug}/
|
|
||||||
playlist.db
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
database
|
|
||||||
|
|
||||||
Playlists: (lib/playlist.cpp)
|
|
||||||
Playlists are a new feature in Ocarina 6 and are modeled after Gmail
|
|
||||||
labels. Ocarina 6.0 will support two different playlists that the
|
|
||||||
user can add tracks to: banned and favorites.
|
|
||||||
|
|
||||||
Future releases will add support for more playlists.
|
|
||||||
|
|
||||||
- Database:
|
|
||||||
Database<database :: IndexEntry> playlist_db
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
- API
|
|
||||||
void playlist :: init():
|
|
||||||
Load the playlist database.
|
|
||||||
|
|
||||||
void playlist :: add(name, track_id);
|
|
||||||
Add the track_id to the playlist named "name". Save the
|
|
||||||
database to disk.
|
|
||||||
Throw -EEXIST if "name" does not exist.
|
|
||||||
|
|
||||||
void playlist :: del(name, track_id);
|
|
||||||
Remove the track_id from the playlist named "name". Save the
|
|
||||||
database to disk. Attempting to remove a track_id that does
|
|
||||||
not exist is not an error.
|
|
||||||
Throw -EEXIST if "name" does not exist.
|
|
||||||
|
|
||||||
const std::set<unsigned int> &playlist :: get_tracks(name);
|
|
||||||
Return the set of tracks representing the requested group.
|
|
||||||
Throw -EEXIST if "name" does not exist.
|
|
||||||
|
|
||||||
void playlist :: clear();
|
|
||||||
This function only exists if CONFIG_TEST is enabled. Clear
|
|
||||||
(reset) the playlist database.
|
|
|
@ -1,140 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
playqueue.h
|
|
||||||
ocarina/lib/
|
|
||||||
playqueue.cpp
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
file
|
|
||||||
|
|
||||||
Playqueue: (lib/playqueue.cpp)
|
|
||||||
Playqueues are a list of songs that the user has requested to play.
|
|
||||||
|
|
||||||
- Flags:
|
|
||||||
enum playqueue_flags {
|
|
||||||
PQ_ENABLED (1 << 0),
|
|
||||||
PQ_RANDOM (1 << 1),
|
|
||||||
PQ_REPEAT (1 << 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
- Sort order:
|
|
||||||
enum sort_t {
|
|
||||||
SORT_ARTIST = 1,
|
|
||||||
SORT_ALBUM = 2,
|
|
||||||
SORT_COUNT = 3,
|
|
||||||
SORT_GENRE = 4,
|
|
||||||
SORT_LENGTH = 5,
|
|
||||||
SORT_PLAYED = 6,
|
|
||||||
SORT_TITLE = 7,
|
|
||||||
SORT_TRACK = 8,
|
|
||||||
SORT_YEAR = 9,
|
|
||||||
};
|
|
||||||
|
|
||||||
- Playqueue:
|
|
||||||
class Playqueue {
|
|
||||||
private:
|
|
||||||
vector<track_id> tracks;
|
|
||||||
list<sort_t> sort_order; /* default = { SORT_ARTIST,
|
|
||||||
SORT_YEAR,
|
|
||||||
SORT_TRACK };
|
|
||||||
unsigned int cur;
|
|
||||||
unsigned int flags;
|
|
||||||
unsigned int length;
|
|
||||||
public:
|
|
||||||
Playqueue(flags);
|
|
||||||
void write(File &);
|
|
||||||
void read(File &);
|
|
||||||
|
|
||||||
void set_flag(playqueue_flags);
|
|
||||||
void unset_flag(playqueue_flags);
|
|
||||||
const unsigned int get_flags();
|
|
||||||
string get_length();
|
|
||||||
|
|
||||||
unsigned int add(track_id);
|
|
||||||
unsigned int add_front(track_id);
|
|
||||||
void del(playqueue_id);
|
|
||||||
unsigned int size();
|
|
||||||
|
|
||||||
void add_sort(sort_t);
|
|
||||||
void reset_sort(sort_t);
|
|
||||||
|
|
||||||
unsigned int next();
|
|
||||||
void reset_cur();
|
|
||||||
}
|
|
||||||
|
|
||||||
File << flags << sort_order.size() << sort_order[0] << ... << sort_order[1];
|
|
||||||
File << tracks.size() << tracks[0] << tracks[1] << ... << tracks[N];
|
|
||||||
|
|
||||||
- API
|
|
||||||
Playqueue :: Playlist(unsigned int flags);
|
|
||||||
Create a new playqueue with the appropriate flags set.
|
|
||||||
sort_order = { (SORT_ARTIST, true), (SORT_YEAR, true),
|
|
||||||
(SORT_TRACK, true) };
|
|
||||||
|
|
||||||
unsigned int Playqueue :: add(unsigned int track_id);
|
|
||||||
unsigned int Playqueue :: add_front(unsigned int track_id);
|
|
||||||
Add a new track to the tracks vector and return the index. If
|
|
||||||
add_front is called, the track will be added to the front of
|
|
||||||
the playqueue (index = 0).
|
|
||||||
length += track.length.
|
|
||||||
|
|
||||||
void Playqueue :: del(unsigned int playqueue_id);
|
|
||||||
Erase tracks[playqueue_id] from the tracks vector.
|
|
||||||
length -= track.length.
|
|
||||||
|
|
||||||
void Playqueue :: del_track(unsigned int track_id);
|
|
||||||
Erase all tracks with track id track_id.
|
|
||||||
|
|
||||||
void Playqueue :: set_flag(playqueue_flags flag);
|
|
||||||
void Playqueue :: unset_flag(playqueue_flags flag);
|
|
||||||
Set or unset the given flag.
|
|
||||||
|
|
||||||
const unsigned int Playqueue :: get_flags();
|
|
||||||
Return the currently enabled flags.
|
|
||||||
|
|
||||||
song Playqueue :: get_length();
|
|
||||||
Convert the length variable into a string and return the result
|
|
||||||
to the caller.
|
|
||||||
|
|
||||||
unsigned int Playqueue :: size();
|
|
||||||
Return tracks.size();
|
|
||||||
|
|
||||||
void Playqueue :: write(File &);
|
|
||||||
void Playqueue :: read(File &);
|
|
||||||
Read or write the playqueue to the file.
|
|
||||||
|
|
||||||
void Playqueue :: reset_sort();
|
|
||||||
Reset the sort_order list to empty.
|
|
||||||
|
|
||||||
void Playqueue :: add_sort(sort_t type, bool ascending);
|
|
||||||
Add a new term to the sort order.
|
|
||||||
|
|
||||||
void Playqueue :: sort();
|
|
||||||
Perform a stable sort on the entire playqueue. Compare tracks
|
|
||||||
based on the sort_order list.
|
|
||||||
|
|
||||||
unsigned int Playqueue :: next();
|
|
||||||
Return the next track_id to play.
|
|
||||||
|
|
||||||
if (tracks.size() == 0)
|
|
||||||
throw -EEXIST;
|
|
||||||
|
|
||||||
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)):
|
|
||||||
length -= track.length;
|
|
||||||
tracks.erase(cur);
|
|
||||||
return track;
|
|
||||||
|
|
||||||
void Playqueue :: reset_cur();
|
|
||||||
This function is intended to be used by the audio layer when
|
|
||||||
managing the recently played playqueue.
|
|
||||||
|
|
||||||
cur = 0;
|
|
|
@ -1,17 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
print.h
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
error
|
|
||||||
|
|
||||||
Printing: (include/print.h>
|
|
||||||
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.
|
|
|
@ -1,14 +0,0 @@
|
||||||
== Files ==
|
|
||||||
ocarina/include/
|
|
||||||
version.h
|
|
||||||
|
|
||||||
== Depends ==
|
|
||||||
error
|
|
||||||
|
|
||||||
Versioning: (include/version.h)
|
|
||||||
This file contains a simple function for returning a string stating
|
|
||||||
the current version.
|
|
||||||
|
|
||||||
API:
|
|
||||||
const char *get_version();
|
|
||||||
Returns a string describing the current version.
|
|
Loading…
Reference in New Issue