local SCRIPT_TITLE = 'Lyrics to simple notes'

--[[

Synthesizer V Studio Pro Script
 
lua file name: Lyrics2SimpleNotesPanel.lua

Generates simple notes with variable spacing between notes based on random rhythm.
Fill lyrics into generated notes

Update: 1 - Creation

Notice: Works only with script panel 
		introduced with Synthesizer V version >= 2.1.2

2026 - JF AVILES
--]]

function getClientInfo()
	return {
		name = SV:T(SCRIPT_TITLE),
		-- category = "_JFA_Panels",
		author = "JFAVILES",
		versionNumber = 1,
		minEditorVersion = 131329,
		type = "SidePanelSection"
	}
end

-- Generated by JFA TranslateScripts.lua
function getTranslations(langCode)
	return getArrayLanguageStrings()[langCode]
end

-- Generated by JFA TranslateScripts.lua
function getArrayLanguageStrings()
	return {
		["en-us"] = {
			{"Version", "Version"},
			{"author", "author"},
			{"minEditorVersion", "minEditorVersion"},
			{"Lyrics to melodies", "Lyrics to melodies"},
			{"No lyrics in text area!", "No lyrics in text area!"},
			{"Speed fast", "Speed fast"},
			{"Speed medium", "Speed medium"},
			{"Speed slow", "Speed slow"},
			{"Note fixed: 1/16", "Note fixed: 1/16"},
			{"Note fixed: 1/32", "Note fixed: 1/32"},
			{"Note fixed: 1/64", "Note fixed: 1/64"},
			{"No lyrics found!", "No lyrics found!"},
			{"Apply", "Apply"},
			{"Select a rhythm", "Select a rhythm"},
			{"Reduce duration", "Reduce duration"},
			{"Note position to measure bar", "Note position to measure bar"},
		},
	}
end

-- Define a class "NotesObject"
NotesObject = {
	displayVersion = true,	-- display version
	displayAuthor = false,	-- display author
	currentSeconds = 0,
	defaultLyrics = "la",
	linefeed = "[^\r\n]+",
	words = "%S+",
	octaveRange = 1,
	quarterDivider = 4,
	measureBarVal = 8,		-- Place notes on starting measure bar 1/8
	minTimeSpacing = 1/8,	-- default minimum time(s) between notes
	errorMessages = {},
	currentPlayheadSeconds = 0,
	beginGroup = nil,
	endGroup = nil,
	hostinfo = nil,
	osType = "",
	osName = "",
	hostName = "",
	languageCode = "", 
	hostVersion = "",
	hostVersionNumber = 0,
	debug = false,
	saved_melodies = {},	-- Storage for generated melodies
	styles = {},
	rhythms = {},
	measureBar = {},
	rhythmsList = {},
	controls = {},				-- controls panel
	applyButtonValue = nil, 	-- button apply
	createProjectValue = nil, 	-- button create project
	statusTextValue = nil,   	-- text panel
	measureBarChoice = {},
	infosToDisplay = "",
	logs = {},
	lyrics2NotesTextChoice = {},
	coefMinValue = 0.25,
	coefMaxValue = 1,
	coefInterval = 0.25,
	durationCoef = 0.75	-- Multiplier/divider for note duration (1 = normal, 0.5 = shorter)
}

-- Constructor method for the NotesObject class
function NotesObject:new()
    local notesObject = {}
    setmetatable(notesObject, self)
    self.__index = self
	
	self:getHostInformations()
	
	-- Get playhead first measure
	self.currentSeconds = self:getPlayhead()
	
	self.styles = self:getStylesData()
	self.rhythms = self:getRhythmsData()

	self.measureBar = self:getMeasureBarData()

	self.controls = self:getControls()
	
	self:initializeControlsValues()
	self:setControlsCallback()
	
	self.applyButtonValue = SV:create("WidgetValue")
	self.lyrics2NotesTextValue = SV:create("WidgetValue")
	self.lyrics2NotesTextValue:setValue("")
	self.lyrics2NotesTextValue:setEnabled(false)
	self.createProjectValue = SV:create("WidgetValue")
	self.statusTextValue = SV:create("WidgetValue")
	self.statusTextValue:setValue("")
	self.statusTextValue:setEnabled(false)
	
	self:setButtonApplyControlCallback()

	-- load combox data
	self:getComboLists()
	
	local infos = getClientInfo()

	self.infosToDisplay = ""
	if self.displayVersion then
		self.infosToDisplay = self.infosToDisplay .. SV:T("Version") .. ": " ..  infos.versionNumber
		if self.displayAuthor then
			self.infosToDisplay = self.infosToDisplay .. " - " .. SV:T("author") .. ": " .. infos.author
		end
	end
	-- self.infosToDisplay = self.infosToDisplay .. SV:T("minEditorVersion") .. ": " ..  infos.minEditorVersion
	self:addTextPanel(self.infosToDisplay)
	self:addTextPanel(SV:T("Lyrics to melodies") .. "...")
	
    return self
