diff --git a/README.md b/README.md index d0c7a7c..39fbbd6 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,29 @@ | BTN_VOL_DOWN | | 32 | Buttons pull to GND if pushed -> Internal Pull-Up needed! + +# RFID stuff +The mapping of rfid tags to files uses the ID of the +tag. Create a file `ids.txt` in a folder containing +one or more IDs will lead to the folder beginning to play +when a tag with that id is on the reader. + +The ID should be a 8 character long, downcase string +containing the ID in hexadecimal. E.g. `23b1aa7d`. + +## Special modes +You can also save data on the tags to further manipulate +the system. Position of the data is irrelevant, the whole +tag will be searched. + +Using `[random]` will play the files in a random order. +`[random:2]` will randomize everything except the first 2 +files. This can be useful for having the favorite song of +your kids playing, but after that getting a bit of randomness. + +Using `[lock]` will turn this key into a key for the locking +mode. Scanning the tag enables locking mode. The next album +started will keep running until the end. Removing the tag +will be deactivated, as are the buttons for prev and next +track. You can disable locking mode by again scanning the +lock tag again. diff --git a/include/controller.h b/include/controller.h index c961c77..a568e62 100644 --- a/include/controller.h +++ b/include/controller.h @@ -3,14 +3,19 @@ #include #include "config.h" #include "player.h" +#include "playlist.h" +#include "playlist_manager.h" #include "mqtt_client.h" #include +enum ControllerState { NORMAL, LOCKING, LOCKED }; + class Controller { private: MFRC522* _rfid; - SPIMaster* _spi; MQTTClient* _mqtt_client; + PlaylistManager* _pm; + ControllerState _state = NORMAL; bool _rfid_enabled = true; void _check_rfid(); void _check_serial(); @@ -33,7 +38,7 @@ private: unsigned long _last_mqtt_report_at = 0; void _send_mqtt_report(); public: - Controller(Player* p, SPIMaster* s); + Controller(Player* p, PlaylistManager* pm); void set_mqtt_client(MQTTClient* m); String get_status_json(); void loop(); diff --git a/include/player.h b/include/player.h index 6056786..7c442da 100644 --- a/include/player.h +++ b/include/player.h @@ -2,9 +2,8 @@ #include "config.h" #include #include -#include -#include #include "spi_master.h" +#include "playlist.h" #define SCI_MODE 0x00 #define SCI_STATUS 0x01 @@ -38,13 +37,7 @@ class Player { private: enum state { uninitialized, idle, playing, stopping, - system_sound_while_playing, system_sound_while_stopped, sleeping, recording }; - struct album_state { - uint8_t index; - uint32_t position; - }; - void _check_system_sound(String filename); void _reset(); void _init(); void _wait(); @@ -59,11 +52,8 @@ private: void _flush_and_cancel(); int8_t _get_endbyte(); void _flush(uint count, int8_t fill_byte); - void _set_last_track(const char* album, uint8_t track, uint32_t position); - std::map _last_tracks; - String _random_album(); - void _play_file(String filename, uint32_t offset); uint32_t _id3_tag_offset(File f); + void _play_file(String filename, uint32_t offset); void _finish_playing(); void _finish_stopping(bool turn_speaker_off); void _mute(); @@ -74,21 +64,15 @@ private: void _patch_adpcm(); void _speaker_off(); void _speaker_on(); - void _fill_id_to_folder_map(); - String _foldername_for_id(String id); SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0); SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0); SPISettings* _spi_settings = &_spi_settings_slow; - std::list _files_in_dir(String dir); - String _find_album_dir(String album); File _file; uint8_t _buffer[32]; - String _playing_album; - uint8_t _playing_index; - uint8_t _playing_album_songs; uint32_t _current_play_position; + Playlist* _current_playlist; uint _refills; uint8_t _volume; uint16_t _stop_delay; @@ -96,26 +80,17 @@ private: SPIMaster* _spi; unsigned long _stopped_at; public: - std::map id_to_folder_map; - Player(SPIMaster* s); void vol_up(); void vol_down(); void track_next(); void track_prev(); bool is_playing(); - - bool play_id(String id); - bool play_album(String album); - void play_random_album(); - bool play_song(String album, uint8_t song_index, uint32_t offset=0); - void play_system_sound(String filename); + bool play(); + bool play(Playlist* p); void stop(bool turn_speaker_off=true); bool loop(); void set_volume(uint8_t vol, bool save = true); - std::list ls(String path, bool withFiles=true, bool withDirs=true, bool withHidden=false); - String album() { return _playing_album; } - uint8_t track() { return _playing_index; } uint32_t position() { return _current_play_position; } uint8_t volume() { return _volume; } }; diff --git a/include/playlist.h b/include/playlist.h new file mode 100644 index 0000000..c034246 --- /dev/null +++ b/include/playlist.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +class Playlist { +private: + uint32_t _position = 0; + uint32_t _current_track = 0; + bool _shuffled = false; + std::vector _files; +public: + Playlist(String path); + bool has_track_next(); + bool has_track_prev(); + bool track_next(); + bool track_prev(); + void track_restart(); + void reset(); + bool is_empty(); + String get_current_file(); + uint32_t get_position(); + void set_position(uint32_t p); + void shuffle(uint8_t random_offset=0); + bool is_fresh(); +}; diff --git a/include/playlist_manager.h b/include/playlist_manager.h new file mode 100644 index 0000000..32dc323 --- /dev/null +++ b/include/playlist_manager.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "playlist.h" + +class PlaylistManager { +private: + std::map _map; + std::map _playlists; +public: + PlaylistManager(); + Playlist* get_playlist_for_id(String id); + void dump_ids(); +}; diff --git a/include/spi_master.h b/include/spi_master.h index 16b0541..83b62c0 100644 --- a/include/spi_master.h +++ b/include/spi_master.h @@ -6,7 +6,7 @@ class SPIMaster { public: - SPIMaster() { + static void init() { PIN_SD_CS_SETUP(); PIN_VS1053_XCS_SETUP(); PIN_VS1053_XDCS_SETUP(); @@ -14,27 +14,27 @@ public: disable(); } - void select_sd(bool enabled=true) { + static void select_sd(bool enabled=true) { PIN_SD_CS(enabled ? LOW : HIGH); delayMicroseconds(MCP_SPI_SETTING_DELAY); } - void select_vs1053_xcs(bool enabled=true) { + static void select_vs1053_xcs(bool enabled=true) { PIN_VS1053_XCS(enabled ? LOW : HIGH); delayMicroseconds(MCP_SPI_SETTING_DELAY); } - void select_vs1053_xdcs(bool enabled=true) { + static void select_vs1053_xdcs(bool enabled=true) { PIN_VS1053_XDCS(enabled ? LOW : HIGH); delayMicroseconds(MCP_SPI_SETTING_DELAY); } - void select_rc522(bool enabled=true) { + static void select_rc522(bool enabled=true) { PIN_RC522_CS(enabled ? LOW : HIGH); delayMicroseconds(MCP_SPI_SETTING_DELAY); } - void disable() { + static void disable() { PIN_SD_CS(HIGH); PIN_VS1053_XCS(HIGH); PIN_VS1053_XDCS(HIGH); diff --git a/src/controller.cpp b/src/controller.cpp index 747ed8f..7b570b9 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,10 +1,11 @@ #include "controller.h" #include "spi_master.h" #include "config.h" +#include "playlist.h" -Controller::Controller(Player* p, SPIMaster* s) { +Controller::Controller(Player* p, PlaylistManager* pm) { _player = p; - _spi = s; + _pm = pm; _rfid = new MFRC522(17, MFRC522::UNUSED_PIN); BTN_NEXT_SETUP(); @@ -12,13 +13,13 @@ Controller::Controller(Player* p, SPIMaster* s) { BTN_VOL_UP_SETUP(); BTN_VOL_DOWN_SETUP(); - _spi->select_rc522(); + SPIMaster::select_rc522(); DEBUG("Initializing RC522...\n"); _rfid->PCD_Init(); #ifdef SHOW_DEBUG _rfid->PCD_DumpVersionToSerial(); #endif - _spi->select_rc522(false); + SPIMaster::select_rc522(false); INFO("RC522 initialized.\n"); for (uint8_t i=0; iselect_rc522(); + SPIMaster::select_rc522(); if (!_rfid->PICC_ReadCardSerial()) { if (!_rfid->PICC_IsNewCardPresent()) { return 0; @@ -53,7 +54,7 @@ uint32_t Controller::_get_rfid_card_uid() { return 0; } } - _spi->select_rc522(false); + SPIMaster::select_rc522(false); uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3]; return uid; } @@ -63,19 +64,21 @@ void Controller::_check_rfid() { if (_rfid_present) { byte buffer[2]; byte buffer_size = 2; - _spi->select_rc522(); + SPIMaster::select_rc522(); status = _rfid->PICC_WakeupA(buffer, &buffer_size); if (status == MFRC522::STATUS_OK) { // Card is still present. _rfid->PICC_HaltA(); - _spi->select_rc522(false); + SPIMaster::select_rc522(false); return; } - _spi->select_rc522(false); + SPIMaster::select_rc522(false); // Card is now gone _rfid_present = false; INFO("No more RFID card.\n"); - _player->stop(); + if (_state != LOCKED) { + _player->stop(); + } } else { uint32_t uid = _get_rfid_card_uid(); if (uid > 0) { @@ -92,23 +95,63 @@ void Controller::_check_rfid() { String data = _read_rfid_data(); - _player->play_id(s_uid); + Playlist* pl = _pm->get_playlist_for_id(s_uid); + if (data.indexOf("[lock]") != -1) { + if (_state == LOCKED) { + _state = NORMAL; + DEBUG("ControllerState is now UNLOCKED\n"); + } else { + DEBUG("ControllerState is now LOCKING\n"); + _state = LOCKING; + } + } + if (pl==NULL) { + INFO("Could not find album for id '%s'.", s_uid.c_str()); + return; + } + int index; + if (data.indexOf("[random]") != -1 && pl->is_fresh()) { + pl->shuffle(); + } else if ((index=data.indexOf("[random:")) != -1 && pl->is_fresh()) { + String temp = data.substring(index + 8); + index = temp.indexOf("]"); + TRACE("temp: %s, temp.substring(0, %d): %s\n", temp.c_str(), index, temp.substring(0, index).c_str()); + if (index>0) { + uint8_t random_offset = temp.substring(0, index).toInt(); + pl->shuffle(random_offset); + } + } + + if (_state == LOCKED) { + DEBUG("ControllerState is LOCKED, ignoring card.\n"); + return; + } + + if (_state == LOCKING) { + _state = LOCKED; + DEBUG("ControllerState is now LOCKED.\n"); + } + + _player->play(pl); } } } String Controller::_read_rfid_data() { - _spi->select_rc522(); + static MFRC522::MIFARE_Key keys[8] = { + {{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7 + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default + {{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5 + {{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5 + {{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD + {{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A + {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00 + }; + SPIMaster::select_rc522(); DEBUG("Trying to read RFID data...\n"); String data = ""; - MFRC522::MIFARE_Key key; - key.keyByte[0] = 0xD3; - key.keyByte[1] = 0xF7; - key.keyByte[2] = 0xD3; - key.keyByte[3] = 0xF7; - key.keyByte[4] = 0xD3; - key.keyByte[5] = 0xF7; MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak); uint8_t sectors = 0; @@ -119,35 +162,45 @@ String Controller::_read_rfid_data() { default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str()); } + int good_key_index = -1; for (uint8_t sector=1; sectorPCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, &key, &_rfid->uid); - if (status != MFRC522::STATUS_OK) { - DEBUG("PCD_Authenticate() for sector %d failed: %s\n", sector, String(_rfid->GetStatusCodeName(status)).c_str()); - continue; - } - - for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); - if (status != MFRC522::STATUS_OK) { - DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str()); - continue; + + for (int i=0; i<8; i++) { + MFRC522::MIFARE_Key *k = &keys[i]; + TRACE("Trying MIFARE key %02X %02X %02X %02X %02X %02X...\n", k->keyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]); + status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &_rfid->uid); + if (status == MFRC522::STATUS_OK) { + TRACE("Authentication succeeded with key #%d\n", i); + good_key_index = i; + break; } - for (int i=0; i<16; i++) { - if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); + } + if (good_key_index == -1) { + TRACE("Could not find a valid MIFARE key.\n"); + } else { + for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); + if (status != MFRC522::STATUS_OK) { + DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str()); + continue; + } + for (int i=0; i<16; i++) { + if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); + } } } } _rfid->PICC_HaltA(); _rfid->PCD_StopCrypto1(); - DEBUG("Data from RFID: %s", data.c_str()); - _spi->select_rc522(false); + DEBUG("Data from RFID: %s\n", data.c_str()); + SPIMaster::select_rc522(false); return data; } @@ -169,16 +222,15 @@ void Controller::_check_serial() { void Controller::_execute_serial_command(String cmd) { DEBUG("Executing command: %s\n", cmd.c_str()); - if (cmd.equals("ls")) { - _execute_command_ls("/"); - } else if (cmd.startsWith("ls ")) { - _execute_command_ls(cmd.substring(3)); - } else if (cmd.equals("play")) { - _player->play_random_album(); - } else if (cmd.startsWith("play ")) { - _player->play_id(cmd.substring(5)); - } else if (cmd.startsWith("sys ")) { - _player->play_system_sound(cmd.substring(4)); + if (cmd.startsWith("play ")) { + Playlist* p = _pm->get_playlist_for_id(cmd.substring(5)); + _player->play(p); + //} else if (cmd.equals("ls")) { + // _execute_command_ls("/"); + //} else if (cmd.startsWith("ls ")) { + // _execute_command_ls(cmd.substring(3)); + //} else if (cmd.equals("play")) { + // _player->play_random_album(); } else if (cmd.equals("stop")) { _player->stop(); } else if (cmd.equals("help")) { @@ -192,7 +244,7 @@ void Controller::_execute_serial_command(String cmd) { } else if (cmd.equals("n")) { _player->track_next(); } else if (cmd.equals("ids")) { - _execute_command_ids(); + _pm->dump_ids(); } else { ERROR("Unknown command: %s\n", cmd.c_str()); } @@ -201,25 +253,19 @@ void Controller::_execute_serial_command(String cmd) { void Controller::_execute_command_ls(String path) { INFO("Listing contents of %s:\n", path.c_str()); - std::list files = _player->ls(path); - for(std::list::iterator it=files.begin(); it!=files.end(); ++it) { - INFO(" %s\n", (*it).c_str()); - } -} - -void Controller::_execute_command_ids() { - for (std::map::iterator it = _player->id_to_folder_map.begin(); it!=_player->id_to_folder_map.end(); ++it) { - INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str()); - } + // TODO + //std::list files = _player->ls(path); + //for(std::list::iterator it=files.begin(); it!=files.end(); ++it) { + // INFO(" %s\n", (*it).c_str()); + //} } void Controller::_execute_command_help() { INFO("Valid commands are:"); INFO(" help - Displays this help\n"); - INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n"); + //INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n"); INFO(" ids - Lists all known ID-to-folder mappings\n"); INFO(" play [id] - Plays the album with the given id\n"); - INFO(" sys [file]- Plays the file as system sound\n"); INFO(" stop - Stops playback\n"); INFO(" - / + - Decrease or increase the volume\n"); INFO(" p / n - Previous or next track\n"); @@ -227,13 +273,21 @@ void Controller::_execute_command_help() { void Controller::_check_buttons() { if (BTN_PREV() && _debounce_button(0)) { - _player->track_prev(); + if (_state == NORMAL) { + _player->track_prev(); + } else { + DEBUG("Ignoring btn_prev because state is LOCKED.\n"); + } } else if (BTN_VOL_UP() && _debounce_button(1)) { _player->vol_up(); } else if (BTN_VOL_DOWN() && _debounce_button(2)) { _player->vol_down(); } else if (BTN_NEXT() && _debounce_button(3)) { - _player->track_next(); + if (_state == NORMAL) { + _player->track_next(); + } else { + DEBUG("Ignoring btn_next because state is LOCKED.\n"); + } } } @@ -252,13 +306,14 @@ String Controller::get_status_json() { response.concat(_player->is_playing() ? "playing" : "idle"); response.concat("\", "); if (_player->is_playing()) { - response.concat("\"album\": \""); - response.concat(_player->album()); - response.concat("\", \"track\": "); - response.concat(_player->track()); - response.concat(", \"position\": "); - response.concat(_player->position()); - response.concat(", "); + // TODO + //response.concat("\"album\": \""); + //response.concat(_player->album()); + //response.concat("\", \"track\": "); + //response.concat(_player->track()); + //response.concat(", \"position\": "); + //response.concat(_player->position()); + //response.concat(", "); } response.concat("\"volume\": "); response.concat(_player->volume()); diff --git a/src/main.cpp b/src/main.cpp index 18cd21d..0b301ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,10 +8,12 @@ #include "spi_master.h" #include "http_server.h" #include "mqtt_client.h" +#include "playlist_manager.h" #include Controller* controller; Player* player; +PlaylistManager* pm; //HTTPServer* http_server; FtpServer* ftp_server; MQTTClient* mqtt_client; @@ -33,6 +35,7 @@ void setup() { DEBUG("Setting up SPI...\n"); SPI.begin(); SPI.setHwCs(false); + SPIMaster::init(); SPIMaster* spi = new SPIMaster(); INFO("SPI initialized.\n"); @@ -45,9 +48,13 @@ void setup() { } spi->select_sd(false); + DEBUG("Initializing PlaylistManager...\n"); + pm = new PlaylistManager(); + DEBUG("done.\n"); + DEBUG("Initializing Player and Controller...\n"); player = new Player(spi); - controller = new Controller(player, spi); + controller = new Controller(player, pm); INFO("Player and controller initialized.\n"); DEBUG("Connecting to wifi \"%s\"...\n", WIFI_SSID); diff --git a/src/player.cpp b/src/player.cpp index 9a41fda..eec0b5a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -15,8 +15,6 @@ Player::Player(SPIMaster* s) { _spi->disable(); PIN_VS1053_DREQ_SETUP(); - _fill_id_to_folder_map(); - _init(); } @@ -70,14 +68,6 @@ void Player::_init() { INFO("VS1053 initialization completed.\n"); - INFO("Checking system sounds...\n"); - _spi->select_sd(); - _check_system_sound("no_prev_song.mp3"); - _check_system_sound("no_next_song.mp3"); - _check_system_sound("volume_max.mp3"); - _check_system_sound("volume_min.mp3"); - _spi->select_sd(false); - _state = idle; } @@ -149,15 +139,6 @@ void Player::_record() { _state = recording; } -void Player::_check_system_sound(String filename) { - String path = String("/system/") + filename; - if (!SD.exists(path)) { - ERROR("System sound %s is missing on the sd card!\n", path.c_str()); - } else { - DEBUG("%s found.\n", path.c_str()); - } -} - inline void Player::_wait() { while(!PIN_VS1053_DREQ()); } @@ -476,15 +457,15 @@ void Player::set_volume(uint8_t vol, bool save) { } void Player::vol_up() { - if (_volume == VOLUME_MAX) play_system_sound("volume_max.mp3"); - else if (_volume + VOLUME_STEP > VOLUME_MAX) set_volume(VOLUME_MAX); - else set_volume(_volume + VOLUME_STEP); + uint8_t vol = _volume + VOLUME_STEP; + if (vol > VOLUME_MAX) vol=VOLUME_MAX; + set_volume(vol); } void Player::vol_down() { - if (_volume >= VOLUME_MIN + VOLUME_STEP) set_volume(_volume - VOLUME_STEP); - else if (_volume == VOLUME_MIN) play_system_sound("volume_min.mp3"); - else set_volume(VOLUME_MIN); + uint8_t vol = _volume - VOLUME_STEP; + if (vol < VOLUME_MIN) vol=VOLUME_MIN; + set_volume(vol); } void Player::_mute() { @@ -501,26 +482,27 @@ void Player::_unmute() { void Player::track_next() { if (_state != playing) return; - if (_playing_index + 1 >= _playing_album_songs) { - play_system_sound("no_next_song.mp3"); + if (!_current_playlist->has_track_next()) { return; } stop(); - play_song(_playing_album, _playing_index + 1); + _current_playlist->track_next(); + play(); } void Player::track_prev() { if (_state != playing) return; if (_current_play_position > 100000) { stop(); - play_song(_playing_album, _playing_index); + _current_playlist->track_restart(); + play(); } else { - if (_playing_index == 0) { - play_system_sound("no_prev_song.mp3"); + if (!_current_playlist->has_track_prev()) { return; } stop(); - play_song(_playing_album, _playing_index - 1); + _current_playlist->track_prev(); + play(); } } @@ -528,195 +510,21 @@ bool Player::is_playing() { return _state == playing; } -std::list Player::ls(String path, bool withFiles, bool withDirs, bool withHidden) { - _spi->select_sd(); - std::list result; - if (!SD.exists(path)) return result; - File dir = SD.open(path); - File entry; - while (entry = dir.openNextFile()) { - if (!withDirs && entry.isDirectory()) continue; - if (!withFiles && !entry.isDirectory()) continue; - String filename = entry.name(); - if (!withHidden && filename.startsWith(".")) continue; - if (entry.isDirectory()) filename.concat("/"); - result.push_back(filename); - } - _spi->select_sd(false); - result.sort(); - return result; +bool Player::play(Playlist* p) { + _current_playlist = p; + return play(); } -String Player::_find_album_dir(String id) { - _spi->select_sd(); - if (id.endsWith("/")) id = id.substring(0, id.length() - 1); - String id_with_divider = id + " - "; - File root = SD.open("/"); - File entry; - String result = String(""); - while ((result.length()==0) && (entry = root.openNextFile())) { - String name = entry.name() + 1; - TRACE("Checking if '%s' startsWith '%s'...\n", name.c_str(), id.c_str()); - if (entry.isDirectory() && (name.startsWith(id_with_divider) || name.equals(id))) { - result = name; - } - entry.close(); - } - root.close(); - _spi->select_sd(false); - return result; -} - -std::list Player::_files_in_dir(String path) { - _spi->select_sd(); - TRACE("Examining folder %s...\n", path.c_str()); - if (!path.startsWith("/")) path = String("/") + path; - //if (!path.endsWith("/")) path.concat("/"); - std::list result; - if (!SD.exists(path)) { - DEBUG("Could not open path '%s'.\n", path.c_str()); - _spi->select_sd(false); - return result; - } - File dir = SD.open(path); - File entry; - while (entry = dir.openNextFile()) { - String filename = entry.name(); - filename = filename.substring(path.length() + 1); - if (!entry.isDirectory() && - !filename.startsWith(".") && - ( filename.endsWith(".mp3") || - filename.endsWith(".ogg") || - filename.endsWith(".wma") || - filename.endsWith(".mp4") || - filename.endsWith(".mpa"))) { - TRACE(" Adding entry %s\n", entry.name()); - result.push_back(entry.name()); - } else { - TRACE(" Ignoring entry %s\n", filename.c_str()); - } - entry.close(); - } - dir.close(); - _spi->select_sd(false); - result.sort(); - - return result; -} - -void Player::_fill_id_to_folder_map() { - DEBUG("_fill_id_to_folder_map() running..."); - _spi->select_sd(); - File root = SD.open("/"); - File entry; - while (entry = root.openNextFile()) { - String foldername = entry.name(); - // Remove trailing slash - foldername.remove(foldername.length()); - TRACE("Looking at %s...\n", foldername.c_str()); - if (!entry.isDirectory() || foldername.startsWith("/.")) continue; - if (!SD.exists(foldername + "/ids.txt")) { - TRACE("Folder %s does not contain ids.txt -> ignoring\n", foldername.c_str()); - continue; - } - TRACE("Reading contents of %s...\n", (foldername + "/ids.txt").c_str()); - File f = SD.open(foldername + "/ids.txt"); - String buffer = ""; - while (f.available()) { - char c = f.read(); - if (c=='\n' || c=='\r') { - if (buffer.length() > 0) { - id_to_folder_map[buffer] = foldername; - DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str()); - buffer = ""; - } - } else { - buffer.concat(c); - } - } - f.close(); - - if (buffer.length() > 0) { - id_to_folder_map[buffer] = foldername; - DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str()); - } - entry.close(); - } - root.close(); - DEBUG("fill_id_to_folder_map done.\n"); - _spi->select_sd(false); -} - -String Player::_random_album() { - std::list albums = ls("/", false, true, false); - uint8_t rnd = random(albums.size()); - std::list::iterator it = albums.begin(); - for (int i=0; i::iterator it = id_to_folder_map.find(id); - if (it != id_to_folder_map.end()) { - DEBUG("Found folder '%s' for id %s.\n", it->first.c_str(), it->second.c_str()); - return it->second; - } - DEBUG("No folder found for id %s.\n", id.c_str()); - return ""; -} - -bool Player::play_album(String album) { - album_state s = _last_tracks[album.c_str()]; - DEBUG("Last index for album '%s' was %d,%d\n", album.c_str(), s.index, s.position); - return play_song(album, s.index, s.position); -} - -bool Player::play_song(String album, uint8_t index, uint32_t skip_to) { +bool Player::play() { if (_state == sleeping || _state == recording) _wakeup(); if (_state != idle) return false; - DEBUG("Trying to play song at index %d, offset %d of album %s\n", index, skip_to, album.c_str()); - std::list files = _files_in_dir(album); - _playing_album_songs = files.size(); - DEBUG("Found %d songs in album\n", files.size()); - if (index >= files.size()) { - ERROR("No matching file found - not playing.\n"); - return false; - } - String file = *(std::next(files.begin(), index)); + String file = _current_playlist->get_current_file(); + uint32_t position = _current_playlist->get_position(); _state = playing; - _playing_album = album; - _playing_index = index; - _set_last_track(album.c_str(), index, skip_to); - _play_file(file, skip_to); + _play_file(file, position); return true; } -void Player::play_system_sound(String filename) { - String file = filename; - if (!SD.exists(file)) { - ERROR("File %s does not exist!\n", file.c_str()); - return; - } - if (_state == playing) { - stop(); - _state = system_sound_while_playing; - } else { - _state = system_sound_while_stopped; - } - _play_file(file, 0); -} - void Player::_play_file(String file, uint32_t file_offset) { INFO("play_file('%s', %d)\n", file.c_str(), file_offset); _spi->select_sd(); @@ -797,11 +605,10 @@ void Player::_finish_playing() { } void Player::stop(bool turn_speaker_off) { - if (_state != playing /* && _state != system_sound_while_playing && _state != system_sound_while_stopped*/) return; + if (_state != playing) return; INFO("Stopping...\n"); - if (_state == playing) { - _set_last_track(_playing_album.c_str(), _playing_index, (uint32_t)_file.position()); - } + _current_playlist->set_position(_current_play_position); + _state = stopping; _stop_delay = 0; _write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL); @@ -842,18 +649,12 @@ void Player::_refill() { DEBUG("EOF reached.\n"); _skip_to = 0; _finish_playing(); - if (_state == system_sound_while_playing) { - _finish_stopping(false); - play_album(_playing_album); - return; - } else if (_state == system_sound_while_stopped) { - _finish_stopping(true); - return; - } _finish_stopping(false); - bool result = play_song(_playing_album, _playing_index + 1); - if (!result) { - _set_last_track(_playing_album.c_str(), 0, 0); + if (_current_playlist->has_track_next()) { + _current_playlist->track_next(); + play(); + } else { + _current_playlist->reset(); } return; } @@ -880,10 +681,7 @@ void Player::_refill() { } bool Player::_refill_needed() { - return _state==playing || - _state==stopping || - _state==system_sound_while_playing || - _state==system_sound_while_stopped; + return _state==playing || _state==stopping; } bool Player::loop() { @@ -914,8 +712,3 @@ bool Player::loop() { } return false; } - -void Player::_set_last_track(const char* album, uint8_t index, uint32_t position) { - DEBUG("Setting _last_track[%s]=%d,%d.\n", album, index, position); - _last_tracks[album] = {index, position}; -} diff --git a/src/playlist.cpp b/src/playlist.cpp new file mode 100644 index 0000000..f089df9 --- /dev/null +++ b/src/playlist.cpp @@ -0,0 +1,108 @@ +#include +#include "spi_master.h" +#include "config.h" +#include +#include + +Playlist::Playlist(String path) { + // Add files to _files + SPIMaster::select_sd(); + TRACE("Examining folder %s...\n", path.c_str()); + if (!path.startsWith("/")) path = String("/") + path; + if (!SD.exists(path)) { + DEBUG("Could not open path '%s'.\n", path.c_str()); + SPIMaster::select_sd(false); + return; + } + File dir = SD.open(path); + File entry; + while (entry = dir.openNextFile()) { + String filename = entry.name(); + filename = filename.substring(path.length() + 1); + String ext = filename.substring(filename.length() - 4); + if (!entry.isDirectory() && + !filename.startsWith(".") && + ( ext.equals(".mp3") || + ext.equals(".ogg") || + ext.equals(".wma") || + ext.equals(".mp4") || + ext.equals(".mpa"))) { + TRACE(" Adding entry %s\n", entry.name()); + _files.push_back(entry.name()); + } else { + TRACE(" Ignoring entry %s\n", filename.c_str()); + } + entry.close(); + } + dir.close(); + SPIMaster::select_sd(false); + std::sort(_files.begin(), _files.end()); +} + +bool Playlist::has_track_prev() { + return _current_track > 0; +} + +bool Playlist::has_track_next() { + return _current_track < _files.size()-1; +} + +bool Playlist::track_prev() { + if (_current_track > 0) { + _current_track--; + _position = 0; + return true; + } + return false; +} + +bool Playlist::track_next() { + if (_current_track < _files.size()-1) { + _current_track++; + _position = 0; + return true; + } + return false; +} + +void Playlist::track_restart() { + _position = 0; +} + +void Playlist::shuffle(uint8_t random_offset) { + DEBUG("Shuffling the playlist with an offset of %d...\n", random_offset); + for (int i=random_offset; i<_files.size(); i++) { + int j = random(random_offset, _files.size()-1); + if (i!=j) { + TRACE(" Swapping elements %d and %d.\n", i, j); + String temp = _files[i]; + _files[i] = _files[j]; + _files[j] = temp; + } + } + _shuffled = true; + TRACE("Done.\n"); +} + +void Playlist::reset() { + std::sort(_files.begin(), _files.end()); + _current_track = 0; + _position = 0; + _shuffled = false; +} + +String Playlist::get_current_file() { + return _files[_current_track]; +} + +uint32_t Playlist::get_position() { + return _position; +} + +void Playlist::set_position(uint32_t p) { + _position = p; +} + +bool Playlist::is_fresh() { + return !_shuffled && _position==0 && _current_track==0; +} \ No newline at end of file diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp new file mode 100644 index 0000000..9c04c20 --- /dev/null +++ b/src/playlist_manager.cpp @@ -0,0 +1,60 @@ +#include "playlist_manager.h" +#include +#include "spi_master.h" + +PlaylistManager::PlaylistManager() { + SPIMaster::select_sd(); + File root = SD.open("/"); + File entry; + while (entry = root.openNextFile()) { + String foldername = entry.name(); + // Remove trailing slash + foldername.remove(foldername.length()); + TRACE("Looking at %s...", foldername.c_str()); + if (!entry.isDirectory() || foldername.startsWith("/.")) continue; + if (!SD.exists(foldername + "/ids.txt")) { + TRACE("No ids.txt -> ignoring\n"); + continue; + } + File f = SD.open(foldername + "/ids.txt"); + String buffer = ""; + if (f.available()) { + do { + char c = f.read(); + if (!f.available() && c!='\n' && c!='\r') { + buffer.concat(c); + c='\n'; + } + + if (c=='\n' || c=='\r') { + if (buffer.length() > 0) { + _map[buffer] = foldername; + TRACE(" ID %s", buffer.c_str()); + buffer=""; + } + } else { + buffer.concat(c); + } + } while(f.available()); + } + f.close(); + entry.close(); + } + root.close(); + SPIMaster::select_sd(false); +} + +Playlist* PlaylistManager::get_playlist_for_id(String id) { + if (!_map.count(id)) return NULL; + String folder = _map[id]; + if (!_playlists.count(folder)) { + _playlists[folder] = new Playlist(folder); + } + return _playlists[folder]; +} + +void PlaylistManager::dump_ids() { + for (std::map::iterator it = _map.begin(); it!=_map.end(); it++) { + INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str()); + } +}