diff --git a/DESIGN b/DESIGN index ef75959d..5b1fb7f1 100644 --- a/DESIGN +++ b/DESIGN @@ -1305,6 +1305,9 @@ Audio Driver: values are returned to the Audio layer. This layer will derive from the Driver class to implement either the GSTDriver or the TestDriver. +- Seconds -> Nanoseconds conversion: + static const unsigned long O_SECOND = 1000000000; + - Driver: class Driver { protected: @@ -1382,9 +1385,6 @@ Audio: File << current_track->id << endl; -- Seconds -> Nanoseconds conversion: - #define O_SECOND 1000000000 - - API: void audio :: init(int *argc, char ***argv); Initialize the audio driver through argc and argv. Read in @@ -1396,6 +1396,10 @@ Audio: Call the corresponding function from the audio driver, but only if a track is loaded. + void audio :: stop(); + pause() + seek_to(0) + long audio :: position(); long audio :: duration(); Call the corresponding function from the audio driver. Return @@ -1405,10 +1409,6 @@ Audio: Return the current audio position in string form. Return an empty string if there is no current track. - void audio :: stop(); - pause() - seek_to(0) - void audio :: next(); Call the deck :: next() function to get the next track that should be played and use the audio driver to load the track. @@ -1421,15 +1421,15 @@ Audio: Save that track's ID to the cur_track file. - Track *audio :: current_track(); - Return the currently playing Track. - Return NULL if there is no current track. - void audio :: load_track(Track *track); Load the requested track. Save that track's ID to the cur_track file. + Track *audio :: current_track(); + Return the currently playing Track. + Return NULL if there is no current track. + void audio :: pause_after(bool enabled, unsigned int N); If enabled == true: Configure Ocarina to pause playback after N tracks @@ -1437,6 +1437,9 @@ Audio: If enabled == false: Do not automatically pause. + If N is greater than the current pause count then enabled should + be set to true. + bool audio :: pause_enabled(); unsigned int audio :: pause_count(); Use these functions to access the current "pause after N" state. diff --git a/include/audio.h b/include/audio.h index ce5ccd68..8b9dc6c9 100644 --- a/include/audio.h +++ b/include/audio.h @@ -4,32 +4,27 @@ #ifndef OCARINA_AUDIO_H #define OCARINA_AUDIO_H -#include -#include - +#include #include namespace audio { void init(int *, char ***); - void load_state(); - void quit(); void play(); void pause(); - void toggle_play(); - void stop(); - void next(); - void previous(); - void load_trackid(unsigned int); - unsigned int current_trackid(); - Queue *get_recent_pq(); - void seek_to(long); + void stop(); + long position(); - std::string position_str(); long duration(); + std::string position_str(); + + void next(); + void prev(); + void load_track(Track *track); + Track *current_track(); void pause_after(bool, unsigned int); bool pause_enabled(); diff --git a/include/driver.h b/include/driver.h index ef0c8454..11a6994c 100644 --- a/include/driver.h +++ b/include/driver.h @@ -4,6 +4,9 @@ #include +static const unsigned long O_SECOND = 1000000000; + + class Driver { protected: void (*on_eos)(); diff --git a/lib/audio.cpp b/lib/audio.cpp index 12b708e0..b1efcf3b 100644 --- a/lib/audio.cpp +++ b/lib/audio.cpp @@ -5,103 +5,87 @@ #include #include #include -#include #include #include -static bool player_playing = false; -static bool track_loaded = false; -static unsigned int cur_trackid = 0; -static bool o_pause_enabled = false; -static unsigned int o_pause_count = 0; -static bool o_should_pause = false; +static bool _pause_enabled = false; +static unsigned int _pause_count = 0; +static Track *cur_track = NULL; static File f_cur_track("cur_track", 0); -static void handle_pause_count() -{ - if (o_pause_enabled == false) - return; - else if (o_pause_count == 0) { - o_should_pause = true; - o_pause_enabled = false; - get_callbacks()->on_pause(); - } else - o_pause_count--; - get_callbacks()->on_pause_count_changed(o_pause_enabled, o_pause_count); -} - -static void on_error() -{ - audio :: next(); - audio :: play(); -} - -static void on_eos() -{ - Track *track; - - handle_pause_count(); - track = tagdb :: lookup(cur_trackid); - if (track) { - track->played(); - library :: get_queue()->updated(track); - } - audio :: next(); - audio :: seek_to(0); -} - static void save_state() { f_cur_track.open(OPEN_WRITE); - f_cur_track << cur_trackid << std::endl; + f_cur_track << cur_track->id << std::endl; f_cur_track.close(); } -static bool load_song(Track *track) +static void _load_track(Track *track, bool start_playback) { - bool start_playback; - - if (o_should_pause) - start_playback = false; - else - start_playback = driver :: get_driver()->is_playing(); + cur_track = track; + if (!track) + return; driver :: get_driver()->load(track->path()); get_callbacks()->on_track_loaded(track); - if (start_playback) - return driver :: get_driver()->play(); + audio :: play(); else - return driver :: get_driver()->pause(); + audio :: pause(); + save_state(); +} + +static inline void _load_track_default(Track *track) +{ + _load_track(track, driver :: get_driver()->is_playing()); +} + +static bool continue_playback() +{ + bool ret = true; + + if (_pause_enabled) { + if (_pause_count == 0) { + ret = false; + _pause_enabled = false; + } else + _pause_count--; + get_callbacks()->on_pause_count_changed(_pause_enabled, _pause_count); + } + + return ret; +} + +static void on_eos() +{ + if (cur_track) { + cur_track->played(); + library :: get_queue()->updated(cur_track); + } + + _load_track(deck :: next(), continue_playback()); } void audio :: init(int *argc, char ***argv) -{ - driver :: get_driver()->init(argc, argv, on_eos, on_error); -} - -void audio :: load_state() { unsigned int id; + + driver :: get_driver()->init(argc, argv, on_eos, audio :: next); if (f_cur_track.exists()) { f_cur_track.open(OPEN_READ); f_cur_track >> id; f_cur_track.close(); - audio :: load_trackid(id); + audio :: load_track(tagdb :: lookup(id)); } } -void audio :: quit() -{ -} - void audio :: play() { - if (track_loaded == false) + if (!cur_track) return; if (driver :: get_driver()->play()) get_callbacks()->on_play(); @@ -109,18 +93,17 @@ void audio :: play() void audio :: pause() { - if (track_loaded == false) + if (!cur_track) return; if (driver :: get_driver()->pause()) get_callbacks()->on_pause(); } -void audio :: toggle_play() +void audio :: seek_to(long pos) { - if (player_playing == true) - pause(); - else - play(); + if (!cur_track) + return; + driver :: get_driver()->seek_to(pos); } void audio :: stop() @@ -129,76 +112,24 @@ void audio :: stop() seek_to(0); } -void audio :: seek_to(long pos) -{ - if (track_loaded == false) - return; - driver :: get_driver()->seek_to(pos); -} - -void audio :: next() -{ - Track *track; - - track_loaded = false; - track = deck :: next(); - load_song(track); - track_loaded = true; - - cur_trackid = track->id; - save_state(); -} - -void audio :: previous() -{ - Track *track = deck :: prev(); - if (track->id == cur_trackid) - return; - - load_song(track); - cur_trackid = track->id; - save_state(); -} - -void audio :: load_trackid(unsigned int track_id) -{ - Track *track; - - if ((track_id == cur_trackid) && (track_loaded == true)) - return; - - track_loaded = false; - try { - track = tagdb :: lookup(track_id); - } catch (int err) { - return; - } - load_song(track); - track_loaded = true; - - cur_trackid = track_id; - save_state(); - deck :: get_queue()->add(track); -} - -unsigned int audio :: current_trackid() -{ - if (track_loaded == false) - throw -E_EXIST; - return cur_trackid; -} - long audio :: position() { - if (track_loaded == false) + if (!cur_track) return 0; return driver :: get_driver()->position(); } +long audio :: duration() +{ + if (!cur_track) + return 0; + return driver :: get_driver()->duration(); +} + std::string audio :: position_str() { std::stringstream ss; - long cur = position() / GST_SECOND; + long cur = position() / O_SECOND; unsigned int minutes = cur / 60; unsigned int seconds = cur % 60; @@ -209,29 +140,46 @@ std::string audio :: position_str() return ss.str(); } -long audio :: duration() +void audio :: next() { - if (track_loaded == false) - return 0; - return driver :: get_driver()->duration(); + _load_track_default(deck :: next()); +} + +void audio :: prev() +{ + _load_track_default(deck :: prev()); +} + +void audio :: load_track(Track *track) +{ + if (!track || track == cur_track) + return; + + _load_track(track, driver :: get_driver()->is_playing()); + deck :: get_queue()->add(cur_track); +} + +Track *audio :: current_track() +{ + return cur_track; } void audio :: pause_after(bool enabled, unsigned int n) { - if (n > o_pause_count) + if (n > _pause_count) enabled = true; - o_pause_enabled = enabled; - o_pause_count = n; + _pause_enabled = enabled; + _pause_count = n; get_callbacks()->on_pause_count_changed(enabled, n); } bool audio :: pause_enabled() { - return o_pause_enabled; + return _pause_enabled; } unsigned int audio :: pause_count() { - return o_pause_count; + return _pause_count; } diff --git a/lib/driver.cpp b/lib/driver.cpp index 03ffd393..9747c821 100644 --- a/lib/driver.cpp +++ b/lib/driver.cpp @@ -15,7 +15,8 @@ TestDriver :: ~TestDriver() {} void TestDriver :: init(int *argc, char ***argv, void (*eos_cb)(), void (*error_cb)()) { on_eos = eos_cb; on_error = error_cb; } -void TestDriver :: load(const std::string &file) { cur_file = file; } +void TestDriver :: load(const std::string &file) + { cur_file = file; playing = false; cur_pos = 0; } bool TestDriver :: play() { playing = true; return true; } bool TestDriver :: pause() { playing = false; return true; } bool TestDriver :: is_playing() { return playing; } diff --git a/tests/.gitignore b/tests/.gitignore index 4065e64c..b3151414 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -11,3 +11,4 @@ library playlist deck driver +audio diff --git a/tests/Data/cur_track b/tests/Data/cur_track new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/tests/Data/cur_track @@ -0,0 +1 @@ +3 diff --git a/tests/Sconscript b/tests/Sconscript index 02071bdf..90814b58 100644 --- a/tests/Sconscript +++ b/tests/Sconscript @@ -22,6 +22,7 @@ tests = [ ("playlist.cpp", True, [], []), ("deck.cpp", True, [], []), ("driver.cpp", False, [ "driver.cpp" ], []), + ("audio.cpp", True, [ "driver.cpp" ], []), ] diff --git a/tests/audio.cpp b/tests/audio.cpp new file mode 100644 index 00000000..b3c50a43 --- /dev/null +++ b/tests/audio.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2013 (c) Anna Schumaker. + */ +#include +#include +#include +#include "test.h" + +Track *TRACK_NULL = NULL; + +void test_pre_init() +{ + TestDriver *driver = (TestDriver *)driver :: get_driver(); + + test_equal(audio :: current_track(), TRACK_NULL); + + audio :: play(); + test_equal(driver->playing, false); + + driver->playing = true; + audio :: pause(); + test_equal(driver->playing, true); + audio :: stop(); + test_equal(driver->playing, true); + driver->playing = false; + + audio :: seek_to(4242); + test_equal(driver->position(), (long)0); + + driver->cur_pos = 4242; + test_equal(audio :: position(), (long)0); + driver->cur_pos = 0; + + driver->cur_duration = 4242; + test_equal(audio :: duration(), (long)0); + driver->cur_duration = 0; + + audio :: next(); + test_equal(audio :: current_track(), TRACK_NULL); + + audio :: prev(); + test_equal(audio :: current_track(), TRACK_NULL); +} + +void test_init(int argc, char **argv) +{ + Track *track; + + test :: cp_data_dir(); + audio :: init(&argc, &argv); + + track = audio :: current_track(); + test_equal(track, TRACK_NULL); + + tagdb :: init(); + library :: init(); + audio :: init(&argc, &argv); + + track = audio :: current_track(); + test_not_equal(track, TRACK_NULL); +} + +void test_playback_controls() +{ + TestDriver *driver = (TestDriver *)driver :: get_driver(); + + audio :: play(); + test_equal(driver->playing, true); + + audio :: pause(); + test_equal(driver->playing, false); + + audio :: seek_to(4242); + test_equal(driver->cur_pos, (long)4242); + test_equal(audio :: position(), (long)4242); + + audio :: play(); + audio :: stop(); + test_equal(driver->playing, false); + test_equal(driver->cur_pos, (long)0); + + audio :: seek_to(4242); + driver->cur_duration = 424242; + test_equal(audio :: position(), (long)4242); + test_equal(audio :: duration(), (long)424242); + + audio :: seek_to(83 * O_SECOND); + test_equal(audio :: position_str(), (std::string)"1:23"); +} + +void test_track_controls() +{ + Track *track = NULL; + + TestDriver *driver = (TestDriver *)driver :: get_driver(); + library :: get_queue()->unset_flag(Q_RANDOM); + + audio :: pause(); + audio :: next(); + test_not_equal(audio :: current_track()->id, (unsigned)2); + test_equal(driver->is_playing(), false); + + audio :: play(); + audio :: next(); + test_equal(driver->is_playing(), true); + + audio :: load_track(track); + test_not_equal(audio :: current_track(), track); + + track = tagdb :: lookup(0); + audio :: seek_to(4242); + audio :: load_track(track); + test_equal(driver->is_playing(), true); + test_equal(audio :: position(), (long)0); + + audio :: seek_to(4242); + audio :: load_track(track); + test_equal(driver->is_playing(), true); + test_equal(audio :: position(), (long)4242); + + driver->error(); + test_not_equal(audio :: current_track(), track); + + track = audio :: current_track(); + driver->eos(); + test_not_equal(audio :: current_track(), track); +} + +void test_autopause() +{ + TestDriver *driver = (TestDriver *)driver :: get_driver(); + + audio :: play(); + test_equal(audio :: pause_enabled(), false); + test_equal(audio :: pause_count(), (unsigned)0); + + audio :: pause_after(true, 3); + test_equal(audio :: pause_enabled(), true); + test_equal(audio :: pause_count(), (unsigned)3); + + audio :: pause_after(false, 3); + test_equal(audio :: pause_enabled(), false); + test_equal(audio :: pause_count(), (unsigned)3); + + audio :: pause_after(false, 5); + test_equal(audio :: pause_enabled(), true); + test_equal(audio :: pause_count(), (unsigned)5); + + for (int i = 4; i >= 0; i--) { + driver->eos(); + test_equal(audio :: pause_enabled(), true); + test_equal(audio :: pause_count(), (unsigned)i); + test_equal(driver->is_playing(), true); + } + + driver->eos(); + test_equal(audio :: pause_enabled(), false); + test_equal(audio :: pause_count(), (unsigned)0); + test_equal(driver->is_playing(), false); +} + +int main(int argc, char **argv) +{ + run_test("Test Audio Pre-Init", test_pre_init); + run_test("Test Audio Init", test_init, argc, argv); + run_test("Test Audio Playback Controls", test_playback_controls); + run_test("Test Audio Track Controls", test_track_controls); + run_test("Test Audio Automatic Pausing", test_autopause); + return 0; +} diff --git a/tests/audio/Sconscript b/tests/audio/Sconscript deleted file mode 100644 index 4cc17946..00000000 --- a/tests/audio/Sconscript +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/python -Import("Test", "CONFIG") - -CONFIG.AUDIO = True - -Test("audio", "audio.cpp") diff --git a/tests/audio/audio.cpp b/tests/audio/audio.cpp deleted file mode 100644 index 7a4b7a32..00000000 --- a/tests/audio/audio.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2013 (c) Anna Schumaker. - */ -#include -#include -#include -#include -#include - -#include -#include -#include - -void gen_library() -{ - system("tests/library/gen_library.sh"); - print("\n"); -} - -void check_ret(const std :: string &test, bool ret, bool expected) -{ - print("Test %s: ", test.c_str()); - if (ret == expected) - print("Success!\n"); - else - print("Failed.\n"); -} - -template -void check_ret(const std :: string &test, long ret, long expected) -{ - print("Test %s: ", test.c_str()); - if (ret == expected) - print("Success!\n"); - else - print("Failed (Expected %ld but got %ld)\n", expected, ret); -} - -void check_error(int error, int expected) -{ - if (expected == 0) { - if (error == 0) - print("Success!\n"); - else - print("Failed with error: %d\n", error); - } else { - if (error == 0) - print("Failed (expected error: %d)\n", expected); - else - print("Success!\n"); - } -} - -void call_func(const std :: string &test, void (*func)(), int expected) -{ - print("Test %s: ", test.c_str()); - try { - func(); - check_error(0, expected); - } catch (int error) { - check_error(error, expected); - } -} - -void call_func(const std :: string &test, void (*func)(long), long arg, int expected) -{ - print("Test %s: ", test.c_str()); - try { - func(arg); - check_error(0, expected); - } catch (int error) { - check_error(error, expected); - } -} - -/* Call various functions without a track loaded */ -void test_0() -{ - call_func("0a", audio :: play, 0); - call_func("0b", audio :: pause, 0); - call_func("0c", audio :: seek_to, 10, 0); - call_func("0d", audio :: next, -E_EXIST); - call_func("0e", audio :: stop, 0); - check_ret("0f", audio :: position(), 0); - check_ret("0g", audio :: duration(), 0); - try { - print("Test 0h: "); - audio :: current_trackid(); - check_error(0, -E_EXIST); - } catch (int error) { - check_error(error, -E_EXIST); - } - call_func("0i", audio :: previous, -E_EXIST); - print("\n"); -} - -void test_1() -{ - library :: Song song; - library :: lookup(0, &song); - - call_func("1a", audio :: next, 0); - call_func("1b", audio :: play, 0); - call_func("1c", audio :: pause, 0); - call_func("1d", audio :: seek_to, 1 * GST_SECOND, 0); - call_func("1e", audio :: stop, 0); - check_ret("1f", audio :: current_trackid() == 0, true); - check_ret("1g", audio :: position(), 0); - call_func("1h", audio :: previous, 0); - check_ret("1i", audio :: current_trackid() == 0, true); - audio :: next(); - audio :: next(); - call_func("1j", audio :: previous, 0); - check_ret("1k", audio :: current_trackid() == 1, true); - call_func("1l", audio :: previous, 0); - check_ret("1m", audio :: current_trackid() == 0, true); - print("\n"); -} - -/* Test pause_after() */ -unsigned int test_2_count = 0; -int test_2_cb(gpointer data) -{ - long seek_pos, pos, max; - library :: Song song; - GMainLoop *loop = (GMainLoop *)data; - - library :: lookup(audio :: current_trackid(), &song); - pos = audio :: position(); - - switch (test_2_count) { - case 0: - break; - case 1: - check_ret("2g", audio :: duration(), song.track->length * GST_SECOND); - seek_pos = (song.track->length * GST_SECOND) - GST_SECOND; - call_func("2h", audio :: seek_to, seek_pos, 0); - break; - case 2: - max = (song.track->length * GST_SECOND) - GST_SECOND + (501 * GST_MSECOND); - - check_ret("2i", pos <= max, true); - call_func("2j", audio :: stop, 0); - break; - case 3: - check_ret("2k", pos, 0); - call_func("2l", audio :: play, 0); - call_func("2m", audio :: seek_to, audio :: duration() - 1, 0); - break; - case 4: - check_ret("2n", audio :: pause_count(), (long)2); - call_func("2o", audio :: seek_to, audio :: duration() - 1, 0); - break; - case 5: - check_ret("2p", audio :: pause_count(), (long)1); - call_func("2q", audio :: seek_to, audio :: duration() - 1, 0); - break; - case 6: - check_ret("2r", audio :: pause_count(), (long)0); - call_func("2s", audio :: seek_to, audio :: duration() - 1, 0); - break; - case 7: - check_ret("2t", audio :: pause_enabled(), false); - check_ret("2u", audio :: pause_count(), (long)0); - break; - case 8: - pos = audio :: position(); - check_ret("2v", (0 <= pos) && (pos <= GST_MSECOND), true); - break; - case 9: - pos = audio :: position(); - check_ret("2w", (0 <= pos) && (pos <= GST_MSECOND), true); - default: - g_main_quit(loop); - } - - test_2_count++; - return true; -} - -void test_2() -{ - GMainLoop *loop; - - check_ret("2a", audio :: pause_enabled(), false); - check_ret("2b", audio :: pause_count(), (long)0); - audio :: pause_after(true, 3); - check_ret("2c", audio :: pause_enabled(), true); - check_ret("2d", audio :: pause_count(), (long)3); - audio :: next(); - check_ret("2e", audio :: pause_enabled(), true); - check_ret("2f", audio :: pause_count(), (long)3); - - audio :: play(); - loop = g_main_loop_new(NULL, FALSE); - g_timeout_add(500, test_2_cb, loop); - g_main_loop_run(loop); -} - -int main(int argc, char **argv) -{ - Playqueue *pqueue; - - gen_library(); - - /* Initialize before testing */ - audio :: init(&argc, &argv); - test_0(); - - /* Read in library, set up a playlist */ - library::init(); - deck::init(); - library :: reset(); - library :: add_path("/tmp/library/0"); - while (idle :: run_task()); - - pqueue = deck :: create(false); - for (unsigned int i = 0; i < 150; i++) - pqueue->add(i); - - test_1(); - test_2(); - - audio :: quit(); - return 0; -} diff --git a/tests/audio/audio.good b/tests/audio/audio.good deleted file mode 100644 index 9d1fbc72..00000000 --- a/tests/audio/audio.good +++ /dev/null @@ -1,53 +0,0 @@ -Generating library: 0 -Generating library: 1 -Generating library: 2 -Generating library: 3 -Generating library: 4 - -Test 0a: Success! -Test 0b: Success! -Test 0c: Success! -Test 0d: Success! -Test 0e: Success! -Test 0f: Success! -Test 0g: Success! -Test 0h: Success! -Test 0i: Success! - -Test 1a: Success! -Test 1b: Success! -Test 1c: Success! -Test 1d: Success! -Test 1e: Success! -Test 1f: Success! -Test 1g: Success! -Test 1h: Success! -Test 1i: Success! -Test 1j: Success! -Test 1k: Success! -Test 1l: Success! -Test 1m: Success! - -Test 2a: Success! -Test 2b: Success! -Test 2c: Success! -Test 2d: Success! -Test 2e: Success! -Test 2f: Success! -Test 2g: Success! -Test 2h: Success! -Test 2i: Success! -Test 2j: Success! -Test 2k: Success! -Test 2l: Success! -Test 2m: Success! -Test 2n: Success! -Test 2o: Success! -Test 2p: Success! -Test 2q: Success! -Test 2r: Success! -Test 2s: Success! -Test 2t: Success! -Test 2u: Success! -Test 2v: Success! -Test 2w: Success! diff --git a/tests/driver.cpp b/tests/driver.cpp index 8cc32170..a396a6f7 100644 --- a/tests/driver.cpp +++ b/tests/driver.cpp @@ -50,6 +50,12 @@ void test_driver() driver->error(); test_equal(error_count, (unsigned)1); + + driver->play(); + driver->seek_to(4242); + driver->load(file); + test_equal(driver->is_playing(), false); + test_equal(driver->position(), (long)0); } int main(int argc, char **argv)