722 lines
22 KiB
Swift
722 lines
22 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 sections: [SongSection]
|
|
public var instruments: [InstrumentTrack]
|
|
public var vocalists: [Vocalist]
|
|
public var lyrics: Lyrics
|
|
public var productionDirections: [ProductionDirection]
|
|
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,
|
|
sections: [SongSection] = [],
|
|
instruments: [InstrumentTrack] = [],
|
|
vocalists: [Vocalist] = [],
|
|
lyrics: Lyrics = Lyrics(),
|
|
productionDirections: [ProductionDirection] = [],
|
|
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.sections = sections
|
|
self.instruments = instruments
|
|
self.vocalists = vocalists
|
|
self.lyrics = lyrics
|
|
self.productionDirections = productionDirections
|
|
self.sunoOutput = sunoOutput
|
|
self.versions = versions
|
|
self.createdAt = createdAt
|
|
self.updatedAt = updatedAt
|
|
}
|
|
}
|
|
|
|
public extension SongProject {
|
|
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 }
|
|
}
|
|
|
|
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 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 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 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 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 sections: [SongSection]
|
|
public var instruments: [InstrumentTrack]
|
|
public var vocalists: [Vocalist]
|
|
public var lyrics: Lyrics
|
|
public var productionDirections: [ProductionDirection]
|
|
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,
|
|
sections: [SongSection] = [],
|
|
instruments: [InstrumentTrack] = [],
|
|
vocalists: [Vocalist] = [],
|
|
lyrics: Lyrics = Lyrics(),
|
|
productionDirections: [ProductionDirection] = [],
|
|
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.sections = sections
|
|
self.instruments = instruments
|
|
self.vocalists = vocalists
|
|
self.lyrics = lyrics
|
|
self.productionDirections = productionDirections
|
|
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,
|
|
sections: project.sections,
|
|
instruments: project.instruments,
|
|
vocalists: project.vocalists,
|
|
lyrics: project.lyrics,
|
|
productionDirections: project.productionDirections,
|
|
sunoOutput: project.sunoOutput
|
|
)
|
|
}
|
|
}
|