jammittools 0.5.3.2 → 0.5.4
raw patch · 10 files changed
+240/−118 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +105/−0
- Main.hs +47/−38
- README.md +5/−0
- jammittools.cabal +4/−2
- src/Sound/Jammit/Base.hs +16/−14
- src/Sound/Jammit/Export.hs +27/−26
- src/Sound/Jammit/Internal/Audio.hs +3/−4
- src/Sound/Jammit/Internal/Image.hs +11/−12
- src/Sound/Jammit/Internal/PropertyList.hs +13/−13
- src/Sound/Jammit/Internal/TempIO.hs +9/−9
+ CHANGELOG.md view
@@ -0,0 +1,105 @@+## v0.5.4 + + * Program will always also look for songs in the folder it's in + * Add option and script to export all backing tracks without combining them + +## v0.5.3.2 + +Mac-only release, fixes some issues on Sierra + +## v0.5.3 + +Added support for "Bass 2", "Vocals" (instead of "Vocal"), +and "Vocal 1"/"Vocal 2" (instead of "Vocal"/"B Vocals"). + +## v0.5.2 + +Added the ability to export the metronome click track. +Available via the `-m` option, or via the `-x` or `-b` full backup options +(including the `easy-export` script on Windows/Mac). + +## v0.5.1 + +Added a `-b` option for backing up an entire library of songs. + +## v0.5.0.3 + +Fixed some bugs in both audio and image decoding. + +## v0.5.0.1 + +Fixed a bug in reading the `beats.plist` file +(not actually used in the executable). + +## v0.5 + + * Fixed a bug where `"Song Title "` wasn't considered the same as `"Song Title"` + * Much internal cleanup, smaller executable size + * Library: added some more Jammit types (beats, ghost beats, sections) + +## v0.4.1.1 + +Fixes for GHC 7.10 (no new functionality). + +## v0.4.1 + +Uses [`conduit-audio`](http://hackage.haskell.org/package/conduit-audio), +and exposes the audio file as an +[`AudioStream`](http://hackage.haskell.org/package/conduit-audio-0.1/docs/Data-Conduit-Audio.html#t:AudioSource). + +## v0.4 + + * Doesn't require ImageMagick or SoX anymore! + Thanks to [JuicyPixels](https://hackage.haskell.org/package/JuicyPixels), + [HPDF](https://hackage.haskell.org/package/HPDF), + [conduit](https://hackage.haskell.org/package/conduit), + and audio code ported from [aifc2wav](http://sed.free.fr/aifc2wav.html). + * Prints better instructions. + * Prints what it is doing during each step of the `-x dir` export option. + * Error if the search parameters don't narrow down the library + to exactly one song. + +## v0.3.2 + +Fixes for Windows, other platforms are unchanged: + + * Use `wchar_t` for C functions to support non-ASCII filenames + * Look for SoX in Program Files (the installer doesn't put it in the `PATH`) + +## v0.3.1 + + * Added support for Drums 1, Drums 2 and Organ parts + * Better argument handling (quits on unrecognized argument) + +## v0.3 + +Split the package into a library and an executable. +No functional changes to the executable. + +## v0.2.0.1 + +Documentation, packaging, and version bounds improvements. + +## v0.2 + + * Proper Mac support (knows the official library location). + * Changed the long forms of some commands for consistency. + * Removed `-u` and `-b`, replaced with a simple `-c` option which does a dry + run of audio extraction. That is, it does no audio conversion, but exits + with a code of 0 only if all specified audio parts are found. + * Fixed a crash on very large libraries. + +## v0.1.1 + + * `-u` and `-b` audio options for + [`onyxite-customs`](https://github.com/mtolly/onyxite-customs) project + * `-T` and `-R` perform exact title/artist search, + instead of existing `-t` and `-r` "case-insensitive substring" search + +## v0.1 + +Binary compiled with GHC 7.6.3 32-bit on Windows 7 64-bit. +Should also work on Wine. +You must install [ImageMagick](http://www.imagemagick.org/script/index.php) +and [SoX](http://sox.sourceforge.net/) +for sheet music and audio export respectively.
Main.hs view
@@ -1,28 +1,28 @@ {-# LANGUAGE CPP #-} module Main (main) where +import Control.Applicative ((<|>))+import Control.Monad (forM_, unless, (>=>))+import Data.Char (toLower)+import Data.List (nub, partition, sort)+import Data.Maybe (catMaybes, fromMaybe, mapMaybe,+ maybeToList)+import Data.Version (showVersion)+import qualified Paths_jammittools as Paths+import Sound.Jammit.Base+import Sound.Jammit.Export+import qualified System.Console.GetOpt as Opt+import System.Directory (createDirectoryIfMissing)+import qualified System.Environment as Env+import System.Exit (exitFailure)+import System.FilePath (makeValid, takeDirectory, (<.>), (</>))+import Text.PrettyPrint.Boxes (hsep, left, render, text, top, vcat,+ (/+/))+ #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))+import Control.Applicative ((<$>)) #endif-import Control.Applicative ((<|>))-import Control.Monad ((>=>), forM_, unless)-import Data.Char (toLower)-import Data.List (sort, nub, partition)-import Data.Maybe (catMaybes, mapMaybe, fromMaybe, listToMaybe)-import Data.Version (showVersion)-import qualified System.Console.GetOpt as Opt-import qualified System.Environment as Env-import System.Exit (exitFailure) -import System.Directory (createDirectoryIfMissing)-import System.FilePath ((</>), makeValid)-import Text.PrettyPrint.Boxes- (text, vcat, left, render, hsep, top, (/+/))--import Sound.Jammit.Base-import Sound.Jammit.Export-import qualified Paths_jammittools as Paths- printUsage :: IO () printUsage = do prog <- Env.getProgName@@ -59,7 +59,7 @@ backingOrder = [Drums, Guitar, Keyboard, Bass, Vocal] isGuitar p = elem (partToInstrument p) [Guitar, Bass] (gtrs, nongtrs) = partition isGuitar [minBound .. maxBound]- chosenBacking = listToMaybe $ flip mapMaybe backingOrder $ \i ->+ backingTracks = flip mapMaybe backingOrder $ \i -> case getOneResult (Without i) audios of Left _ -> Nothing Right fp -> Just (i, fp)@@ -68,7 +68,7 @@ (Right note, Right tab) -> let parts = [note, tab] systemHeight = sum $ map snd parts- fout = dout </> drop 4 (map toLower (show p) ++ ".pdf")+ fout = dout </> drop 4 (map toLower $ show p) <.> "pdf" in do putStrLn $ "Exporting notation & tab for " ++ show p runSheet [note, tab] (getPageLines systemHeight args) fout@@ -77,7 +77,7 @@ case getOneResult (Notation p) sheets of Left _ -> return () Right note -> let- fout = dout </> drop 4 (map toLower (show p) ++ ".pdf")+ fout = dout </> drop 4 (map toLower $ show p) <.> "pdf" in do putStrLn $ "Exporting notation for " ++ show p runSheet [note] (getPageLines (snd note) args) fout@@ -85,18 +85,23 @@ case getOneResult (Only p) audios of Left _ -> return () Right fp -> let- fout = dout </> drop 4 (map toLower (show p) ++ ".wav")+ fout = dout </> drop 4 (map toLower $ show p) <.> "wav" in do putStrLn $ "Exporting audio for " ++ show p runAudio [fp] [] fout- case chosenBacking of- Nothing -> return ()- Just (inst, fback) -> let- others = [ fp | (Only p, fp) <- audios, partToInstrument p /= inst ]- fout = dout </> "backing.wav"- in do- putStrLn "Exporting backing audio (could take a while)"- runAudio [fback] others fout+ if rawBackings args+ then forM_ backingTracks $ \(inst, fback) -> do+ putStrLn $ "Exporting backing track for " ++ show inst+ let fout = dout </> "backing-" ++ map toLower (show inst) <.> "wav"+ runAudio [fback] [] fout+ else case backingTracks of+ [] -> return ()+ (inst, fback) : _ -> let+ others = [ fp | (Only p, fp) <- audios, partToInstrument p /= inst ]+ fout = dout </> "backing.wav"+ in do+ putStrLn "Exporting backing audio (could take a while)"+ runAudio [fback] others fout clickTracks <- mapM loadBeats $ map (\(fp, _, _) -> fp) matches case catMaybes clickTracks of [] -> return ()@@ -190,14 +195,13 @@ -- to filter it. searchResults :: Args -> IO Library searchResults args = do- jmt <- case jammitDir args of- Just j -> return j+ exeDir <- takeDirectory <$> Env.getExecutablePath+ jmt <- case jammitDir args of+ Just j -> return [j] Nothing -> Env.lookupEnv "JAMMIT" >>= \mv -> case mv of- Just j -> return j- Nothing -> let- err = "Couldn't find Jammit directory. Try -j or the env var JAMMIT"- in fromMaybe (error err) <$> findJammitDir- db <- loadLibrary jmt+ Just j -> return [j]+ Nothing -> maybeToList <$> findJammitDir+ db <- concat <$> mapM loadLibrary (exeDir : jmt) return $ filterLibrary args db -- | Checks that the search actually narrowed down the library to a single song.@@ -273,6 +277,9 @@ , Opt.Option ['c'] ["check"] (Opt.NoArg $ \a -> a { function = CheckPresence }) "function: check presence of audio parts"+ , Opt.Option [] ["raw"]+ (Opt.NoArg $ \a -> a { rawBackings = True })+ "when exporting song/library, extract all individual backing tracks" ] data Args = Args@@ -282,6 +289,7 @@ , pageLines :: Maybe Int , jammitDir :: Maybe FilePath , function :: Function+ , rawBackings :: Bool } data Function@@ -303,6 +311,7 @@ , pageLines = Nothing , jammitDir = Nothing , function = PrintUsage+ , rawBackings = False } partToChar :: Part -> Char
README.md view
@@ -52,6 +52,7 @@ # On Windows and OS X, the official app's library location is used # if you do not specify this flag. # You can also specify the environment variable JAMMIT.+ # Songs in the same folder as the program will also always be found. -T "My Song" # Exact search on title, instead of -t which is case-insensitive substring.@@ -69,6 +70,10 @@ # One system contains a line from each individual part. # If this flag is not given, an appropriate number of systems will be chosen # to get close to an 8.5" by 11" page ratio.++ --raw+ # For -x and -b options, exports all original backing tracks separately+ # instead of producing a combined one. Instrument parts are given by this somewhat terse syntax:
jammittools.cabal view
@@ -1,5 +1,5 @@ Name: jammittools-Version: 0.5.3.2+Version: 0.5.4 Synopsis: Export sheet music and audio from Windows/Mac app Jammit Description: @@ -20,7 +20,9 @@ Category: Sound Homepage: https://github.com/mtolly/jammittools Bug-Reports: https://github.com/mtolly/jammittools/issues-Extra-Source-Files: README.md+Extra-Source-Files:+ README.md+ CHANGELOG.md library exposed-modules:
src/Sound/Jammit/Base.hs view
@@ -1,7 +1,7 @@ {- | Basic types and functions for dealing with Jammit song packages. -}-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE RecordWildCards #-} module Sound.Jammit.Base ( Instrument(..)@@ -23,21 +23,22 @@ , sheetWidth, sheetHeight ) where +import Control.Applicative ((<|>))+import Control.Monad (filterM, forM, guard)+import Data.Char (toLower)+import qualified Data.Map as Map+import Data.Maybe (fromMaybe)+import Sound.Jammit.Internal.PropertyList+import qualified System.Directory as Dir+import System.Environment (lookupEnv)+import System.FilePath (dropTrailingPathSeparator,+ takeFileName, (</>))+import qualified System.Info as Info+ #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))+import Control.Applicative ((<$>)) #endif-import Control.Applicative ((<|>))-import Control.Monad (filterM, guard, forM)-import Data.Char (toLower)-import Data.Maybe (fromMaybe)-import System.Environment (lookupEnv)-import qualified Data.Map as Map-import qualified System.Directory as Dir-import System.FilePath ((</>), takeFileName, dropTrailingPathSeparator)-import qualified System.Info as Info -import Sound.Jammit.Internal.PropertyList- -- | The 'Enum' instance corresponds to the number used in the 'instrument' -- property, and the names (used by 'Show' and 'Read') are capitalized versions -- of those used in the 'skillLevel' property.@@ -236,7 +237,8 @@ songSubdirs dir = case takeFileName $ dropTrailingPathSeparator dir of fn@('.' : _ : _) | fn /= ".." -> return [] _ -> do- isSong <- Dir.doesFileExist $ dir </> "info.plist"+ isSong <- and <$> mapM Dir.doesFileExist+ [dir </> "info.plist", dir </> "tracks.plist"] let here = [dir | isSong] subdirs <- lsAbsolute dir >>= filterM Dir.doesDirectoryExist (here ++) . concat <$> mapM songSubdirs subdirs
src/Sound/Jammit/Export.hs view
@@ -15,23 +15,21 @@ , writeMetronomeTrack ) where -import Control.Applicative (liftA2)-import Control.Monad (forM, forever)-import Data.Char (toLower)-import Data.Int (Int16, Int32)-import Data.List (isInfixOf, sort, isPrefixOf)-import Data.Maybe (catMaybes)--import System.Directory (getDirectoryContents)-import System.FilePath ((</>), splitFileName, takeFileName)--import Sound.Jammit.Internal.Image-import Sound.Jammit.Base-import Sound.Jammit.Internal.Audio-import Sound.Jammit.Internal.TempIO--import qualified Data.Conduit.Audio as A-import Control.Monad.Trans.Resource (MonadResource, runResourceT)+import Control.Applicative (liftA2)+import Control.Monad (forM, forever)+import Control.Monad.Trans.Resource (MonadResource, runResourceT)+import Data.Char (toLower)+import qualified Data.Conduit.Audio as A+import Data.Int (Int16, Int32)+import Data.List (isInfixOf, isPrefixOf, sort)+import Data.Maybe (catMaybes, fromMaybe)+import Sound.Jammit.Base+import Sound.Jammit.Internal.Audio+import Sound.Jammit.Internal.Image+import Sound.Jammit.Internal.TempIO+import System.Directory (getDirectoryContents)+import System.FilePath (splitFileName, takeFileName,+ (</>)) type Library = [(FilePath, Info, [Track])] @@ -81,15 +79,18 @@ _ -> [] audioSource :: (MonadResource m) => FilePath -> IO (A.AudioSource m Int16)-audioSource fp = if takeFileName fp `elem` [tttDrums, tttDrumsBack]- then fmap (A.padStart $ A.Frames 38) $ readIMA fp- else readIMA fp- -- I've only found one audio file where the instruments are not aligned:- -- the drums and drums backing track for Take the Time are 38 samples ahead- -- of the other instruments. So as a hack, we pad the front of them by 38- -- samples to line things up.- where tttDrums = "793EAAE0-6761-44D7-9A9A-1FB451A2A438_jcfx"- tttDrumsBack = "37EE5AA5-4049-4CED-844A-D34F6B165F67_jcfx"+audioSource fp = let+ -- These are hacks that make one instrument line up with the rest of a song.+ timingHacks =+ -- Take the Time (Dream Theater)+ [ ("793EAAE0-6761-44D7-9A9A-1FB451A2A438_jcfx", A.padStart $ A.Frames 38) -- Drums+ , ("37EE5AA5-4049-4CED-844A-D34F6B165F67_jcfx", A.padStart $ A.Frames 38) -- Drums backing+ -- Rockstar (Nickelback)+ , ("FB9A99DB-C70C-47CB-B63F-A78F11AC05D5_jcfx", A.dropStart $ A.Frames 10) -- Vocal+ , ("7998F9B7-8A97-49D0-A3A9-C001FDD1C1DC_jcfx", A.dropStart $ A.Frames 10) -- B Vocals+ , ("C4C543A0-EB1F-4628-A401-C5860D675FA4_jcfx", A.dropStart $ A.Frames 10) -- Vocal backing+ ]+ in fromMaybe id (lookup (takeFileName fp) timingHacks) <$> readIMA fp runAudio :: [FilePath] -- ^ AIFCs to mix in normally
src/Sound/Jammit/Internal/Audio.hs view
@@ -13,10 +13,12 @@ import Control.Monad (liftM2, unless) import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Resource (MonadResource) import qualified Data.ByteString as B-import Data.ByteString.Char8 () -- for IsString instance+import Data.ByteString.Char8 () import qualified Data.ByteString.Unsafe as B import qualified Data.Conduit as C+import qualified Data.Conduit.Audio as A import qualified Data.Conduit.List as CL import qualified Data.Vector.Storable as V import Foreign (Int16, Int32, Ptr, Word16,@@ -26,9 +28,6 @@ import GHC.IO.Handle (HandlePosn (..)) import qualified System.IO as IO import System.IO.Unsafe (unsafePerformIO)--import Control.Monad.Trans.Resource (MonadResource)-import qualified Data.Conduit.Audio as A parseChunk :: IO.Handle -> IO (B.ByteString, (HandlePosn, HandlePosn)) parseChunk h = do
src/Sound/Jammit/Internal/Image.hs view
@@ -3,18 +3,17 @@ , jpegsToPDF ) where -import qualified Codec.Picture as P-import Codec.Picture.Types (convertImage)-import Control.Monad (forM_, replicateM)-import Control.Monad.IO.Class (MonadIO)-import Control.Monad.Trans.Class (lift)-import qualified Graphics.PDF as PDF-import qualified Data.ByteString.Lazy as BL-import qualified Data.Conduit as C-import Data.Maybe (catMaybes)-import qualified Data.Vector.Storable as V--import Sound.Jammit.Internal.TempIO+import qualified Codec.Picture as P+import Codec.Picture.Types (convertImage)+import Control.Monad (forM_, replicateM)+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Trans.Class (lift)+import qualified Data.ByteString.Lazy as BL+import qualified Data.Conduit as C+import Data.Maybe (catMaybes)+import qualified Data.Vector.Storable as V+import qualified Graphics.PDF as PDF+import Sound.Jammit.Internal.TempIO loadPNG :: FilePath -> IO (P.Image P.PixelRGB8) loadPNG fp = do
src/Sound/Jammit/Internal/PropertyList.hs view
@@ -1,8 +1,8 @@ -- | A very simple, non-robust property list parser.-{-# LANGUAGE CPP #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-} module Sound.Jammit.Internal.PropertyList ( readPropertyList , PropertyList(..)@@ -11,18 +11,18 @@ , fromLookup ) where -import qualified Data.Text.IO as TIO-import Text.XML.Light-import qualified Data.Map as Map-import Text.Read (readMaybe)-import Data.Maybe (mapMaybe, fromJust)-import Data.Char (isSpace)-import Control.Monad (guard)+import Control.Monad (guard)+import Data.Char (isSpace)+import qualified Data.Map as Map+import Data.Maybe (fromJust, mapMaybe)+import qualified Data.Text.IO as TIO+import Text.Read (readMaybe)+import Text.XML.Light #if !MIN_VERSION_base(4,8,0)-import Prelude hiding (mapM)-import Data.Traversable (mapM)-import Control.Applicative ((<$>))+import Control.Applicative ((<$>))+import Data.Traversable (mapM)+import Prelude hiding (mapM) #endif data PropertyList
src/Sound/Jammit/Internal/TempIO.hs view
@@ -8,15 +8,15 @@ , liftIO ) where -import Data.List (stripPrefix)-import System.IO (hClose)-import System.IO.Error (catchIOError)--import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Reader (ask, ReaderT(..))-import System.Directory (copyFile, renameFile)-import System.FilePath (splitPath)-import System.IO.Temp (openTempFile, withSystemTempDirectory)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Reader (ReaderT (..), ask)+import Data.List (stripPrefix)+import System.Directory (copyFile, renameFile)+import System.FilePath (splitPath)+import System.IO (hClose)+import System.IO.Error (catchIOError)+import System.IO.Temp (openTempFile,+ withSystemTempDirectory) -- | A wrapper around IO with a designated directory for temporary files. type TempIO = ReaderT FilePath IO