diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31ab0ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Created by https://www.toptal.com/developers/gitignore/api/swift +# Edit at https://www.toptal.com/developers/gitignore?templates=swift + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +# End of https://www.toptal.com/developers/gitignore/api/swift diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1a71a2 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# playmid + +A command line tool for MacOS which uses the system's built-in sound font assets to play **.mid** file. + +![screenshot.jpg](screenshot.jpg) diff --git a/playmid.xcodeproj/project.pbxproj b/playmid.xcodeproj/project.pbxproj index 7b7fabb..4b49547 100644 --- a/playmid.xcodeproj/project.pbxproj +++ b/playmid.xcodeproj/project.pbxproj @@ -6,6 +6,10 @@ objectVersion = 77; objects = { +/* Begin PBXBuildFile section */ + 0E80F2402D1E75BB0071F001 /* Progress in Frameworks */ = {isa = PBXBuildFile; productRef = 0E80F23F2D1E75BB0071F001 /* Progress */; }; +/* End PBXBuildFile section */ + /* Begin PBXCopyFilesBuildPhase section */ 0E80F2322D1E75090071F001 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -35,6 +39,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0E80F2402D1E75BB0071F001 /* Progress in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,6 +82,7 @@ ); name = playmid; packageProductDependencies = ( + 0E80F23F2D1E75BB0071F001 /* Progress */, ); productName = playmid; productReference = 0E80F2342D1E75090071F001 /* playmid */; @@ -106,6 +112,9 @@ ); mainGroup = 0E80F22B2D1E75090071F001; minimizedProjectReferenceProxies = 1; + packageReferences = ( + 0E80F23E2D1E75BB0071F001 /* XCRemoteSwiftPackageReference "Progress.swift" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 0E80F2352D1E75090071F001 /* Products */; projectDirPath = ""; @@ -248,7 +257,11 @@ 0E80F23C2D1E75090071F001 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + MACOSX_DEPLOYMENT_TARGET = 13.5; + PRODUCT_BUNDLE_IDENTIFIER = fun.padao.playmid; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -257,7 +270,11 @@ 0E80F23D2D1E75090071F001 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + MACOSX_DEPLOYMENT_TARGET = 13.5; + PRODUCT_BUNDLE_IDENTIFIER = fun.padao.playmid; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; }; @@ -285,6 +302,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 0E80F23E2D1E75BB0071F001 /* XCRemoteSwiftPackageReference "Progress.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jkandzi/Progress.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.4.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 0E80F23F2D1E75BB0071F001 /* Progress */ = { + isa = XCSwiftPackageProductDependency; + package = 0E80F23E2D1E75BB0071F001 /* XCRemoteSwiftPackageReference "Progress.swift" */; + productName = Progress; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 0E80F22C2D1E75090071F001 /* Project object */; } diff --git a/playmid.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/playmid.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..73f0e31 --- /dev/null +++ b/playmid.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "ce60120d05e30c0299b79ea3b9730d3ccaf4f1d74426ac8b92d14602a0160097", + "pins" : [ + { + "identity" : "progress.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jkandzi/Progress.swift.git", + "state" : { + "revision" : "fed6598735d7982058690acf8f52a0a5fdaeb3e0", + "version" : "0.4.0" + } + } + ], + "version" : 3 +} diff --git a/playmid.xcodeproj/xcshareddata/xcschemes/playmid.xcscheme b/playmid.xcodeproj/xcshareddata/xcschemes/playmid.xcscheme new file mode 100644 index 0000000..e490695 --- /dev/null +++ b/playmid.xcodeproj/xcshareddata/xcschemes/playmid.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playmid.xcodeproj/xcuserdata/zyf.xcuserdatad/xcschemes/xcschememanagement.plist b/playmid.xcodeproj/xcuserdata/zyf.xcuserdatad/xcschemes/xcschememanagement.plist index d38b3c4..0773a29 100644 --- a/playmid.xcodeproj/xcuserdata/zyf.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/playmid.xcodeproj/xcuserdata/zyf.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,13 @@ 0 + SuppressBuildableAutocreation + + 0E80F2332D1E75090071F001 + + primary + + + diff --git a/playmid/main.swift b/playmid/main.swift index dbb27ce..c4f3a61 100644 --- a/playmid/main.swift +++ b/playmid/main.swift @@ -1,11 +1,80 @@ // -// main.swift -// playmid +// playmid.swift +// 使用 mac 自带的音色播放一个 mid 文件 +// play .mid file with the system sound font resources of MacOS // -// Created by 张宇飞 on 2024/12/27. +// Created by 张宇飞 on 2024/12/16. // +import AVFoundation import Foundation +import Progress -print("Hello, World!") +private func formatTime(_ seconds: TimeInterval) -> String { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + formatter.zeroFormattingBehavior = .pad + let formattedString = formatter.string(from: seconds) ?? "00:00" + let milliseconds = Int((seconds.truncatingRemainder(dividingBy: 1)) * 1000) + + return "\(formattedString).\(String(format: "%03d", milliseconds))" +} + +private func timeToTicks(_ secs: TimeInterval) -> Int { + let ms = Int((secs.truncatingRemainder(dividingBy: 1)) * 1000) + let s = Int(secs.rounded(.towardZero)) + return s * 1000 + ms +} + +private func parseArgs() -> URL { + let args = CommandLine.arguments + if args.count != 2 { + let p = URL(fileURLWithPath: args[0]).lastPathComponent + print("Usage: \(p) ") + exit(-1) + } + + return URL(fileURLWithPath: args[1]) +} + +private struct ProgressPlayerCursor: ProgressElementType { + let player: AVMIDIPlayer + + init(player: AVMIDIPlayer) { + self.player = player + } + + public func value(_: ProgressBar) -> String { + return formatTime(player.currentPosition) + } +} + +private func main() { + if let player = try? AVMIDIPlayer(contentsOf: parseArgs(), soundBankURL: nil) { + var running = true + player.play { + running = false + } + + let totalTicks = timeToTicks(player.duration) + let duration = formatTime(player.duration) + var bar = ProgressBar( + count: totalTicks, + configuration: [ + ProgressPlayerCursor(player: player), + ProgressBarLine(barLength: 60), + ProgressString(string: duration), + ]) + + while running { + let ticks = timeToTicks(player.currentPosition) + bar.setValue(ticks) + usleep(500_000) + } + bar.setValue(totalTicks) + } +} + +main() diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000..212a78c Binary files /dev/null and b/screenshot.jpg differ