Compare commits
4
Commits
e7a46772ec
...
9ddac746e5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ddac746e5 | ||
|
|
ecbf07d1ea | ||
|
|
b2bf04e78b | ||
|
|
b51102b65d |
@@ -0,0 +1,72 @@
|
|||||||
|
import MusicAssistantCore
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct InstrumentBrowserView: View {
|
||||||
|
private let catalog: LocalInstrumentCatalog
|
||||||
|
|
||||||
|
@State private var searchText = ""
|
||||||
|
@State private var selectedFamilyCategory: String?
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
init(catalog: LocalInstrumentCatalog = LocalInstrumentCatalog()) {
|
||||||
|
self.catalog = catalog
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Picker("Family", selection: $selectedFamilyCategory) {
|
||||||
|
Text("All families").tag(String?.none)
|
||||||
|
|
||||||
|
ForEach(catalog.familyCategories, id: \.self) { familyCategory in
|
||||||
|
Text(familyCategory).tag(Optional(familyCategory))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
List(
|
||||||
|
catalog.instruments(
|
||||||
|
matching: searchText,
|
||||||
|
familyCategory: selectedFamilyCategory
|
||||||
|
),
|
||||||
|
id: \.id
|
||||||
|
) { instrument in
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(instrument.name)
|
||||||
|
.font(.body.weight(.medium))
|
||||||
|
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(instrument.familyCategory)
|
||||||
|
|
||||||
|
if let regionOrigin = instrument.regionOrigin {
|
||||||
|
Text(regionOrigin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Instrument Catalog")
|
||||||
|
.searchable(text: $searchText, prompt: "Search instruments")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button("Done") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: 460, minHeight: 520)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
InstrumentBrowserView()
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ struct ProjectInspectorView: View {
|
|||||||
@State private var newInstrumentName = ""
|
@State private var newInstrumentName = ""
|
||||||
@State private var newVocalistLabel = ""
|
@State private var newVocalistLabel = ""
|
||||||
@State private var newLanguageName = ""
|
@State private var newLanguageName = ""
|
||||||
|
@State private var isInstrumentBrowserPresented = false
|
||||||
@State private var isSaving = false
|
@State private var isSaving = false
|
||||||
@State private var saveStatus: SaveStatus?
|
@State private var saveStatus: SaveStatus?
|
||||||
|
|
||||||
@@ -62,6 +63,12 @@ struct ProjectInspectorView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section("Instruments") {
|
Section("Instruments") {
|
||||||
|
Button {
|
||||||
|
isInstrumentBrowserPresented = true
|
||||||
|
} label: {
|
||||||
|
Label("Browse catalog", systemImage: "magnifyingglass")
|
||||||
|
}
|
||||||
|
|
||||||
ForEach($project.instruments, id: \.instrumentId) { $instrument in
|
ForEach($project.instruments, id: \.instrumentId) { $instrument in
|
||||||
Toggle(isOn: $instrument.selected) {
|
Toggle(isOn: $instrument.selected) {
|
||||||
Text(instrument.variant?.isEmpty == false ? instrument.variant ?? instrument.instrumentId : instrument.instrumentId)
|
Text(instrument.variant?.isEmpty == false ? instrument.variant ?? instrument.instrumentId : instrument.instrumentId)
|
||||||
@@ -138,6 +145,9 @@ struct ProjectInspectorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(nsColor: .windowBackgroundColor))
|
.background(Color(nsColor: .windowBackgroundColor))
|
||||||
|
.sheet(isPresented: $isInstrumentBrowserPresented) {
|
||||||
|
InstrumentBrowserView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var durationSecondsBinding: Binding<String> {
|
private var durationSecondsBinding: Binding<String> {
|
||||||
|
|||||||
@@ -0,0 +1,328 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct LocalInstrumentCatalog: Sendable {
|
||||||
|
public let items: [InstrumentCatalogItem]
|
||||||
|
|
||||||
|
public init(items: [InstrumentCatalogItem] = LocalInstrumentCatalog.worldwideItems) {
|
||||||
|
self.items = items
|
||||||
|
}
|
||||||
|
|
||||||
|
public var familyCategories: [String] {
|
||||||
|
Array(Set(items.map(\.familyCategory)))
|
||||||
|
.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func instruments(
|
||||||
|
matching query: String,
|
||||||
|
familyCategory: String? = nil
|
||||||
|
) -> [InstrumentCatalogItem] {
|
||||||
|
let searchTokens = Self.searchTokens(for: query)
|
||||||
|
let selectedFamilyCategory = familyCategory?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
return items
|
||||||
|
.filter { item in
|
||||||
|
let matchesSearch = searchTokens.isEmpty || searchTokens.allSatisfy { token in
|
||||||
|
Self.searchableTerms(for: item).contains { $0.contains(token) }
|
||||||
|
}
|
||||||
|
let matchesFamily = selectedFamilyCategory?.isEmpty != false || item.familyCategory == selectedFamilyCategory
|
||||||
|
return matchesSearch && matchesFamily
|
||||||
|
}
|
||||||
|
.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static let worldwideItems: [InstrumentCatalogItem] =
|
||||||
|
western + middleEastern + african + southAsian + eastAsian + southeastAsian + latinAmerican + folkAndModern
|
||||||
|
|
||||||
|
private static let western: [InstrumentCatalogItem] = [
|
||||||
|
item("piano", "Piano", "Keyboard", "Western"),
|
||||||
|
item("upright-piano", "Upright Piano", "Keyboard", "Western"),
|
||||||
|
item("grand-piano", "Grand Piano", "Keyboard", "Western"),
|
||||||
|
item("electric-piano", "Electric Piano", "Keyboard", "Western"),
|
||||||
|
item("harpsichord", "Harpsichord", "Keyboard", "Western"),
|
||||||
|
item("celesta", "Celesta", "Keyboard", "Western"),
|
||||||
|
item("pipe-organ", "Pipe Organ", "Keyboard", "Western"),
|
||||||
|
item("electric-organ", "Electric Organ", "Keyboard", "Western"),
|
||||||
|
item("accordion", "Accordion", "Free reed", "Europe"),
|
||||||
|
item("concertina", "Concertina", "Free reed", "Europe"),
|
||||||
|
item("melodica", "Melodica", "Free reed", "Western"),
|
||||||
|
item("violin", "Violin", "Bowed strings", "Western", aliases: ["fiddle"]),
|
||||||
|
item("viola", "Viola", "Bowed strings", "Western"),
|
||||||
|
item("cello", "Cello", "Bowed strings", "Western", aliases: ["violoncello"]),
|
||||||
|
item("double-bass", "Double Bass", "Bowed strings", "Western", aliases: ["upright bass"]),
|
||||||
|
item("concert-harp", "Concert Harp", "Plucked strings", "Western"),
|
||||||
|
item("classical-guitar", "Classical Guitar", "Plucked strings", "Western", aliases: ["nylon-string guitar"]),
|
||||||
|
item("acoustic-guitar", "Acoustic Guitar", "Plucked strings", "Western"),
|
||||||
|
item("electric-guitar", "Electric Guitar", "Plucked strings", "Western"),
|
||||||
|
item("bass-guitar", "Bass Guitar", "Plucked strings", "Western"),
|
||||||
|
item("twelve-string-guitar", "Twelve-string Guitar", "Plucked strings", "Western"),
|
||||||
|
item("mandolin", "Mandolin", "Plucked strings", "Europe"),
|
||||||
|
item("banjo", "Banjo", "Plucked strings", "North America"),
|
||||||
|
item("ukulele", "Ukulele", "Plucked strings", "Oceania"),
|
||||||
|
item("resonator-guitar", "Resonator Guitar", "Plucked strings", "North America", aliases: ["dobro"]),
|
||||||
|
item("lap-steel-guitar", "Lap Steel Guitar", "Plucked strings", "North America"),
|
||||||
|
item("pedal-steel-guitar", "Pedal Steel Guitar", "Plucked strings", "North America"),
|
||||||
|
item("flute", "Concert Flute", "Woodwinds", "Western", aliases: ["flute"]),
|
||||||
|
item("piccolo", "Piccolo", "Woodwinds", "Western"),
|
||||||
|
item("recorder", "Recorder", "Woodwinds", "Europe"),
|
||||||
|
item("oboe", "Oboe", "Woodwinds", "Western"),
|
||||||
|
item("english-horn", "English Horn", "Woodwinds", "Western", aliases: ["cor anglais"]),
|
||||||
|
item("bassoon", "Bassoon", "Woodwinds", "Western"),
|
||||||
|
item("contrabassoon", "Contrabassoon", "Woodwinds", "Western"),
|
||||||
|
item("clarinet", "Clarinet", "Woodwinds", "Western"),
|
||||||
|
item("bass-clarinet", "Bass Clarinet", "Woodwinds", "Western"),
|
||||||
|
item("soprano-saxophone", "Soprano Saxophone", "Woodwinds", "Western", aliases: ["soprano sax"]),
|
||||||
|
item("alto-saxophone", "Alto Saxophone", "Woodwinds", "Western", aliases: ["alto sax"]),
|
||||||
|
item("tenor-saxophone", "Tenor Saxophone", "Woodwinds", "Western", aliases: ["tenor sax"]),
|
||||||
|
item("baritone-saxophone", "Baritone Saxophone", "Woodwinds", "Western", aliases: ["baritone sax"]),
|
||||||
|
item("trumpet", "Trumpet", "Brass", "Western"),
|
||||||
|
item("cornet", "Cornet", "Brass", "Western"),
|
||||||
|
item("flugelhorn", "Flugelhorn", "Brass", "Western"),
|
||||||
|
item("trombone", "Trombone", "Brass", "Western"),
|
||||||
|
item("bass-trombone", "Bass Trombone", "Brass", "Western"),
|
||||||
|
item("french-horn", "French Horn", "Brass", "Western", aliases: ["horn"]),
|
||||||
|
item("tuba", "Tuba", "Brass", "Western"),
|
||||||
|
item("euphonium", "Euphonium", "Brass", "Western"),
|
||||||
|
item("sousaphone", "Sousaphone", "Brass", "North America"),
|
||||||
|
item("drum-kit", "Drum Kit", "Percussion", "Western", aliases: ["drum set"]),
|
||||||
|
item("snare-drum", "Snare Drum", "Percussion", "Western"),
|
||||||
|
item("bass-drum", "Bass Drum", "Percussion", "Western", aliases: ["kick drum"]),
|
||||||
|
item("timpani", "Timpani", "Percussion", "Western", aliases: ["kettle drums"]),
|
||||||
|
item("xylophone", "Xylophone", "Mallet percussion", "Western"),
|
||||||
|
item("marimba", "Marimba", "Mallet percussion", "Central America"),
|
||||||
|
item("vibraphone", "Vibraphone", "Mallet percussion", "Western", aliases: ["vibes"]),
|
||||||
|
item("glockenspiel", "Glockenspiel", "Mallet percussion", "Europe"),
|
||||||
|
item("tubular-bells", "Tubular Bells", "Mallet percussion", "Western", aliases: ["orchestral chimes"]),
|
||||||
|
item("triangle", "Triangle", "Percussion", "Western"),
|
||||||
|
item("tambourine", "Tambourine", "Frame drums", "Western"),
|
||||||
|
item("cowbell", "Cowbell", "Percussion", "Western"),
|
||||||
|
item("cymbals", "Cymbals", "Percussion", "Western"),
|
||||||
|
item("gong", "Gong", "Percussion", "East Asia"),
|
||||||
|
item("castanets", "Castanets", "Percussion", "Spain"),
|
||||||
|
item("claves", "Claves", "Percussion", "Latin America"),
|
||||||
|
item("harmonica", "Harmonica", "Free reed", "Western", aliases: ["mouth organ"])
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let middleEastern: [InstrumentCatalogItem] = [
|
||||||
|
item("oud", "Oud", "Plucked strings", "Middle East", aliases: ["ud"], terms: ["عود"]),
|
||||||
|
item("qanun", "Qanun", "Zithers", "Middle East", aliases: ["kanun"], terms: ["قانون"]),
|
||||||
|
item("ney", "Ney", "End-blown flutes", "Middle East", aliases: ["nai"], terms: ["ناي"]),
|
||||||
|
item("daf", "Daf", "Frame drums", "Middle East", terms: ["دف"]),
|
||||||
|
item("riq", "Riq", "Frame drums", "Middle East", aliases: ["riqq"], terms: ["رق"]),
|
||||||
|
item("darbuka", "Darbuka", "Goblet drums", "Middle East", aliases: ["doumbek", "tabla"], terms: ["دربكة"]),
|
||||||
|
item("kamancheh", "Kamancheh", "Bowed strings", "Middle East", aliases: ["kamanche"]),
|
||||||
|
item("santur", "Santur", "Hammered zithers", "Middle East"),
|
||||||
|
item("baglama", "Baglama", "Plucked strings", "Middle East", aliases: ["saz"]),
|
||||||
|
item("buzuq", "Buzuq", "Plucked strings", "Middle East", aliases: ["bozuk"]),
|
||||||
|
item("mijwiz", "Mijwiz", "Double reeds", "Middle East"),
|
||||||
|
item("arghul", "Arghul", "Double reeds", "Middle East"),
|
||||||
|
item("zurna", "Zurna", "Double reeds", "Middle East"),
|
||||||
|
item("naqqara", "Naqqara", "Kettle drums", "Middle East"),
|
||||||
|
item("tombak", "Tombak", "Goblet drums", "Middle East", aliases: ["zarb"]),
|
||||||
|
item("setar", "Setar", "Plucked strings", "Middle East"),
|
||||||
|
item("tar", "Tar", "Plucked strings", "Middle East"),
|
||||||
|
item("duduk", "Duduk", "Double reeds", "Middle East"),
|
||||||
|
item("kemence", "Kemenche", "Bowed strings", "Middle East"),
|
||||||
|
item("saz-bagpipes", "Tulum", "Bagpipes", "Middle East")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let african: [InstrumentCatalogItem] = [
|
||||||
|
item("djembe", "Djembe", "Hand drums", "West Africa"),
|
||||||
|
item("talking-drum", "Talking Drum", "Tension drums", "West Africa", aliases: ["dundun"]),
|
||||||
|
item("kora", "Kora", "Harps", "West Africa"),
|
||||||
|
item("balafon", "Balafon", "Mallet percussion", "West Africa"),
|
||||||
|
item("mbira", "Mbira", "Lamellophones", "Southern Africa", aliases: ["thumb piano"]),
|
||||||
|
item("kalimba", "Kalimba", "Lamellophones", "Africa"),
|
||||||
|
item("shekere", "Shekere", "Shakers", "West Africa"),
|
||||||
|
item("udu", "Udu", "Clay drums", "West Africa"),
|
||||||
|
item("ngoni", "Ngoni", "Plucked strings", "West Africa"),
|
||||||
|
item("xalam", "Xalam", "Plucked strings", "West Africa"),
|
||||||
|
item("dunun", "Dunun", "Bass drums", "West Africa"),
|
||||||
|
item("ashiko", "Ashiko", "Hand drums", "West Africa"),
|
||||||
|
item("marovany", "Marovany", "Box zithers", "Madagascar"),
|
||||||
|
item("endongo", "Endongo", "Harps", "East Africa"),
|
||||||
|
item("masenqo", "Masenqo", "Bowed strings", "East Africa"),
|
||||||
|
item("krin", "Krin", "Slit drums", "West Africa"),
|
||||||
|
item("agogo", "Agogo Bells", "Percussion", "West Africa"),
|
||||||
|
item("goje", "Goje", "Bowed strings", "West Africa"),
|
||||||
|
item("adungu", "Adungu", "Harps", "East Africa"),
|
||||||
|
item("ekwe", "Ekwe", "Slit drums", "West Africa")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let southAsian: [InstrumentCatalogItem] = [
|
||||||
|
item("sitar", "Sitar", "Plucked strings", "South Asia"),
|
||||||
|
item("tabla", "Tabla", "Hand drums", "South Asia"),
|
||||||
|
item("tanpura", "Tanpura", "Drone strings", "South Asia", aliases: ["tambura"]),
|
||||||
|
item("sarod", "Sarod", "Plucked strings", "South Asia"),
|
||||||
|
item("bansuri", "Bansuri", "Side-blown flutes", "South Asia"),
|
||||||
|
item("shehnai", "Shehnai", "Double reeds", "South Asia"),
|
||||||
|
item("santoor", "Santoor", "Hammered zithers", "South Asia"),
|
||||||
|
item("harmonium", "Harmonium", "Free reed", "South Asia"),
|
||||||
|
item("mridangam", "Mridangam", "Hand drums", "South Asia"),
|
||||||
|
item("veena", "Veena", "Plucked strings", "South Asia"),
|
||||||
|
item("sarangi", "Sarangi", "Bowed strings", "South Asia"),
|
||||||
|
item("dholak", "Dholak", "Hand drums", "South Asia"),
|
||||||
|
item("dhol", "Dhol", "Bass drums", "South Asia"),
|
||||||
|
item("ghatam", "Ghatam", "Clay drums", "South Asia"),
|
||||||
|
item("kanjira", "Kanjira", "Frame drums", "South Asia"),
|
||||||
|
item("nadaswaram", "Nadaswaram", "Double reeds", "South Asia"),
|
||||||
|
item("esraj", "Esraj", "Bowed strings", "South Asia"),
|
||||||
|
item("pakhawaj", "Pakhawaj", "Hand drums", "South Asia"),
|
||||||
|
item("khol", "Khol", "Hand drums", "South Asia"),
|
||||||
|
item("ektara", "Ektara", "Drone strings", "South Asia"),
|
||||||
|
item("rabab", "Rabab", "Plucked strings", "South Asia"),
|
||||||
|
item("morsing", "Morsing", "Jaw harps", "South Asia"),
|
||||||
|
item("nagada", "Nagada", "Kettle drums", "South Asia")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let eastAsian: [InstrumentCatalogItem] = [
|
||||||
|
item("erhu", "Erhu", "Bowed strings", "East Asia"),
|
||||||
|
item("guzheng", "Guzheng", "Zithers", "East Asia", aliases: ["Chinese zither"]),
|
||||||
|
item("pipa", "Pipa", "Plucked strings", "East Asia"),
|
||||||
|
item("dizi", "Dizi", "Side-blown flutes", "East Asia"),
|
||||||
|
item("xiao", "Xiao", "End-blown flutes", "East Asia"),
|
||||||
|
item("suona", "Suona", "Double reeds", "East Asia"),
|
||||||
|
item("sheng", "Sheng", "Mouth organs", "East Asia"),
|
||||||
|
item("yangqin", "Yangqin", "Hammered zithers", "East Asia"),
|
||||||
|
item("ruan", "Ruan", "Plucked strings", "East Asia"),
|
||||||
|
item("guqin", "Guqin", "Zithers", "East Asia"),
|
||||||
|
item("liuqin", "Liuqin", "Plucked strings", "East Asia"),
|
||||||
|
item("hulusi", "Hulusi", "Free reed", "East Asia"),
|
||||||
|
item("morin-khuur", "Morin Khuur", "Bowed strings", "Central Asia", aliases: ["horsehead fiddle"]),
|
||||||
|
item("koto", "Koto", "Zithers", "Japan"),
|
||||||
|
item("shamisen", "Shamisen", "Plucked strings", "Japan"),
|
||||||
|
item("shakuhachi", "Shakuhachi", "End-blown flutes", "Japan"),
|
||||||
|
item("biwa", "Biwa", "Plucked strings", "Japan"),
|
||||||
|
item("taiko", "Taiko", "Drums", "Japan"),
|
||||||
|
item("hichiriki", "Hichiriki", "Double reeds", "Japan"),
|
||||||
|
item("ryuteki", "Ryuteki", "Side-blown flutes", "Japan"),
|
||||||
|
item("gayageum", "Gayageum", "Zithers", "Korea"),
|
||||||
|
item("geomungo", "Geomungo", "Zithers", "Korea"),
|
||||||
|
item("haegeum", "Haegeum", "Bowed strings", "Korea"),
|
||||||
|
item("daegeum", "Daegeum", "Side-blown flutes", "Korea"),
|
||||||
|
item("janggu", "Janggu", "Hourglass drums", "Korea"),
|
||||||
|
item("piri", "Piri", "Double reeds", "Korea"),
|
||||||
|
item("ajaeng", "Ajaeng", "Bowed strings", "Korea"),
|
||||||
|
item("bawu", "Bawu", "Free reed", "East Asia"),
|
||||||
|
item("konghou", "Konghou", "Harps", "East Asia")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let southeastAsian: [InstrumentCatalogItem] = [
|
||||||
|
item("angklung", "Angklung", "Shaken idiophones", "Southeast Asia"),
|
||||||
|
item("gamelan", "Gamelan", "Metallophones", "Southeast Asia"),
|
||||||
|
item("kendang", "Kendang", "Hand drums", "Southeast Asia"),
|
||||||
|
item("suling", "Suling", "Side-blown flutes", "Southeast Asia"),
|
||||||
|
item("rebab", "Rebab", "Bowed strings", "Southeast Asia"),
|
||||||
|
item("kulintang", "Kulintang", "Gong chimes", "Southeast Asia"),
|
||||||
|
item("agung", "Agung", "Gongs", "Southeast Asia"),
|
||||||
|
item("khene", "Khene", "Mouth organs", "Southeast Asia"),
|
||||||
|
item("ranat-ek", "Ranat Ek", "Mallet percussion", "Southeast Asia"),
|
||||||
|
item("saw-duang", "Saw Duang", "Bowed strings", "Southeast Asia"),
|
||||||
|
item("tro", "Tro", "Bowed strings", "Southeast Asia"),
|
||||||
|
item("saung-gauk", "Saung Gauk", "Harps", "Southeast Asia"),
|
||||||
|
item("pat-waing", "Pat Waing", "Drums", "Southeast Asia"),
|
||||||
|
item("rondalla", "Rondalla", "Plucked strings", "Southeast Asia"),
|
||||||
|
item("bandurria", "Bandurria", "Plucked strings", "Southeast Asia"),
|
||||||
|
item("tinikling-bamboo", "Bamboo Percussion", "Percussion", "Southeast Asia"),
|
||||||
|
item("sape", "Sape", "Plucked strings", "Southeast Asia"),
|
||||||
|
item("serunai", "Serunai", "Double reeds", "Southeast Asia")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let latinAmerican: [InstrumentCatalogItem] = [
|
||||||
|
item("conga", "Conga", "Hand drums", "Latin America"),
|
||||||
|
item("bongo", "Bongo", "Hand drums", "Latin America"),
|
||||||
|
item("timbales", "Timbales", "Drums", "Latin America"),
|
||||||
|
item("cajon", "Cajon", "Box drums", "Latin America"),
|
||||||
|
item("guiro", "Guiro", "Scrapers", "Latin America"),
|
||||||
|
item("maracas", "Maracas", "Shakers", "Latin America"),
|
||||||
|
item("cuica", "Cuica", "Friction drums", "Latin America"),
|
||||||
|
item("berimbau", "Berimbau", "Musical bows", "Brazil"),
|
||||||
|
item("bandoneon", "Bandoneon", "Free reed", "South America"),
|
||||||
|
item("charango", "Charango", "Plucked strings", "Andes"),
|
||||||
|
item("cuatro", "Cuatro", "Plucked strings", "Latin America"),
|
||||||
|
item("tres", "Tres", "Plucked strings", "Cuba"),
|
||||||
|
item("vihuela", "Vihuela", "Plucked strings", "Mexico"),
|
||||||
|
item("requinto", "Requinto", "Plucked strings", "Latin America"),
|
||||||
|
item("zampogna", "Zampona", "Panpipes", "Andes", aliases: ["siku"]),
|
||||||
|
item("quena", "Quena", "End-blown flutes", "Andes"),
|
||||||
|
item("bombo-leguero", "Bombo Leguero", "Bass drums", "South America"),
|
||||||
|
item("surdo", "Surdo", "Bass drums", "Brazil"),
|
||||||
|
item("pandeiro", "Pandeiro", "Frame drums", "Brazil"),
|
||||||
|
item("candombe-drums", "Candombe Drums", "Hand drums", "South America"),
|
||||||
|
item("tambora", "Tambora", "Bass drums", "Caribbean"),
|
||||||
|
item("guira", "Guira", "Scrapers", "Caribbean"),
|
||||||
|
item("bata-drums", "Bata Drums", "Hand drums", "Caribbean"),
|
||||||
|
item("arpa-llanera", "Arpa Llanera", "Harps", "Latin America"),
|
||||||
|
item("marimbola", "Marimbola", "Lamellophones", "Caribbean"),
|
||||||
|
item("tiple", "Tiple", "Plucked strings", "South America")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static let folkAndModern: [InstrumentCatalogItem] = [
|
||||||
|
item("bagpipes", "Bagpipes", "Bagpipes", "Europe"),
|
||||||
|
item("tin-whistle", "Tin Whistle", "Folk winds", "Ireland", aliases: ["penny whistle"]),
|
||||||
|
item("bodhran", "Bodhran", "Frame drums", "Ireland"),
|
||||||
|
item("dulcimer", "Appalachian Dulcimer", "Folk strings", "North America"),
|
||||||
|
item("hammered-dulcimer", "Hammered Dulcimer", "Hammered zithers", "Europe"),
|
||||||
|
item("hurdy-gurdy", "Hurdy-gurdy", "Folk strings", "Europe"),
|
||||||
|
item("nyckelharpa", "Nyckelharpa", "Bowed strings", "Europe"),
|
||||||
|
item("balalaika", "Balalaika", "Plucked strings", "Europe"),
|
||||||
|
item("domra", "Domra", "Plucked strings", "Europe"),
|
||||||
|
item("bouzouki", "Bouzouki", "Plucked strings", "Greece"),
|
||||||
|
item("cimbalom", "Cimbalom", "Hammered zithers", "Europe"),
|
||||||
|
item("jaw-harp", "Jaw Harp", "Jaw harps", "Folk", aliases: ["mouth harp"]),
|
||||||
|
item("didgeridoo", "Didgeridoo", "Aerophones", "Oceania"),
|
||||||
|
item("clapsticks", "Clapsticks", "Percussion", "Oceania"),
|
||||||
|
item("native-american-flute", "Native American Flute", "End-blown flutes", "North America"),
|
||||||
|
item("fiddle", "Fiddle", "Folk strings", "Folk"),
|
||||||
|
item("washboards", "Washboard", "Percussion", "North America"),
|
||||||
|
item("musical-saw", "Musical Saw", "Folk instruments", "Folk"),
|
||||||
|
item("theremin", "Theremin", "Electronic", "Modern"),
|
||||||
|
item("modular-synthesizer", "Modular Synthesizer", "Electronic", "Modern", aliases: ["modular synth"]),
|
||||||
|
item("synthesizer", "Synthesizer", "Electronic", "Modern", aliases: ["synth"]),
|
||||||
|
item("drum-machine", "Drum Machine", "Electronic", "Modern"),
|
||||||
|
item("sampler", "Sampler", "Electronic", "Modern"),
|
||||||
|
item("turntables", "Turntables", "Electronic", "Modern", aliases: ["DJ decks"]),
|
||||||
|
item("midi-controller", "MIDI Controller", "Electronic", "Modern"),
|
||||||
|
item("electric-violin", "Electric Violin", "Electronic strings", "Modern"),
|
||||||
|
item("electric-cello", "Electric Cello", "Electronic strings", "Modern"),
|
||||||
|
item("electronic-drum-pad", "Electronic Drum Pad", "Electronic percussion", "Modern"),
|
||||||
|
item("vocoder", "Vocoder", "Electronic", "Modern"),
|
||||||
|
item("keytar", "Keytar", "Electronic", "Modern")
|
||||||
|
]
|
||||||
|
|
||||||
|
private static func item(
|
||||||
|
_ id: String,
|
||||||
|
_ name: String,
|
||||||
|
_ familyCategory: String,
|
||||||
|
_ regionOrigin: String? = nil,
|
||||||
|
aliases: [String] = [],
|
||||||
|
terms: [String] = []
|
||||||
|
) -> InstrumentCatalogItem {
|
||||||
|
InstrumentCatalogItem(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
familyCategory: familyCategory,
|
||||||
|
regionOrigin: regionOrigin,
|
||||||
|
aliases: aliases,
|
||||||
|
searchTerms: terms
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func searchableTerms(for item: InstrumentCatalogItem) -> [String] {
|
||||||
|
searchTokens(for: [item.name] + item.aliases + item.searchTerms)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func searchTokens(for query: String) -> [String] {
|
||||||
|
searchTokens(for: [query])
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func searchTokens(for values: [String]) -> [String] {
|
||||||
|
values
|
||||||
|
.joined(separator: " ")
|
||||||
|
.folding(
|
||||||
|
options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive],
|
||||||
|
locale: Locale(identifier: "en_US_POSIX")
|
||||||
|
)
|
||||||
|
.split(whereSeparator: { $0.isWhitespace || $0 == "-" })
|
||||||
|
.map(String.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import MusicAssistantCore
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class LocalInstrumentCatalogTests: XCTestCase {
|
||||||
|
func testWorldwideCatalogCoversRequiredRegionsAndInstrumentTypes() {
|
||||||
|
let catalog = LocalInstrumentCatalog()
|
||||||
|
let expectedRegions = [
|
||||||
|
"Western", "Middle East", "Africa", "South Asia", "East Asia", "Southeast Asia", "Latin America", "Modern"
|
||||||
|
]
|
||||||
|
let expectedInstrumentIDs = [
|
||||||
|
"piano", "oud", "djembe", "sitar", "erhu", "angklung", "charango", "bagpipes", "synthesizer"
|
||||||
|
]
|
||||||
|
|
||||||
|
XCTAssertGreaterThanOrEqual(catalog.items.count, 200)
|
||||||
|
XCTAssertTrue(expectedRegions.allSatisfy { region in
|
||||||
|
catalog.items.contains { $0.regionOrigin?.contains(region) == true }
|
||||||
|
})
|
||||||
|
XCTAssertTrue(expectedInstrumentIDs.allSatisfy { id in
|
||||||
|
catalog.items.contains { $0.id == id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWorldwideCatalogHasUniqueStableInstrumentIDs() {
|
||||||
|
let items = LocalInstrumentCatalog.worldwideItems
|
||||||
|
|
||||||
|
XCTAssertEqual(Set(items.map(\.id)).count, items.count)
|
||||||
|
XCTAssertTrue(items.allSatisfy { !$0.name.isEmpty && !$0.familyCategory.isEmpty })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSearchMatchesNamesAliasesAndArabicSearchTerms() {
|
||||||
|
let catalog = LocalInstrumentCatalog()
|
||||||
|
|
||||||
|
XCTAssertEqual(catalog.instruments(matching: "concert flute").map(\.id), ["flute"])
|
||||||
|
XCTAssertEqual(catalog.instruments(matching: "nylon string").map(\.id), ["classical-guitar"])
|
||||||
|
XCTAssertEqual(catalog.instruments(matching: "عود").map(\.id), ["oud"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFamilyCategoriesAndFilteringComeFromCatalogMetadata() {
|
||||||
|
let catalog = LocalInstrumentCatalog()
|
||||||
|
let pluckedStrings = catalog.instruments(matching: "", familyCategory: "Plucked strings")
|
||||||
|
|
||||||
|
XCTAssertTrue(catalog.familyCategories.contains("Plucked strings"))
|
||||||
|
XCTAssertTrue(pluckedStrings.contains { $0.id == "oud" })
|
||||||
|
XCTAssertTrue(pluckedStrings.allSatisfy { $0.familyCategory == "Plucked strings" })
|
||||||
|
XCTAssertTrue(
|
||||||
|
catalog.instruments(matching: "guitar", familyCategory: "Plucked strings")
|
||||||
|
.allSatisfy { $0.familyCategory == "Plucked strings" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -77,13 +77,13 @@ requirement is missing and blocks implementation, record it in
|
|||||||
|
|
||||||
- [x] Create structured catalog models for instrument id, name,
|
- [x] Create structured catalog models for instrument id, name,
|
||||||
family/category, optional region/origin and aliases/search terms.
|
family/category, optional region/origin and aliases/search terms.
|
||||||
- [ ] Provide large worldwide catalog data covering Western, Middle
|
- [x] Provide large worldwide catalog data covering Western, Middle
|
||||||
Eastern, African, South Asian, East Asian, Southeast Asian, Latin
|
Eastern, African, South Asian, East Asian, Southeast Asian, Latin
|
||||||
American, traditional, folk, orchestral, electronic and modern
|
American, traditional, folk, orchestral, electronic and modern
|
||||||
instruments.
|
instruments.
|
||||||
- [ ] Keep catalog data out of SwiftUI views.
|
- [x] Keep catalog data out of SwiftUI views.
|
||||||
- [ ] Build searchable instrument browser.
|
- [x] Build searchable instrument browser.
|
||||||
- [ ] Add browsing/filtering by family/category.
|
- [x] Add browsing/filtering by family/category.
|
||||||
- [ ] Add browsing/filtering by region/origin where useful.
|
- [ ] Add browsing/filtering by region/origin where useful.
|
||||||
- [ ] Add checkbox-based multi-select and deselect behavior.
|
- [ ] Add checkbox-based multi-select and deselect behavior.
|
||||||
- [ ] Persist selected instruments on the current Song Project.
|
- [ ] Persist selected instruments on the current Song Project.
|
||||||
|
|||||||
Reference in New Issue
Block a user