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