diff --git a/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift b/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift new file mode 100644 index 0000000..a78ae32 --- /dev/null +++ b/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift @@ -0,0 +1,284 @@ +public struct LocalInstrumentCatalog: Sendable { + public let items: [InstrumentCatalogItem] + + public init(items: [InstrumentCatalogItem] = LocalInstrumentCatalog.worldwideItems) { + self.items = items + } + + 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 + ) + } +} diff --git a/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift b/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift new file mode 100644 index 0000000..c4bba8a --- /dev/null +++ b/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift @@ -0,0 +1,29 @@ +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 }) + } +} diff --git a/docs/TASKS.md b/docs/TASKS.md index 40fce1a..b5d0545 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -77,7 +77,7 @@ requirement is missing and blocks implementation, record it in - [x] Create structured catalog models for instrument id, name, 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 American, traditional, folk, orchestral, electronic and modern instruments.