end

-- Show message dialog
function NotesObject:show(message)
	SV:showMessageBox(SV:T(SCRIPT_TITLE), message)
end

-- Get selected groups
function NotesObject:getSelectedGroups()
	return SV:getArrangement():getSelection():getSelectedGroups()
end

-- Get current track
function NotesObject:getCurrentTrack()
	return SV:getMainEditor():getCurrentTrack()
end

-- Get timeAxis
function NotesObject:getTimeAxis()
	return self:getProject():getTimeAxis()
end

-- Get project
function NotesObject:getProject()
	return SV:getProject()
end

-- Add log into self.logs
function NotesObject:addLogs(message)
	table.insert(self.logs, message)
end

-- Display logs into panel
function NotesObject:addLogsInPanel()
	for i = 1, #self.logs do
		self:addTextPanel(self.logs[i])
	end
end

-- Display message box in panel
function NotesObject:addTextPanel(message)
	local old = self.statusTextValue:getValue()
	local sepLine = "\r"
	if #old > 0 then
		message = old .. sepLine .. message
	end
	self.statusTextValue:setValue(message)
end

-- Clear display message in panel
function NotesObject:clearTextPanel()
	self.statusTextValue:setValue("")
end

-- Store error message
function NotesObject:error(message)
	table.insert(self.errorMessages, message)
end

-- Get controls panel
function NotesObject:getControls()

	local controls = {
		durationCoef = {
			value = SV:create("WidgetValue"),
			defaultValue = self.durationCoef,
			paramKey = "durationCoef"
		},
		scaleKey = {
			value = SV:create("WidgetValue"),
			defaultValue = 0, -- C
			paramKey = "scaleKey"
		},
		rhythm = {
			value = SV:create("WidgetValue"),
			defaultValue = 1, -- medium
			paramKey = "rhythm"
		},
		rhythmLyrics = {
			value = SV:create("WidgetValue"),
			defaultValue = 0, -- sentimental
			paramKey = "rhythmLyrics"
		},
		measureBar = {
			value = SV:create("WidgetValue"),
			defaultValue = 0, -- measureBar = 8
			paramKey = "measureBar"
		}
	}
	return controls
end

-- Initialize widget values
function NotesObject:initializeControlsValues()
	-- Initialize widget values
	for key, control in pairs(self.controls) do
		control.value:setValue(control.defaultValue)
		-- self:addLogs(key .. "=" .. tostring(control.defaultValue))
		-- self:addLogs(key .. "=" .. self:getObjectProperties(control))
	end
end

-- Set controls callback
function NotesObject:setControlsCallback()
	for key, control in pairs(self.controls) do
		control.value:setValueChangeCallback(function()
			-- do something may be
				-- self:addLogsInPanel()
				if control.paramKey == "rhythm" then
					local rhythm = self.rhythmsList[self.controls.rhythm.value:getValue() + 1]
					local rhythm_config = self:get_rhythm(rhythm).val
				end
			end
		)
	end
end

-- Display message
function NotesObject:displayMessage(message)
	self:clearTextPanel()
	self:addTextPanel(self.infosToDisplay)
	self:addTextPanel(message)
end

-- Set button Apply control callback
function NotesObject:setButtonApplyControlCallback()

	-- Button create melody
	self.applyButtonValue:setValueChangeCallback(function()
			self:getProject():newUndoRecord()
			
			local lyrics = self.lyrics2NotesTextValue:getValue()
			if #lyrics > 0 then
				self:lyrics2NotesGroup(lyrics)
			else
				self:show(SV:T("No lyrics in text area!"))
			end
		end
	)
