Persist selections and compile song output
This commit is contained in:
@@ -13,3 +13,83 @@ public struct CompiledSunoOutput: Equatable, Sendable {
|
||||
self.stylePrompt = stylePrompt
|
||||
}
|
||||
}
|
||||
|
||||
public struct SongProjectPromptCompiler: PromptCompiling {
|
||||
public init() {}
|
||||
|
||||
public func compile(project: SongProject) throws -> CompiledSunoOutput {
|
||||
CompiledSunoOutput(
|
||||
lyricsText: lyricsText(for: project),
|
||||
stylePrompt: stylePrompt(for: project)
|
||||
)
|
||||
}
|
||||
|
||||
private func lyricsText(for project: SongProject) -> String {
|
||||
if !project.lyrics.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return project.lyrics.text
|
||||
}
|
||||
|
||||
return project.orderedSections
|
||||
.compactMap { section in
|
||||
let lyrics = project.lyrics.sectionTexts[section.id] ?? section.lyrics
|
||||
let trimmedLyrics = lyrics.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedLyrics.isEmpty else { return nil }
|
||||
return "[\(section.title)]\n\(trimmedLyrics)"
|
||||
}
|
||||
.joined(separator: "\n\n")
|
||||
}
|
||||
|
||||
private func stylePrompt(for project: SongProject) -> String {
|
||||
let context = SongGenerationContext(project: project)
|
||||
var components: [String] = []
|
||||
|
||||
append(project.genres.map(\.name).joined(separator: " + "), to: &components)
|
||||
append(project.moods.map(\.name).joined(separator: ", "), to: &components)
|
||||
append(context.sunoStyleInstrumentPhrase, to: &components)
|
||||
append(vocalPhrase(for: project), to: &components)
|
||||
append(musicalParametersPhrase(for: project), to: &components)
|
||||
append(project.productionDirections.map(\.text).joined(separator: ", "), to: &components)
|
||||
|
||||
return components.joined(separator: "; ")
|
||||
}
|
||||
|
||||
private func vocalPhrase(for project: SongProject) -> String {
|
||||
project.vocalists
|
||||
.map { vocalist in
|
||||
[
|
||||
vocalist.label,
|
||||
vocalist.voiceType,
|
||||
vocalist.genderSelection,
|
||||
vocalist.performanceStyle
|
||||
]
|
||||
.compactMap { normalized($0) }
|
||||
.joined(separator: " ")
|
||||
}
|
||||
.filter { !$0.isEmpty }
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
|
||||
private func musicalParametersPhrase(for project: SongProject) -> String {
|
||||
var parameters: [String] = []
|
||||
|
||||
if let bpm = project.bpm?.value {
|
||||
parameters.append("\(bpm) BPM")
|
||||
}
|
||||
|
||||
append(project.key?.value.map { "key \($0)" }, to: ¶meters)
|
||||
append(project.scale?.value.map { "\($0) scale" }, to: ¶meters)
|
||||
append(project.maqam?.value.map { "maqam \($0)" }, to: ¶meters)
|
||||
|
||||
return parameters.joined(separator: ", ")
|
||||
}
|
||||
|
||||
private func append(_ value: String?, to components: inout [String]) {
|
||||
guard let normalizedValue = normalized(value) else { return }
|
||||
components.append(normalizedValue)
|
||||
}
|
||||
|
||||
private func normalized(_ value: String?) -> String? {
|
||||
let trimmedValue = value?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmedValue?.isEmpty == false ? trimmedValue : nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user