-- | SHARC XML file IO. module Sound.Analysis.SHARC where import Text.XML.Light {- xml -} -- | A SHARC instrument. data Instrument = Instrument {instrument_id :: String ,notes :: [Note]} -- | A SHARC note. data Note = Note {frequency :: Double ,partials :: [Partial]} -- | A SHARC partial. data Partial = Partial {partial :: Int ,amplitude :: Double ,phase :: Double} -- | Read a SHARC XML file. read_sharc :: FilePath -> IO (Either String [Instrument]) read_sharc fn = do s <- readFile fn return (case parseXMLDoc s of Just e -> Right (parse_tree e) Nothing -> Left "parse failed") -- * Partial analysis -- | Calculate the frequency of a 'Partial' given the fundamental frequency. -- -- > partial_frequency 440 (Partial 3 0.1 0) == 1320 partial_frequency :: Double -> Partial -> Double partial_frequency f = (* f) . fromIntegral . partial -- | Calculate the @(frequency,amplitude,phase)@ triple of a -- 'Partial' given the fundamental frequency. -- -- > partial_triple 440 (Partial 3 0.1 pi) == (1320,0.1,pi) partial_triple :: Double -> Partial -> (Double,Double,Double) partial_triple f p = (partial_frequency f p,amplitude p,phase p) -- * Note analysis -- | Translate a 'Note' into (/frequency/,/amplitude/,/phase/) triples. -- -- > note_spectra (Note 440 [Partial 3 0.1 pi]) == [(1320,0.1,pi)] note_spectra :: Note -> [(Double,Double,Double)] note_spectra n = let f = frequency n p = partials n f' = map ((* f) . fromIntegral . partial) p in zip3 f' (map amplitude p) (map phase p) -- | The number of partials at a 'Note'. note_n_partials :: Note -> Int note_n_partials = length . partials -- * Amplitude transformations -- | Apply a linear scalar to the amplitude of a 'Partial'. partial_scale_amplitude :: Double -> Partial -> Partial partial_scale_amplitude x (Partial i a p) = Partial i (x * a) p -- | Apply a linear scalar to the amplitudes of all 'Partial's at a 'Note'. note_scale_amplitude :: Double -> Note -> Note note_scale_amplitude x (Note f p) = let p' = map (partial_scale_amplitude x) p in Note f p' -- | Find the minimum and maximum amplitudes of all 'Partial's at a -- 'Note'. note_amplitude_minmax :: Note -> (Double,Double) note_amplitude_minmax n = let a = map amplitude (partials n) in (minimum a,maximum a) -- | Normalise a 'Note' so the maximum amplitude of any 'Partial' is -- @1.0@. note_normalise :: Note -> Note note_normalise n = let (_,u) = note_amplitude_minmax n in note_scale_amplitude (recip u) n -- | Find the minimum and maximum amplitudes of all 'Partial's at all -- 'Note's of an 'Instrument'. instrument_amplitude_minmax :: Instrument -> (Double,Double) instrument_amplitude_minmax i = let (l,r) = unzip (map note_amplitude_minmax (notes i)) in (minimum l,maximum r) -- | Normalise an 'Instrument' so the maximum amplitude of any -- 'Partial' is @1.0@. instrument_normalise :: Instrument -> Instrument instrument_normalise i = let (_,u) = instrument_amplitude_minmax i n = notes i in i {notes = map (note_scale_amplitude (recip u)) n} -- * XML parsers -- | The root 'Element' of a SHARC xml graphs is a @tree@, which -- parses to a list of 'Instrument's. parse_tree :: Element -> [Instrument] parse_tree = map parse_instrument . findChildren (unqual "instrument") -- | Parse an @instrument@ 'Element'. parse_instrument :: Element -> Instrument parse_instrument e = let Just i = findAttr (unqual "id") e n = findChildren (unqual "note") e in Instrument i (map parse_note n) -- | Parse a @note@ 'Element'. parse_note :: Element -> Note parse_note e = let Just f = findAttr (unqual "fundHz") e p = findChildren (unqual "a") e in Note (read f) (map parse_partial p) -- | Parse an @a@ 'Element'. parse_partial :: Element -> Partial parse_partial e = let a = strContent e Just n = findAttr (unqual "n") e Just p = findAttr (unqual "p") e in Partial (read n) (read a) (read p)