end

-- Get next measure bar
function NotesObject:getNextMeasure(groupName, currentBlicksPos)
	local next = 0
	if currentBlicksPos == 0 then
		next = 0
	else
		local posSecond = self:getTimeAxis():getSecondsFromBlick(currentBlicksPos)
		local posSecondFloor = math.floor(posSecond) + 1
		local nextPosMod = (posSecondFloor % 2)
		local nextPos = posSecondFloor + nextPosMod
		next = self:getTimeAxis():getBlickFromSeconds(nextPos)
	end	
	return next
end

-- Get styles data
function NotesObject:getStylesData()
	local styles = {}
	
	local stylesReference = { 
		{name = SV:T("Style pop"), key="pop", val = {
			scale_preference = "major",
			octave_range = 1,
			jump_probability = 0.3,
			repetition_chance = 0.4,
			rhythm_complexity = 0.5,
			base_spacing = 0.08,	-- Spacing configurations
			spacing_variation = 0.1,
			syncopation_chance = 0.2,
			staccato_chance = 0.4
		}}
	}
	
	-- loop into data
	for i = 1, #stylesReference do
			table.insert(styles, stylesReference[i])
	end
	
	return styles
end

-- Get rhythms data
function NotesObject:getRhythmsData()
	local rhythms = {}

	local rhythmsReference = {
		{name = SV:T("Speed fast"), key = "fast",
			val = {
			tempo = 140,
			durations = {0.5, 0.5, 0.5, 0.25, 0.5}, -- Rhythm configurations (note durations and spacing multipliers)
			spacing_multiplier = 0.8,	-- Faster = less spacing
			groove_factor = 1.2,		-- Slightly uneven timing
			speed = 8 					-- note divider
		}},
		{name = SV:T("Speed medium"), key = "medium",
			val = {
			tempo = 120,
			durations = {0.5, 1, 0.5, 1, 0.5},
			spacing_multiplier = 1.0,
			groove_factor = 1.1,
			speed = 6
		}},
		{name = SV:T("Speed slow"), key = "slow",
			val = {
			tempo = 70,
			durations = {1, 2, 1, 1.5, 2},
			spacing_multiplier = 1.5,	-- Slower = more breathing room
			groove_factor = 1.05,
			speed = 4
		}}
	}
	
	-- loop into data
	for i = 1, #rhythmsReference do
		table.insert(rhythms, rhythmsReference[i])
	end
	
	return rhythms
end

-- Get measure bar data
function NotesObject:getMeasureBarData()
	local measureBar = {}
	
	local measureBarReference = {
		{name = SV:T("Note fixed: 1/16"), val = 8 }, -- 8 in 1s (16 in 2s)
		{name = SV:T("Note fixed: 1/32"), val = 16 },
		{name = SV:T("Note fixed: 1/64"), val = 32 }
	}
	
	-- loop into data
	for i = 1, #measureBarReference do
		table.insert(measureBar, measureBarReference[i])
	end
	
	return measureBar
end

-- Get playhead position in first nearest measure
function NotesObject:getPlayhead()
	-- Get playhead first measure
	self.currentPlayheadSeconds = SV:getPlayback():getPlayhead()
	local currentBlick = self:getTimeAxis():getBlickFromSeconds(self.currentPlayheadSeconds)
	-- Get first measure
	local measureBlick = self:getFirstMesure(currentBlick, 0)
	local newPositionSeconds = self:getTimeAxis():getSecondsFromBlick(measureBlick)
	return newPositionSeconds
end

-- Display error messages
function NotesObject:displayErrors()
	local result = ""
	if #self.errorMessages > 0 then
		for _, m in pairs(self.errorMessages) do
			result = result .. m .. "\r"
		end
	end
	return result
end

-- Get host informations
function NotesObject:getHostInformations()
	self.hostinfo = SV:getHostInfo()
	self.osType = self.hostinfo.osType  -- "macOS", "Linux", "Unknown", "Windows"
	self.osName = self.hostinfo.osName
	self.hostName = self.hostinfo.hostName
	self.hostVersion = self.hostinfo.hostVersion
	self.hostVersionNumber = self.hostinfo.hostVersionNumber
end

