From 20c0f9744f4a6cffcdbd4fd459dd9deb51b441d6 Mon Sep 17 00:00:00 2001 From: fegger Date: Sat, 20 Jun 2026 22:04:26 +0200 Subject: [PATCH] Refactor metadata and internal data structures Replace Metadata.h with Containers.h, introducing a HashTable implementation with SIMD acceleration and specialized track tracking. Cleanup PlayerEngine logic by merging it into the playback flow and refactoring several internal API signatures. --- include/Containers.h | 88 +++++++++++++++++++++ include/Database.h | 7 +- include/File.h | 14 ++-- include/Metadata.h | 28 ------- include/PlaybackEngine.h | 2 +- include/Player.h | 22 ++++-- include/PlayerEngine.h | 36 --------- include/Tag.h | 24 +++--- sound_serve.db | Bin 0 -> 45056 bytes src/Database.cpp | 45 +++++++++-- src/File.cpp | 22 ++++-- src/HashTable.cpp | 163 +++++++++++++++++++++++++++++++++++++++ src/PlaybackEngine.cpp | 2 +- src/PlayerEngine.cpp | 54 ------------- 14 files changed, 345 insertions(+), 162 deletions(-) create mode 100644 include/Containers.h delete mode 100644 include/Metadata.h delete mode 100644 include/PlayerEngine.h create mode 100644 sound_serve.db create mode 100644 src/HashTable.cpp delete mode 100644 src/PlayerEngine.cpp diff --git a/include/Containers.h b/include/Containers.h new file mode 100644 index 0000000..c27d057 --- /dev/null +++ b/include/Containers.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +struct Metadata { + TagLib::String artist; + TagLib::String title; + TagLib::String album; + TagLib::String albumArtist; + unsigned int year; + TagLib::VariantList artwork; + TagLib::String genre; + unsigned int trackNr; + double length; + std::string format; + unsigned int sampleRate; + unsigned int bitRate; +}; + +struct Track { + TagLib::String artist; + TagLib::String title; + TagLib::String album; + unsigned int trackNr; + unsigned int length; + std::string filePath; + std::string_view artwork; + std::string_view genre; + std::string format; +}; + +using trackPtr = std::shared_ptr; + +class HashTable { + public: + static constexpr uint8_t EMPTY = 0x80; + static constexpr uint8_t DELETED = 0xFE; + + explicit HashTable ( size_t capacity = 64 ); + + // delete disabled - requires rehash + HashTable ( const HashTable & ) = delete; + HashTable &operator= ( const HashTable & ) = delete; + + HashTable ( HashTable && ) noexcept = default; + HashTable &operator= ( HashTable && ) noexcept = default; + + void ht_insert ( const std::string &key, trackPtr track ); + trackPtr ht_lookup ( const std::string &key ) const; + bool ht_delete ( const std::string &key ); + + std::vector listAll () const; + + void save ( const std::string &filename ) const; + std::vector> load ( const std::string &filename ); + + std::size_t size () const noexcept { return size_; } + std::size_t capacity () const noexcept { return ctrl_.size(); } + + private: + static constexpr double MAX_LOAD = 0.75; + static constexpr std::size_t GROUP_SIZE = 32; + + struct Entry { + std::string key; + uint64_t hash = 0; + trackPtr track; + }; + + // helpers + static uint64_t hashString ( const std::string &key ) noexcept; + static uint8_t fingerprint ( uint64_t hash ) noexcept; + std::ptrdiff_t probe ( const std::string &key, uint64_t hash, bool insert ) const noexcept; + void rehash ( std::size_t newCapacity ); + + std::vector ctrl_; + std::vector entries_; + std::size_t size_ = 0; + double loadFactor_ = static_cast ( size_ ) / static_cast ( ctrl_.size()); + + +}; // Class HashTable diff --git a/include/Database.h b/include/Database.h index 2dd35e3..fdb0c5f 100644 --- a/include/Database.h +++ b/include/Database.h @@ -1,6 +1,6 @@ #pragma once -#include "Metadata.h" +#include "Containers.h" #include @@ -10,8 +10,9 @@ class Database { public: Database( const char* filename, sqlite3** db ); ~Database(); - void fetch( const std::string& query ); - void fetchAlbum( const std::string& album ); + void fetch( const std::string& query ) const; + void fetchAlbum( const std::string& album ) const; + void fetchAll() const; void addSong( ); private: sqlite3* db_; diff --git a/include/File.h b/include/File.h index 6d5f598..53bb902 100644 --- a/include/File.h +++ b/include/File.h @@ -1,22 +1,22 @@ #pragma once +#include "Containers.h" + #include #include #include -#define MUSIC_FOLDER = {"data/"}; namespace fs = std::filesystem; class File { public: void importFile( std::string fileName ); - void importFolder( std::string folderPath ); - std::vector filenames_; + void importFolder( const std::string& folderPath, std::vector& tracks ); + void readFileTag( const char* fileName ); + std::string parseFiletype( const std::string& path ); + private: std::string path_; - fs::path musicFolder_ MUSIC_FOLDER; + fs::path musicFolder_ = "data/"; std::string fileName_; - fs::path fp_ = musicFolder_ / fileName_; }; - - std::string_view parseFiletype( std::string_view path ); diff --git a/include/Metadata.h b/include/Metadata.h deleted file mode 100644 index 1571ab7..0000000 --- a/include/Metadata.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -struct Metadata { - TagLib::String artist; - TagLib::String title; - TagLib::String album; - TagLib::String albumArtist; - unsigned int year; - TagLib::VariantList artwork; - TagLib::String genre; - unsigned int trackNr; - double length; - std::string format; - unsigned int sampleRate; - unsigned int bitRate; -}; - -struct Track { - std::string_view artist; - std::string_view title; - std::string_view album; - unsigned int trackNr; - unsigned int length; - const char *filePath; -}; diff --git a/include/PlaybackEngine.h b/include/PlaybackEngine.h index bbe6253..ef8c56f 100644 --- a/include/PlaybackEngine.h +++ b/include/PlaybackEngine.h @@ -28,7 +28,7 @@ class PlaybackEngine { void seek ( ma_uint64 frame ); int isFinished () const; - ma_uint64 getPosition (); + uint64_t getPosition (); private: ma_device device_; diff --git a/include/Player.h b/include/Player.h index 4539da4..369ced1 100644 --- a/include/Player.h +++ b/include/Player.h @@ -1,6 +1,6 @@ #pragma once -#include "../include/Metadata.h" +#include "../include/Containers.h" #include "../include/PlaybackEngine.h" #include "../include/miniaudio.h" @@ -8,7 +8,8 @@ #include #include -using Queue = std::vector; +using trackVec = std::vector; +using ht = HashTable; class Player { public: @@ -33,16 +34,16 @@ class Player { virtual std::string &getAlbum () const; virtual unsigned int getTrackNr () const; - // Player Queue - Queue getQueue () const; - Queue clearQueue (); + // Player trackVec + trackVec getQueue () const; + trackVec clearQueue (); void addToQueue ( Track &track ); - Queue rempveFromQueue ( Track &track ); - Queue shuffleQueue (); + trackVec rempveFromQueue ( Track &track ); + trackVec shuffleQueue (); protected: PlaybackEngine PE_; - Queue queue_; + trackVec queue_; private: std::string artist_; @@ -53,4 +54,9 @@ class Player { unsigned int samplerate_ = 48000; unsigned int channels_ = 2; + + trackVec tracks; + ht songs; + ht artists; + ht albums; }; diff --git a/include/PlayerEngine.h b/include/PlayerEngine.h deleted file mode 100644 index 8fee9f3..0000000 --- a/include/PlayerEngine.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "miniaudio.h" - -class PlayerEngine { - private: - ma_engine engine_; - ma_engine_config engineConfig_; - ma_device device_; - ma_device_config deviceConfig_; - unsigned int sampleRate_; - unsigned int channels_ = 2; - ma_format format_ = ma_format_unknown; - ma_context context_; - - - public: - PlayerEngine(); - PlayerEngine( unsigned int sampleRate, unsigned int channels, ma_format format ); - ~PlayerEngine(); - ma_device getDevice() const; - unsigned int getChannels() const; - unsigned int getSampleRate() const; - ma_format getFormat() const; - - void setDevice( ma_device device ); - void setSampleRate( unsigned int sampleRate ); - void setChannels( unsigned int channels ); - void setFormat( ma_format format ); - - ma_device_data_proc data_callback(); - void configureDevice( ma_context& context ); - void initDevice (); - void initEngine(); - -}; diff --git a/include/Tag.h b/include/Tag.h index 4d12100..2500f0f 100644 --- a/include/Tag.h +++ b/include/Tag.h @@ -1,20 +1,26 @@ #pragma once -#include "Metadata.h" +#include "containers.h" +//#include "file.h" + +#include #include #include #include #include -class Tag { - public: - void readTag( TagLib::FileName fileName ); - Metadata getMetadata() const; +namespace fs = std::filesystem; - const std::string& getFilePath() const; - const auto getArtwork() const; +class tag { + public: + void readtag( TagLib::filename filename ); + metadata getmetadata() const; + + const std::string& getfilepath() const; + const auto getartwork() const; private: - Metadata metadata_; - std::string filePath_; + metadata metadata_; + std::string filepath_; + fs::path musicdir = music_folder; }; diff --git a/sound_serve.db b/sound_serve.db new file mode 100644 index 0000000000000000000000000000000000000000..ba442c8a64002f791adf2a4ecf6ada21f60e30a9 GIT binary patch literal 45056 zcmeI)Pfy!s9Kdl0Lm+O0>0v?{muI<*EOb&=_1u}ng{)v`NG5S2ZZOosaj5NVgB#tS z+ulH5z^=Qm*S&*DyY;w3@7rakJ@yk=Ln+IqPA$^cQvBz!pWpL5pWow1mX&NhSo0H4 z?DeCbn~2-moR-UJp9-OAnjt^ehT93by&*r7@^iZ6PIoTZ8rsL-|5?0#tj!qD_1WU= zPsL}(bMqIobp6}e7sgZL=_PeJg#ZEwAb z5Uce>t!Y~=TePg@HCvQbVOcC1g|fd}F0`Gk#)@Ot#d2dsG@RCwEKmF-@XCeHt!8c2 zYA$|q=T0RpcB90PlZ&gk!NWoC;!|0RPE;-oqX%BNpFAoT>Kkp@qcrfIA9$N?T6RH` zU-qMeS5F)U?oq8j2xWIv*v)2R-D+-&`}X!?I*m>$qy56-;0E^(GpI3Z7p@XAWtLbPZK7E>=nB?a_-1;WlCh!G|;{mMl#{U z>(UZgRl$GZM%_nlbUF#zPUAt_J`+B#F06KIqv6P*tlLiO)g1gcz|E=re6^a}&dlP+ zfqahJiM_`IFYJ2f4)Y_5=T0t$ZqHkiH7h3{>C^KI3%Ms%l?RnWs#BEo(m!a)#-!^rQs(UCatj)Xf%`hM7ryq+gzMJH{rBVL}>;;i1Tx91LmMAX_% zIUid)t;V{&)w0$%m&73S<>*v{mCDV@{Jgc0)BJGP`)W8O)sQ^i8Fp{y)^KO4I+vfn zTYBx~>5Ur8tFvwT)O%vgW^?kSpIKycAy(_@5eYR-;=jK;E}B>Ah(~YNm#5u7%on2~ z`*SnBQN~)T*Df2W`lj2trM7x84LLVfTrNTY0R#|0009ILKmY**5I_KdD=Lt_FHGpA8TD6(>GOZy{82N1H-9sKHGejr$paPy5I_I{1Q0*~0R#|0009IL zc$)%7Ue^}d_r+)K@$vMOuDzG;%uVXrhxKUC6K=RGmfbim>bmxU9eCX&>W6-}I3W*Z ztyTX}to9F$oD4GD=lB0_Go7@J00IagfB*srAb ( sqlite3_column_text( stmt, 0 )); - t.title = reinterpret_cast ( sqlite3_column_text( stmt, 1 )); - t.album = reinterpret_cast ( sqlite3_column_text( stmt, 1 )); - t.trackNr = sqlite3_column_int( stmt, 3 ); + t.artist = sqlite3_column_text( stmt, 0 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 0 )) : ""; + t.title = sqlite3_column_text( stmt, 1 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 1 )) : ""; + t.album = sqlite3_column_text( stmt, 1 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 1 )) : ""; + t.trackNr = sqlite3_column_int( stmt, 3 ) ? sqlite3_column_int( stmt, 3 ) : 0; p.addToQueue( t ); } - + sqlite3_finalize( stmt ); +} + +void db::fetchAll() const { + sqlite3_stmt* stmt; + const char* sql = "SELECT * FROM music"; + + int rc = sqlite3_prepare_v2( db_, sql, -1, &stmt, nullptr ); + + if ( rc != SQLITE_OK ) { + throw std::runtime_error( sqlite3_errmsg( db_ )); + } + + while ( (rc = sqlite3_step( stmt )) == SQLITE_ROW ) { + Track t; + Player p; + + t.artist = sqlite3_column_text( stmt, 0 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 0 )) : ""; + t.title = sqlite3_column_text( stmt, 1 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 1 )) : ""; + t.album = sqlite3_column_text( stmt, 1 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 1 )) : ""; + t.trackNr = sqlite3_column_int( stmt, 3 ) ? sqlite3_column_int( stmt, 3 ) : 0; + t.filePath = sqlite3_column_text( stmt, 4 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 4 )) : ""; + t.length = sqlite3_column_int( stmt, 5 ) ? sqlite3_column_int ( stmt, 5 ) : 0; + t.artwork = sqlite3_column_text( stmt, 6 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 6 )) : ""; + t.genre = sqlite3_column_text( stmt, 7 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 7 )) : ""; + t.format = sqlite3_column_text( stmt, 8 ) ? reinterpret_cast ( sqlite3_column_text( stmt, 8 )) : ""; + } + sqlite3_finalize( stmt ); } diff --git a/src/File.cpp b/src/File.cpp index e7abbe1..b5a791a 100644 --- a/src/File.cpp +++ b/src/File.cpp @@ -3,24 +3,32 @@ #include #include +#include namespace fs = std::filesystem; -void File::importFolder( std::string folderPath ) { +void File::importFolder(const std::string& folderPath, std::vector& tracks ) { for ( const auto& audioFile : fs::directory_iterator{ folderPath }) { - filenames_.reserve( 20 ); - filenames_.push_back( audioFile.path() ); + Track t; + t.filePath = static_cast(audioFile.path()); + tracks.push_back( t ); } } -void File::importFile( std::string fileName ) { - fileName_ = fileName; - fs::path fp = fp_; +void File::readFileTag( const char* fileName, std::vector& tracks ) { + TagLib::FileRef f( fileName ); + Track t; + if ( !f.isNull() ) { + t.artist = ( f.tag()->artist().isEmpty() ) ? "" : ( f.tag()->artist()); + t.title = ( f.tag()->title().isEmpty() ) ? "" : ( f.tag()->title()); + t.album = ( f.tag()->album().isEmpty() ) ? "" : ( f.tag()->album()); + t.trackNr = ( f.tag()->track() ); + } } -std::string_view parseFiletype( std::string_view path ){ +std::string File::parseFiletype( const std::string& path ){ size_t length = path.size(); size_t dot = path.find_last_of( "."); diff --git a/src/HashTable.cpp b/src/HashTable.cpp new file mode 100644 index 0000000..167a58b --- /dev/null +++ b/src/HashTable.cpp @@ -0,0 +1,163 @@ +#include "../include/Containers.h" + +#include +#include +#include + +#ifndef __AVX2__ +#include +#endif + +using ht = HashTable; +using trackPtr = std::shared_ptr; + + +ht::HashTable( std::size_t capacity ) :ctrl_ ( capacity, EMPTY ), entries_ ( capacity ), size_( 0 ) { + if ( !capacity ) throw std::invalid_argument( "Capacity must be > 0" ); +} + +uint64_t ht::hashString( const std::string& key ) noexcept { + uint64_t hash = 14695981039346656037ULL; + for ( char c : key ) { + hash ^= static_cast ( c ); + hash *= 1099511628211ULL; + } + return hash; +} + +uint8_t ht::fingerprint ( uint64_t hash ) noexcept { + return static_cast ( hash & 0x7F ); // Use lower 7 bits for fingerprint +} + +std::ptrdiff_t ht::probe ( const std::string &key, uint64_t hash, bool insert ) const noexcept { + const std::size_t capacity = ctrl_.size(); + const uint8_t fp = fingerprint ( hash ); + +#ifdef __AVX2__ + std::size_t group = ( hash % capacity ) & ~( GROUP_SIZE - 1 ); // Align to group boundary + __m256i fpVec = _mm256_set1_epi8 ( fp ); + __m256i emptyVec = _mm256_set1_epi8 ( static_cast ( EMPTY ) ); + + std::ptrdiff_t firstTombstone = -1; + + for ( ;; ) { + __m256i ctrlVec = _mm256_loadu_si256 ( reinterpret_cast ( &ctrl_[ group ] ) ); + uint32_t matchMask = _mm256_movemask_epi8 ( _mm256_cmpeq_epi8 ( ctrlVec, fpVec ) ); + uint32_t emptyMask = _mm256_movemask_epi8 ( _mm256_cmpeq_epi8 ( ctrlVec, emptyVec ) ); + + // check candidates + for ( uint32_t mask = matchMask; mask != 0; mask &= ( mask - 1 ) ) { + std::ptrdiff_t idx = ( group + static_cast ( __builtin_ctz ( mask ) ) ) % capacity; + + if ( entries_[ idx ].hash == hash && entries_[ idx ].key == key ) { + return static_cast ( idx ); // Found + } + } + // record first tombstone for this group + if ( insert && firstTombstone == -1 ) { + for ( std::size_t i = 0; i < GROUP_SIZE; ++i ) { + std::size_t idx = ( group + i ) % capacity; + if ( ctrl_[ idx ] == DELETED ) { + firstTombstone = static_cast ( idx ); + break; + } + } + } + + if ( emptyMask ) { + if ( !insert ) + return -1; // Not found + std::size_t slot = ( group + static_cast ( __builtin_ctz ( emptyMask ) ) ) % capacity; + return firstTombstone != -1 ? firstTombstone : static_cast ( slot ); // Insert here + } + group = ( group + GROUP_SIZE ) % capacity; // Move to next group + } +#else // Fallback to scalar probing + std::size_t group = ( hash % capacity ) & ~( GROUP_SIZE - 1 ); // Align to group boundary + std::ptrdiff_t firstTombstone = -1; + + for ( ;; ) { + bool foundEmpty = false; + std::size_t emptyIdx = 0; + + for ( std::size_t i = 0; i < GROUP_SIZE; ++i ) { + std::size_t idx = ( group + i ) % capacity; + uint8_t c = ctrl_.at( idx ); + + if ( c == EMPTY ) { + if ( !foundEmpty ) { + foundEmpty = true; + emptyIdx = idx; + } + } else if ( c == DELETED ) { + if ( insert && firstTombstone == -1 ) { + firstTombstone = static_cast ( idx ); + } + } else if ( c == fp && entries_.at( idx ).hash == hash && entries_.at( idx ).key == key ) { + return static_cast ( idx ); // Found + } + } + + if ( foundEmpty ) { + if ( !insert ) + return -1; // Not found + return firstTombstone != -1 ? firstTombstone : static_cast ( emptyIdx ); // Insert here + } + group = ( group + GROUP_SIZE ) % capacity; // Move to next group + } // end probeloop +#endif +} // probe + +void ht::ht_insert( const std::string& key, trackPtr track ) { + //double loadFactor = static_cast ( size_ ) / static_cast ( ctrl_.size()); + + if ( loadFactor_ > MAX_LOAD ) rehash( ctrl_.size() * 2 ); + + uint64_t hash = hashString ( key ); + auto idx = static_cast ( probe ( key, hash, true ) ); + + if ( ctrl_.at( idx ) == EMPTY || ctrl_.at( idx ) == DELETED ) { + Entry tmp; + tmp.key = key; + tmp.hash = hash; + tmp.track = track; + entries_.at( idx ) = std::move( tmp ); + ++size_; + } else { + entries_.at( idx ).track = track; + } + ctrl_.at( idx ) = fingerprint( hash ); +} + +trackPtr ht::ht_lookup( const std::string& key ) const { + uint64_t hash = hashString( key ); + auto idx = probe( key, hash, false ); + if ( idx != -1 ) return entries_.at( static_cast ( idx )).track; + return nullptr; +} + +bool ht::ht_delete( const std::string& key ) { + uint64_t hash = hashString( key ); + auto raw = probe( key, hash, false ); + + if ( raw == -1 ) return false; + + auto idx = static_cast( raw ); + ctrl_.at( idx ) = DELETED; + entries_.at( idx ) = Entry(); + --size_; + return true; +} + +void ht::rehash( std::size_t newCapacity ) { + ht newTable( newCapacity ); + for ( std::size_t i = 0; i < ctrl_.size(); ++i ) { + if ( ctrl_.at( i ) != EMPTY && ctrl_.at( i ) != DELETED ) { + const Entry& entry = entries_.at( i ); + newTable.ht_insert( entry.key, entry.track ); + } + } + ctrl_ = std::move( newTable.ctrl_ ); + entries_ = std::move( newTable.entries_ ); + size_ = newTable.size_; +} diff --git a/src/PlaybackEngine.cpp b/src/PlaybackEngine.cpp index da26a4e..02b0512 100644 --- a/src/PlaybackEngine.cpp +++ b/src/PlaybackEngine.cpp @@ -69,7 +69,7 @@ void PE::seek ( ma_uint64 frame ) { ma_decoder_seek_to_pcm_frame ( &state.decoder, frame ); } -auto PE::getPosition () { +uint64_t PE::getPosition () { ma_decoder_get_cursor_in_pcm_frames ( &state.decoder, &cursor_ ); return cursor_; } diff --git a/src/PlayerEngine.cpp b/src/PlayerEngine.cpp deleted file mode 100644 index 6fe45fe..0000000 --- a/src/PlayerEngine.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "../include/PlayerEngine.h" -#include - -using pe = PlayerEngine; - -// Constructor -pe::PlayerEngine(){ - configureDevice(context_ ); - initDevice(); - ma_device_start( &device_ ); -} -pe::PlayerEngine( unsigned int sampleRate, unsigned int channels, ma_format format ) : sampleRate_{ sampleRate }, channels_{ channels }, format_( format ) { - configureDevice(context_ ); - initDevice(); - ma_device_start( &device_ ); -} - - pe::~PlayerEngine(){ - ma_device_uninit( &device_ ); - } - - // Getters - ma_device pe::getDevice() const { return device_; } - ma_format pe::getFormat() const { return format_; } - unsigned int pe::getSampleRate() const { return sampleRate_; } - unsigned int pe::getChannels() const { return channels_; } - - // Setters - void pe::setDevice( ma_device device ) { device_ = device; } - void pe::setSampleRate ( unsigned int sampleRate ) { sampleRate_ = sampleRate; } - void pe::setChannels( unsigned int channels ) { channels_ = channels; } - void pe::setFormat( ma_format format ) { format_ = format; } - - void pe::configureDevice( ma_context& context ) { - ma_device_config conf = deviceConfig_; - conf = ma_device_config_init( ma_device_type_playback); - conf.playback.format = format_; - conf.playback.channels = channels_; - conf.sampleRate = sampleRate_; - conf.dataCallback = data_callback(); - } - void pe::initDevice() { - if( ma_device_init( NULL, &deviceConfig_, &device_ ) != MA_SUCCESS) { - throw std::runtime_error{ "Device could not be initialized." }; - } - } - - void initEngine() { - ma_result result; - ma_engine_config conf; - - conf = ma_engine_config_init(); - // TODO resource Manager - }