Implement initial song project domain models
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import MusicAssistantCore
|
||||
import XCTest
|
||||
|
||||
final class AppConfigurationTests: XCTestCase {
|
||||
func testDevelopmentConfigurationReadsKnownEnvironmentKeys() {
|
||||
let configuration = AppConfiguration.development(environment: [
|
||||
"MUSIC_ASSISTANT_OPENAI_API_KEY": "openai-test-key"
|
||||
])
|
||||
|
||||
XCTAssertEqual(configuration.openAIAPIKey, "openai-test-key")
|
||||
}
|
||||
|
||||
func testDevelopmentConfigurationAllowsMissingSecrets() {
|
||||
let configuration = AppConfiguration.development(environment: [:])
|
||||
|
||||
XCTAssertNil(configuration.openAIAPIKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import MusicAssistantCore
|
||||
import XCTest
|
||||
|
||||
final class ProductBoundarySummaryTests: XCTestCase {
|
||||
func testInitialBoundaryKeepsSunoGenerationOutsideTheApp() {
|
||||
let summary = ProductBoundarySummary.initial.description
|
||||
|
||||
XCTAssertTrue(summary.contains("Suno"))
|
||||
XCTAssertTrue(summary.contains("without starting generation"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user