InstrumentKit provides type-safe access to localized musical instruments and their tunings.
- For the time being, this package is focused on collecting, validating, and localizing data for string instruments only. Once string instruments feel accurate and stable and the library is extremely tested, the plan will be to add brass, keyboards, percussions, and more.
- The library currently leverages an internal dependency for musical notation (
NoteKit
), but should be reaplced by the more accurate and better maintainedAudioKit/Tonic
as soon as possible. Musical math and theory is difficult, andInstrumentKit
doesn't want to be in the business of musical math or theory. - Provide system for ensuring paritally localized models provide English values by default. Currently, they fall back on the
localizationKey
, meaning"Guitar"
translated to a partial localization without the"guitar"
key will show up as"guitar"
, not"Guitar"
.- This already works for missing localizations, but doesn't work for partial localizations.
- This functionality shouldn't be much more work to achieve without increasing performance.
Swift Package Manager is built into the Swift toolchain and is the preferred way of integrating the SDK.
In addition to being compatible with Apple platforms (iOS, macOS, tvOS, watchOS), the package is also fully compatible with Linux, making it perfect for Server-side Swift projects.
For Swift package projects, simply add the following line to your Package.swift file in the dependencies section, replacing x.x.x
with the latest version:
dependencies: [
.package(url: "https://github.com/bdrelling/InstrumentKit", .from: "x.x.x"),
]
For app projects, simply follow the Apple documentation on adding package dependencies to your app.
struct StringInstrument {
let localizationKey: String
let name: String
let numberOfStrings: Int
let numberOfCourses: Int
let tunings: [Tuning]
}
struct Tuning {
let localizationKey: String
let name: String
let notes: [Note]
}
Get a specific string instrument.
let guitar: StringInstrument = .guitar
let ukulele: StringInstrument = .ukulele
let irishBouzouki: StringInstrument = .irishBouzouki
Get all string instruments.
let instruments: [StringInstrument] = StringInstrument.allCases
let instruments: [StringInstrument] = .allCases
Collections provide an
.allCases
convenience extension, so you don't have to write out the name of the element each time.
Get a specific tuning for an instrument.
let guitarStandard: Tuning = Tuning.Guitar.standard.rawValue
Get all tunings for an instrument.
let guitarTunings: [Tuning] = StringInstrument.guitar.tunings
let guitarTunings: [Tuning] = Tuning.Guitar.allTunings
allTunings
is provided as a convenience forallCases.map(\.rawValue)
.
By default, every StringInstrument
and Tuning
will be localized to Locale.current
. However, you can also localize models on the fly simply by calling .localized(to:)
with a Locale
, locale identifier (String
), or using the SupportedLanguage
enum
.
Localize a model.
let guitarra: StringInstrument = .guitar.localized(to: "es")
let estandard: Tuning = Tuning.Guitar.standard.localized(to: "es")
Localize a collection of models.
let spanishInstruments: [StringInstrument] = .allCases.localized(to: "es")
let spanishGuitarTunings: [Tuning] = Tuning.Guitar.allTunings.localized(to: "es")
Looking for localized instrument and tuning definitions but don't use Swift? No problem!
All instruments and tunings in this package are also available by making API requests to instruments.fyi. You can fetch all instruments, specific instruments, tunings, and more.
For more information, see instruments.fyi or visit instruments.fyi.
String
localization usage in this module matches standard usage of Bundle.localizedString(forKey:value:table:)
and NSLocalizedString
. By initializing these strings through localization tables every time, it ensures that anyone consuming the module will get localization out of the box without any additional work, as they will always localize using Locale.current
.
Seeing the following in Tuning+Definitions.swift
might make you uncomfortable:
enum Guitar: Tuning, CaseIterable {
case standard = "standard: E2 A2 D3 G3 B3 E4"
case dropD = "drop_d: D2 A2 D3 G3 B3 E4"
case openD = "open_d: D2 A2 D3 F#3 A3 D4"
}
This approach was not taken lightly. Instruments have dozens of commonly applicable tunings, and there are hundreds of instruments. Maintaining a large data set without an easy-to-parse method of analyzing, comparing, and fact-checking that data becomes extremely difficult over time.
String
parsing of a Tuning
takes place in the special init
at the bottom of Tuning+Definitions.swift
. By making Tuning
conform to ExpressibleByStringLiteral
, it allows us to create CaseIterable
enums that are easy to validate and provide Tuning
collection and organization functionality out of the box.
The logic is simple and very easy to validate: loop through every Tuning
in the project and if any of our localization keys in any of our supported languages come back with an error of any sort, numerous unit tests in the project will blow up.
Additionally, these enums are only initialized once, meaning that if you keep accessing one of these enums over and over, you can be sure that it won't be re-parsing the Tuning
definition each time.
If you have an alternative to propose, feel free to open an issue or pull request. Several approaches were considered before landing on String
-initialized Tuning
s, but a fresh set of eyes is always helpful. If you have a performant way of defining this data that is type-safe yet provides the same level of convenience and readability (for maintenance and data accuracy), I'm interested!
Discussions, issues, and pull requests are more than welcome, for development, corrections, and/or localizations.
If you're providing corrections and/or localizations, please provide as many additional sources as you can for validation in order to help ensure we can get the corrections integrated as quickly as possible.
Special thanks to AudioKit for all of their expertise and support.
This project is released under the MIT license. See LICENSE for details.