diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## HTagLib 1.2.1
+
+* Maintenance release with more modern and minimal dependencies.
+
 ## HTagLib 1.2.0
 
 * Compiles with GHC 8.4.1.
diff --git a/LICENSE.md b/LICENSE.md
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-Copyright © 2015–2018 Mark Karpov
+Copyright © 2015–present Mark Karpov
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![Hackage](https://img.shields.io/hackage/v/htaglib.svg?style=flat)](https://hackage.haskell.org/package/htaglib)
 [![Stackage Nightly](http://stackage.org/package/htaglib/badge/nightly)](http://stackage.org/nightly/package/htaglib)
 [![Stackage LTS](http://stackage.org/package/htaglib/badge/lts)](http://stackage.org/lts/package/htaglib)
-[![Build Status](https://travis-ci.org/mrkkrp/htaglib.svg?branch=master)](https://travis-ci.org/mrkkrp/htaglib)
+![CI](https://github.com/mrkkrp/htaglib/workflows/CI/badge.svg?branch=master)
 
 * [Alternatives](#alternatives)
 * [A note for FLAC users](#a-note-for-flac-users)
@@ -14,8 +14,8 @@
     * [Conclusion](#conclusion)
 * [License](#license)
 
-This is Haskell bindings to [TagLib](https://taglib.github.io/), library for
-reading and editing meta-data of several popular audio formats.
+This is Haskell bindings to [TagLib](https://taglib.github.io/), a library
+for reading and editing meta-data of several popular audio formats.
 
 It works with the following formats:
 
@@ -35,23 +35,21 @@
 
 ## Alternatives
 
-There is at least two Haskell bindings doing “the same” thing:
+There is at least two other Haskell bindings to TagLib:
 
 * [`libtagc`](https://hackage.haskell.org/package/libtagc)
 * [`taglib`](https://hackage.haskell.org/package/taglib)
 
-Both are very low level, without any protection or higher-level
-abstractions, not really type-safe. I personally don't want to use them, so
-I wrote this.
+Both are low level, without any type safety or higher-level abstractions.
 
 ## A note for FLAC users
 
-If you want to work with FLAC, there is
-a [complete Haskell binding](https://github.com/mrkkrp/flac) to
-`libFLAC`—reference FLAC implementation. It allows to work with all FLAC
-metadata (read and write) and also provides Haskell API to stream encoder
-and stream decoder. Please prefer that package if you don't need to work
-with other audio formats.
+If you want to work with FLAC, there is a [complete Haskell
+binding](https://github.com/mrkkrp/flac) to `libFLAC`—the reference FLAC
+implementation. It allows us to work with all FLAC metadata (read and write)
+and also provides a Haskell API to the stream encoder and the stream
+decoder. Please prefer that package if you don't need to work with other
+audio formats.
 
 ## Quick start
 
@@ -59,13 +57,6 @@
 install the library itself. If you're on a Unix-like system, chances are
 you'll have it in the official repositories of your distro.
 
-After installation of the library, install `htaglib` package using Cabal or
-Stack (recommended):
-
-```
-$ stack install htaglib
-```
-
 ### Reading meta data
 
 Now to the hacking. It's recommended that you define a record representing
@@ -79,25 +70,23 @@
 import System.Environment (getArgs)
 
 data AudioTrack = AudioTrack
-  { atTitle   :: Title
-  , atArtist  :: Artist
-  , atAlbum   :: Album
-  , atComment :: Comment
-  , atGenre   :: Genre
-  , atYear    :: Maybe Year
-  , atTrack   :: Maybe TrackNumber }
-  deriving Show
+  { atTitle :: Title,
+    atArtist :: Artist,
+    atAlbum :: Album,
+    atComment :: Comment,
+    atGenre :: Genre,
+    atYear :: Maybe Year,
+    atTrack :: Maybe TrackNumber
+  }
+  deriving (Show)
 ```
 
-A couple of notes here. We use unique types for every component of meta
-data, so it's more difficult to use track title in lieu of track artist, for
-example. Meta data that is represented by strings also has smart
-constructors, they replace zero bytes with spaces, this is necessary to
-avoid troubles when your Haskell strings go to the C-level (well, zero-bytes
-in strings is rather edge case, but it should be mentioned). Of course,
-`Title`, `Artist`, `Album`, `Comment`, and `Genre` all are instances of
-`IsString`, so just turn on `OverloadedStrings` and you can use normal
-string literals to create data of these types.
+We use unique types for every component of meta data, so it's more difficult
+to use a track title instead of a track artist, for example. String meta
+data types have smart constructors, but `Title`, `Artist`, `Album`,
+`Comment`, and `Genre` all are instances of `IsString`, so it is enough to
+turn on the `OverloadedStrings` language extension to use normal string
+literals to create values of these types.
 
 `Year` and `TrackNumber` may be not set or missing, in this case you get
 `Nothing`. This is possible with string-based fields too, but in that case
@@ -106,26 +95,27 @@
 
 OK, it's time to read some info. There is `TagGetter` type which is an
 applicative functor. You first construct `TagGetter` which will retrieve
-entire `AudioTrack` for you using applicative style:
+entire `AudioTrack` for you using the applicative style:
 
 ```haskell
 audioTrackGetter :: TagGetter AudioTrack
-audioTrackGetter = AudioTrack
-  <$> titleGetter
-  <*> artistGetter
-  <*> albumGetter
-  <*> commentGetter
-  <*> genreGetter
-  <*> yearGetter
-  <*> trackNumberGetter
+audioTrackGetter =
+  AudioTrack
+    <$> titleGetter
+    <*> artistGetter
+    <*> albumGetter
+    <*> commentGetter
+    <*> genreGetter
+    <*> yearGetter
+    <*> trackNumberGetter
 ```
 
-Perfect, now use `getTags` to read entire record:
+Perfect, now use `getTags` to read the entire record:
 
 ```haskell
 main :: IO ()
 main = do
-  path  <- head <$> getArgs
+  path <- head <$> getArgs
   track <- getTags path audioTrackGetter
   print track
 ```
@@ -135,13 +125,13 @@
 ```
 $ ./example "/home/mark/music/David Bowie/1977, Low/01 Speed of Life.flac"
 AudioTrack
-  { atTitle   = Title   "Speed of Life"
-  , atArtist  = Artist  "David Bowie"
-  , atAlbum   = Album   "Low"
-  , atComment = Comment ""
-  , atGenre   = Genre   ""
-  , atYear    = Just    (Year 1977)
-  , atTrack   = Just    (TrackNumber 1)
+  { atTitle = Title "Speed of Life",
+    atArtist = Artist "David Bowie",
+    atAlbum = Album "Low",
+    atComment = Comment "",
+    atGenre = Genre "",
+    atYear = Just (Year 1977),
+    atTrack = Just (TrackNumber 1)
   }
 ```
 
@@ -149,49 +139,42 @@
 etc. but it's not shown here for simplicity, consult Haddocks for more
 information.
 
-N.B. If you need to extract duration of tracks, TagLib only returns number
-of seconds as an integer. This means that if you want to calculate total
-duration, you'll have slightly incorrect result. Proper solution is to
-extract duration as floating-point number, for that we recommend bindings to
+N.B. If you need to extract the duration, TagLib only returns the number of
+seconds as an integer. This means that if you want to calculate the total
+duration of an album, you'll get a slightly incorrect result. The right
+solution is to extract the duration as floating-point number, for that we
+recommend the bindings to
 `libsndfile`—[`hsndfile`](https://hackage.haskell.org/package/hsndfile) (or
 the above-mentioned `flac` package for Haskell if you work with FLAC).
 
 ### Writing meta data
 
-We cannot use applicative interface to set tags. There are several reasons:
-
-* Applicative interface in general is better for extracting or parsing (or
-  rather assembling complex parsers from more basic ones).
-
-* Some fields like sample rate or length can only be read, not set.
-
-* We may wish to set one or two fields selectively, not everything.
-
-Solution: use monoids. `TagSetter` is an instance of `Monoid`. This means
-that we can set title and artist of audio track like this:
+`TagSetter` is an instance of `Monoid`. This means that we can set title and
+artist of audio track like this:
 
 ```haskell
 main :: IO ()
 main = do
   (path : title : artist : _) <- getArgs
   setTags path Nothing $
-    titleSetter (mkTitle title) <>
-    artistSetter (mkArtist artist)
+    titleSetter (mkTitle title)
+      <> artistSetter (mkArtist artist)
   track <- getTags path audioTrackGetter
   print track
 ```
 
-This code loads file and changes “title” and “artist” meta data fields.
+This code loads a file and changes the “title” and “artist” meta data
+fields.
 
-## Conclusion
+## Contribution
 
-With the interface provided by `getTags` and `setTags` it's not possible to
-forget to close file or free some resource. You can read all meta data at
-once directly into your data structure in type-safe manner. Writing meta
-data should be trivial too. Have fun!
+Issues, bugs, and questions may be reported in [the GitHub issue tracker for
+this project](https://github.com/mrkkrp/htaglib/issues).
 
+Pull requests are also welcome.
+
 ## License
 
-Copyright © 2015–2018 Mark Karpov
+Copyright © 2015–present Mark Karpov
 
 Distributed under BSD 3 clause license.
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Main (main) where
-
-import Distribution.Simple
-
-main :: IO ()
-main = defaultMain
diff --git a/Sound/HTagLib.hs b/Sound/HTagLib.hs
--- a/Sound/HTagLib.hs
+++ b/Sound/HTagLib.hs
@@ -1,79 +1,81 @@
 -- |
 -- Module      :  Sound.HTagLib
--- Copyright   :  © 2015–2018 Mark Karpov
+-- Copyright   :  © 2015–present Mark Karpov
 -- License     :  BSD 3 clause
 --
 -- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- This module provides a complete high-level interface to TagLib. This is
--- the module you should import to use in your projects.
-
+-- This module provides an interface to TagLib. This is the module you
+-- should import to use in your projects.
 module Sound.HTagLib
   ( -- * Getters
-    getTags
-  , getTags'
-  , titleGetter
-  , artistGetter
-  , albumGetter
-  , commentGetter
-  , genreGetter
-  , yearGetter
-  , trackNumberGetter
-  , durationGetter
-  , bitRateGetter
-  , sampleRateGetter
-  , channelsGetter
+    getTags,
+    getTags',
+    titleGetter,
+    artistGetter,
+    albumGetter,
+    commentGetter,
+    genreGetter,
+    yearGetter,
+    trackNumberGetter,
+    durationGetter,
+    bitRateGetter,
+    sampleRateGetter,
+    channelsGetter,
+
     -- * Setters
-  , setTags
-  , setTags'
-  , titleSetter
-  , artistSetter
-  , albumSetter
-  , commentSetter
-  , genreSetter
-  , yearSetter
-  , trackNumberSetter
+    setTags,
+    setTags',
+    titleSetter,
+    artistSetter,
+    albumSetter,
+    commentSetter,
+    genreSetter,
+    yearSetter,
+    trackNumberSetter,
+
     -- * Data types
-  , Title
-  , mkTitle
-  , unTitle
-  , Artist
-  , mkArtist
-  , unArtist
-  , Album
-  , mkAlbum
-  , unAlbum
-  , Comment
-  , mkComment
-  , unComment
-  , Genre
-  , mkGenre
-  , unGenre
-  , Year
-  , mkYear
-  , unYear
-  , TrackNumber
-  , mkTrackNumber
-  , unTrackNumber
-  , Duration
-  , mkDuration
-  , unDuration
-  , BitRate
-  , mkBitRate
-  , unBitRate
-  , SampleRate
-  , mkSampleRate
-  , unSampleRate
-  , Channels
-  , mkChannels
-  , unChannels
-  , FileType (..)
-  , ID3v2Encoding (..)
-  , TagGetter
-  , TagSetter
-  , HTagLibException (..) )
+    Title,
+    mkTitle,
+    unTitle,
+    Artist,
+    mkArtist,
+    unArtist,
+    Album,
+    mkAlbum,
+    unAlbum,
+    Comment,
+    mkComment,
+    unComment,
+    Genre,
+    mkGenre,
+    unGenre,
+    Year,
+    mkYear,
+    unYear,
+    TrackNumber,
+    mkTrackNumber,
+    unTrackNumber,
+    Duration,
+    mkDuration,
+    unDuration,
+    BitRate,
+    mkBitRate,
+    unBitRate,
+    SampleRate,
+    mkSampleRate,
+    unSampleRate,
+    Channels,
+    mkChannels,
+    unChannels,
+    FileType (..),
+    ID3v2Encoding (..),
+    TagGetter,
+    TagSetter,
+    HTagLibException (..),
+  )
 where
 
 import Sound.HTagLib.Getter
diff --git a/Sound/HTagLib/Getter.hs b/Sound/HTagLib/Getter.hs
--- a/Sound/HTagLib/Getter.hs
+++ b/Sound/HTagLib/Getter.hs
@@ -1,145 +1,139 @@
+{-# LANGUAGE DeriveFunctor #-}
+
 -- |
 -- Module      :  Sound.HTagLib.Getter
--- Copyright   :  © 2015–2018 Mark Karpov
+-- Copyright   :  © 2015–present Mark Karpov
 -- License     :  BSD 3 clause
 --
 -- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- A high-level applicative interface for reading of audio meta data. You
--- don't need to import this module directly, import "Sound.HTagLib"
--- instead.
-
-{-# LANGUAGE CPP #-}
-
+-- An applicative interface for reading of audio meta data. You don't need
+-- to import this module directly, import "Sound.HTagLib" instead.
 module Sound.HTagLib.Getter
   ( -- * High-level API
-    TagGetter
-  , getTags
-  , getTags'
+    TagGetter,
+    getTags,
+    getTags',
+
     -- * Built-in getters
-  , titleGetter
-  , artistGetter
-  , albumGetter
-  , commentGetter
-  , genreGetter
-  , yearGetter
-  , trackNumberGetter
-  , durationGetter
-  , bitRateGetter
-  , sampleRateGetter
-  , channelsGetter )
+    titleGetter,
+    artistGetter,
+    albumGetter,
+    commentGetter,
+    genreGetter,
+    yearGetter,
+    trackNumberGetter,
+    durationGetter,
+    bitRateGetter,
+    sampleRateGetter,
+    channelsGetter,
+  )
 where
 
 import Control.Monad.IO.Class
+import Sound.HTagLib.Internal qualified as I
 import Sound.HTagLib.Type
-import qualified Sound.HTagLib.Internal as I
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
-
--- | A composable entity that can be used with 'getTags' or 'getTags''
--- functions to read batch of meta parameters.
-
-newtype TagGetter a = TagGetter { runGetter :: I.FileId -> IO a }
-
-instance Functor TagGetter where
-  fmap f x = TagGetter $ \fid -> f <$> runGetter x fid
+-- | A composable entity that can be passed to the 'getTags' or 'getTags''
+-- functions to read multiple meta data fields at once.
+newtype TagGetter a = TagGetter {runGetter :: I.FileId -> IO a}
+  deriving (Functor)
 
 instance Applicative TagGetter where
-  pure    = TagGetter . const . return
-  x <*> y = TagGetter $ \fid ->
-    do f <- runGetter x fid
-       f <$> runGetter y fid
+  pure = TagGetter . const . return
+  x <*> y = TagGetter $ \fid -> do
+    f <- runGetter x fid
+    f <$> runGetter y fid
 
--- | @getTags path g@ will try to read file located at @path@ and read meta
--- data of the file using getter @g@. Type of file will be guessed from its
--- extension. If this is not satisfactory and you want to explicitly specify
--- the file type, see 'getTags'' variation of this function.
+-- | @getTags path g@ will try to read the file located at @path@ and read
+-- the meta data of the file using the getter @g@. The type of the file will
+-- be guessed from its extension. If this is not satisfactory and you want
+-- to explicitly specify the file type, see 'getTags'' variation of this
+-- function.
 --
--- In the case of trouble 'I.HTagLibException' will be thrown.
-
-getTags :: MonadIO m
-  => FilePath          -- ^ Path to audio file
-  -> TagGetter a       -- ^ Getter
-  -> m a              -- ^ Extracted data
+-- Throws 'I.HTagLibException'.
+getTags ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Getter
+  TagGetter a ->
+  -- | Extracted data
+  m a
 getTags path = execGetter path Nothing
 
--- | This is essentially the same as 'getTags', but allows to explicitly
+-- | This is essentially the same as 'getTags', but allows us to explicitly
 -- choose file type (see 'FileType').
-
-getTags' :: MonadIO m
-  => FilePath          -- ^ Path to audio file
-  -> FileType          -- ^ Type of audio file
-  -> TagGetter a       -- ^ Getter
-  -> m a               -- ^ Extracted data
+getTags' ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Type of audio file
+  FileType ->
+  -- | Getter
+  TagGetter a ->
+  -- | Extracted data
+  m a
 getTags' path t = execGetter path (Just t)
 
 -- | This is the most general way to read meta data from file. 'getTags' and
 -- 'getTags'' are just wrappers around the function.
-
-execGetter :: MonadIO m
-  => FilePath         -- ^ Path to audio file
-  -> Maybe FileType   -- ^ Type of audio file (if known)
-  -> TagGetter a      -- ^ Getter
-  -> m a              -- ^ Extracted data
+execGetter ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Type of audio file (if known)
+  Maybe FileType ->
+  -- | Getter
+  TagGetter a ->
+  -- | Extracted data
+  m a
 execGetter path t = liftIO . I.withFile path t . runGetter
 
--- | Getter to retrieve track title.
-
+-- | Getter to retrieve the track title.
 titleGetter :: TagGetter Title
 titleGetter = TagGetter I.getTitle
 
--- | Getter to retrieve track artist.
-
+-- | Getter to retrieve the track artist.
 artistGetter :: TagGetter Artist
 artistGetter = TagGetter I.getArtist
 
--- | Getter to retrieve track album.
-
+-- | Getter to retrieve the track album.
 albumGetter :: TagGetter Album
 albumGetter = TagGetter I.getAlbum
 
--- | Getter to retrieve track comment.
-
+-- | Getter to retrieve the track comment.
 commentGetter :: TagGetter Comment
 commentGetter = TagGetter I.getComment
 
--- | Getter to retrieve genre of the track.
-
+-- | Getter to retrieve the genre of the track.
 genreGetter :: TagGetter Genre
 genreGetter = TagGetter I.getGenre
 
--- | Getter to retrieve year to the track (returns 'Nothing' if the data is
--- missing).
-
+-- | Getter to retrieve the year of the track (returns 'Nothing' if the data
+-- is missing).
 yearGetter :: TagGetter (Maybe Year)
 yearGetter = TagGetter I.getYear
 
--- | Getter to retrieve track number (returns 'Nothing' if the data is
+-- | Getter to retrieve the track number (returns 'Nothing' if the data is
 -- missing).
-
 trackNumberGetter :: TagGetter (Maybe TrackNumber)
 trackNumberGetter = TagGetter I.getTrackNumber
 
--- | Getter to retrieve duration in seconds.
-
+-- | Getter to retrieve the duration in seconds.
 durationGetter :: TagGetter Duration
 durationGetter = TagGetter I.getDuration
 
--- | Getter to retrieve bit rate.
-
+-- | Getter to retrieve the bit rate.
 bitRateGetter :: TagGetter BitRate
 bitRateGetter = TagGetter I.getBitRate
 
--- | Getter to retrieve sample rate.
-
+-- | Getter to retrieve the sample rate.
 sampleRateGetter :: TagGetter SampleRate
 sampleRateGetter = TagGetter I.getSampleRate
 
--- | Getter to retrieve number of channels in audio data.
-
+-- | Getter to retrieve the number of channels of the audio data.
 channelsGetter :: TagGetter Channels
 channelsGetter = TagGetter I.getChannels
diff --git a/Sound/HTagLib/Internal.hs b/Sound/HTagLib/Internal.hs
--- a/Sound/HTagLib/Internal.hs
+++ b/Sound/HTagLib/Internal.hs
@@ -1,51 +1,53 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
 -- |
 -- Module      :  Sound.HTagLib.Internal
--- Copyright   :  © 2015–2018 Mark Karpov
+-- Copyright   :  © 2015–present Mark Karpov
 -- License     :  BSD 3 clause
 --
 -- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- Low-level interaction with underlying C API. You don't want to use this,
--- see "Sound.HTagLib" instead.
-
-{-# LANGUAGE CPP                      #-}
-{-# LANGUAGE EmptyDataDecls           #-}
-{-# LANGUAGE ForeignFunctionInterface #-}
-
+-- Low-level interaction with the underlying C API. You don't want to use
+-- this, see "Sound.HTagLib" instead.
 module Sound.HTagLib.Internal
   ( -- * Data types
-    FileId
+    FileId,
+
     -- * File API
-  , withFile
-  , saveFile
+    withFile,
+    saveFile,
+
     -- * Tag API
-  , getTitle
-  , getArtist
-  , getAlbum
-  , getComment
-  , getGenre
-  , getYear
-  , getTrackNumber
-  , setTitle
-  , setArtist
-  , setAlbum
-  , setComment
-  , setGenre
-  , setYear
-  , setTrackNumber
+    getTitle,
+    getArtist,
+    getAlbum,
+    getComment,
+    getGenre,
+    getYear,
+    getTrackNumber,
+    setTitle,
+    setArtist,
+    setAlbum,
+    setComment,
+    setGenre,
+    setYear,
+    setTrackNumber,
+
     -- * Audio properties API
-  , getDuration
-  , getBitRate
-  , getSampleRate
-  , getChannels
+    getDuration,
+    getBitRate,
+    getSampleRate,
+    getChannels,
+
     -- * Special convenience ID3v2 functions
-  , id3v2SetEncoding )
+    id3v2SetEncoding,
+  )
 where
 
-import Control.Exception (throw, bracket)
-import Control.Monad (when, unless)
+import Control.Exception (bracket, throw)
+import Control.Monad (unless, when)
 import Data.ByteString (packCString, useAsCString)
 import Data.Maybe (fromJust)
 import Data.Text (Text)
@@ -53,19 +55,16 @@
 import Foreign
 import Foreign.C.String
 import Foreign.C.Types
-import qualified Sound.HTagLib.Type as T
-
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative ((<$>))
-#endif
+import Sound.HTagLib.Type qualified as T
 
 data TagLibFile
+
 data TagLibTag
+
 data TagLibProperties
 
 -- | This is an abstraction representing opened file. Other modules can
 -- pass it around and treat it like a black box.
-
 newtype FileId = FileId (Ptr TagLibFile)
 
 ----------------------------------------------------------------------------
@@ -166,19 +165,23 @@
 ----------------------------------------------------------------------------
 -- File API
 
--- | Open audio file and return its ID (an opaque type that the rest of
--- library can pass around). In case of trouble 'IOException' is thrown.
-
-newFile
-  :: FilePath          -- ^ Path to audio file
-  -> Maybe T.FileType  -- ^ Type of file (or it will be guessed)
-  -> IO FileId         -- ^ Id to pass around
+-- | Open an audio file and return its ID (an opaque type that the rest of
+-- library can pass around).
+--
+-- Throws 'IOException'.
+newFile ::
+  -- | Path to audio file
+  FilePath ->
+  -- | Type of file (or it will be guessed)
+  Maybe T.FileType ->
+  -- | Id to pass around
+  IO FileId
 newFile path ftype = do
   c_taglib_set_string_management_enabled $ fromBool False
   ptr <- withCString path $ \cstr ->
     case ftype of
       Nothing -> c_taglib_file_new cstr
-      Just t  -> c_taglib_file_new_type cstr (enumToCInt t)
+      Just t -> c_taglib_file_new_type cstr (enumToCInt t)
   when (ptr == nullPtr) $
     throw (T.OpeningFailed path)
   valid <- toBool <$> c_taglib_file_is_valid ptr
@@ -187,28 +190,31 @@
   return (FileId ptr)
 
 -- | Free file given its ID. Every time you open a file, free it.
-
 freeFile :: FileId -> IO ()
 freeFile (FileId ptr) = c_taglib_file_free ptr
 
--- | Open audio file located at specified path, execute some actions given
--- its 'FileId' and then free the file.
-
-withFile
-  :: FilePath          -- ^ Path to audio file
-  -> Maybe T.FileType  -- ^ Type of file (or it will be guessed)
-  -> (FileId -> IO a)  -- ^ Computation depending of 'FileId'
-  -> IO a              -- ^ Result value
+-- | Open an audio file located at the specified path, execute some actions
+-- given its 'FileId' and then free the file.
+withFile ::
+  -- | Path to audio file
+  FilePath ->
+  -- | Type of file (or it will be guessed)
+  Maybe T.FileType ->
+  -- | Computation depending of 'FileId'
+  (FileId -> IO a) ->
+  -- | Result value
+  IO a
 withFile path t = bracket (newFile path t) freeFile
 
--- | Save file given its ID. Given 'FilePath' just tells what to put into
--- exception if the action fails, it doesn't specify where to save the file
--- (it's determined by 'FileId').
-
-saveFile
-  :: FilePath          -- ^ File name to use in exceptions
-  -> FileId            -- ^ File identifier
-  -> IO ()
+-- | Save the file given its ID. Given 'FilePath' just tells what to mention
+-- in the exception messages if the action fails, it doesn't specify where
+-- to save the file (it's determined by the 'FileId').
+saveFile ::
+  -- | File name to use in exceptions
+  FilePath ->
+  -- | File identifier
+  FileId ->
+  IO ()
 saveFile path (FileId ptr) = do
   success <- toBool <$> c_taglib_file_save ptr
   unless success $
@@ -217,163 +223,165 @@
 ----------------------------------------------------------------------------
 -- Tag API
 
--- | Get title tag associated with file.
-
+-- | Get the title tag.
 getTitle :: FileId -> IO T.Title
 getTitle = fmap T.mkTitle . getStrValue c_taglib_tag_title
 
--- | Get artist tag associated with file.
-
+-- | Get the artist tag.
 getArtist :: FileId -> IO T.Artist
 getArtist = fmap T.mkArtist . getStrValue c_taglib_tag_artist
 
--- | Get album tag associated with file.
-
+-- | Get the album tag.
 getAlbum :: FileId -> IO T.Album
 getAlbum = fmap T.mkAlbum . getStrValue c_taglib_tag_album
 
--- | Get comment tag associated with file.
-
+-- | Get the comment tag.
 getComment :: FileId -> IO T.Comment
 getComment = fmap T.mkComment . getStrValue c_taglib_tag_comment
 
--- | Get genre tag associated with file.
-
+-- | Get the genre tag.
 getGenre :: FileId -> IO T.Genre
 getGenre = fmap T.mkGenre . getStrValue c_taglib_tag_genre
 
--- | Get year tag associated with file.
-
+-- | Get the year tag.
 getYear :: FileId -> IO (Maybe T.Year)
 getYear = fmap T.mkYear . getIntValue c_taglib_tag_year
 
--- | Get track number associated with file.
-
+-- | Get the track number.
 getTrackNumber :: FileId -> IO (Maybe T.TrackNumber)
 getTrackNumber = fmap T.mkTrackNumber . getIntValue c_taglib_tag_track
 
--- | Set title of track associated with file.
-
+-- | Set the title tag.
 setTitle :: T.Title -> FileId -> IO ()
 setTitle v = setStrValue c_taglib_tag_set_title (T.unTitle v)
 
--- | Set artist of track associated with file.
-
+-- | Set the artist tag.
 setArtist :: T.Artist -> FileId -> IO ()
 setArtist v = setStrValue c_taglib_tag_set_artist (T.unArtist v)
 
--- | Set album of track associated with file.
-
+-- | Set the album tag.
 setAlbum :: T.Album -> FileId -> IO ()
 setAlbum v = setStrValue c_taglib_tag_set_album (T.unAlbum v)
 
--- | Set comment of track associated with file.
-
+-- | Set the comment tag.
 setComment :: T.Comment -> FileId -> IO ()
 setComment v = setStrValue c_taglib_tag_set_comment (T.unComment v)
 
--- | Set genre of track associated with file.
-
+-- | Set the genre tag.
 setGenre :: T.Genre -> FileId -> IO ()
 setGenre v = setStrValue c_taglib_tag_set_genre (T.unGenre v)
 
--- | Set year of track associated with file.
-
+-- | Set the year tag.
 setYear :: Maybe T.Year -> FileId -> IO ()
 setYear v = setIntValue c_taglib_tag_set_year (T.unYear <$> v)
 
--- | Set track number of track associated with file.
-
+-- | Set the track number tag.
 setTrackNumber :: Maybe T.TrackNumber -> FileId -> IO ()
 setTrackNumber v = setIntValue c_taglib_tag_set_track (T.unTrackNumber <$> v)
 
 ----------------------------------------------------------------------------
 -- Audio properties API
 
--- | Get duration of track associated with file.
-
+-- | Get the duration.
 getDuration :: FileId -> IO T.Duration
-getDuration = fmap (fromJust . T.mkDuration)
-  . getIntProperty c_taglib_properties_length
-
--- | Get bit rate of track associated with file.
+getDuration =
+  fmap (fromJust . T.mkDuration)
+    . getIntProperty c_taglib_properties_length
 
+-- | Get the bit rate.
 getBitRate :: FileId -> IO T.BitRate
-getBitRate = fmap (fromJust . T.mkBitRate)
-  . getIntProperty c_taglib_properties_bitrate
-
--- | Get sample rate of track associated with file.
+getBitRate =
+  fmap (fromJust . T.mkBitRate)
+    . getIntProperty c_taglib_properties_bitrate
 
+-- | Get the sample rate.
 getSampleRate :: FileId -> IO T.SampleRate
-getSampleRate = fmap (fromJust . T.mkSampleRate)
-  . getIntProperty c_taglib_properties_samplerate
-
--- | Get number of channels in track associated with file.
+getSampleRate =
+  fmap (fromJust . T.mkSampleRate)
+    . getIntProperty c_taglib_properties_samplerate
 
+-- | Get the number of channels.
 getChannels :: FileId -> IO T.Channels
-getChannels = fmap (fromJust . T.mkChannels)
-  . getIntProperty c_taglib_properties_channels
+getChannels =
+  fmap (fromJust . T.mkChannels)
+    . getIntProperty c_taglib_properties_channels
 
 ----------------------------------------------------------------------------
 -- Special convenience ID3v2 functions
 
--- | Set the default encoding for ID3v2 frames that are written to tags.
-
+-- | Set the default encoding for ID3v2 frames.
 id3v2SetEncoding :: T.ID3v2Encoding -> IO ()
 id3v2SetEncoding = c_taglib_id3v2_set_default_text_encoding . enumToCInt
 
 ----------------------------------------------------------------------------
 -- Helpers
 
-getStrValue
-  :: (Ptr TagLibTag -> IO CString) -- ^ How to get string from the resource
-  -> FileId            -- ^ File ID
-  -> IO Text           -- ^ String result
+getStrValue ::
+  -- | How to get a string from the resource
+  (Ptr TagLibTag -> IO CString) ->
+  -- | File ID
+  FileId ->
+  -- | String result
+  IO Text
 getStrValue getStr (FileId ptr) = do
-  tag    <- c_taglib_file_tag ptr
-  cstr   <- getStr tag
+  tag <- c_taglib_file_tag ptr
+  cstr <- getStr tag
   result <- packCString cstr
   free cstr
   return (decodeUtf8 result)
 
-getIntValue :: Integral a
-  => (Ptr TagLibTag -> IO a) -- ^ How to get value from the resource
-  -> FileId            -- ^ File ID
-  -> IO Int            -- ^ Result value
+getIntValue ::
+  (Integral a) =>
+  -- | How to get a value from the resource
+  (Ptr TagLibTag -> IO a) ->
+  -- | File ID
+  FileId ->
+  -- | Result value
+  IO Int
 getIntValue getInt (FileId ptr) = do
-  tag  <- c_taglib_file_tag ptr
+  tag <- c_taglib_file_tag ptr
   cint <- getInt tag
   return (fromIntegral cint)
 
-setStrValue
-  :: (Ptr TagLibTag -> CString -> IO ()) -- ^ Setting routine
-  -> Text              -- ^ New string value
-  -> FileId            -- ^ File ID
-  -> IO ()
+setStrValue ::
+  -- | Setting routine
+  (Ptr TagLibTag -> CString -> IO ()) ->
+  -- | New string value
+  Text ->
+  -- | File ID
+  FileId ->
+  IO ()
 setStrValue setStr str (FileId ptr) = do
   tag <- c_taglib_file_tag ptr
   useAsCString (encodeUtf8 str) $ \cstr ->
     setStr tag cstr
 
-setIntValue :: Integral a
-  => (Ptr TagLibTag -> a -> IO ()) -- ^ Setting routine
-  -> Maybe Int         -- ^ New value
-  -> FileId            -- ^ File ID
-  -> IO ()
+setIntValue ::
+  (Integral a) =>
+  -- | Setting routine
+  (Ptr TagLibTag -> a -> IO ()) ->
+  -- | New value
+  Maybe Int ->
+  -- | File ID
+  FileId ->
+  IO ()
 setIntValue setUInt int (FileId ptr) = do
   tag <- c_taglib_file_tag ptr
   setUInt tag (maybe 0 fromIntegral int)
 
-getIntProperty :: Integral a
-  => (Ptr TagLibProperties -> IO a) -- ^ How to get value from the resource
-  -> FileId            -- ^ File ID
-  -> IO Int            -- ^ Result
+getIntProperty ::
+  (Integral a) =>
+  -- | How to get a value from the resource
+  (Ptr TagLibProperties -> IO a) ->
+  -- | File ID
+  FileId ->
+  -- | Result
+  IO Int
 getIntProperty getInt (FileId ptr) = do
   properties <- c_taglib_file_properties ptr
-  value      <- getInt properties
+  value <- getInt properties
   return (fromIntegral value)
 
--- | Convert Haskell enumeration to C enumeration (an integer).
-
-enumToCInt :: Enum a => a -> CInt
+-- | Convert a Haskell enumeration to a C enumeration (an integer).
+enumToCInt :: (Enum a) => a -> CInt
 enumToCInt = fromIntegral . fromEnum
diff --git a/Sound/HTagLib/Setter.hs b/Sound/HTagLib/Setter.hs
--- a/Sound/HTagLib/Setter.hs
+++ b/Sound/HTagLib/Setter.hs
@@ -1,44 +1,39 @@
+{-# LANGUAGE RecordWildCards #-}
+
 -- |
 -- Module      :  Sound.HTagLib.Setter
--- Copyright   :  © 2015–2018 Mark Karpov
+-- Copyright   :  © 2015–present Mark Karpov
 -- License     :  BSD 3 clause
 --
 -- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- A high-level interface for writing audio meta data. You don't need to
--- import this module directly, import "Sound.HTagLib" instead.
-
-{-# LANGUAGE CPP             #-}
-{-# LANGUAGE RecordWildCards #-}
-
+-- An interface for writing audio meta data. You don't need to import this
+-- module directly, import "Sound.HTagLib" instead.
 module Sound.HTagLib.Setter
   ( -- * High-level API
-    TagSetter
-  , setTags
-  , setTags'
+    TagSetter,
+    setTags,
+    setTags',
+
     -- * Built-in setters
-  , titleSetter
-  , artistSetter
-  , albumSetter
-  , commentSetter
-  , genreSetter
-  , yearSetter
-  , trackNumberSetter )
+    titleSetter,
+    artistSetter,
+    albumSetter,
+    commentSetter,
+    genreSetter,
+    yearSetter,
+    trackNumberSetter,
+  )
 where
 
 import Control.Applicative ((<|>))
 import Control.Monad.IO.Class
 import Data.Foldable (forM_)
-import Data.Semigroup (Semigroup (..))
+import Sound.HTagLib.Internal qualified as I
 import Sound.HTagLib.Type
-import qualified Sound.HTagLib.Internal as I
 
-#if !MIN_VERSION_base(4,8,0)
-import Data.Monoid hiding ((<>))
-#endif
-
 -- | A composable entity that can be used together with the 'setTags' or the
 -- 'setTags'' functions to write meta data to an audio file.
 --
@@ -47,113 +42,121 @@
 -- > titleSetter "foo" <> titleSetter "bar"
 --
 -- The first value wins.
-
 data TagSetter = TagSetter
-  { sdTitle       :: Maybe Title
-  , sdArtist      :: Maybe Artist
-  , sdAlbum       :: Maybe Album
-  , sdComment     :: Maybe Comment
-  , sdGenre       :: Maybe Genre
-  , sdYear        :: Maybe (Maybe Year)
-  , sdTrackNumber :: Maybe (Maybe TrackNumber) }
+  { sdTitle :: Maybe Title,
+    sdArtist :: Maybe Artist,
+    sdAlbum :: Maybe Album,
+    sdComment :: Maybe Comment,
+    sdGenre :: Maybe Genre,
+    sdYear :: Maybe (Maybe Year),
+    sdTrackNumber :: Maybe (Maybe TrackNumber)
+  }
 
 -- | @since 1.2.0
-
 instance Semigroup TagSetter where
-  x <> y = let f g = g x <|> g y in TagSetter
-    { sdTitle       = f sdTitle
-    , sdArtist      = f sdArtist
-    , sdAlbum       = f sdAlbum
-    , sdComment     = f sdComment
-    , sdGenre       = f sdGenre
-    , sdYear        = f sdYear
-    , sdTrackNumber = f sdTrackNumber }
+  x <> y =
+    let f g = g x <|> g y
+     in TagSetter
+          { sdTitle = f sdTitle,
+            sdArtist = f sdArtist,
+            sdAlbum = f sdAlbum,
+            sdComment = f sdComment,
+            sdGenre = f sdGenre,
+            sdYear = f sdYear,
+            sdTrackNumber = f sdTrackNumber
+          }
 
 instance Monoid TagSetter where
-  mempty = TagSetter
-    { sdTitle       = Nothing
-    , sdArtist      = Nothing
-    , sdAlbum       = Nothing
-    , sdComment     = Nothing
-    , sdGenre       = Nothing
-    , sdYear        = Nothing
-    , sdTrackNumber = Nothing }
+  mempty =
+    TagSetter
+      { sdTitle = Nothing,
+        sdArtist = Nothing,
+        sdAlbum = Nothing,
+        sdComment = Nothing,
+        sdGenre = Nothing,
+        sdYear = Nothing,
+        sdTrackNumber = Nothing
+      }
   mappend = (<>)
 
--- | Set tags in specified file using the given setter.
+-- | Set tags in a specified file using the given setter.
 --
--- In the case of trouble 'I.HTagLibException' will be thrown.
-
-setTags :: MonadIO m
-  => FilePath          -- ^ Path to audio file
-  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames
-  -> TagSetter         -- ^ Setter
-  -> m ()
+-- Throws 'I.HTagLibException'.
+setTags ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Encoding for ID3v2 frames
+  Maybe ID3v2Encoding ->
+  -- | Setter
+  TagSetter ->
+  m ()
 setTags path enc = execSetter path enc Nothing
 
--- | Similar to 'setTags', but you can also specify type of audio file
--- explicitly (otherwise it's guessed from file extension).
-
-setTags' :: MonadIO m
-  => FilePath          -- ^ Path to audio file
-  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames
-  -> FileType          -- ^ Type of audio file
-  -> TagSetter         -- ^ Setter
-  -> m ()
+-- | Similar to 'setTags', but you can also specify the type of the audio
+-- file explicitly (otherwise it's guessed from the file extension).
+setTags' ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Encoding for ID3v2 frames
+  Maybe ID3v2Encoding ->
+  -- | Type of audio file
+  FileType ->
+  -- | Setter
+  TagSetter ->
+  m ()
 setTags' path enc t = execSetter path enc (Just t)
 
 -- | The most general way to set meta data. 'setTags' and 'setTags'' are
 -- just wrappers around this function.
-
-execSetter :: MonadIO m
-  => FilePath          -- ^ Path to audio file
-  -> Maybe ID3v2Encoding -- ^ Encoding for ID3v2 frames
-  -> Maybe FileType    -- ^ Type of audio file (if known)
-  -> TagSetter         -- ^ Setter
-  -> m ()
+execSetter ::
+  (MonadIO m) =>
+  -- | Path to audio file
+  FilePath ->
+  -- | Encoding for ID3v2 frames
+  Maybe ID3v2Encoding ->
+  -- | Type of audio file (if known)
+  Maybe FileType ->
+  -- | Setter
+  TagSetter ->
+  m ()
 execSetter path enc t TagSetter {..} = liftIO . I.withFile path t $ \fid -> do
   forM_ enc I.id3v2SetEncoding
   let writeTag x f = forM_ x (`f` fid)
-  writeTag sdTitle       I.setTitle
-  writeTag sdArtist      I.setArtist
-  writeTag sdAlbum       I.setAlbum
-  writeTag sdComment     I.setComment
-  writeTag sdGenre       I.setGenre
-  writeTag sdYear        I.setYear
+  writeTag sdTitle I.setTitle
+  writeTag sdArtist I.setArtist
+  writeTag sdAlbum I.setAlbum
+  writeTag sdComment I.setComment
+  writeTag sdGenre I.setGenre
+  writeTag sdYear I.setYear
   writeTag sdTrackNumber I.setTrackNumber
   I.saveFile path fid
 
--- | Setter for track title.
-
+-- | Setter for the track title.
 titleSetter :: Title -> TagSetter
-titleSetter x = mempty { sdTitle = Just x }
-
--- | Setter for track artist.
+titleSetter x = mempty {sdTitle = Just x}
 
+-- | Setter for the track artist.
 artistSetter :: Artist -> TagSetter
-artistSetter x = mempty { sdArtist = Just x }
-
--- | Setter for track album.
+artistSetter x = mempty {sdArtist = Just x}
 
+-- | Setter for the track album.
 albumSetter :: Album -> TagSetter
-albumSetter x = mempty { sdAlbum = Just x }
-
--- | Setter for track comment.
+albumSetter x = mempty {sdAlbum = Just x}
 
+-- | Setter for the track comment.
 commentSetter :: Comment -> TagSetter
-commentSetter x = mempty { sdComment = Just x }
-
--- | Setter for track genre.
+commentSetter x = mempty {sdComment = Just x}
 
+-- | Setter for the track genre.
 genreSetter :: Genre -> TagSetter
-genreSetter x = mempty { sdGenre = Just x }
-
--- | Setter for year tag, use 'Nothing' to clear the field.
+genreSetter x = mempty {sdGenre = Just x}
 
+-- | Setter for the year tag, use 'Nothing' to clear the field.
 yearSetter :: Maybe Year -> TagSetter
-yearSetter x = mempty { sdYear = Just x }
-
--- | Setter for track number, use 'Nothing' to clear the field.
+yearSetter x = mempty {sdYear = Just x}
 
+-- | Setter for the track number, use 'Nothing' to clear the field.
 trackNumberSetter :: Maybe TrackNumber -> TagSetter
-trackNumberSetter x = mempty { sdTrackNumber = Just x }
+trackNumberSetter x = mempty {sdTrackNumber = Just x}
diff --git a/Sound/HTagLib/Type.hs b/Sound/HTagLib/Type.hs
--- a/Sound/HTagLib/Type.hs
+++ b/Sound/HTagLib/Type.hs
@@ -1,6 +1,9 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+
 -- |
 -- Module      :  Sound.HTagLib.Type
--- Copyright   :  © 2015–2018 Mark Karpov
+-- Copyright   :  © 2015–present Mark Karpov
 -- License     :  BSD 3 clause
 --
 -- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
@@ -8,275 +11,247 @@
 -- Portability :  portable
 --
 -- Definitions of types used to represent various tags and audio properties.
-
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE OverloadedStrings  #-}
-
 module Sound.HTagLib.Type
-  ( Title
-  , mkTitle
-  , unTitle
-  , Artist
-  , mkArtist
-  , unArtist
-  , Album
-  , mkAlbum
-  , unAlbum
-  , Comment
-  , mkComment
-  , unComment
-  , Genre
-  , mkGenre
-  , unGenre
-  , Year
-  , mkYear
-  , unYear
-  , TrackNumber
-  , mkTrackNumber
-  , unTrackNumber
-  , Duration
-  , mkDuration
-  , unDuration
-  , BitRate
-  , mkBitRate
-  , unBitRate
-  , SampleRate
-  , mkSampleRate
-  , unSampleRate
-  , Channels
-  , mkChannels
-  , unChannels
-  , FileType (..)
-  , ID3v2Encoding (..)
-  , HTagLibException (..) )
+  ( Title,
+    mkTitle,
+    unTitle,
+    Artist,
+    mkArtist,
+    unArtist,
+    Album,
+    mkAlbum,
+    unAlbum,
+    Comment,
+    mkComment,
+    unComment,
+    Genre,
+    mkGenre,
+    unGenre,
+    Year,
+    mkYear,
+    unYear,
+    TrackNumber,
+    mkTrackNumber,
+    unTrackNumber,
+    Duration,
+    mkDuration,
+    unDuration,
+    BitRate,
+    mkBitRate,
+    unBitRate,
+    SampleRate,
+    mkSampleRate,
+    unSampleRate,
+    Channels,
+    mkChannels,
+    unChannels,
+    FileType (..),
+    ID3v2Encoding (..),
+    HTagLibException (..),
+  )
 where
 
 import Control.Exception (Exception)
 import Data.String
 import Data.Text (Text)
+import Data.Text qualified as T
 import Data.Typeable (Typeable)
-import qualified Data.Text as T
 
 -- | Title tag.
-
 newtype Title = Title Text deriving (Show, Eq, Ord)
 
 instance IsString Title where
   fromString = mkTitle . fromString
 
--- | Construction of 'Title' type, null bytes are converted to spaces.
-
+-- | Construction of 'Title' values, null bytes are converted to spaces.
 mkTitle :: Text -> Title
 mkTitle = Title . avoidNulls
 
 -- | Convert 'Title' to 'Text'.
-
 unTitle :: Title -> Text
 unTitle (Title x) = x
 
 -- | Artist tag.
-
 newtype Artist = Artist Text deriving (Show, Eq, Ord)
 
 instance IsString Artist where
   fromString = mkArtist . fromString
 
--- | Construction of 'Artist' type, null bytes are converted to spaces.
-
+-- | Construction of 'Artist' values, null bytes are converted to spaces.
 mkArtist :: Text -> Artist
 mkArtist = Artist . avoidNulls
 
 -- | Convert 'Artist' to 'Text'.
-
 unArtist :: Artist -> Text
 unArtist (Artist x) = x
 
 -- | Album tag.
-
 newtype Album = Album Text deriving (Show, Eq, Ord)
 
 instance IsString Album where
   fromString = mkAlbum . fromString
 
--- | Construction of 'Album' type, null bytes are converted to spaces.
-
+-- | Construction of 'Album' values, null bytes are converted to spaces.
 mkAlbum :: Text -> Album
 mkAlbum = Album . avoidNulls
 
 -- | Convert 'Album' to 'Text'.
-
 unAlbum :: Album -> Text
 unAlbum (Album x) = x
 
 -- | Comment tag.
-
 newtype Comment = Comment Text deriving (Show, Eq, Ord)
 
 instance IsString Comment where
   fromString = mkComment . fromString
 
--- | Construction of 'Comment' type, null bytes are converted to spaces.
-
+-- | Construction of 'Comment' values, null bytes are converted to spaces.
 mkComment :: Text -> Comment
 mkComment = Comment . avoidNulls
 
 -- | Convert 'Comment' to 'Text'.
-
 unComment :: Comment -> Text
 unComment (Comment x) = x
 
 -- | Genre tag.
-
 newtype Genre = Genre Text deriving (Show, Eq, Ord)
 
 instance IsString Genre where
   fromString = mkGenre . fromString
 
--- | Construction of 'Genre' type, null bytes are converted to spaces.
-
+-- | Construction of 'Genre' values, null bytes are converted to spaces.
 mkGenre :: Text -> Genre
 mkGenre = Genre . avoidNulls
 
 -- | Convert 'Genre' to 'Text'.
-
 unGenre :: Genre -> Text
 unGenre (Genre x) = x
 
 -- | Year tag.
-
 newtype Year = Year Int deriving (Show, Eq, Ord)
 
--- | Construction of 'Year' type, non-positive values result in 'Nothing'.
-
+-- | Construction of 'Year' values, non-positive values result in 'Nothing'.
 mkYear :: Int -> Maybe Year
 mkYear = fmap Year . atLeast 1
 
 -- | Convert 'Year' to 'Int'.
-
 unYear :: Year -> Int
 unYear (Year x) = x
 
 -- | Track number tag.
-
 newtype TrackNumber = TrackNumber Int deriving (Show, Eq, Ord)
 
--- | Construction of 'TrackNumber' type, non-positive values result in
+-- | Construction of 'TrackNumber' values, non-positive values result in
 -- 'Nothing'.
-
 mkTrackNumber :: Int -> Maybe TrackNumber
 mkTrackNumber = fmap TrackNumber . atLeast 1
 
 -- | Convert 'TrackNumber' to 'Int'.
-
 unTrackNumber :: TrackNumber -> Int
 unTrackNumber (TrackNumber x) = x
 
 -- | Duration in seconds.
-
 newtype Duration = Duration Int deriving (Show, Eq, Ord)
 
 -- | Construction of 'Duration' values, negative values result in 'Nothing'.
-
 mkDuration :: Int -> Maybe Duration
 mkDuration = fmap Duration . atLeast 0
 
 -- | Convert 'Duration' to 'Int'.
-
 unDuration :: Duration -> Int
 unDuration (Duration x) = x
 
 -- | Bit rate in kb/s.
-
 newtype BitRate = BitRate Int deriving (Show, Eq, Ord)
 
--- | Construction of 'BitRate' values, negative values result in
--- 'Nothing'.
-
+-- | Construction of 'BitRate' values, negative values result in 'Nothing'.
 mkBitRate :: Int -> Maybe BitRate
 mkBitRate = fmap BitRate . atLeast 0
 
 -- | Convert 'BitRate' to 'Int'.
-
 unBitRate :: BitRate -> Int
 unBitRate (BitRate x) = x
 
 -- | Sample rate in Hz.
-
 newtype SampleRate = SampleRate Int deriving (Show, Eq, Ord)
 
 -- | Construction of 'SampleRate' values, non-positive values result in
 -- 'Nothing'.
-
 mkSampleRate :: Int -> Maybe SampleRate
 mkSampleRate = fmap SampleRate . atLeast 1
 
 -- | Convert 'SampleRate' to 'Int'.
-
 unSampleRate :: SampleRate -> Int
 unSampleRate (SampleRate x) = x
 
 -- | Number of channels in the audio stream.
-
 newtype Channels = Channels Int deriving (Show, Eq, Ord)
 
 -- | Construction of 'Channels' values, non-positive values result in
 -- 'Nothing'.
-
 mkChannels :: Int -> Maybe Channels
 mkChannels = fmap Channels . atLeast 1
 
 -- | Convert 'Channels' to 'Int'.
-
 unChannels :: Channels -> Int
 unChannels (Channels x) = x
 
 -- | Replace null bytes with spaces.
-
 avoidNulls :: Text -> Text
 avoidNulls = T.replace "\0" " "
 
 -- | @atLeast a b@ returns @Just b@ is @b@ is greater or equal to @a@,
--- otherwise result is @Nothing@.
-
+-- otherwise the result is @Nothing@.
 atLeast :: Int -> Int -> Maybe Int
 atLeast a b = if b >= a then Just b else Nothing
 
--- | Types of files TagLib can work with. This may be used to explicitly
--- specify type of file instead of relying on the TagLib's ability to guess
--- type of file from its extension.
-
+-- | The types of files TagLib can work with. This may be used to explicitly
+-- specify the type of a file instead of relying on the TagLib's ability to
+-- guess the type of a file from its extension.
 data FileType
-  = MPEG               -- ^ MPEG
-  | OggVorbis          -- ^ Ogg vorbis
-  | FLAC               -- ^ FLAC
-  | MPC                -- ^ MPC
-  | OggFlac            -- ^ Ogg FLAC
-  | WavPack            -- ^ Wav pack
-  | Speex              -- ^ Speex
-  | TrueAudio          -- ^ True audio
-  | MP4                -- ^ MP4
-  | ASF                -- ^ ASF
-    deriving (Show, Eq, Enum)
+  = -- | MPEG
+    MPEG
+  | -- | Ogg vorbis
+    OggVorbis
+  | -- | FLAC
+    FLAC
+  | -- | MPC
+    MPC
+  | -- | Ogg FLAC
+    OggFlac
+  | -- | Wav pack
+    WavPack
+  | -- | Speex
+    Speex
+  | -- | True audio
+    TrueAudio
+  | -- | MP4
+    MP4
+  | -- | ASF
+    ASF
+  deriving (Show, Eq, Enum)
 
 -- | Encoding for ID3v2 frames that are written to tags.
-
 data ID3v2Encoding
-  = ID3v2Latin1        -- ^ Latin1
-  | ID3v2UTF16         -- ^ UTF-16
-  | ID3v2UTF16BE       -- ^ UTF-16 big endian
-  | ID3v2UTF8          -- ^ UTF-8
-    deriving (Show, Eq, Enum)
+  = -- | Latin1
+    ID3v2Latin1
+  | -- | UTF-16
+    ID3v2UTF16
+  | -- | UTF-16 big endian
+    ID3v2UTF16BE
+  | -- | UTF-8
+    ID3v2UTF8
+  deriving (Show, Eq, Enum)
 
 -- | The data type represents exceptions specific to the library.
-
 data HTagLibException
-  = OpeningFailed FilePath
-    -- ^ Attempt to open audio file to read its tags failed
-  | InvalidFile FilePath
-    -- ^ File can be opened, but it doesn't contain any information that can
+  = -- | An attempt to open an audio file to read its tags failed
+    OpeningFailed FilePath
+  | -- | The file can be opened, but it doesn't contain any information that can
     -- be interpreted by the library
-  | SavingFailed FilePath
-    -- ^ Saving failed
-    deriving (Eq, Show, Typeable)
+    InvalidFile FilePath
+  | -- | Saving failed
+    SavingFailed FilePath
+  deriving (Eq, Show, Typeable)
 
 instance Exception HTagLibException
diff --git a/htaglib.cabal b/htaglib.cabal
--- a/htaglib.cabal
+++ b/htaglib.cabal
@@ -1,72 +1,80 @@
-name:                 htaglib
-version:              1.2.0
-cabal-version:        1.18
-tested-with:          GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.1
-license:              BSD3
-license-file:         LICENSE.md
-author:               Mark Karpov <markkarpov92@gmail.com>
-maintainer:           Mark Karpov <markkarpov92@gmail.com>
-homepage:             https://github.com/mrkkrp/htaglib
-bug-reports:          https://github.com/mrkkrp/htaglib/issues
-category:             Sound, Foreign
-synopsis:             Bindings to TagLib, audio meta-data library
-build-type:           Simple
-description:          Bindings to TagLib, audio meta-data library.
-extra-doc-files:      CHANGELOG.md
-                    , README.md
-                    , audio-samples/README.md
-data-files:           audio-samples/*.flac
-                    , audio-samples/*.mp3
+cabal-version:   2.4
+name:            htaglib
+version:         1.2.1
+license:         BSD-3-Clause
+license-file:    LICENSE.md
+maintainer:      Mark Karpov <markkarpov92@gmail.com>
+author:          Mark Karpov <markkarpov92@gmail.com>
+tested-with:     ghc ==9.4.7 ghc ==9.6.3 ghc ==9.8.1
+homepage:        https://github.com/mrkkrp/htaglib
+bug-reports:     https://github.com/mrkkrp/htaglib/issues
+synopsis:        Bindings to TagLib, audio meta-data library
+description:     Bindings to TagLib, audio meta-data library.
+category:        Sound, Foreign
+build-type:      Simple
+data-files:
+    audio-samples/*.flac
+    audio-samples/*.mp3
 
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+    audio-samples/README.md
+
 source-repository head
-  type:               git
-  location:           https://github.com/mrkkrp/htaglib.git
+    type:     git
+    location: https://github.com/mrkkrp/htaglib.git
 
 flag dev
-  description:        Turn on development settings.
-  manual:             True
-  default:            False
+    description: Turn on development settings.
+    default:     False
+    manual:      True
 
 library
-  build-depends:      base         >= 4.7 && < 5.0
-                    , bytestring   >= 0.9 && < 0.11
-                    , text         >= 1.0 && < 1.3
-                    , transformers >= 0.4 && < 0.6
-  if !impl(ghc >= 8.0)
-    build-depends:    semigroups       == 0.18.*
-  extra-libraries:    tag_c
-  exposed-modules:    Sound.HTagLib
-  other-modules:      Sound.HTagLib.Type
-                    , Sound.HTagLib.Getter
-                    , Sound.HTagLib.Setter
-                    , Sound.HTagLib.Internal
-  if flag(dev)
-    ghc-options:      -Wall -Werror
-  else
-    ghc-options:      -O2 -Wall
-  if flag(dev) && impl(ghc >= 8.0)
-    ghc-options:      -Wcompat
-                      -Wincomplete-record-updates
-                      -Wincomplete-uni-patterns
-                      -Wnoncanonical-monad-instances
-                      -Wnoncanonical-monadfail-instances
-  default-language:   Haskell2010
+    exposed-modules:  Sound.HTagLib
+    other-modules:
+        Sound.HTagLib.Type
+        Sound.HTagLib.Getter
+        Sound.HTagLib.Setter
+        Sound.HTagLib.Internal
 
+    default-language: GHC2021
+    extra-libraries:  tag_c
+    build-depends:
+        base >=4.15 && <5,
+        bytestring >=0.9 && <0.12,
+        text >=1 && <2.2
+
+    if flag(dev)
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
+
+    else
+        ghc-options: -O2 -Wall
+
 test-suite tests
-  main-is:            Spec.hs
-  hs-source-dirs:     tests
-  type:               exitcode-stdio-1.0
-  if flag(dev)
-    ghc-options:      -Wall -Werror
-  else
-    ghc-options:      -O2 -Wall
-  other-modules:      Sound.HTagLib.GetterSpec
-                    , Sound.HTagLib.SetterSpec
-                    , Sound.HTagLib.Test.Util
-  build-depends:      base       >= 4.7 && < 5.0
-                    , directory  >= 1.2 && < 1.4
-                    , filepath   >= 1.4 && < 2.0
-                    , hspec      >= 2.0 && < 3.0
-                    , htaglib
-  build-tools:        hspec-discover >= 2.0 && < 3.0
-  default-language:   Haskell2010
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    build-tool-depends: hspec-discover:hspec-discover >=2 && <3
+    hs-source-dirs:     tests
+    other-modules:
+        Sound.HTagLib.GetterSpec
+        Sound.HTagLib.SetterSpec
+        Sound.HTagLib.Test.Util
+
+    default-language:   GHC2021
+    build-depends:
+        base >=4.15 && <5,
+        directory >=1.2 && <1.4,
+        filepath >=1.4 && <2,
+        hspec >=2 && <3,
+        htaglib
+
+    if flag(dev)
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
+
+    else
+        ghc-options: -O2 -Wall
diff --git a/tests/Sound/HTagLib/GetterSpec.hs b/tests/Sound/HTagLib/GetterSpec.hs
--- a/tests/Sound/HTagLib/GetterSpec.hs
+++ b/tests/Sound/HTagLib/GetterSpec.hs
@@ -1,20 +1,14 @@
-{-# LANGUAGE CPP #-}
-
 module Sound.HTagLib.GetterSpec (spec) where
 
 import Sound.HTagLib
 import Sound.HTagLib.Test.Util
 import Test.Hspec
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
-
 spec :: Spec
 spec =
   describe "getters" $ do
     mapM_ (withFile $ const simpleGetter) fileList
-    mapM_ (withFile specializedGetter)    fileList
+    mapM_ (withFile specializedGetter) fileList
 
 simpleGetter :: AudioTags -> Expectation
 simpleGetter tags = do
diff --git a/tests/Sound/HTagLib/SetterSpec.hs b/tests/Sound/HTagLib/SetterSpec.hs
--- a/tests/Sound/HTagLib/SetterSpec.hs
+++ b/tests/Sound/HTagLib/SetterSpec.hs
@@ -1,23 +1,18 @@
-{-# LANGUAGE CPP               #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Sound.HTagLib.SetterSpec (spec) where
 
 import Sound.HTagLib
 import Sound.HTagLib.Test.Util
-import System.Directory (getTemporaryDirectory, copyFile)
-import System.FilePath ((</>), takeFileName)
+import System.Directory (copyFile, getTemporaryDirectory)
+import System.FilePath (takeFileName, (</>))
 import Test.Hspec
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
-
 spec :: Spec
 spec =
   describe "setters" $ do
     mapM_ (withFile $ const simpleSetter) fileList
-    mapM_ (withFile specializedSetter)    fileList
+    mapM_ (withFile specializedSetter) fileList
 
 dupeFile :: FilePath -> IO FilePath
 dupeFile path = do
@@ -26,14 +21,16 @@
   return newPath
 
 updateSampleTags :: AudioTags -> AudioTags
-updateSampleTags tags = tags
-  { atTitle       = mkTitle "title'"
-  , atArtist      = mkArtist "artist'"
-  , atAlbum       = mkAlbum "album'"
-  , atComment     = mkComment "comment'"
-  , atGenre       = mkGenre "genre'"
-  , atYear        = mkYear 2056
-  , atTrackNumber = mkTrackNumber 8 }
+updateSampleTags tags =
+  tags
+    { atTitle = mkTitle "title'",
+      atArtist = mkArtist "artist'",
+      atAlbum = mkAlbum "album'",
+      atComment = mkComment "comment'",
+      atGenre = mkGenre "genre'",
+      atYear = mkYear 2056,
+      atTrackNumber = mkTrackNumber 8
+    }
 
 simpleSetter :: AudioTags -> Expectation
 simpleSetter tags = do
@@ -41,7 +38,7 @@
   dupe <- dupeFile path
   setTags dupe Nothing sampleSetter
   extracted <- getTags dupe (sampleGetter dupe)
-  extracted `shouldMatchTags` updateSampleTags (tags { atFileName = dupe })
+  extracted `shouldMatchTags` updateSampleTags (tags {atFileName = dupe})
 
 specializedSetter :: FileType -> AudioTags -> Expectation
 specializedSetter t tags = do
@@ -49,4 +46,4 @@
   dupe <- dupeFile path
   setTags' dupe Nothing t sampleSetter
   extracted <- getTags dupe (sampleGetter dupe)
-  extracted `shouldMatchTags` updateSampleTags (tags { atFileName = dupe })
+  extracted `shouldMatchTags` updateSampleTags (tags {atFileName = dupe})
diff --git a/tests/Sound/HTagLib/Test/Util.hs b/tests/Sound/HTagLib/Test/Util.hs
--- a/tests/Sound/HTagLib/Test/Util.hs
+++ b/tests/Sound/HTagLib/Test/Util.hs
@@ -1,111 +1,116 @@
-{-# LANGUAGE CPP               #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Sound.HTagLib.Test.Util
-  ( AudioTags (..)
-  , sampleGetter
-  , sampleSetter
-  , fileList
-  , withFile
-  , shouldMatchTags )
+  ( AudioTags (..),
+    sampleGetter,
+    sampleSetter,
+    fileList,
+    withFile,
+    shouldMatchTags,
+  )
 where
 
 import Data.Maybe (fromJust)
 import Sound.HTagLib
 import Test.Hspec
 
-#if !MIN_VERSION_base(4,8,0)
-import Control.Applicative
-#endif
-#if !MIN_VERSION_base(4,11,0)
-import Data.Monoid
-#endif
-
 data AudioTags = AudioTags
-  { atFileName    :: FilePath
-  , atTitle       :: Title
-  , atArtist      :: Artist
-  , atAlbum       :: Album
-  , atComment     :: Comment
-  , atGenre       :: Genre
-  , atYear        :: Maybe Year
-  , atTrackNumber :: Maybe TrackNumber
-  , atDuration    :: Duration
-  , atBitRate     :: BitRate
-  , atSampleRate  :: SampleRate
-  , atChannels    :: Channels }
+  { atFileName :: FilePath,
+    atTitle :: Title,
+    atArtist :: Artist,
+    atAlbum :: Album,
+    atComment :: Comment,
+    atGenre :: Genre,
+    atYear :: Maybe Year,
+    atTrackNumber :: Maybe TrackNumber,
+    atDuration :: Duration,
+    atBitRate :: BitRate,
+    atSampleRate :: SampleRate,
+    atChannels :: Channels
+  }
   deriving (Show, Eq)
 
 sampleGetter :: FilePath -> TagGetter AudioTags
-sampleGetter path = AudioTags
-  <$> pure path
-  <*> titleGetter
-  <*> artistGetter
-  <*> albumGetter
-  <*> commentGetter
-  <*> genreGetter
-  <*> yearGetter
-  <*> trackNumberGetter
-  <*> durationGetter
-  <*> bitRateGetter
-  <*> sampleRateGetter
-  <*> channelsGetter
+sampleGetter path =
+  AudioTags
+    <$> pure path
+    <*> titleGetter
+    <*> artistGetter
+    <*> albumGetter
+    <*> commentGetter
+    <*> genreGetter
+    <*> yearGetter
+    <*> trackNumberGetter
+    <*> durationGetter
+    <*> bitRateGetter
+    <*> sampleRateGetter
+    <*> channelsGetter
 
 sampleSetter :: TagSetter
 sampleSetter =
-  mempty <>
-  titleSetter (mkTitle "title'") <>
-  artistSetter (mkArtist "artist'") <>
-  albumSetter (mkAlbum "album'") <>
-  commentSetter (mkComment "comment'") <>
-  genreSetter (mkGenre "genre'") <>
-  yearSetter (mkYear 2056) <>
-  trackNumberSetter (mkTrackNumber 8)
+  mempty
+    <> titleSetter (mkTitle "title'")
+    <> artistSetter (mkArtist "artist'")
+    <> albumSetter (mkAlbum "album'")
+    <> commentSetter (mkComment "comment'")
+    <> genreSetter (mkGenre "genre'")
+    <> yearSetter (mkYear 2056)
+    <> trackNumberSetter (mkTrackNumber 8)
 
 sampleTags :: AudioTags
-sampleTags = AudioTags
-  { atFileName    = undefined
-  , atTitle       = "title"
-  , atArtist      = "artist"
-  , atAlbum       = "album"
-  , atComment     = "comment"
-  , atGenre       = "genre"
-  , atYear        = mkYear 2055
-  , atTrackNumber = mkTrackNumber 7
-  , atDuration    = fromJust $ mkDuration 0
-  , atBitRate     = fromJust $ mkBitRate 0
-  , atSampleRate  = fromJust $ mkSampleRate 44100
-  , atChannels    = fromJust $ mkChannels 2 }
+sampleTags =
+  AudioTags
+    { atFileName = undefined,
+      atTitle = "title",
+      atArtist = "artist",
+      atAlbum = "album",
+      atComment = "comment",
+      atGenre = "genre",
+      atYear = mkYear 2055,
+      atTrackNumber = mkTrackNumber 7,
+      atDuration = fromJust $ mkDuration 0,
+      atBitRate = fromJust $ mkBitRate 0,
+      atSampleRate = fromJust $ mkSampleRate 44100,
+      atChannels = fromJust $ mkChannels 2
+    }
 
 fileList :: [(FileType, AudioTags)]
 fileList =
-  [ (FLAC, sampleTags
-     { atBitRate = fromJust $ mkBitRate 217
-     , atFileName = "audio-samples/sample.flac" })
-  , (MPEG, sampleTags
-     { atBitRate = fromJust $ mkBitRate 136
-     , atFileName = "audio-samples/sample.mp3"  }) ]
-
--- | Call given function with provided data.
+  [ ( FLAC,
+      sampleTags
+        { atBitRate = fromJust $ mkBitRate 217,
+          atFileName = "audio-samples/sample.flac"
+        }
+    ),
+    ( MPEG,
+      sampleTags
+        { atBitRate = fromJust $ mkBitRate 136,
+          atFileName = "audio-samples/sample.mp3"
+        }
+    )
+  ]
 
-withFile
-  :: (FileType -> AudioTags -> Expectation)
-  -> (FileType, AudioTags)
-  -> Spec
+-- | Call a given function with the provided data.
+withFile ::
+  (FileType -> AudioTags -> Expectation) ->
+  (FileType, AudioTags) ->
+  Spec
 withFile f (t, tags) = it name (f t tags)
-  where name = "using file: " ++ show (atFileName tags) ++ " (" ++ show t ++ ")"
-
--- | Create an expectation that two collections of tags match. However, if
--- bit rate of the first is zero (which is the case with older versions of
--- TagLib when it's used with such short files as our samples), allow bit
--- rate values differ.
+  where
+    name = "using file: " ++ show (atFileName tags) ++ " (" ++ show t ++ ")"
 
-shouldMatchTags
-  :: AudioTags         -- ^ Tags to test
-  -> AudioTags         -- ^ Correct tags to test against
-  -> Expectation
+-- | Create an expectation that the two collections of tags match. However,
+-- if the bit rate in the first collection is zero (which is the case with
+-- the older versions of TagLib when it's used with such short files as our
+-- samples), allow bit rate values to differ.
+shouldMatchTags ::
+  -- | Tags to test
+  AudioTags ->
+  -- | The value to test against
+  AudioTags ->
+  Expectation
 shouldMatchTags given expected =
   let zeroBitRate = fromJust (mkBitRate 0)
-  in if atBitRate given == zeroBitRate
-       then given `shouldBe` expected { atBitRate = zeroBitRate }
-       else given `shouldBe` expected
+   in if atBitRate given == zeroBitRate
+        then given `shouldBe` expected {atBitRate = zeroBitRate}
+        else given `shouldBe` expected
