Persist selections and compile song output
This commit is contained in:
@@ -48,6 +48,35 @@ final class LocalSongProjectStoreTests: XCTestCase {
|
||||
XCTAssertEqual(updatedProject, project)
|
||||
}
|
||||
|
||||
func testPersistsSelectedInstrumentsOnSongProject() async throws {
|
||||
let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL)
|
||||
var project = SongProject(
|
||||
id: "instrument-selection-project",
|
||||
title: "Instrument Selection",
|
||||
idea: "Persist selected catalog instruments"
|
||||
)
|
||||
|
||||
project.setInstrumentSelected(id: "oud", isSelected: true, variant: "Oud")
|
||||
project.setInstrumentSelected(id: "violin", isSelected: true, variant: "Violin")
|
||||
project.setInstrumentSelected(id: "oud", isSelected: false)
|
||||
|
||||
try await store.create(project)
|
||||
|
||||
var openedProject = try await store.open(id: project.id)
|
||||
XCTAssertFalse(openedProject.isInstrumentSelected(id: "oud"))
|
||||
XCTAssertTrue(openedProject.isInstrumentSelected(id: "violin"))
|
||||
XCTAssertEqual(openedProject.instrumentTrack(for: "oud")?.variant, "Oud")
|
||||
XCTAssertEqual(openedProject.selectedInstrumentIDs, ["violin"])
|
||||
|
||||
openedProject.setInstrumentSelected(id: "oud", isSelected: true, variant: "Oud")
|
||||
try await store.save(openedProject)
|
||||
|
||||
let savedProject = try await store.open(id: project.id)
|
||||
XCTAssertTrue(savedProject.isInstrumentSelected(id: "oud"))
|
||||
XCTAssertTrue(savedProject.isInstrumentSelected(id: "violin"))
|
||||
XCTAssertEqual(savedProject.selectedInstrumentIDs, ["oud", "violin"])
|
||||
}
|
||||
|
||||
func testLoadsProjectListSortedByMostRecentUpdate() async throws {
|
||||
let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL)
|
||||
let older = SongProject(
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import MusicAssistantCore
|
||||
import XCTest
|
||||
|
||||
final class SongGenerationContextTests: XCTestCase {
|
||||
func testContextIncludesOnlySelectedInstrumentsWithArrangementDetails() {
|
||||
let project = SongProject(
|
||||
title: "Instrument Context",
|
||||
idea: "Use selected instruments in generation",
|
||||
sections: [
|
||||
SongSection(id: "intro", type: .intro, title: "Opening"),
|
||||
SongSection(id: "chorus", type: .chorus, title: "Final Chorus", order: 1)
|
||||
],
|
||||
instruments: [
|
||||
InstrumentTrack(
|
||||
instrumentId: "oud",
|
||||
selected: true,
|
||||
variant: "Arabic Oud",
|
||||
playingStyle: "tremolo",
|
||||
role: "lead motif",
|
||||
autoArrangementEnabled: true,
|
||||
placements: [
|
||||
InstrumentPlacement(
|
||||
sectionId: "intro",
|
||||
startTime: 0,
|
||||
endTime: 12,
|
||||
direction: "solo opening"
|
||||
)
|
||||
]
|
||||
),
|
||||
InstrumentTrack(
|
||||
instrumentId: "violin",
|
||||
selected: false,
|
||||
variant: "Violin",
|
||||
role: "deselected counterline"
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
let context = SongGenerationContext(project: project)
|
||||
|
||||
XCTAssertTrue(context.hasSelectedInstruments)
|
||||
XCTAssertEqual(context.selectedInstruments.map(\.instrumentId), ["oud"])
|
||||
XCTAssertEqual(context.selectedInstruments.first?.displayName, "Arabic Oud")
|
||||
XCTAssertEqual(context.selectedInstruments.first?.playingStyle, "tremolo")
|
||||
XCTAssertEqual(context.selectedInstruments.first?.role, "lead motif")
|
||||
XCTAssertEqual(context.selectedInstruments.first?.autoArrangementEnabled, true)
|
||||
XCTAssertEqual(context.selectedInstruments.first?.placements.first?.sectionTitle, "Opening")
|
||||
XCTAssertEqual(context.selectedInstruments.first?.placements.first?.sectionType, .intro)
|
||||
XCTAssertEqual(
|
||||
context.sunoStyleInstrumentPhrase,
|
||||
"Arabic Oud (lead motif, tremolo, Opening 0s-12s solo opening)"
|
||||
)
|
||||
}
|
||||
|
||||
func testAIRequestsCarrySongGenerationContextFromProjects() {
|
||||
let project = SongProject(
|
||||
title: "Context Request",
|
||||
idea: "Carry selected instruments",
|
||||
instruments: [
|
||||
InstrumentTrack(instrumentId: "qanun", selected: true, variant: "Qanun"),
|
||||
InstrumentTrack(instrumentId: "piano", selected: false, variant: "Piano")
|
||||
]
|
||||
)
|
||||
|
||||
let generationRequest = SongProjectGenerationRequest(
|
||||
context: AIRequestContext(userInstruction: "Generate."),
|
||||
seedProject: project
|
||||
)
|
||||
let updateRequest = SongProjectUpdateRequest(
|
||||
context: AIRequestContext(userInstruction: "Arrange."),
|
||||
project: project,
|
||||
allowedScopes: [.arrangement]
|
||||
)
|
||||
|
||||
XCTAssertEqual(generationRequest.songGenerationContext.selectedInstruments.map(\.instrumentId), ["qanun"])
|
||||
XCTAssertEqual(updateRequest.songGenerationContext.selectedInstruments.map(\.displayName), ["Qanun"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import MusicAssistantCore
|
||||
import XCTest
|
||||
|
||||
final class SongProjectPromptCompilerTests: XCTestCase {
|
||||
func testCompilerUsesSelectedInstrumentsInSunoStylePrompt() throws {
|
||||
let project = SongProject(
|
||||
title: "Compiled Song",
|
||||
idea: "Prepare Suno output",
|
||||
genres: [
|
||||
GenreStyle(id: "arabic-pop", name: "Arabic Pop"),
|
||||
GenreStyle(id: "cinematic", name: "Cinematic")
|
||||
],
|
||||
moods: [MoodTag(id: "hopeful", name: "Hopeful")],
|
||||
bpm: ManualAutoValue(mode: .manual, value: 96),
|
||||
maqam: ManualAutoValue(mode: .manual, value: "Hijaz"),
|
||||
sections: [
|
||||
SongSection(id: "chorus", type: .chorus, title: "Chorus", lyrics: "Section chorus")
|
||||
],
|
||||
instruments: [
|
||||
InstrumentTrack(
|
||||
instrumentId: "oud",
|
||||
selected: true,
|
||||
variant: "Arabic Oud",
|
||||
playingStyle: "picked",
|
||||
role: "main hook",
|
||||
placements: [InstrumentPlacement(sectionId: "chorus", direction: "answer the vocal")]
|
||||
),
|
||||
InstrumentTrack(
|
||||
instrumentId: "drum-kit",
|
||||
selected: false,
|
||||
variant: "Drum Kit"
|
||||
)
|
||||
],
|
||||
vocalists: [Vocalist(id: "lead", label: "Lead", voiceType: "warm tenor")],
|
||||
lyrics: Lyrics(text: "Approved lyrics"),
|
||||
productionDirections: [ProductionDirection(id: "lift", text: "wide chorus lift")]
|
||||
)
|
||||
|
||||
let output = try SongProjectPromptCompiler().compile(project: project)
|
||||
|
||||
XCTAssertEqual(output.lyricsText, "Approved lyrics")
|
||||
XCTAssertTrue(output.stylePrompt.contains("Arabic Pop + Cinematic"))
|
||||
XCTAssertTrue(output.stylePrompt.contains("Arabic Oud (main hook, picked, Chorus answer the vocal)"))
|
||||
XCTAssertTrue(output.stylePrompt.contains("Lead warm tenor"))
|
||||
XCTAssertTrue(output.stylePrompt.contains("96 BPM"))
|
||||
XCTAssertTrue(output.stylePrompt.contains("maqam Hijaz"))
|
||||
XCTAssertTrue(output.stylePrompt.contains("wide chorus lift"))
|
||||
XCTAssertFalse(output.stylePrompt.contains("Drum Kit"))
|
||||
}
|
||||
|
||||
func testCompilerFallsBackToOrderedSectionLyrics() throws {
|
||||
let project = SongProject(
|
||||
title: "Section Lyrics",
|
||||
idea: "Compile sections",
|
||||
sections: [
|
||||
SongSection(id: "chorus", type: .chorus, title: "Chorus", order: 1, lyrics: "Hook line"),
|
||||
SongSection(id: "verse", type: .verse, title: "Verse", order: 0, lyrics: "Verse line")
|
||||
]
|
||||
)
|
||||
|
||||
let output = try SongProjectPromptCompiler().compile(project: project)
|
||||
|
||||
XCTAssertEqual(output.lyricsText, "[Verse]\nVerse line\n\n[Chorus]\nHook line")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user