-- Get style from style name
function NotesObject:get_style(style_name)
	local style = nil
	for i, v in ipairs(self.styles) do
		if v.name == style_name then
			style = v
			break
		end
	end
	return style
end

-- Get rhythm from rhythm name
function NotesObject:get_rhythm(rhythm_name)
	local rhythm = nil
	for i, v in ipairs(self.rhythms) do
		if v.name == rhythm_name then
			rhythm = v
			break
		end
	end
	return rhythm
end

-- Get measureBar name
function NotesObject:get_measureBar(measureBar_name)
	local measureBar = nil
	for i, v in ipairs(self.measureBar) do
		if v.name == measureBar_name then
			measureBar = v
			break
		end
	end
	return measureBar
end

-- Calculate spacing between notes
function NotesObject:calculate_spacing(style_config, rhythm_config)
    local base_spacing = style_config.base_spacing * rhythm_config.spacing_multiplier
    
    -- Add variation
    local variation = (math.random() - 0.5) * style_config.spacing_variation
    local spacing = base_spacing + variation
    
    -- Apply syncopation (occasional off-beat emphasis)
    if math.random() < style_config.syncopation_chance then
        spacing = spacing + (math.random() * 0.1 - 0.05)
    end
    
    -- Apply groove factor (slight timing imperfections that feel human)
    local groove_variation = (math.random() - 0.5) * 0.02 * rhythm_config.groove_factor
    spacing = spacing + groove_variation
    
    -- Ensure spacing is not negative
    return math.max(0, spacing)
end

-- Calculate note duration with style modifications
function NotesObject:calculate_note_duration(base_duration, style_config, rhythm_config)
    local duration = base_duration
    
    -- Apply staccato effect (shortens notes)
    if math.random() < style_config.staccato_chance then
        duration = duration * (0.6 + math.random() * 0.3)  -- 60-90% of original duration
    end
    
    -- Add slight duration variation based on rhythm complexity
    local variation = (math.random() - 0.5) * 0.1 * style_config.rhythm_complexity
    duration = duration * (1 + variation)
    
    -- Apply groove factor to duration
    local groove_variation = (math.random() - 0.5) * 0.05 * rhythm_config.groove_factor
    duration = duration * (1 + groove_variation)
    
    return math.max(0.1, duration)  -- Ensure minimum duration
end

-- Get next measure Position
function NotesObject:getNextMeasurePos(current_time, timeMeasureBar, melodyLength)
	local newtime =  current_time
	local current_time_Int = math.floor(current_time)
	
	local new_next_time = current_time_Int
	local maxTime = melodyLength * 10
	for i = 1, maxTime do
		if current_time <= new_next_time then
			newtime = new_next_time
			break
		end
		new_next_time = timeMeasureBar * (i + current_time_Int)
	end
	return newtime
end

-- Get current project tempo
function NotesObject:getProjectTempo(seconds)
	local tempoActive = 120 -- default
	local blicks = self:getTimeAxis():getBlickFromSeconds(seconds)
	local tempoMarks = self:getTimeAxis():getAllTempoMarks()
	for iTempo = 1, #tempoMarks do
		local tempoMark = tempoMarks[iTempo]
		if tempoMark ~= nil and blicks >= tempoMark.position then
			tempoActive = tempoMark.bpm
		end
	end
	return math.floor(tempoActive)
end

-- Get object properties (debug only)
function NotesObject:getObjectProperties(obj, level)
	local result = ""
	local level = level or 0
	local maxLevel = 3
	level = level + 1
	
	for k, v in pairs(obj) do
		if obj[k] ~= nil then
			result = result .. "(level: " .. level .. ") " .. k .. "=" .. tostring(v) .. "\r"
			if type(v) == "table" then
				-- result = result .. ", size:" .. #v .. ": "
				if level < maxLevel then
					result = result .. self:getObjectProperties(v, level) .. "\r"
				else
					result = result .. "\r"
				end
			end
		end
	end
	return result
end

-- Split string by sep char
function NotesObject:split(str, sep)
	local result = {}
	local regex = ("([^%s]+)"):format(sep)
	for each in str:gmatch(regex) do
		table.insert(result, each)
	end
	return result
end

