packages feed

acousticbrainz-client (empty) → 0.1.0.0

raw patch · 33 files changed

+1117/−0 lines, 33 filesdep +acousticbrainz-clientdep +aesondep +base-compat-batteriessetup-changed

Dependencies added: acousticbrainz-client, aeson, base-compat-batteries, base-noprelude, bytestring, exceptions, filepath, microlens-aeson, microlens-platform, pretty-simple, relude, scientific, streaming-bytestring, streaming-utils, tasty, tasty-golden, text

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ acousticbrainz-client.cabal view
@@ -0,0 +1,69 @@+cabal-version:       2.2+name:                acousticbrainz-client+version:             0.1.0.0+synopsis:            AcousticBrainz API client+-- description:+-- bug-reports:+license:             CC0-1.0+author:              koral+maintainer:          chahine.moreau@gmail.com+-- category:+build-type:          Simple+-- extra-source-files:  CHANGELOG.md+tested-with: GHC ==8.6.3 || ==8.4.4 || ==8.2.2 || ==8.0.2+++common common+  build-depends: base-noprelude >= 4.7, base-compat-batteries, relude+  default-language: Haskell2010+  other-modules:+    Prelude++library+  import: common+  exposed-modules:+    AcousticBrainz.FiniteDistribution+    AcousticBrainz.HighLevel+    AcousticBrainz.HighLevel.Dortmund+    AcousticBrainz.HighLevel.ElectronicClassification+    AcousticBrainz.HighLevel.ISMIR04Rhythm+    AcousticBrainz.HighLevel.MirexMood+    AcousticBrainz.HighLevel.Rosamerica+    AcousticBrainz.HighLevel.Tzanetakis+    AcousticBrainz.LowLevel+    AcousticBrainz.LowLevel.Algorithm.BpmHistogramDescriptors+    AcousticBrainz.LowLevel.Algorithm.Chords+    AcousticBrainz.LowLevel.Algorithm.EnergyBand+    AcousticBrainz.LowLevel.Algorithm.HighResolutionFeatures+    AcousticBrainz.LowLevel.Algorithm.HPCP+    AcousticBrainz.LowLevel.Algorithm.Key+    AcousticBrainz.LowLevel.Algorithm.SilenceRate+    AcousticBrainz.LowLevel.BarkBands+    AcousticBrainz.LowLevel.Bands+    AcousticBrainz.LowLevel.CepstralCoefficients+    AcousticBrainz.LowLevel.EquivalentRectangularBandwidth+    AcousticBrainz.LowLevel.MelBands+    AcousticBrainz.LowLevel.Rhythm+    AcousticBrainz.LowLevel.Spectral+    AcousticBrainz.LowLevel.Tonal+    AcousticBrainz.MetaData+    AcousticBrainz.StatisticalUnits+    AcousticBrainz.Version+    MusicBrainz+  -- other-extensions:+  build-depends:       aeson, bytestring, exceptions, microlens-platform, microlens-aeson, scientific, streaming-bytestring, streaming-utils, text+  hs-source-dirs:      src++test-suite golden-tests+  import: common+  type: exitcode-stdio-1.0+  main-is: GoldenTests.hs+  build-depends:+    acousticbrainz-client -any,+    aeson,+    filepath,+    pretty-simple,+    tasty -any,+    tasty-golden -any,+    text+  hs-source-dirs: test
+ src/AcousticBrainz/FiniteDistribution.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE StandaloneDeriving    #-}+{-# LANGUAGE TupleSections         #-}+module AcousticBrainz.FiniteDistribution where++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific++newtype FiniteDistribution a = FiniteDistribution (Map a Scientific)++deriving instance Eq a => Eq (FiniteDistribution a)+deriving instance Ord a => Ord (FiniteDistribution a)+deriving instance (Ord a, Read a) => Read (FiniteDistribution a)+deriving instance Show a => Show (FiniteDistribution a)++++parseFiniteDistribution :: Ord a => [(a, Text)] -> Value -> Parser (FiniteDistribution a)+parseFiniteDistribution valueKeys = withObject "all" $ \v ->+  FiniteDistribution . fromList <$> sequence (do+    (value, name) <- valueKeys+    return $ (value,) <$> v .: name+  )+++
+ src/AcousticBrainz/HighLevel.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE StandaloneDeriving    #-}+{-# LANGUAGE UndecidableInstances  #-}+module AcousticBrainz.HighLevel where++import           AcousticBrainz.FiniteDistribution+import           AcousticBrainz.MetaData+import qualified AcousticBrainz.HighLevel.Dortmund                 as Dortmund+import qualified AcousticBrainz.HighLevel.ElectronicClassification as ElectronicClassification+import qualified AcousticBrainz.HighLevel.ISMIR04Rhythm            as ISMIR04Rhythm+import qualified AcousticBrainz.HighLevel.MirexMood                as Mirex+import qualified AcousticBrainz.HighLevel.Rosamerica               as Rosamerica+import qualified AcousticBrainz.HighLevel.Tzanetakis               as Tzanetakis+import           AcousticBrainz.Version+import           MusicBrainz++import           Control.Monad.Catch+import           Data.Aeson+import           Data.Aeson.Types+import qualified Data.ByteString.Streaming                         as Q+import           Data.ByteString.Streaming.HTTP+import           Data.Scientific+import qualified Data.Text                                         as Text+import           Lens.Micro.Aeson++-- | Call the following endpoint: https://acousticbrainz.readthedocs.io/api.html#get--api-v1-(uuid-mbid)-high-level .+getHighLevelData :: MonadIO m => MonadThrow m => MusicBrainzIdentifier -> m HighLevelResponse+getHighLevelData (MusicBrainzIdentifier identifier) = io $ do+  request <- parseRequest $ "https://acousticbrainz.org/api/v1/" <> Text.unpack identifier <> "/high-level"+  manager <- newManager tlsManagerSettings+  withHTTP request manager $ \r ->+    r & responseBody & Q.toLazy_ <&> eitherDecode >>= liftEither+  where liftEither (Left e) = throwM $ HighLevelException e+        liftEither (Right a) = return a+++data Danceability = Danceable | NotDanceable deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Danceability) where+  parseJSON = parseFiniteDistribution [(Danceable, "danceable"), (NotDanceable, "not_danceable")]+++data Gender = Female | Male deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Gender) where+  parseJSON = parseFiniteDistribution [(Female, "female"), (Male, "male")]+++data Acoustic = Acoustic | NotAcoustic deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Acoustic) where+  parseJSON = parseFiniteDistribution [(Acoustic, "acoustic"), (NotAcoustic, "not_acoustic")]+++data Aggressive = Aggressive | NotAggressive deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Aggressive) where+  parseJSON = parseFiniteDistribution [(Aggressive, "aggressive"), (NotAggressive, "not_aggressive")]+++data Electronic = Electronic | NotElectronic deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Electronic) where+  parseJSON = parseFiniteDistribution [(Electronic, "electronic"), (NotElectronic, "not_electronic")]+++data Happy = Happy | NotHappy deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Happy) where+  parseJSON = parseFiniteDistribution [(Happy, "happy"), (NotHappy, "not_happy")]+++data Party = Party | NotParty deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Party) where+  parseJSON = parseFiniteDistribution [(Party, "party"), (NotParty, "not_party")]+++data Relaxed = Relaxed | NotRelaxed deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Relaxed) where+  parseJSON = parseFiniteDistribution [(Relaxed, "relaxed"), (NotRelaxed, "not_relaxed")]+++data Sad = Sad | NotSad deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Sad) where+  parseJSON = parseFiniteDistribution [(Sad, "sad"), (NotSad, "not_sad")]+++data Timbre = Bright | Dark deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Timbre) where+  parseJSON = parseFiniteDistribution [(Bright, "bright"), (Dark, "dark")]+++data Tonal = Atonal | Tonal deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Tonal) where+  parseJSON = parseFiniteDistribution [(Atonal, "atonal"), (Tonal, "tonal")]+++data Vocal = Instrumental | Vocal deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Vocal) where+  parseJSON = parseFiniteDistribution [(Instrumental, "instrumental"), (Vocal, "voice")]+++data Feature t = Feature Version (FiniteDistribution t)++deriving instance Eq t => Eq (Feature t)+deriving instance Ord t => Ord (Feature t)+deriving instance (Ord t, Read t) => Read (Feature t)+deriving instance Show t => Show (Feature t)++instance (FromJSON (FiniteDistribution t)) => FromJSON (Feature t) where+  parseJSON = withObject "Feature" $ \v -> Feature+    <$> v .: "version"+    <*> v .: "all"+++data HighLevelData = HighLevelData+  { _danceability    :: Feature Danceability+  , _gender          :: Feature Gender+  , _genreDortmund   :: Feature Dortmund.Genre+  , _genreElectronic :: Feature ElectronicClassification.Genre+  , _genreRosamerica :: Feature Rosamerica.Genre+  , _genreTzanetakis :: Feature Tzanetakis.Genre+  , _ismir04rhythm   :: Feature ISMIR04Rhythm.Genre+  , _mirexMood       :: Feature Mirex.Mood+  , _moodAcoustic    :: Feature Acoustic+  , _moodAggressive  :: Feature Aggressive+  , _moodElectronic  :: Feature Electronic+  , _moodHappy       :: Feature Happy+  , _moodParty       :: Feature Party+  , _moodRelaxed     :: Feature Relaxed+  , _moodSad         :: Feature Sad+  , _timbre          :: Feature Timbre+  , _tonal           :: Feature Tonal+  , _vocal           :: Feature Vocal+  } deriving(Eq, Ord, Read, Show)++instance FromJSON HighLevelData where+  parseJSON = withObject "highlevel" $ \v -> HighLevelData+    <$> v .: "danceability"+    <*> v .: "gender"+    <*> v .: "genre_dortmund"+    <*> v .: "genre_electronic"+    <*> v .: "genre_rosamerica"+    <*> v .: "genre_tzanetakis"+    <*> v .: "ismir04_rhythm"+    <*> v .: "moods_mirex"+    <*> v .: "mood_acoustic"+    <*> v .: "mood_aggressive"+    <*> v .: "mood_electronic"+    <*> v .: "mood_happy"+    <*> v .: "mood_party"+    <*> v .: "mood_relaxed"+    <*> v .: "mood_sad"+    <*> v .: "timbre"+    <*> v .: "tonal_atonal"+    <*> v .: "voice_instrumental"++++parseMetaData = withObject "metadata" $ \v -> MetaData+  <$> v .: "audio_properties"+  <*> v .: "tags"+  <*> (v .: "version" >>= withObject "version" (.: "highlevel"))++data HighLevelResponse = HighLevelResponse+  { _data     :: HighLevelData+  , _metadata :: MetaData+  } deriving(Eq, Read, Show)++instance FromJSON HighLevelResponse where+  parseJSON = withObject "response" $ \v -> HighLevelResponse+    <$> v .: "highlevel"+    <*> (v .: "metadata" >>= parseMetaData)+++newtype HighLevelException = HighLevelException String deriving(Eq, Ord, Read, Show)+instance Exception HighLevelException
+ src/AcousticBrainz/HighLevel/Dortmund.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Dortmund genre classification: http://www-ai.cs.uni-dortmund.de/audio.html .+module AcousticBrainz.HighLevel.Dortmund where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson+import           Data.Aeson.Types++data Genre = Alternative | Blues | Electronic | FolkCountry | FunkSoulRnB | Jazz | Pop | Raphiphop | Rock+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Genre) where+  parseJSON = parseFiniteDistribution+    [(Alternative, "alternative")+    , (Blues, "blues")+    , (Electronic, "electronic")+    , (FolkCountry, "folkcountry")+    , (FunkSoulRnB, "funksoulrnb")+    , (Jazz, "jazz")+    , (Pop, "pop")+    , (Raphiphop, "raphiphop")+    , (Rock, "rock")+    ]
+ src/AcousticBrainz/HighLevel/ElectronicClassification.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+module AcousticBrainz.HighLevel.ElectronicClassification where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson                        as JSON+import           Data.Aeson.Types                  as JSON+++data Genre = Ambient | DnB | House | Techno | Trance+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Genre) where+  parseJSON = parseFiniteDistribution+    [ (Ambient, "ambient")+    , (DnB, "dnb")+    , (House, "house")+    , (Techno, "techno")+    , (Trance, "trance")+    ]+
+ src/AcousticBrainz/HighLevel/ISMIR04Rhythm.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+-- | ISMIR 2004 rhythm classification.+module AcousticBrainz.HighLevel.ISMIR04Rhythm where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson+import           Data.Aeson.Types++data Genre = ChaChaCha | Jive | Quickstep | RumbaAmerican | RumbaInternational | RumbaMisc | Samba | Tango | VienneseWaltz | Waltz+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Genre) where+  parseJSON = parseFiniteDistribution+    [ (ChaChaCha, "ChaChaCha")+    , (Jive, "Jive")+    , (Quickstep, "Quickstep")+    , (RumbaAmerican, "Rumba-American")+    , (RumbaInternational, "Rumba-International")+    , (RumbaMisc, "Rumba-Misc")+    , (Samba, "Samba")+    , (VienneseWaltz, "VienneseWaltz")+    , (Waltz, "Waltz")+    ]
+ src/AcousticBrainz/HighLevel/MirexMood.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+-- | K-Pop mood classification: https://www.music-ir.org/mirex/wiki/2019:Audio_K-POP_Mood_Classification .+module AcousticBrainz.HighLevel.MirexMood where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson+import           Data.Aeson.Types++data Mood = Cluster1 | Cluster2 | Cluster3 | Cluster4 | Cluster5+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Mood) where+  parseJSON = parseFiniteDistribution+    [ (Cluster1, "Cluster1")+    , (Cluster2, "Cluster2")+    , (Cluster3, "Cluster3")+    , (Cluster4, "Cluster4")+    , (Cluster5, "Cluster5")+    ]
+ src/AcousticBrainz/HighLevel/Rosamerica.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+module AcousticBrainz.HighLevel.Rosamerica where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson                        as JSON+import           Data.Aeson.Types                  as JSON++data Genre = Cla | Dan | Hip | Jaz | Pop | Rhy | Roc | Spe+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Genre) where+  parseJSON = parseFiniteDistribution+    [ (Cla, "cla")+    , (Dan, "dan")+    , (Hip, "hip")+    , (Jaz, "jaz")+    , (Pop, "pop")+    , (Rhy, "rhy")+    , (Roc, "roc")+    , (Spe, "spe")+    ]
+ src/AcousticBrainz/HighLevel/Tzanetakis.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+module AcousticBrainz.HighLevel.Tzanetakis where++import           AcousticBrainz.FiniteDistribution++import           Data.Aeson                        as JSON+import           Data.Aeson.Types                  as JSON++data Genre = Blu | Cla | Cou | Dis | Hip | Jaz | Met | Pop | Reg | Roc+  deriving(Eq, Ord, Read, Show)++instance FromJSON (FiniteDistribution Genre) where+  parseJSON = parseFiniteDistribution+    [ (Blu, "blu")+    , (Cla, "cla")+    , (Cou, "cou")+    , (Dis, "dis")+    , (Hip, "hip")+    , (Jaz, "jaz")+    , (Met, "met")+    , (Pop, "pop")+    , (Reg, "reg")+    , (Roc, "roc")+    ]+
+ src/AcousticBrainz/LowLevel.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Low-level descriptors: https://essentia.upf.edu/documentation/streaming_extractor_music.html#low-level .+module AcousticBrainz.LowLevel where++import           AcousticBrainz.StatisticalUnits+import           AcousticBrainz.MetaData+import qualified AcousticBrainz.LowLevel.Algorithm.SilenceRate as SilenceRate+import           AcousticBrainz.LowLevel.BarkBands+import           AcousticBrainz.LowLevel.CepstralCoefficients+import qualified AcousticBrainz.LowLevel.EquivalentRectangularBandwidth as EquivalentRectangularBandwidth+import           AcousticBrainz.LowLevel.MelBands+import           AcousticBrainz.LowLevel.Rhythm+import           AcousticBrainz.LowLevel.Spectral+import           AcousticBrainz.LowLevel.Tonal+import           MusicBrainz++import           Control.Monad.Catch+import           Data.Aeson                     as JSON+import           Data.Aeson.Types               as JSON+import qualified Data.ByteString.Lazy           as Lazy+import qualified Data.ByteString.Streaming      as Q+import           Data.ByteString.Streaming.HTTP+import           Data.Scientific+import qualified Data.Text                      as Text++-- | Call the following endpoint: https://acousticbrainz.readthedocs.io/api.html#get--api-v1-(uuid-mbid)-low-level .+getLowLevelData :: MonadIO m => MonadThrow m => MusicBrainzIdentifier -> m LowLevelResponse+getLowLevelData (MusicBrainzIdentifier identifier) = io $ do+  request <- parseRequest $ "https://acousticbrainz.org/api/v1/" <> Text.unpack identifier <> "/low-level"+  manager <- newManager tlsManagerSettings+  withHTTP request manager $ \r ->+    r & responseBody & Q.toLazy_ <&> eitherDecode >>= liftEither+  where liftEither (Left e) = throwM $ LowLevelException e+        liftEither (Right a) = return a++data LowLevelResponse = LowLevelResponse+  { _data     :: LowLevelData+  , _tonal    :: Tonal+  , _rhythm   :: Rhythm+  , _metadata :: MetaData+  } deriving(Eq, Read, Show)++instance FromJSON LowLevelResponse where+  parseJSON = withObject "response" $ \v -> LowLevelResponse+    <$> v .: "lowlevel"+    <*> v .: "tonal"+    <*> v .: "rhythm"+    <*> (v .: "metadata" >>= parseMetaData)+++parseMetaData = withObject "metadata" $ \v -> MetaData+  <$> v .: "audio_properties"+  <*> v .: "tags"+  <*> v .: "version"+++data LowLevelData = LowLevelData+  { _averageLoudness :: Scientific+  , _barkBands :: BarkBands+  , _dissonance :: StatisticalUnits+  , _dynamicComplexity :: Scientific+  , _equivalentRectangularBandwidthBands :: EquivalentRectangularBandwidth.Bands+  , _gfcc :: CepstralCoefficients+  , _highFrequencyContent :: StatisticalUnits+  , _melBands :: MelBands+  , _mfcc :: CepstralCoefficients+  , _pitchSalience :: StatisticalUnits+  , _silenceRate :: SilenceRate.Output+  , _spectral :: Spectral+  , _zeroCrossingRate :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)++instance FromJSON LowLevelData where+  parseJSON value = flip (withObject "lowlevel") value $ \v -> LowLevelData+    <$> v .: "average_loudness"+    <*> parseJSON value+    <*> v .: "dissonance"+    <*> v .: "dynamic_complexity"+    <*> parseJSON value+    <*> v .: "gfcc"+    <*> v .: "hfc"+    <*> parseJSON value+    <*> v .: "mfcc"+    <*> v .: "pitch_salience"+    <*> parseJSON value+    <*> parseJSON value+    <*> v .: "zerocrossingrate"+++newtype LowLevelException = LowLevelException String deriving(Eq, Ord, Read, Show)+instance Exception LowLevelException
+ src/AcousticBrainz/LowLevel/Algorithm/BpmHistogramDescriptors.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE OverloadedStrings #-}+-- | BpmHistogramDescriptors algorithm: https://essentia.upf.edu/documentation/reference/streaming_BpmHistogramDescriptors.html .+module AcousticBrainz.LowLevel.Algorithm.BpmHistogramDescriptors where++import           AcousticBrainz.StatisticalUnits++import           Data.Aeson+import           Data.Aeson.Types+++data Output = Output+  { _firstPeakBpm     :: StatisticalUnits+  , _firstPeakSpread  :: StatisticalUnits+  , _firstPeakWeight  :: StatisticalUnits+  , _secondPeakBpm    :: StatisticalUnits+  , _secondPeakSpread :: StatisticalUnits+  , _secondPeakWeight :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "BpmHistogramDescriptors" $ \v -> Output+    <$> v .: "bpm_histogram_first_peak_bpm"+    <*> v .: "bpm_histogram_first_peak_spread"+    <*> v .: "bpm_histogram_first_peak_weight"+    <*> v .: "bpm_histogram_second_peak_bpm"+    <*> v .: "bpm_histogram_second_peak_spread"+    <*> v .: "bpm_histogram_second_peak_weight"
+ src/AcousticBrainz/LowLevel/Algorithm/Chords.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Chords algorithms:+-- - https://essentia.upf.edu/documentation/reference/streaming_ChordsDetection.html+-- - https://essentia.upf.edu/documentation/reference/streaming_ChordsDescriptors.html+module AcousticBrainz.LowLevel.Algorithm.Chords where++import           AcousticBrainz.StatisticalUnits+import qualified AcousticBrainz.LowLevel.Algorithm.Key as Key++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data Output = Output+  { _changesRate :: Scientific+  , _histogram   :: [Scientific]+  , _key         :: Text+  , _numberRate  :: Scientific+  , _scale       :: Key.Scale+  , _strength    :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "chords" $ \v -> Output+    <$> v .: "chords_changes_rate"+    <*> v .: "chords_histogram"+    <*> v .: "chords_key"+    <*> v .: "chords_number_rate"+    <*> v .: "chords_scale"+    <*> v .: "chords_strength"
+ src/AcousticBrainz/LowLevel/Algorithm/EnergyBand.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+-- | EnergyBand algorithm: https://essentia.upf.edu/documentation/reference/streaming_EnergyBand.html .+module AcousticBrainz.LowLevel.Algorithm.EnergyBand where++import           AcousticBrainz.StatisticalUnits++import           Data.Aeson+import           Data.Aeson.Types+++data Output = Output+  { _high       :: StatisticalUnits+  , _low        :: StatisticalUnits+  , _middleHigh :: StatisticalUnits+  , _middleLow  :: StatisticalUnits++  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "EnergyBand" $ \v -> Output+    <$> v .: "spectral_energyband_high"+    <*> v .: "spectral_energyband_low"+    <*> v .: "spectral_energyband_middle_high"+    <*> v .: "spectral_energyband_middle_low"
+ src/AcousticBrainz/LowLevel/Algorithm/HPCP.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+-- | HPCP algorithm: https://essentia.upf.edu/documentation/reference/streaming_HPCP.html .+module AcousticBrainz.LowLevel.Algorithm.HPCP where++import           AcousticBrainz.StatisticalUnits++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data Output = Output+  { _harmonicPitchClassProfile           :: [StatisticalUnits]+  , _transposedHarmonicPitchClassProfile :: [Scientific]+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON value = flip (withObject "HPCP") value $ \v -> Output+    <$> (_statisticalUnitsList <$> v .: "hpcp")+    <*> v .: "thpcp"
+ src/AcousticBrainz/LowLevel/Algorithm/HighResolutionFeatures.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+-- | HighResolutionFeatures algorithm: https://essentia.upf.edu/documentation/reference/streaming_HighResolutionFeatures.html .+module AcousticBrainz.LowLevel.Algorithm.HighResolutionFeatures where++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data Output = Output+  { _tuningEqualTemperedDeviation :: Scientific+  , _tuningNonTemperedEnergyRatio :: Scientific+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "HighResolutionFeatures" $ \v -> Output+    <$> v .: "tuning_equal_tempered_deviation"+    <*> v .: "tuning_nontempered_energy_ratio"
+ src/AcousticBrainz/LowLevel/Algorithm/Key.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Key algorithm: https://essentia.upf.edu/documentation/reference/streaming_Key.html .+module AcousticBrainz.LowLevel.Algorithm.Key where++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data Output = Output+  { _key :: Text+  , _scale :: Scale+  , _strength :: Scientific+  , _tuningDiatonicStrength :: Scientific+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "key" $ \v -> Output+    <$> v .: "key_key"+    <*> v .: "key_scale"+    <*> v .: "key_strength"+    <*> v .: "tuning_diatonic_strength"++-- data Key = A | B | C | D | E | F | G deriving(Eq, Ord, Read, Show)++-- instance FromJSON Key where+--   parseJSON = withText "key" $ \t ->+--     either (const $ fail $ "Invalid key: " <> toString t) return $ readEither t++data Scale = Major | Minor deriving(Eq, Ord, Read, Show)++instance FromJSON Scale where+  parseJSON = withText "scale" $ \t ->+    if t == "major"+    then return Major+    else if t == "minor"+    then return Minor+    else fail $ "Invalid key scale: " <> toString t
+ src/AcousticBrainz/LowLevel/Algorithm/SilenceRate.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+-- | SilenceRate algorithm: https://essentia.upf.edu/documentation/reference/streaming_SilenceRate.html .+module AcousticBrainz.LowLevel.Algorithm.SilenceRate where++import           AcousticBrainz.StatisticalUnits++import           Data.Aeson+import           Data.Aeson.Types+++data Output = Output+  { _20dB :: StatisticalUnits+  , _30dB :: StatisticalUnits+  , _60dB :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Output where+  parseJSON = withObject "SilenceRate" $ \v -> Output+    <$> v .: "silence_rate_20dB"+    <*> v .: "silence_rate_30dB"+    <*> v .: "silence_rate_60dB"
+ src/AcousticBrainz/LowLevel/Bands.hs view
@@ -0,0 +1,12 @@+module AcousticBrainz.LowLevel.Bands where++import           AcousticBrainz.StatisticalUnits++data Bands = Bands+  { _bands      :: [StatisticalUnits]+  , _crest      :: StatisticalUnits+  , _flatnessDb :: StatisticalUnits+  , _kurtosis   :: StatisticalUnits+  , _skewness   :: StatisticalUnits+  , _spread     :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)
+ src/AcousticBrainz/LowLevel/BarkBands.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.LowLevel.BarkBands where++import           AcousticBrainz.StatisticalUnits+import           AcousticBrainz.LowLevel.Bands++import           Data.Aeson                         as JSON+import           Data.Aeson.Types                   as JSON++newtype BarkBands = BarkBands Bands deriving(Eq, Ord, Read, Show)++instance FromJSON BarkBands where+  parseJSON = withObject "BarkBands" $ \v -> fmap BarkBands (Bands+    <$> (_statisticalUnitsList <$> v .: "barkbands")+    <*> v .: "barkbands_crest"+    <*> v .: "barkbands_flatness_db"+    <*> v .: "barkbands_kurtosis"+    <*> v .: "barkbands_skewness"+    <*> v .: "barkbands_spread")
+ src/AcousticBrainz/LowLevel/CepstralCoefficients.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.LowLevel.CepstralCoefficients where++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data CepstralCoefficients = CepstralCoefficients+  { _covariance        :: [[Scientific]]+  , _inverseCovariance :: [[Scientific]]+  , _mean              :: [Scientific]+  } deriving(Eq, Ord, Read, Show)++instance FromJSON CepstralCoefficients where+  parseJSON = withObject "CepstralCoefficients" $ \v -> CepstralCoefficients+    <$> v .: "cov"+    <*> v .: "icov"+    <*> v .: "mean"
+ src/AcousticBrainz/LowLevel/EquivalentRectangularBandwidth.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.LowLevel.EquivalentRectangularBandwidth where++import           AcousticBrainz.StatisticalUnits+import qualified AcousticBrainz.LowLevel.Bands      as Generic++import           Data.Aeson                         as JSON+import           Data.Aeson.Types                   as JSON++newtype Bands = Bands Generic.Bands deriving(Eq, Ord, Read, Show)++instance FromJSON Bands where+  parseJSON = withObject "Equivalent Rectangular Bandwidth Bands" $ \v -> fmap Bands (Generic.Bands+    <$> (_statisticalUnitsList <$> v .: "erbbands")+    <*> v .: "erbbands_crest"+    <*> v .: "erbbands_flatness_db"+    <*> v .: "erbbands_kurtosis"+    <*> v .: "erbbands_skewness"+    <*> v .: "erbbands_spread")
+ src/AcousticBrainz/LowLevel/MelBands.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.LowLevel.MelBands where++import           AcousticBrainz.StatisticalUnits+import           AcousticBrainz.LowLevel.Bands++import           Data.Aeson+import           Data.Aeson.Types++newtype MelBands = MelBands Bands deriving(Eq, Ord, Read, Show)++instance FromJSON MelBands where+  parseJSON = withObject "MelBands" $ \v -> fmap MelBands (Bands+    <$> (_statisticalUnitsList <$> v .: "melbands")+    <*> v .: "melbands_crest"+    <*> v .: "melbands_flatness_db"+    <*> v .: "melbands_kurtosis"+    <*> v .: "melbands_skewness"+    <*> v .: "melbands_spread")
+ src/AcousticBrainz/LowLevel/Rhythm.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Rhythm descriptors.+module AcousticBrainz.LowLevel.Rhythm where++import           AcousticBrainz.StatisticalUnits+import qualified AcousticBrainz.LowLevel.Algorithm.BpmHistogramDescriptors as BpmHistogramDescriptors++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific++data Rhythm = Rhythm+  { _beatsCount              :: Int+  , _beatsLoudness           :: StatisticalUnits+  , _beatsLoudnessBandRatio  :: [StatisticalUnits]+  , _beatsPosition           :: [Scientific]+  , _beatsPerMinute          :: Scientific+  , _beatsPerMinuteHistogram :: BpmHistogramDescriptors.Output+  , _danceability            :: Scientific+  , _onsetRate               :: Scientific+  } deriving(Eq, Read, Show)++instance FromJSON Rhythm where+  parseJSON value = flip (withObject "rhythm") value $ \v -> Rhythm+    <$> v .: "beats_count"+    <*> v .: "beats_loudness"+    <*> (_statisticalUnitsList <$> v .: "beats_loudness_band_ratio")+    <*> v .: "beats_position"+    <*> v .: "bpm"+    <*> parseJSON value+    <*> v .: "danceability"+    <*> v .: "onset_rate"
+ src/AcousticBrainz/LowLevel/Spectral.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.LowLevel.Spectral where++import           AcousticBrainz.StatisticalUnits+import qualified AcousticBrainz.LowLevel.Algorithm.EnergyBand as EnergyBand++import           Data.Aeson+import           Data.Aeson.Types++data Spectral = Spectral+  { _centroid             :: StatisticalUnits+  , _complexity           :: StatisticalUnits+  , _contrastCoefficients :: [StatisticalUnits]+  , _contrastValleys      :: [StatisticalUnits]+  , _decrease             :: StatisticalUnits+  , _energy               :: StatisticalUnits+  , _energyBand           :: EnergyBand.Output+  , _entropy              :: StatisticalUnits+  , _flux                 :: StatisticalUnits+  , _kurtosis             :: StatisticalUnits+  , _rms                  :: StatisticalUnits+  , _rolloff              :: StatisticalUnits+  , _skewness             :: StatisticalUnits+  , _spread               :: StatisticalUnits+  , _strongPeak           :: StatisticalUnits+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Spectral where+  parseJSON value = flip (withObject "spectral") value $ \v -> Spectral+    <$> v .: "spectral_centroid"+    <*> v .: "spectral_complexity"+    <*> (_statisticalUnitsList <$> v .: "spectral_contrast_coeffs")+    <*> (_statisticalUnitsList <$> v .: "spectral_contrast_valleys")+    <*> v .: "spectral_decrease"+    <*> v .: "spectral_energy"+    <*> parseJSON value+    <*> v .: "spectral_entropy"+    <*> v .: "spectral_flux"+    <*> v .: "spectral_kurtosis"+    <*> v .: "spectral_rms"+    <*> v .: "spectral_rolloff"+    <*> v .: "spectral_skewness"+    <*> v .: "spectral_spread"+    <*> v .: "spectral_strongpeak"
+ src/AcousticBrainz/LowLevel/Tonal.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Tonal descriptors.+module AcousticBrainz.LowLevel.Tonal where++import           AcousticBrainz.StatisticalUnits+import qualified AcousticBrainz.LowLevel.Algorithm.Chords                 as Chords+import qualified AcousticBrainz.LowLevel.Algorithm.HighResolutionFeatures as HighResolutionFeatures+import qualified AcousticBrainz.LowLevel.Algorithm.HPCP                   as HPCP+import qualified AcousticBrainz.LowLevel.Algorithm.Key                    as Key++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific++data Tonal = Tonal+  { _chords                            :: Chords.Output+  , _highResolutionFeatures            :: HighResolutionFeatures.Output+  , _hpcp                              :: HPCP.Output+  , _harmonicPitchClassProfilesEntropy :: StatisticalUnits+  , _key                               :: Key.Output+  , _tuningFrequency                   :: Scientific+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Tonal where+  parseJSON value = flip (withObject "tonal") value $ \v -> Tonal+    <$> parseJSON value+    <*> parseJSON value+    <*> parseJSON value+    <*> v .: "hpcp_entropy"+    <*> parseJSON value+    <*> v .: "tuning_frequency"+
+ src/AcousticBrainz/MetaData.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings     #-}+module AcousticBrainz.MetaData where++import           AcousticBrainz.Version++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data MetaData = MetaData+  { _audioProperties :: AudioProperties+  , _tags            :: Tags+  , _version         :: Version+  } deriving(Eq, Read, Show)++++data AudioProperties = AudioProperties+  { _analysisSampleRate :: Natural+  , _bitRate            :: Natural+  , _codec              :: Text+  , _downmix            :: Text+  , _equalLoudness      :: Natural+  , _length             :: Scientific+  , _lossless           :: Bool+  , _md5Encoded         :: Text+  , _replayGain         :: Scientific+  , _sampleRate         :: Natural+  } deriving(Eq, Ord, Read, Show)++instance FromJSON AudioProperties where+  parseJSON = withObject "audio_properties" $ \v -> AudioProperties+    <$> v .: "analysis_sample_rate"+    <*> v .: "bit_rate"+    <*> v .: "codec"+    <*> v .: "downmix"+    <*> v .: "equal_loudness"+    <*> v .: "length"+    <*> ((int2bool <$> v .: "lossless") <|> v .: "lossless")+    <*> v .: "md5_encoded"+    <*> v .: "replay_gain"+    <*> v .: "sample_rate"+    where int2bool :: Int -> Bool+          int2bool 0 = False+          int2bool _ = True+++newtype Tags = Tags (HashMap Text Value) deriving(Eq, Read, Show)++instance FromJSON Tags where+  parseJSON = withObject "tags" $ pure . Tags
+ src/AcousticBrainz/StatisticalUnits.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE PartialTypeSignatures #-}+-- | Result of PoolAggregator algorithm: https://essentia.upf.edu/documentation/reference/streaming_PoolAggregator.html .+module AcousticBrainz.StatisticalUnits where++import           Data.Aeson+import           Data.Aeson.Types+import           Data.Scientific+++data StatisticalUnits = StatisticalUnits+  { _meanOfDerivative :: Scientific+  , _meanOfSecondDerivative :: Scientific+  , _varianceOfDerivative :: Scientific+  , _varianceOfSecondDerivative :: Scientific+  , _maximum :: Scientific+  , _mean :: Scientific+  , _median :: Scientific+  , _minimum :: Scientific+  , _variance :: Scientific+  } deriving(Eq, Ord, Read, Show)++instance FromJSON StatisticalUnits where+  parseJSON = withObject "distribution metrics" $ \v -> StatisticalUnits+    <$> v .: "dmean"+    <*> v .: "dmean2"+    <*> v .: "dvar"+    <*> v .: "dvar2"+    <*> v .: "max"+    <*> v .: "mean"+    <*> v .: "median"+    <*> v .: "min"+    <*> v .: "var"++-- | This newtype is only useful for the associated 'FromJSON' instance.+newtype StatisticalUnitsList = StatisticalUnitsList { _statisticalUnitsList :: [StatisticalUnits] }++instance FromJSON StatisticalUnitsList where+  parseJSON = withObject "distribution metrics list" $ \v -> do+    dmean  <- v .: "dmean"+    dmean2 <- v .: "dmean2"+    dvar <- v .: "dvar"+    dvar2 <- v .: "dvar2"+    max <- v .: "max"+    mean <- v .: "mean"+    median <- v .: "median"+    min <- v .: "min"+    var <- v .: "var"+    let a = zipWith StatisticalUnits dmean dmean2+        b = zipWith ($) a dvar+        c = zipWith ($) b dvar2+        d = zipWith ($) c max+        e = zipWith ($) d mean+        f = zipWith ($) e median+        g = zipWith ($) f min+        h = zipWith ($) g var+    return $ StatisticalUnitsList h
+ src/AcousticBrainz/Version.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE OverloadedStrings #-}+module AcousticBrainz.Version where++import           Data.Aeson+import           Data.Aeson.Types+++data Version = Version+  { _essentia             :: EssentiaVersion+  , _extractor            :: Text+  , _gaia                 :: Maybe GaiaVersion+  , _modelsEssentiaGitSha :: Text+  } deriving(Eq, Ord, Read, Show)++instance FromJSON Version where+  parseJSON value = flip (withObject "version") value $ \v -> Version+    <$> parseJSON value+    <*> v .: "extractor"+    <*> ((Just <$> parseJSON value) <|> pure Nothing)+    <*> (v .: "models_essentia_git_sha" <|> pure mempty)++-- | Cf https://github.com/MTG/essentia/releases .+data EssentiaVersion = EssentiaVersion+  { _essentiaVersion  :: Text+  , _essentiaBuildSha :: Text+  , _essentiaGitSha   :: Text+  } deriving(Eq, Ord, Read, Show)++instance FromJSON EssentiaVersion where+  parseJSON = withObject "EssentiaVersion" $ \v -> EssentiaVersion+    <$> v .: "essentia"+    <*> v .: "essentia_build_sha"+    <*> v .: "essentia_git_sha"++-- | Cf https://github.com/MTG/gaia/releases .+data GaiaVersion = GaiaVersion+  { _gaiaVersion :: Text+  , _gaiaGitSha  :: Text+  } deriving(Eq, Ord, Read, Show)++instance FromJSON GaiaVersion where+  parseJSON = withObject "GaiaVersion" $ \v -> GaiaVersion+    <$> v .: "gaia"+    <*> v .: "gaia_git_sha"
+ src/MusicBrainz.hs view
@@ -0,0 +1,16 @@+module MusicBrainz where++import           Control.Monad.Catch+import           Data.Text           as Text+++-- | Cf https://musicbrainz.org/doc/MusicBrainz_Identifier .+newtype MusicBrainzIdentifier = MusicBrainzIdentifier Text deriving(Eq, Ord, Read, Show)++newtype InvalidMusicBrainzIdentifier = InvalidMusicBrainzIdentifier Text deriving(Eq, Ord, Read, Show)+instance Exception InvalidMusicBrainzIdentifier++mkMusicBrainzIdentifier :: MonadThrow m => Text -> m MusicBrainzIdentifier+mkMusicBrainzIdentifier rawIdentifier = do+  when (Text.length rawIdentifier /= 36) $ throwM $ InvalidMusicBrainzIdentifier rawIdentifier+  return $ MusicBrainzIdentifier rawIdentifier
+ src/Prelude.hs view
@@ -0,0 +1,6 @@+module Prelude (module Relude, io) where++import Relude++io :: MonadIO m => IO a -> m a+io = liftIO
+ test/GoldenTests.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+import           AcousticBrainz.HighLevel+import           AcousticBrainz.LowLevel+import           MusicBrainz++import           Data.Aeson+import           Data.String+import qualified Data.Text                as Text+import qualified Data.Text.IO             as Text+import qualified Data.Text.Lazy.Encoding  as Lazy+import           System.FilePath+import           Test.Tasty+import           Test.Tasty.Golden        (findByExtension, goldenVsString)+import           Text.Pretty.Simple++main :: IO ()+main = do+  a <- highLevelTests+  b <- lowLevelTests+  defaultMain $ testGroup "Golden tests" [a, b]++highLevelTests :: IO TestTree+highLevelTests = do+  files <- findByExtension [".highlevel"] "test"++  return $ testGroup "High level tests" $ do+    file <- files+    let goldenFile = addExtension file ".golden"+    return $ goldenVsString file goldenFile $+      Text.readFile file <&> Text.strip >>= mkMusicBrainzIdentifier >>= getHighLevelData <&> pShowNoColor <&> Lazy.encodeUtf8++lowLevelTests :: IO TestTree+lowLevelTests = do+  files <- findByExtension [".lowlevel"] "test"++  return $ testGroup "Low level tests" $ do+    file <- files+    let goldenFile = addExtension file ".golden"+    return $ goldenVsString file goldenFile $+      Text.readFile file <&> Text.strip >>= mkMusicBrainzIdentifier >>= getLowLevelData <&> pShowNoColor <&> Lazy.encodeUtf8
+ test/Prelude.hs view
@@ -0,0 +1,6 @@+module Prelude (module Relude, io) where++import Relude++io :: MonadIO m => IO a -> m a+io = liftIO