2021-04-27 11:19:52 +00:00
# include <iostream>
# include <fstream>
# include <iomanip>
# include <filesystem>
# include "AudioFile.h"
# include "json.hpp"
2021-04-27 15:15:32 +00:00
# include "cxxopts.hpp"
2021-04-27 11:19:52 +00:00
# include "FFT.hpp"
2021-04-27 15:15:32 +00:00
# define PRINTER(s, x) if(!s.quiet) { std::cout << x; }
struct Settings {
std : : vector < std : : filesystem : : path > files ;
bool quiet ;
float splitInterval ;
2021-04-28 18:10:52 +00:00
double minFreq , maxFreq ;
2021-04-27 15:15:32 +00:00
} ;
Settings Parse ( int argc , char * * argv ) ;
2021-04-27 11:19:52 +00:00
int main ( int argc , char * * argv )
{
2021-04-27 15:15:32 +00:00
Settings setts ;
setts = Parse ( argc , argv ) ;
2021-04-27 11:19:52 +00:00
2021-04-27 15:15:32 +00:00
int numFiles = setts . files . size ( ) ;
for ( auto & file : setts . files ) {
2021-04-27 11:19:52 +00:00
AudioFile < double > audioFile ;
2021-04-27 15:15:32 +00:00
if ( ! audioFile . load ( file . string ( ) ) )
{
continue ;
}
std : : string filename = file . filename ( ) . string ( ) ;
2021-04-27 11:19:52 +00:00
int sampleRate = audioFile . getSampleRate ( ) ;
int numChannels = audioFile . getNumChannels ( ) ;
nlohmann : : json output ;
for ( int c = 1 ; c < = numChannels ; c + + ) {
2021-04-27 15:15:32 +00:00
PRINTER ( setts , " \r Analyzing " < < filename < < " ... Channel " < < c < < " / " < < numChannels < < " 0% " ) ;
2021-04-27 11:19:52 +00:00
std : : string chName = " channel_ " + std : : to_string ( c ) ;
output [ chName ] = nlohmann : : json : : array ( ) ;
2021-04-27 15:15:32 +00:00
if ( setts . splitInterval = = 0.0f )
{
2021-04-28 18:10:52 +00:00
std : : vector < std : : pair < double , double > > spectrum =
FFT (
audioFile . samples [ c - 1 ] . cbegin ( ) ,
audioFile . samples [ c - 1 ] . cend ( ) ,
sampleRate ,
setts . minFreq , setts . maxFreq ) ;
2021-04-27 15:15:32 +00:00
output [ chName ] = nlohmann : : json : : array ( ) ;
for ( const std : : pair < double , double > & pair : spectrum ) {
output [ chName ] . push_back ( { { " freq " , pair . first } , { " mag " , pair . second } } ) ;
}
}
else
{
int sampleInterval = sampleRate * setts . splitInterval / 1000 ;
int currentSample ;
for ( currentSample = 0 ; currentSample < audioFile . samples [ c - 1 ] . size ( ) ; currentSample + = sampleInterval )
{
std : : vector < std : : pair < double , double > > spectrum =
FFT (
audioFile . samples [ c - 1 ] . cbegin ( ) + currentSample ,
std : : min (
audioFile . samples [ c - 1 ] . cbegin ( ) + currentSample + sampleInterval ,
2021-04-28 18:10:52 +00:00
audioFile . samples [ c - 1 ] . cend ( )
) ,
sampleRate ,
setts . minFreq , setts . maxFreq
2021-04-27 15:15:32 +00:00
) ;
output [ chName ] . push_back ( {
{ " begin " , currentSample } ,
{ " end " , currentSample + sampleInterval } ,
{ " spectrum " , nlohmann : : json : : array ( ) }
} ) ;
for ( const std : : pair < double , double > & pair : spectrum ) {
output [ chName ] . back ( ) [ " spectrum " ] . push_back ( { { " freq " , pair . first } , { " mag " , pair . second } } ) ;
}
PRINTER ( setts , " \r Analyzing " < < filename < < " ... Channel " < < c < < " / " < < numChannels < < " " < < ( int ) std : : floor ( ( float ) currentSample / ( float ) audioFile . samples [ c - 1 ] . size ( ) * 100.0f ) < < " % " ) ;
}
2021-04-27 11:19:52 +00:00
}
}
2021-04-27 15:15:32 +00:00
std : : ofstream ofs ( file . replace_extension ( " json " ) ) ;
2021-04-27 11:19:52 +00:00
ofs < < std : : setw ( 4 ) < < output < < std : : endl ;
ofs . close ( ) ;
2021-04-27 15:15:32 +00:00
PRINTER ( setts , " \r Analyzing " < < filename < < " ... 100% " < < std : : endl ) ;
2021-04-27 11:19:52 +00:00
}
return 0 ;
}
2021-04-27 15:15:32 +00:00
Settings Parse ( int argc , char * * argv )
2021-04-27 11:19:52 +00:00
{
2021-04-27 15:15:32 +00:00
Settings setts ;
try
{
cxxopts : : Options options ( " spectralyze " , " Fourier transforms audio files " ) ;
options
. set_width ( 70 )
2021-04-28 18:10:52 +00:00
. positional_help ( " FILE1 [FILE2...] " )
2021-04-27 15:15:32 +00:00
. add_options ( )
( " q,quiet " , " Suppress text output " , cxxopts : : value < bool > ( ) - > default_value ( " false " ) )
( " i,interval " , " Splits audio file into intervals of length i milliseconds and transforms them individually (0 to not split file) " , cxxopts : : value < float > ( ) )
2021-04-28 18:10:52 +00:00
( " f,frequency " , " Defines the frequency range of the output spectrum (Default: all the frequencies) " , cxxopts : : value < std : : vector < double > > ( ) )
2021-04-27 15:15:32 +00:00
( " files " , " Files to fourier transform " , cxxopts : : value < std : : vector < std : : filesystem : : path > > ( ) )
( " h,help " , " Print usage " )
;
options . parse_positional ( " files " ) ;
auto result = options . parse ( argc , argv ) ;
if ( result . count ( " help " ) )
{
std : : cout < < options . help ( ) < < std : : endl ;
exit ( 0 ) ;
}
2021-04-28 18:10:52 +00:00
if ( ! result . count ( " frequency " ) )
{
setts . minFreq = 0.0f ;
setts . maxFreq = 0.0f ;
}
else
{
setts . minFreq = result [ " frequency " ] . as < std : : vector < double > > ( ) [ 0 ] ;
setts . maxFreq = result [ " frequency " ] . as < std : : vector < double > > ( ) [ 1 ] ;
}
2021-04-27 15:15:32 +00:00
if ( ! result . count ( " files " ) )
{
std : : cerr < < " At least one positional argument is required. " < < std : : endl ;
exit ( 1 ) ;
}
setts . files = result [ " files " ] . as < std : : vector < std : : filesystem : : path > > ( ) ;
setts . quiet = ( result . count ( " quiet " ) ? result [ " quiet " ] . as < bool > ( ) : false ) ;
setts . splitInterval = ( result . count ( " interval " ) ? result [ " interval " ] . as < float > ( ) : 0.0f ) ;
2021-04-28 18:10:52 +00:00
if ( setts . maxFreq < = setts . minFreq & & ( setts . maxFreq ! = 0 ) )
{
std : : cerr < < " Maximum frequency cannot be smaller than minimum frequency " < < std : : endl ;
exit ( 1 ) ;
}
2021-04-27 15:15:32 +00:00
}
catch ( const cxxopts : : OptionException & e )
{
std : : cout < < " Invalid parameters: " < < e . what ( ) < < std : : endl ;
exit ( 1 ) ;
}
return setts ;
2021-04-27 11:19:52 +00:00
}