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 +2/−0
- acousticbrainz-client.cabal +69/−0
- src/AcousticBrainz/FiniteDistribution.hs +29/−0
- src/AcousticBrainz/HighLevel.hs +186/−0
- src/AcousticBrainz/HighLevel/Dortmund.hs +25/−0
- src/AcousticBrainz/HighLevel/ElectronicClassification.hs +22/−0
- src/AcousticBrainz/HighLevel/ISMIR04Rhythm.hs +25/−0
- src/AcousticBrainz/HighLevel/MirexMood.hs +21/−0
- src/AcousticBrainz/HighLevel/Rosamerica.hs +23/−0
- src/AcousticBrainz/HighLevel/Tzanetakis.hs +26/−0
- src/AcousticBrainz/LowLevel.hs +91/−0
- src/AcousticBrainz/LowLevel/Algorithm/BpmHistogramDescriptors.hs +27/−0
- src/AcousticBrainz/LowLevel/Algorithm/Chords.hs +31/−0
- src/AcousticBrainz/LowLevel/Algorithm/EnergyBand.hs +24/−0
- src/AcousticBrainz/LowLevel/Algorithm/HPCP.hs +20/−0
- src/AcousticBrainz/LowLevel/Algorithm/HighResolutionFeatures.hs +18/−0
- src/AcousticBrainz/LowLevel/Algorithm/Key.hs +38/−0
- src/AcousticBrainz/LowLevel/Algorithm/SilenceRate.hs +21/−0
- src/AcousticBrainz/LowLevel/Bands.hs +12/−0
- src/AcousticBrainz/LowLevel/BarkBands.hs +19/−0
- src/AcousticBrainz/LowLevel/CepstralCoefficients.hs +19/−0
- src/AcousticBrainz/LowLevel/EquivalentRectangularBandwidth.hs +19/−0
- src/AcousticBrainz/LowLevel/MelBands.hs +19/−0
- src/AcousticBrainz/LowLevel/Rhythm.hs +32/−0
- src/AcousticBrainz/LowLevel/Spectral.hs +44/−0
- src/AcousticBrainz/LowLevel/Tonal.hs +32/−0
- src/AcousticBrainz/MetaData.hs +52/−0
- src/AcousticBrainz/StatisticalUnits.hs +58/−0
- src/AcousticBrainz/Version.hs +44/−0
- src/MusicBrainz.hs +16/−0
- src/Prelude.hs +6/−0
- test/GoldenTests.hs +41/−0
- test/Prelude.hs +6/−0
+ 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