-- Get first mesure after first note (or next position + n)
function NotesObject:getFirstMesure(notePos, nextPos)
	local measurePos = 0
	local measureBlick = 0
	local measureFirst = self:getTimeAxis():getMeasureAt(notePos) + nextPos
	local checkExistingMeasureMark = self:getTimeAxis():getMeasureMarkAt(measureFirst)
	
	if checkExistingMeasureMark ~= nil then
		if checkExistingMeasureMark.position == measureFirst then
			measurePos = checkExistingMeasureMark.position
			measureBlick = checkExistingMeasureMark.positionBlick
		else 
			self:getTimeAxis():addMeasureMark(measureFirst, 
						checkExistingMeasureMark.numerator, 
						checkExistingMeasureMark.denominator)
			local measureMark = self:getTimeAxis():getMeasureMarkAt(measureFirst)
			measurePos = measureMark.position
			measureBlick = measureMark.positionBlick
			self:getTimeAxis():removeMeasureMark(measureFirst)
		end
	else
		-- Temporary measure mark addition
		self:getTimeAxis():addMeasureMark(measureFirst, 4, 4)
		local measureMark = self:getTimeAxis():getMeasureMarkAt(measureFirst)
		measurePos = measureMark.position
		measureBlick = measureMark.positionBlick
		self:getTimeAxis():removeMeasureMark(measureFirst)
	end
	return measureBlick
end

