Model language and pronunciation settings
This commit is contained in:
@@ -109,6 +109,51 @@ public extension SongProject {
|
|||||||
genres.removeAll { $0.id == id }
|
genres.removeAll { $0.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var languageIDs: Set<String> {
|
||||||
|
Set(languages.map(\.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialectIDs: Set<String> {
|
||||||
|
Set(dialects.map(\.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func language(for languageID: String) -> LanguageSelection? {
|
||||||
|
languages.first { $0.id == languageID }
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialect(for dialectID: String) -> DialectSelection? {
|
||||||
|
dialects.first { $0.id == dialectID }
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialects(forLanguageID languageID: String) -> [DialectSelection] {
|
||||||
|
dialects.filter { $0.languageID == languageID }
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func upsertLanguage(_ language: LanguageSelection) {
|
||||||
|
if let index = languages.firstIndex(where: { $0.id == language.id }) {
|
||||||
|
languages[index] = language
|
||||||
|
} else {
|
||||||
|
languages.append(language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func removeLanguage(id languageID: String) {
|
||||||
|
languages.removeAll { $0.id == languageID }
|
||||||
|
dialects.removeAll { $0.languageID == languageID }
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func upsertDialect(_ dialect: DialectSelection) {
|
||||||
|
if let index = dialects.firstIndex(where: { $0.id == dialect.id }) {
|
||||||
|
dialects[index] = dialect
|
||||||
|
} else {
|
||||||
|
dialects.append(dialect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func removeDialect(id dialectID: String) {
|
||||||
|
dialects.removeAll { $0.id == dialectID }
|
||||||
|
}
|
||||||
|
|
||||||
var selectedInstrumentTracks: [InstrumentTrack] {
|
var selectedInstrumentTracks: [InstrumentTrack] {
|
||||||
instruments.filter(\.selected)
|
instruments.filter(\.selected)
|
||||||
}
|
}
|
||||||
@@ -299,16 +344,39 @@ public struct DialectSelection: Codable, Equatable, Identifiable, Sendable {
|
|||||||
|
|
||||||
public struct ArabicPronunciationSettings: Codable, Equatable, Sendable {
|
public struct ArabicPronunciationSettings: Codable, Equatable, Sendable {
|
||||||
public var isEnabled: Bool
|
public var isEnabled: Bool
|
||||||
|
public var diacritizationPolicy: ArabicDiacritizationPolicy
|
||||||
|
public var tanweenPolicy: ArabicTanweenPolicy
|
||||||
public var userNotes: String?
|
public var userNotes: String?
|
||||||
public var preservedSpellings: [String]
|
public var preservedSpellings: [String]
|
||||||
|
|
||||||
public init(isEnabled: Bool = false, userNotes: String? = nil, preservedSpellings: [String] = []) {
|
public init(
|
||||||
|
isEnabled: Bool = false,
|
||||||
|
diacritizationPolicy: ArabicDiacritizationPolicy = .unspecified,
|
||||||
|
tanweenPolicy: ArabicTanweenPolicy = .unspecified,
|
||||||
|
userNotes: String? = nil,
|
||||||
|
preservedSpellings: [String] = []
|
||||||
|
) {
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
|
self.diacritizationPolicy = diacritizationPolicy
|
||||||
|
self.tanweenPolicy = tanweenPolicy
|
||||||
self.userNotes = userNotes
|
self.userNotes = userNotes
|
||||||
self.preservedSpellings = preservedSpellings
|
self.preservedSpellings = preservedSpellings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ArabicDiacritizationPolicy: String, Codable, Equatable, Sendable {
|
||||||
|
case unspecified
|
||||||
|
case pronunciationTargeted
|
||||||
|
case fullTashkeel
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ArabicTanweenPolicy: String, Codable, Equatable, Sendable {
|
||||||
|
case unspecified
|
||||||
|
case preserveUserInput
|
||||||
|
case addWhenPronunciationRequires
|
||||||
|
case removeWhenUnwanted
|
||||||
|
}
|
||||||
|
|
||||||
public struct GenreStyle: Codable, Equatable, Identifiable, Sendable {
|
public struct GenreStyle: Codable, Equatable, Identifiable, Sendable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public var name: String
|
public var name: String
|
||||||
|
|||||||
@@ -235,4 +235,48 @@ final class SongProjectModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertNil(project.vocalist(for: "harmony"))
|
XCTAssertNil(project.vocalist(for: "harmony"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testLanguagesDialectsAndArabicPronunciationSettingsAreModeled() {
|
||||||
|
var project = SongProject(
|
||||||
|
id: "project-7",
|
||||||
|
title: "Multilingual Song",
|
||||||
|
idea: "Arabic and English hook"
|
||||||
|
)
|
||||||
|
let arabic = LanguageSelection(id: "arabic", name: "Arabic")
|
||||||
|
let english = LanguageSelection(id: "english", name: "English")
|
||||||
|
let levantine = DialectSelection(id: "levantine", name: "Levantine", languageID: arabic.id)
|
||||||
|
let gulf = DialectSelection(id: "gulf", name: "Gulf", languageID: arabic.id)
|
||||||
|
|
||||||
|
project.upsertLanguage(arabic)
|
||||||
|
project.upsertLanguage(english)
|
||||||
|
project.upsertLanguage(LanguageSelection(id: "english", name: "English"))
|
||||||
|
project.upsertDialect(levantine)
|
||||||
|
project.upsertDialect(gulf)
|
||||||
|
project.arabicPronunciationSettings = ArabicPronunciationSettings(
|
||||||
|
isEnabled: true,
|
||||||
|
diacritizationPolicy: .pronunciationTargeted,
|
||||||
|
tanweenPolicy: .preserveUserInput,
|
||||||
|
userNotes: "Preserve dialect phrasing",
|
||||||
|
preservedSpellings: ["ya habibi"]
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(project.languageIDs, ["arabic", "english"])
|
||||||
|
XCTAssertEqual(project.dialectIDs, ["levantine", "gulf"])
|
||||||
|
XCTAssertEqual(project.language(for: arabic.id), arabic)
|
||||||
|
XCTAssertEqual(project.dialects(forLanguageID: arabic.id), [levantine, gulf])
|
||||||
|
XCTAssertTrue(project.arabicPronunciationSettings.isEnabled)
|
||||||
|
XCTAssertEqual(project.arabicPronunciationSettings.diacritizationPolicy, .pronunciationTargeted)
|
||||||
|
XCTAssertEqual(project.arabicPronunciationSettings.tanweenPolicy, .preserveUserInput)
|
||||||
|
|
||||||
|
project.removeDialect(id: gulf.id)
|
||||||
|
|
||||||
|
XCTAssertNil(project.dialect(for: gulf.id))
|
||||||
|
XCTAssertEqual(project.dialects(forLanguageID: arabic.id), [levantine])
|
||||||
|
|
||||||
|
project.removeLanguage(id: arabic.id)
|
||||||
|
|
||||||
|
XCTAssertNil(project.language(for: arabic.id))
|
||||||
|
XCTAssertEqual(project.dialects(forLanguageID: arabic.id), [])
|
||||||
|
XCTAssertEqual(project.languageIDs, ["english"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ requirement is missing and blocks implementation, record it in
|
|||||||
- [x] Model song sections with order and optional timestamps.
|
- [x] Model song sections with order and optional timestamps.
|
||||||
- [x] Model instruments, variants, roles and placements.
|
- [x] Model instruments, variants, roles and placements.
|
||||||
- [x] Model multiple vocalists and section assignments.
|
- [x] Model multiple vocalists and section assignments.
|
||||||
- [ ] Model language/dialect and Arabic pronunciation settings.
|
- [x] Model language/dialect and Arabic pronunciation settings.
|
||||||
- [ ] Model BPM, key, scale and maqam with Manual/Auto state.
|
- [ ] Model BPM, key, scale and maqam with Manual/Auto state.
|
||||||
- [ ] Model emotional arc and production directions.
|
- [ ] Model emotional arc and production directions.
|
||||||
- [ ] Add serialization tests.
|
- [ ] Add serialization tests.
|
||||||
|
|||||||
Reference in New Issue
Block a user