195 lines
7.9 KiB
Swift
195 lines
7.9 KiB
Swift
import MusicAssistantCore
|
|
import XCTest
|
|
|
|
final class SongProjectModelTests: XCTestCase {
|
|
func testSongProjectStoresCoreDomainCollections() {
|
|
let section = SongSection(
|
|
id: "section-verse-1",
|
|
type: .verse,
|
|
title: "Verse 1",
|
|
startTime: 12,
|
|
endTime: 42,
|
|
lyrics: "Draft verse",
|
|
emotion: "hopeful",
|
|
energy: 6,
|
|
vocalDirection: "soft lead",
|
|
productionDirection: "light percussion"
|
|
)
|
|
let placement = InstrumentPlacement(
|
|
sectionId: section.id,
|
|
startTime: section.startTime,
|
|
endTime: section.endTime,
|
|
direction: "enter after the first line"
|
|
)
|
|
let instrument = InstrumentTrack(
|
|
instrumentId: "oud",
|
|
selected: true,
|
|
variant: "Arabic oud",
|
|
playingStyle: "picked",
|
|
role: "main melodic hook",
|
|
autoArrangementEnabled: false,
|
|
placements: [placement]
|
|
)
|
|
let vocalist = Vocalist(
|
|
id: "lead",
|
|
label: "Lead vocalist",
|
|
voiceType: "warm tenor",
|
|
genderSelection: "male",
|
|
performanceStyle: "sung",
|
|
assignedSections: [section.id]
|
|
)
|
|
let project = SongProject(
|
|
id: "project-1",
|
|
title: "Desert Lights",
|
|
idea: "A hopeful Arabic pop song",
|
|
duration: SongDuration(seconds: 180),
|
|
conversationMode: .discuss,
|
|
languages: [LanguageSelection(id: "arabic", name: "Arabic")],
|
|
dialects: [DialectSelection(id: "levantine", name: "Levantine", languageID: "arabic")],
|
|
arabicPronunciationSettings: ArabicPronunciationSettings(isEnabled: true),
|
|
genres: [GenreStyle(id: "arabic-pop", name: "Arabic Pop")],
|
|
moods: [MoodTag(id: "hopeful", name: "Hopeful")],
|
|
emotionalArc: [EmotionalArcPoint(id: "arc-1", sectionID: section.id, emotion: "hopeful")],
|
|
bpm: ManualAutoValue(mode: .manual, value: 96),
|
|
key: ManualAutoValue(mode: .auto),
|
|
scale: ManualAutoValue(mode: .auto),
|
|
maqam: ManualAutoValue(mode: .manual, value: "Hijaz"),
|
|
sections: [section],
|
|
instruments: [instrument],
|
|
vocalists: [vocalist],
|
|
lyrics: Lyrics(text: "Draft lyrics", sectionTexts: [section.id: "Draft verse"]),
|
|
productionDirections: [ProductionDirection(id: "prod-1", sectionID: section.id, text: "add lift")],
|
|
sunoOutput: SunoOutput(lyricsText: "Draft lyrics", stylePrompt: "Arabic pop with oud")
|
|
)
|
|
|
|
XCTAssertEqual(project.sections.first?.type, .verse)
|
|
XCTAssertEqual(project.instruments.first?.instrumentId, "oud")
|
|
XCTAssertEqual(project.instruments.first?.placements.first?.sectionId, section.id)
|
|
XCTAssertEqual(project.vocalists.first?.assignedSections, [section.id])
|
|
XCTAssertEqual(project.bpm?.mode, .manual)
|
|
XCTAssertEqual(project.maqam?.value, "Hijaz")
|
|
}
|
|
|
|
func testSongProjectSnapshotCopiesVersionableState() {
|
|
let project = SongProject(
|
|
id: "project-2",
|
|
title: "Night Drive",
|
|
idea: "Electronic track",
|
|
genres: [GenreStyle(id: "synthwave", name: "Synthwave")],
|
|
instruments: [InstrumentTrack(instrumentId: "synthesizer")]
|
|
)
|
|
|
|
let snapshot = SongProjectSnapshot(project: project)
|
|
let version = SongVersion(label: "Initial direction", snapshot: snapshot)
|
|
|
|
XCTAssertEqual(version.snapshot.title, project.title)
|
|
XCTAssertEqual(version.snapshot.idea, project.idea)
|
|
XCTAssertEqual(version.snapshot.genres, project.genres)
|
|
XCTAssertEqual(version.snapshot.instruments, project.instruments)
|
|
}
|
|
|
|
func testGenreStylesAreModeledAsMultiSelect() {
|
|
let arabicPop = GenreStyle(id: "arabic-pop", name: "Arabic Pop")
|
|
let synthwave = GenreStyle(id: "synthwave", name: "Synthwave")
|
|
var project = SongProject(
|
|
id: "project-3",
|
|
title: "Hybrid",
|
|
idea: "Blend traditional melody with electronic production"
|
|
)
|
|
|
|
project.selectGenreStyle(arabicPop)
|
|
project.selectGenreStyle(synthwave)
|
|
project.selectGenreStyle(arabicPop)
|
|
|
|
XCTAssertEqual(project.genres, [arabicPop, synthwave])
|
|
XCTAssertTrue(project.isGenreStyleSelected(arabicPop))
|
|
XCTAssertEqual(project.selectedGenreStyleIDs, ["arabic-pop", "synthwave"])
|
|
|
|
project.deselectGenreStyle(id: arabicPop.id)
|
|
|
|
XCTAssertEqual(project.genres, [synthwave])
|
|
XCTAssertFalse(project.isGenreStyleSelected(arabicPop))
|
|
}
|
|
|
|
func testSongSectionsKeepExplicitOrderAndOptionalTimestamps() {
|
|
var project = SongProject(
|
|
id: "project-4",
|
|
title: "Section Flow",
|
|
idea: "Build a classic song structure"
|
|
)
|
|
let intro = SongSection(id: "intro", type: .intro, title: "Intro")
|
|
let verse = SongSection(
|
|
id: "verse",
|
|
type: .verse,
|
|
title: "Verse",
|
|
startTime: 15,
|
|
endTime: 45
|
|
)
|
|
let chorus = SongSection(id: "chorus", type: .chorus, title: "Chorus")
|
|
|
|
project.appendSection(verse)
|
|
project.appendSection(chorus)
|
|
project.appendSection(intro)
|
|
project.moveSection(id: intro.id, toOrder: 0)
|
|
|
|
XCTAssertEqual(project.orderedSections.map(\.id), ["intro", "verse", "chorus"])
|
|
XCTAssertNil(project.orderedSections.first?.startTime)
|
|
XCTAssertNil(project.orderedSections.first?.endTime)
|
|
XCTAssertEqual(project.orderedSections[1].startTime, 15)
|
|
XCTAssertEqual(project.orderedSections[1].endTime, 45)
|
|
XCTAssertEqual(project.orderedSections.map(\.order), [0, 1, 2])
|
|
}
|
|
|
|
func testInstrumentsTrackVariantsRolesSelectionAndPlacements() {
|
|
var project = SongProject(
|
|
id: "project-5",
|
|
title: "Instrument Plan",
|
|
idea: "Feature oud and violin with clear entries"
|
|
)
|
|
let oud = InstrumentTrack(
|
|
instrumentId: "oud",
|
|
selected: true,
|
|
variant: "Arabic oud",
|
|
playingStyle: "tremolo",
|
|
role: "main hook",
|
|
autoArrangementEnabled: false
|
|
)
|
|
let introPlacement = InstrumentPlacement(
|
|
sectionId: "intro",
|
|
startTime: 0,
|
|
endTime: 12,
|
|
direction: "solo intro"
|
|
)
|
|
let chorusPlacement = InstrumentPlacement(
|
|
sectionId: "chorus",
|
|
direction: "double the vocal melody"
|
|
)
|
|
|
|
project.selectInstrument(oud)
|
|
project.addInstrumentPlacement(introPlacement, to: oud.instrumentId)
|
|
project.addInstrumentPlacement(chorusPlacement, to: "violin")
|
|
project.configureInstrument(
|
|
id: "violin",
|
|
variant: "solo violin",
|
|
playingStyle: "legato",
|
|
role: "countermelody",
|
|
autoArrangementEnabled: true
|
|
)
|
|
|
|
XCTAssertEqual(project.selectedInstrumentIDs, ["oud", "violin"])
|
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.variant, "Arabic oud")
|
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.role, "main hook")
|
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.placements, [introPlacement])
|
|
XCTAssertEqual(project.instrumentTrack(for: "violin")?.variant, "solo violin")
|
|
XCTAssertEqual(project.instrumentTrack(for: "violin")?.playingStyle, "legato")
|
|
XCTAssertEqual(project.instrumentTrack(for: "violin")?.role, "countermelody")
|
|
XCTAssertEqual(project.instrumentTrack(for: "violin")?.placements, [chorusPlacement])
|
|
|
|
project.deselectInstrument(id: "oud")
|
|
|
|
XCTAssertFalse(project.isInstrumentSelected(id: "oud"))
|
|
XCTAssertEqual(project.selectedInstrumentIDs, ["violin"])
|
|
XCTAssertEqual(project.instrumentTrack(for: "oud")?.placements, [introPlacement])
|
|
}
|
|
}
|