-- lyrics to Notes to group
function NotesObject:lyrics2NotesGroup(lyrics)
	math.randomseed(os.time())
	local resultFunction = false	
	local scaleKeyCtrl = self.controls.scaleKey.value:getValue()
	local rhythmCtrl = self.controls.rhythm.value:getValue()
	local measureBarCtrl = self.controls.measureBar.value:getValue()
	local newGrouptRef = {}
	local noteGroup = {}
	local result = ""

	local root_note = "C"
	local style_name = "Style pop"
    local style_config = self:get_style(style_name).val
	local rhythm_name = self.rhythmsList[rhythmCtrl + 1]
    local rhythm_config = self:get_rhythm(rhythm_name).val
    local rhythm_pattern = rhythm_config.durations
	
	self.durationCoef = self.controls.durationCoef.value:getValue()
    -- Get rootnote as default pitch from C3
	local defaultPitch = 48 + self.controls.scaleKey.value:getValue()
	
	-- measure bar default 8 for 1/16
	self.measureBarVal = self:get_measureBar(self.measureBarList[measureBarCtrl + 1]).val
	self.currentSeconds = SV:getPlayback():getPlayhead()
	self.BPM = self:getProjectTempo(self.currentSeconds)
	local BPM_ratio = 120 / self.BPM
	local measureBarTime = BPM_ratio / self.measureBarVal
	self.minTimeSpacing = measureBarTime
	
	if #lyrics == 0  then
		self:show(SV:T("No lyrics found!"))
	else
		local previousNoteOnset = nil
		local previousNoteDuration = 0
		local previousMidi_note = 48
		local previousWord = ""

		local matchSentences = string.gmatch(lyrics, self.linefeed)
		for lyricsLine in matchSentences do
		
			-- Create a note group
			local noteGroup = SV:create("NoteGroup")
			local newGrouptRef = SV:create("NoteGroupReference")
			local iNote = 0
			local noteOnset = nil
			local noteDuration = 0
			local noteGetEnd = 0
			local newNote = nil
			local matchWordsCount = 0
			local current_time = 0
			local new_next_time = 0
			
			-- Count words in line
			local matchWords = string.gmatch(lyricsLine, self.words)
			for word in matchWords do
				matchWordsCount = matchWordsCount + 1			
			end
			
			-- Get words from lyrics
			local matchWords = string.gmatch(lyricsLine, self.words)
			for word in matchWords do
				iNote = iNote + 1
				-- Get base duration from rhythm pattern
				local duration_index = ((iNote - 1) % #rhythm_pattern) + 1
				local base_duration = rhythm_pattern[duration_index] * self.durationCoef
				
				-- Calculate actual note duration with style modifications
				local note_duration = self:calculate_note_duration(base_duration, style_config, rhythm_config)
				
				if new_next_time > 0 then
					current_time = new_next_time
				end
			
				-- Create note event
				local midi_note = defaultPitch
				result = result .. "midi_note: " .. midi_note
				result = result .. ", current_time: " .. current_time
				result = result .. ", note_duration: " .. note_duration
				result = result .. "\r"
						
				-- Calculate spacing after this note
				local spacing = self:calculate_spacing(style_config, rhythm_config)
				
				if spacing < self.minTimeSpacing then
					spacing = 0
					result = result .. ", new: " .. spacing
				end
				-- Update current time (note duration + spacing)
				local next_time = current_time + note_duration + spacing
				result = result .. ", next_time: " .. next_time
				
				-- Place next note to next measure bar 1/16
				new_next_time = self:getNextMeasurePos(next_time, measureBarTime, matchWordsCount) -- Get next measure position
				result = result .. ", new_next_time: " .. new_next_time .. "\r"
				
				-- Force duration to join notes (without space)
				if new_next_time > next_time then
					-- note_duration = note_duration + new_next_time - next_time
					note_duration = new_next_time - current_time
				end
				
				-- current note recorded and created in next loop
				noteOnset = math.floor(self:getTimeAxis():getBlickFromSeconds(current_time))
				noteDuration = math.floor(self:getTimeAxis():getBlickFromSeconds(note_duration))
				
				if iNote == 1 then
				else
					-- New note for one	word (note from previous loop)
					newNote = SV:create("Note")
					newNote:setLyrics(previousWord)

					-- Set new duration if it is too long
					if iNote < matchWordsCount then					
						local previousNoteGetEnd = previousNoteOnset + previousNoteDuration
						if previousNoteGetEnd > noteOnset then
							previousNoteDuration = noteOnset - previousNoteOnset
							result = result .. ", previousNoteDuration: " .. previousNoteDuration .. "\r"
						end
					end
			
					newNote:setDuration(previousNoteDuration)
					-- newNote:setPitch(previousMidi_note)
					newNote:setPitch(defaultPitch)
					newNote:setOnset(previousNoteOnset)
					
					-- Add the note to the note group
					noteGroup:addNote(newNote)
				end
				previousNoteOnset = noteOnset
				previousNoteDuration = noteDuration
				previousMidi_note = midi_note
				previousWord = word				
				
			end
			
			-- New note for one	word (note from last loop)
			local newNote = SV:create("Note")
			newNote:setLyrics(previousWord)
			newNote:setDuration(previousNoteDuration)
			newNote:setPitch(previousMidi_note)
			newNote:setOnset(previousNoteOnset)
					
			-- Add the note to the note group
			noteGroup:addNote(newNote)
			
			noteGroup:setName(lyricsLine)			
			-- Add the note group to the project
			self:getProject():addNoteGroup(noteGroup)

			newGrouptRef:setTarget(noteGroup)
			
			self.currentSeconds = SV:getPlayback():getPlayhead()
			local checkGroup = self:getGroupRef(self:getCurrentTrack(), self.currentSeconds)
			if checkGroup ~= nil then
				self.currentSeconds = self:getNextEmptyPosition(self:getCurrentTrack(), checkGroup:getEnd())
				SV:getPlayback():seek(self.currentSeconds)
			else
				local newPositionBlicks = self:getTimeAxis():getBlickFromSeconds(self.currentSeconds)
				measureBlick = self:getFirstMesure(newPositionBlicks, 0)
				self.currentSeconds = self:getTimeAxis():getSecondsFromBlick(measureBlick)
				SV:getPlayback():seek(self.currentSeconds)
			end
			
			local startPositionBlicks = self:getTimeAxis():getBlickFromSeconds(self.currentSeconds)
			local firstNote = noteGroup:getNote(1)
			local lastNote = noteGroup:getNote(noteGroup:getNumNotes())
			
			local newTimeOffset, newDuration = self:getGroupNewTimeRange(startPositionBlicks, firstNote, lastNote)
			newGrouptRef:setTimeOffset(newTimeOffset)			
			newGrouptRef:setTimeRange(startPositionBlicks, newDuration) -- v2.1.1		
			
			self:getCurrentTrack():addGroupReference(newGrouptRef)
		end

		if self.debug then SV:setHostClipboard(result) end
		
		resultFunction = true
	end
	return result
end

-- Get group new time range
function NotesObject:getGroupNewTimeRange(startPositionBlicks, firstNote, lastNote)
	local endPositionBlicks = lastNote:getOnset() + lastNote:getDuration() - firstNote:getOnset()
	-- New end position to next bar
	local newEndPosition = self:getFirstMesure(endPositionBlicks, 1) - 10

	return	startPositionBlicks - firstNote:getOnset(), newEndPosition
end

-- Get next empty position on track
function NotesObject:getNextEmptyPosition(currentTrack, lastPositionBlicks)
	local measureBlick = self:getFirstMesure(lastPositionBlicks, 0)
	local seconds = self:getTimeAxis():getSecondsFromBlick(measureBlick)
	local checkGroup = self:getGroupRef(currentTrack, seconds)
	
	if checkGroup ~= nil then
		-- A group is found there
		-- Next measure bar
		measureBlick = self:getFirstMesure(checkGroup:getEnd(), 1)
		-- Recursive check to get empty place in track
		seconds = self:getNextEmptyPosition(currentTrack, measureBlick)
	end
	 
	return seconds
end

-- Get group reference in time position
function NotesObject:getGroupRef(track, time)
	local groupRefFound = nil
	local numGroups = track:getNumGroups()
	local blicksPos = self:getTimeAxis():getBlickFromSeconds(time)
	
	-- All groups except the main group
	for iGroup = 1, numGroups do
		local groupRef = track:getGroupReference(iGroup)
		if not groupRef:isMain() then
			local blickSeconds = self:getTimeAxis():getSecondsFromBlick(groupRef:getOnset())
			
			-- Get group on timing pos
			if blicksPos >= groupRef:getOnset() and blicksPos <= groupRef:getEnd() then
				groupRefFound = groupRef
				break
			end
		end
	end						
	return groupRefFound
end

-- Get combo box data
function NotesObject:getComboLists()
	self.rhythmsList = {}
	self.measureBarList = {}
	
	-- rhythms list
	for key, v in ipairs(self.rhythms) do
		table.insert(self.rhythmsList, self.rhythms[key].name)
	end
	-- measure bar list and data
	for key, v in ipairs(self.measureBar) do
		table.insert(self.measureBarList, self.measureBar[key].name)
	end
end

-- Get section
function NotesObject:getSection()
	
	-- Define all ComboBox	
	self:setComboChoices()

	self.lyrics2NotesTextChoice = {
		type = "Container",
		columns = {
			{
				name = "lyrics2Notes",
				type = "TextArea",
				label = "Lyrics",
				height = 200,
				value = self.lyrics2NotesTextValue
			}
		}
	}
	

	-- Define CheckBox & button & textarea
	local section = {
		title = SV:T(SCRIPT_TITLE),
		rows = {
			self.lyrics2NotesTextChoice,
			self.durationCoefChoice,
			self.measureBarChoice,
			{
				type = "Container",
				columns = {
					{
						type = "Button",
						text = SV:T("Apply"),
						width = 1.0,
						value = self.applyButtonValue
					}
				}
			}
		}
	}
	return section
end

-- Set ComboBox choices
function NotesObject:setComboChoices()
	-- Define ComboBox		
	
	self.durationCoefChoice = 
		{
			type = "Container",
			columns = {
				{
					type = "Slider",
					text = SV:T("Reduce duration"),
					format = "%1.2f coef",
					minValue = self.coefMinValue, 
					maxValue = self.coefMaxValue, 
					interval = self.coefInterval,
					value = self.controls.durationCoef.value,
					width = 1.0
				}
			}
		}

	self.measureBarChoice = {
        type = "Container",
        columns = {
			{
				type = "ComboBox",
				text = SV:T("Note position to measure bar"),
				value = self.controls.measureBar.value,
				choices = self.measureBarList,
				width = 1.0
			}
        }
    }

end

-- Get panel section state
function NotesObject:getPanelSectionState()

	self.applyButtonValue:setEnabled(true)
	self.createProjectValue:setEnabled(true)
	local errors = self:displayErrors()
	if #errors > 0 then
		self:addTextPanel(errors)
	end
	
	-- Get section data
	local section = self:getSection()

	return section
end

-- Initialize main internal object	
local notesObject = NotesObject:new()

-- Get panel section state called by Synthesizer V internal system
function getSidePanelSectionState()

	local section = notesObject:getPanelSectionState()

	return section
end