Files
music-assistant/Sources/MusicAssistantCore/Domain/SongProject.swift
T
2026-09-15 19:07:55 +02:00

1012 lines
31 KiB
Swift

import Foundation
public struct SongProject: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var title: String
public var idea: String
public var duration: SongDuration?
public var conversationMode: ConversationMode
public var languages: [LanguageSelection]
public var dialects: [DialectSelection]
public var arabicPronunciationSettings: ArabicPronunciationSettings
public var genres: [GenreStyle]
public var moods: [MoodTag]
public var emotionalArc: [EmotionalArcPoint]
public var bpm: ManualAutoValue<Int>?
public var key: ManualAutoValue<String>?
public var scale: ManualAutoValue<String>?
public var maqam: ManualAutoValue<String>?
public var structureMode: ControlMode?
public var sections: [SongSection]
public var instruments: [InstrumentTrack]
public var vocalists: [Vocalist]
public var lyrics: Lyrics
public var productionDirections: [ProductionDirection]
public var productionMode: ControlMode?
public var userLocks: SongProjectUserLocks?
public var sunoOutput: SunoOutput?
public var versions: [SongVersion]
public let createdAt: Date
public var updatedAt: Date
public init(
id: String = UUID().uuidString,
title: String,
idea: String,
duration: SongDuration? = nil,
conversationMode: ConversationMode = .auto,
languages: [LanguageSelection] = [],
dialects: [DialectSelection] = [],
arabicPronunciationSettings: ArabicPronunciationSettings = ArabicPronunciationSettings(),
genres: [GenreStyle] = [],
moods: [MoodTag] = [],
emotionalArc: [EmotionalArcPoint] = [],
bpm: ManualAutoValue<Int>? = nil,
key: ManualAutoValue<String>? = nil,
scale: ManualAutoValue<String>? = nil,
maqam: ManualAutoValue<String>? = nil,
structureMode: ControlMode? = .auto,
sections: [SongSection] = [],
instruments: [InstrumentTrack] = [],
vocalists: [Vocalist] = [],
lyrics: Lyrics = Lyrics(),
productionDirections: [ProductionDirection] = [],
productionMode: ControlMode? = .auto,
userLocks: SongProjectUserLocks? = SongProjectUserLocks(),
sunoOutput: SunoOutput? = nil,
versions: [SongVersion] = [],
createdAt: Date = Date(),
updatedAt: Date = Date()
) {
self.id = id
self.title = title
self.idea = idea
self.duration = duration
self.conversationMode = conversationMode
self.languages = languages
self.dialects = dialects
self.arabicPronunciationSettings = arabicPronunciationSettings
self.genres = genres
self.moods = moods
self.emotionalArc = emotionalArc
self.bpm = bpm
self.key = key
self.scale = scale
self.maqam = maqam
self.structureMode = structureMode
self.sections = sections
self.instruments = instruments
self.vocalists = vocalists
self.lyrics = lyrics
self.productionDirections = productionDirections
self.productionMode = productionMode
self.userLocks = userLocks
self.sunoOutput = sunoOutput
self.versions = versions
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}
public extension SongProject {
var isStructureAutomatic: Bool {
structureMode != .manual
}
var isProductionAutomatic: Bool {
productionMode != .manual
}
mutating func setStructureMode(_ mode: ControlMode) {
structureMode = mode
}
mutating func setProductionMode(_ mode: ControlMode) {
productionMode = mode
}
var lockedUpdateScopes: [SongProjectUpdateScope] {
userLocks?.scopes ?? []
}
func isUpdateScopeLocked(_ scope: SongProjectUpdateScope) -> Bool {
userLocks?.contains(scope) ?? false
}
mutating func lockUpdateScope(_ scope: SongProjectUpdateScope) {
var userLocks = userLocks ?? SongProjectUserLocks()
userLocks.insert(scope)
self.userLocks = userLocks
}
mutating func unlockUpdateScope(_ scope: SongProjectUpdateScope) {
guard var userLocks else { return }
userLocks.remove(scope)
self.userLocks = userLocks
}
var orderedSections: [SongSection] {
sections
.enumerated()
.sorted { left, right in
if left.element.order == right.element.order {
return left.offset < right.offset
}
return left.element.order < right.element.order
}
.map(\.element)
}
var selectedGenreStyleIDs: Set<String> {
Set(genres.map(\.id))
}
func isGenreStyleSelected(_ genreStyle: GenreStyle) -> Bool {
selectedGenreStyleIDs.contains(genreStyle.id)
}
mutating func selectGenreStyle(_ genreStyle: GenreStyle) {
guard !isGenreStyleSelected(genreStyle) else { return }
genres.append(genreStyle)
}
mutating func deselectGenreStyle(id: String) {
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 }
}
mutating func applyReviewedArabicLyrics(_ reviewedLyrics: String) {
let originalLyrics = lyrics.text
lyrics.text = reviewedLyrics
if sunoOutput?.lyricsText == originalLyrics {
sunoOutput?.lyricsText = reviewedLyrics
}
}
mutating func setBPMManually(_ value: Int) {
bpm = ManualAutoValue(mode: .manual, value: value)
}
mutating func setBPMAutomatic() {
bpm = ManualAutoValue(mode: .auto)
}
mutating func setKeyManually(_ value: String) {
key = ManualAutoValue(mode: .manual, value: value)
}
mutating func setKeyAutomatic() {
key = ManualAutoValue(mode: .auto)
}
mutating func setScaleManually(_ value: String) {
scale = ManualAutoValue(mode: .manual, value: value)
}
mutating func setScaleAutomatic() {
scale = ManualAutoValue(mode: .auto)
}
mutating func setMaqamManually(_ value: String) {
maqam = ManualAutoValue(mode: .manual, value: value)
}
mutating func setMaqamAutomatic() {
maqam = ManualAutoValue(mode: .auto)
}
func emotionalArcPoint(for pointID: String) -> EmotionalArcPoint? {
emotionalArc.first { $0.id == pointID }
}
func emotionalArcPoints(forSectionID sectionID: String) -> [EmotionalArcPoint] {
emotionalArc.filter { $0.sectionID == sectionID }
}
mutating func upsertEmotionalArcPoint(_ point: EmotionalArcPoint) {
if let index = emotionalArc.firstIndex(where: { $0.id == point.id }) {
emotionalArc[index] = point
} else {
emotionalArc.append(point)
}
}
mutating func removeEmotionalArcPoint(id pointID: String) {
emotionalArc.removeAll { $0.id == pointID }
}
func productionDirection(for directionID: String) -> ProductionDirection? {
productionDirections.first { $0.id == directionID }
}
func productionDirections(forSectionID sectionID: String) -> [ProductionDirection] {
productionDirections.filter { $0.sectionID == sectionID }
}
mutating func upsertProductionDirection(_ direction: ProductionDirection) {
if let index = productionDirections.firstIndex(where: { $0.id == direction.id }) {
productionDirections[index] = direction
} else {
productionDirections.append(direction)
}
}
mutating func removeProductionDirection(id directionID: String) {
productionDirections.removeAll { $0.id == directionID }
}
var selectedInstrumentTracks: [InstrumentTrack] {
instruments.filter(\.selected)
}
var selectedInstrumentIDs: Set<String> {
Set(selectedInstrumentTracks.map(\.instrumentId))
}
func instrumentTrack(for instrumentId: String) -> InstrumentTrack? {
instruments.first { $0.instrumentId == instrumentId }
}
func isInstrumentSelected(id instrumentId: String) -> Bool {
instrumentTrack(for: instrumentId)?.selected == true
}
mutating func selectInstrument(_ instrument: InstrumentTrack) {
var selectedInstrument = instrument
selectedInstrument.selected = true
if let index = instruments.firstIndex(where: { $0.instrumentId == instrument.instrumentId }) {
instruments[index] = selectedInstrument
} else {
instruments.append(selectedInstrument)
}
}
mutating func deselectInstrument(id instrumentId: String) {
guard let index = instruments.firstIndex(where: { $0.instrumentId == instrumentId }) else { return }
instruments[index].selected = false
}
mutating func setInstrumentSelected(
id instrumentId: String,
isSelected: Bool,
variant: String? = nil
) {
guard isSelected else {
deselectInstrument(id: instrumentId)
return
}
if let index = instruments.firstIndex(where: { $0.instrumentId == instrumentId }) {
instruments[index].selected = true
if instruments[index].variant == nil {
instruments[index].variant = variant
}
} else {
instruments.append(
InstrumentTrack(
instrumentId: instrumentId,
selected: true,
variant: variant
)
)
}
}
mutating func configureInstrument(
id instrumentId: String,
variant: String?,
playingStyle: String?,
role: String?,
autoArrangementEnabled: Bool
) {
guard let index = instruments.firstIndex(where: { $0.instrumentId == instrumentId }) else { return }
instruments[index].variant = variant
instruments[index].playingStyle = playingStyle
instruments[index].role = role
instruments[index].autoArrangementEnabled = autoArrangementEnabled
}
mutating func addInstrumentPlacement(_ placement: InstrumentPlacement, to instrumentId: String) {
if let index = instruments.firstIndex(where: { $0.instrumentId == instrumentId }) {
instruments[index].placements.append(placement)
} else {
instruments.append(
InstrumentTrack(
instrumentId: instrumentId,
selected: true,
placements: [placement]
)
)
}
}
var vocalistIDs: Set<String> {
Set(vocalists.map(\.id))
}
func vocalist(for vocalistID: String) -> Vocalist? {
vocalists.first { $0.id == vocalistID }
}
func vocalistsAssigned(toSectionID sectionID: String) -> [Vocalist] {
vocalists.filter { $0.assignedSections.contains(sectionID) }
}
mutating func upsertVocalist(_ vocalist: Vocalist) {
var normalizedVocalist = vocalist
normalizedVocalist.assignedSections = Self.uniqueOrderedIDs(vocalist.assignedSections)
if let index = vocalists.firstIndex(where: { $0.id == vocalist.id }) {
vocalists[index] = normalizedVocalist
} else {
vocalists.append(normalizedVocalist)
}
}
mutating func removeVocalist(id vocalistID: String) {
vocalists.removeAll { $0.id == vocalistID }
}
mutating func assignSection(_ sectionID: String, toVocalistID vocalistID: String) {
guard let index = vocalists.firstIndex(where: { $0.id == vocalistID }) else { return }
guard !vocalists[index].assignedSections.contains(sectionID) else { return }
vocalists[index].assignedSections.append(sectionID)
}
mutating func unassignSection(_ sectionID: String, fromVocalistID vocalistID: String) {
guard let index = vocalists.firstIndex(where: { $0.id == vocalistID }) else { return }
vocalists[index].assignedSections.removeAll { $0 == sectionID }
}
mutating func createVersion(
id: String = UUID().uuidString,
label: String,
createdAt: Date = Date()
) -> SongVersion {
let version = SongVersion(
id: id,
label: label,
snapshot: SongProjectSnapshot(project: self),
createdAt: createdAt
)
versions.append(version)
return version
}
func version(id versionID: String) -> SongVersion? {
versions.first { $0.id == versionID }
}
mutating func restoreVersion(id versionID: String, restoredAt: Date = Date()) -> Bool {
guard let version = version(id: versionID) else { return false }
restoreSnapshot(version.snapshot, updatedAt: restoredAt)
return true
}
mutating func restoreSnapshot(_ snapshot: SongProjectSnapshot, updatedAt: Date = Date()) {
title = snapshot.title
idea = snapshot.idea
duration = snapshot.duration
conversationMode = snapshot.conversationMode
languages = snapshot.languages
dialects = snapshot.dialects
arabicPronunciationSettings = snapshot.arabicPronunciationSettings
genres = snapshot.genres
moods = snapshot.moods
emotionalArc = snapshot.emotionalArc
bpm = snapshot.bpm
key = snapshot.key
scale = snapshot.scale
maqam = snapshot.maqam
structureMode = snapshot.structureMode
sections = snapshot.sections
instruments = snapshot.instruments
vocalists = snapshot.vocalists
lyrics = snapshot.lyrics
productionDirections = snapshot.productionDirections
productionMode = snapshot.productionMode
userLocks = snapshot.userLocks
sunoOutput = snapshot.sunoOutput
self.updatedAt = updatedAt
}
mutating func appendSection(_ section: SongSection) {
var orderedSection = section
orderedSection.order = nextSectionOrder
sections.append(orderedSection)
}
mutating func moveSection(id: String, toOrder targetOrder: Int) {
var ordered = orderedSections
guard let sourceIndex = ordered.firstIndex(where: { $0.id == id }) else { return }
let section = ordered.remove(at: sourceIndex)
let boundedTargetOrder = min(max(targetOrder, 0), ordered.count)
ordered.insert(section, at: boundedTargetOrder)
sections = Self.sectionsWithNormalizedOrder(ordered)
}
mutating func normalizeSectionOrder() {
sections = Self.sectionsWithNormalizedOrder(orderedSections)
}
private var nextSectionOrder: Int {
(sections.map(\.order).max() ?? -1) + 1
}
private static func sectionsWithNormalizedOrder(_ sections: [SongSection]) -> [SongSection] {
sections.enumerated().map { index, section in
var orderedSection = section
orderedSection.order = index
return orderedSection
}
}
private static func uniqueOrderedIDs(_ ids: [String]) -> [String] {
var seenIDs = Set<String>()
return ids.filter { seenIDs.insert($0).inserted }
}
}
public enum ConversationMode: String, Codable, Equatable, Sendable {
case auto
case discuss
}
public enum ControlMode: String, Codable, Equatable, Sendable {
case manual
case auto
}
public struct ManualAutoValue<Value: Codable & Equatable & Sendable>: Codable, Equatable, Sendable {
public var mode: ControlMode
public var value: Value?
public init(mode: ControlMode, value: Value? = nil) {
self.mode = mode
self.value = value
}
}
public struct SongDuration: Codable, Equatable, Sendable {
public var seconds: Int?
public var description: String?
public init(seconds: Int? = nil, description: String? = nil) {
self.seconds = seconds
self.description = description
}
}
public struct LanguageSelection: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public init(id: String = UUID().uuidString, name: String) {
self.id = id
self.name = name
}
}
public struct DialectSelection: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public var languageID: String?
public init(id: String = UUID().uuidString, name: String, languageID: String? = nil) {
self.id = id
self.name = name
self.languageID = languageID
}
}
public struct ArabicPronunciationSettings: Codable, Equatable, Sendable {
public var isEnabled: Bool
public var diacritizationPolicy: ArabicDiacritizationPolicy
public var tanweenPolicy: ArabicTanweenPolicy
public var userNotes: String?
public var preservedSpellings: [String]
public init(
isEnabled: Bool = false,
diacritizationPolicy: ArabicDiacritizationPolicy = .unspecified,
tanweenPolicy: ArabicTanweenPolicy = .unspecified,
userNotes: String? = nil,
preservedSpellings: [String] = []
) {
self.isEnabled = isEnabled
self.diacritizationPolicy = diacritizationPolicy
self.tanweenPolicy = tanweenPolicy
self.userNotes = userNotes
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 let id: String
public var name: String
public var aliases: [String]
public init(id: String = UUID().uuidString, name: String, aliases: [String] = []) {
self.id = id
self.name = name
self.aliases = aliases
}
}
public struct GenreStyleCatalogItem: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public var familyCategory: String
public var regionOrigin: String?
public var aliases: [String]
public var searchTerms: [String]
public init(
id: String,
name: String,
familyCategory: String,
regionOrigin: String? = nil,
aliases: [String] = [],
searchTerms: [String] = []
) {
self.id = id
self.name = name
self.familyCategory = familyCategory
self.regionOrigin = regionOrigin
self.aliases = aliases
self.searchTerms = searchTerms
}
public var genreStyle: GenreStyle {
GenreStyle(id: id, name: name, aliases: aliases)
}
}
public struct MoodTag: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public init(id: String = UUID().uuidString, name: String) {
self.id = id
self.name = name
}
}
public struct EmotionalArcPoint: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var sectionID: String?
public var emotion: String
public var energy: Int?
public var direction: String?
public init(
id: String = UUID().uuidString,
sectionID: String? = nil,
emotion: String,
energy: Int? = nil,
direction: String? = nil
) {
self.id = id
self.sectionID = sectionID
self.emotion = emotion
self.energy = energy
self.direction = direction
}
}
public struct Lyrics: Codable, Equatable, Sendable {
public var text: String
public var sectionTexts: [String: String]
public init(text: String = "", sectionTexts: [String: String] = [:]) {
self.text = text
self.sectionTexts = sectionTexts
}
}
public struct ProductionDirection: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var sectionID: String?
public var text: String
public init(id: String = UUID().uuidString, sectionID: String? = nil, text: String) {
self.id = id
self.sectionID = sectionID
self.text = text
}
}
public struct InstrumentCatalogItem: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public var familyCategory: String
public var regionOrigin: String?
public var aliases: [String]
public var searchTerms: [String]
public init(
id: String,
name: String,
familyCategory: String,
regionOrigin: String? = nil,
aliases: [String] = [],
searchTerms: [String] = []
) {
self.id = id
self.name = name
self.familyCategory = familyCategory
self.regionOrigin = regionOrigin
self.aliases = aliases
self.searchTerms = searchTerms
}
}
public struct SongSection: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var type: SongSectionType
public var title: String
public var order: Int
public var startTime: TimeInterval?
public var endTime: TimeInterval?
public var lyrics: String
public var emotion: String?
public var energy: Int?
public var vocalDirection: String?
public var productionDirection: String?
public init(
id: String = UUID().uuidString,
type: SongSectionType,
title: String,
order: Int = 0,
startTime: TimeInterval? = nil,
endTime: TimeInterval? = nil,
lyrics: String = "",
emotion: String? = nil,
energy: Int? = nil,
vocalDirection: String? = nil,
productionDirection: String? = nil
) {
self.id = id
self.type = type
self.title = title
self.order = order
self.startTime = startTime
self.endTime = endTime
self.lyrics = lyrics
self.emotion = emotion
self.energy = energy
self.vocalDirection = vocalDirection
self.productionDirection = productionDirection
}
}
public enum SongSectionType: String, Codable, Equatable, Sendable {
case intro
case verse
case preChorus
case chorus
case bridge
case outro
case custom
}
public struct InstrumentTrack: Codable, Equatable, Sendable {
public var instrumentId: String
public var selected: Bool
public var variant: String?
public var playingStyle: String?
public var role: String?
public var autoArrangementEnabled: Bool
public var placements: [InstrumentPlacement]
public init(
instrumentId: String,
selected: Bool = true,
variant: String? = nil,
playingStyle: String? = nil,
role: String? = nil,
autoArrangementEnabled: Bool = true,
placements: [InstrumentPlacement] = []
) {
self.instrumentId = instrumentId
self.selected = selected
self.variant = variant
self.playingStyle = playingStyle
self.role = role
self.autoArrangementEnabled = autoArrangementEnabled
self.placements = placements
}
}
public struct InstrumentPlacement: Codable, Equatable, Sendable {
public var sectionId: String?
public var startTime: TimeInterval?
public var endTime: TimeInterval?
public var direction: String?
public init(
sectionId: String? = nil,
startTime: TimeInterval? = nil,
endTime: TimeInterval? = nil,
direction: String? = nil
) {
self.sectionId = sectionId
self.startTime = startTime
self.endTime = endTime
self.direction = direction
}
}
public struct VocalistPreset: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var label: String
public var familyCategory: String
public var regionOrigin: String?
public var voiceType: String?
public var genderSelection: String?
public var performanceStyle: String?
public var aliases: [String]
public var searchTerms: [String]
public init(
id: String,
label: String,
familyCategory: String,
regionOrigin: String? = nil,
voiceType: String? = nil,
genderSelection: String? = nil,
performanceStyle: String? = nil,
aliases: [String] = [],
searchTerms: [String] = []
) {
self.id = id
self.label = label
self.familyCategory = familyCategory
self.regionOrigin = regionOrigin
self.voiceType = voiceType
self.genderSelection = genderSelection
self.performanceStyle = performanceStyle
self.aliases = aliases
self.searchTerms = searchTerms
}
public var vocalist: Vocalist {
Vocalist(
id: id,
label: label,
voiceType: voiceType,
genderSelection: genderSelection,
performanceStyle: performanceStyle
)
}
}
public struct Vocalist: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var label: String
public var voiceType: String?
public var genderSelection: String?
public var performanceStyle: String?
public var assignedSections: [String]
public init(
id: String = UUID().uuidString,
label: String,
voiceType: String? = nil,
genderSelection: String? = nil,
performanceStyle: String? = nil,
assignedSections: [String] = []
) {
self.id = id
self.label = label
self.voiceType = voiceType
self.genderSelection = genderSelection
self.performanceStyle = performanceStyle
self.assignedSections = assignedSections
}
}
public struct SunoOutput: Codable, Equatable, Sendable {
public var lyricsText: String
public var stylePrompt: String
public var additionalFields: [String: String]
public var generatedAt: Date
public init(
lyricsText: String,
stylePrompt: String,
additionalFields: [String: String] = [:],
generatedAt: Date = Date()
) {
self.lyricsText = lyricsText
self.stylePrompt = stylePrompt
self.additionalFields = additionalFields
self.generatedAt = generatedAt
}
}
public struct SongVersion: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var label: String
public var snapshot: SongProjectSnapshot
public let createdAt: Date
public init(
id: String = UUID().uuidString,
label: String,
snapshot: SongProjectSnapshot,
createdAt: Date = Date()
) {
self.id = id
self.label = label
self.snapshot = snapshot
self.createdAt = createdAt
}
}
public struct SongProjectSnapshot: Codable, Equatable, Sendable {
public var title: String
public var idea: String
public var duration: SongDuration?
public var conversationMode: ConversationMode
public var languages: [LanguageSelection]
public var dialects: [DialectSelection]
public var arabicPronunciationSettings: ArabicPronunciationSettings
public var genres: [GenreStyle]
public var moods: [MoodTag]
public var emotionalArc: [EmotionalArcPoint]
public var bpm: ManualAutoValue<Int>?
public var key: ManualAutoValue<String>?
public var scale: ManualAutoValue<String>?
public var maqam: ManualAutoValue<String>?
public var structureMode: ControlMode?
public var sections: [SongSection]
public var instruments: [InstrumentTrack]
public var vocalists: [Vocalist]
public var lyrics: Lyrics
public var productionDirections: [ProductionDirection]
public var productionMode: ControlMode?
public var userLocks: SongProjectUserLocks?
public var sunoOutput: SunoOutput?
public init(
title: String,
idea: String,
duration: SongDuration? = nil,
conversationMode: ConversationMode = .auto,
languages: [LanguageSelection] = [],
dialects: [DialectSelection] = [],
arabicPronunciationSettings: ArabicPronunciationSettings = ArabicPronunciationSettings(),
genres: [GenreStyle] = [],
moods: [MoodTag] = [],
emotionalArc: [EmotionalArcPoint] = [],
bpm: ManualAutoValue<Int>? = nil,
key: ManualAutoValue<String>? = nil,
scale: ManualAutoValue<String>? = nil,
maqam: ManualAutoValue<String>? = nil,
structureMode: ControlMode? = .auto,
sections: [SongSection] = [],
instruments: [InstrumentTrack] = [],
vocalists: [Vocalist] = [],
lyrics: Lyrics = Lyrics(),
productionDirections: [ProductionDirection] = [],
productionMode: ControlMode? = .auto,
userLocks: SongProjectUserLocks? = SongProjectUserLocks(),
sunoOutput: SunoOutput? = nil
) {
self.title = title
self.idea = idea
self.duration = duration
self.conversationMode = conversationMode
self.languages = languages
self.dialects = dialects
self.arabicPronunciationSettings = arabicPronunciationSettings
self.genres = genres
self.moods = moods
self.emotionalArc = emotionalArc
self.bpm = bpm
self.key = key
self.scale = scale
self.maqam = maqam
self.structureMode = structureMode
self.sections = sections
self.instruments = instruments
self.vocalists = vocalists
self.lyrics = lyrics
self.productionDirections = productionDirections
self.productionMode = productionMode
self.userLocks = userLocks
self.sunoOutput = sunoOutput
}
public init(project: SongProject) {
self.init(
title: project.title,
idea: project.idea,
duration: project.duration,
conversationMode: project.conversationMode,
languages: project.languages,
dialects: project.dialects,
arabicPronunciationSettings: project.arabicPronunciationSettings,
genres: project.genres,
moods: project.moods,
emotionalArc: project.emotionalArc,
bpm: project.bpm,
key: project.key,
scale: project.scale,
maqam: project.maqam,
structureMode: project.structureMode,
sections: project.sections,
instruments: project.instruments,
vocalists: project.vocalists,
lyrics: project.lyrics,
productionDirections: project.productionDirections,
productionMode: project.productionMode,
userLocks: project.userLocks,
sunoOutput: project.sunoOutput
)
}
}