Skip to content

Commit

Permalink
Release SWS CF_Preview API demo v1.0 (#1302)
Browse files Browse the repository at this point in the history
  • Loading branch information
cfillion authored Feb 2, 2024
1 parent 3748e66 commit a206262
Showing 1 changed file with 330 additions and 0 deletions.
330 changes: 330 additions & 0 deletions Development/cfillion_SWS CF_Preview API demo.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
-- @description SWS CF_Preview API demo
-- @author cfillion
-- @version 1.0
-- @donation https://reapack.com/donate

dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Extensions/API/imgui.lua')
('0.8.5')

local ImGui = {}
for name, func in pairs(reaper) do
name = name:match('^ImGui_(.+)$')
if name then ImGui[name] = func end
end

local SCRIPT_NAME = select(2, reaper.get_action_context()):match("([^/\\_]+)%.lua$")
local FLT_MIN, FLT_MAX = ImGui.NumericLimits_Float()
local ctx = ImGui.CreateContext(SCRIPT_NAME)
local sans_serif = ImGui.CreateFont('sans-serif', 13)
ImGui.Attach(ctx, sans_serif)

local preview
local file = reaper.GetExtState('sws_test', 'preview_file')
local volume, pan, fadeInLen, fadeOutLen, loop, reverse = 1, 0, 0, 0, false, false
local outputChan, outputProj, outputTrack, outToTrack = 0, nil, nil, false
local playRate, pitch, preservePitch, measureAlign, seekPos = 1, 0, true, 0, 0
local psMode, psModes, psSubMode, psSubModes = 1, {{v=-1, n='Project default'}}, 1
local peakChans = 2

local function updatePitchShiftSubmodes()
psSubModes = {}
local i = 0
while true do
local mode = reaper.EnumPitchShiftSubModes(psModes[psMode].v, i)
if not mode then break end
psSubModes[#psSubModes + 1] = mode
i = i + 1
end
end

local i = 0
while true do
local rv, mode = reaper.EnumPitchShiftModes(i)
if not rv then break end
if mode then psModes[#psModes + 1] = {v=i, n=mode} end
i = i + 1
end
updatePitchShiftSubmodes()

local function VAL2DB(x)
if x < 0.0000000298023223876953125 then
return -150
else
return math.max(-150, math.log(x) * 8.6858896380650365530225783783321)
end
end

local function formatChan(chan)
local n = chan & 1023
if (chan & 1024) == 0 then
return ('Channel %d/%d'):format(n + 1, n + 2)
else
return ('Channel %d'):format(n + 1)
end
end

local function pitchMode()
if psModes[psMode].v == -1 then
return -1
else
return (psModes[psMode].v << 16) | (psSubMode - 1)
end
end

local function outputSelect()
local rv = false
if ImGui.RadioButton(ctx, 'Hardware output', not outToTrack) then
outToTrack = false
rv = true
end
ImGui.SameLine(ctx)
ImGui.SetNextItemWidth(ctx, 110)
if ImGui.BeginCombo(ctx, '##chan', formatChan(outputChan)) then
local monoChanged
monoChanged, outputChan = ImGui.CheckboxFlags(ctx, 'Mono', outputChan, 1024)
if monoChanged then rv = true end
ImGui.Spacing(ctx)
local chan = outputChan & 1023
local monoFlag = (outputChan & 1024)
local skip = monoFlag == 0 and 2 or 1
for i = 0, reaper.GetNumAudioOutputs()-1, skip do
if ImGui.Selectable(ctx, formatChan(i | monoFlag), chan == i) then
outputChan = i | monoFlag
outToTrack = false
rv = true
end
end
ImGui.EndCombo(ctx)
end

local numTracks = reaper.GetNumTracks()
if not reaper.ValidatePtr(outputProj, 'ReaProject*') or
not reaper.ValidatePtr2(outputProj, outputTrack, 'MediaTrack*') then
outToTrack = false
outputTrack = reaper.GetTrack(nil, 0)
outputProj = reaper.EnumProjects(-1)
rv = true
end
ImGui.BeginDisabled(ctx, not outputTrack and numTracks == 0)
ImGui.SameLine(ctx)
if ImGui.RadioButton(ctx, 'Through track', outToTrack) then
outToTrack = true
rv = true
end
ImGui.SameLine(ctx)
ImGui.SetNextItemWidth(ctx, -FLT_MIN)
local trackName = '<no track>'
if outputTrack then trackName = select(2, reaper.GetTrackName(outputTrack)) end
if ImGui.BeginCombo(ctx, '##track', trackName) then
for i = 0, numTracks - 1 do
local track = reaper.GetTrack(nil, i)
trackName = select(2, reaper.GetTrackName(track))
if ImGui.Selectable(ctx, trackName, track == outputTrack) then
outputTrack = track
outputProj = reaper.EnumProjects(-1)
outToTrack = true
rv = true
end
end
ImGui.EndCombo(ctx)
end
ImGui.EndDisabled(ctx)

return rv
end

local function pitchShiftSelect()
local rv = false

ImGui.SetNextItemWidth(ctx, 200)
if ImGui.BeginCombo(ctx, 'Pitch shift mode', psModes[psMode].n) then
for i, mode in ipairs(psModes) do
if ImGui.Selectable(ctx, mode.n, psMode == i) then
psMode = i
psSubMode = 1
updatePitchShiftSubmodes()
rv = true
end
end
ImGui.EndCombo(ctx)
end

ImGui.SameLine(ctx)
ImGui.BeginDisabled(ctx, #psSubModes == 0)
ImGui.SetNextItemWidth(ctx, -62)
if ImGui.BeginCombo(ctx, 'Submode', psSubModes[psSubMode] or '') then
for i, submode in ipairs(psSubModes) do
if ImGui.Selectable(ctx, submode, psSubMode == i) then
psSubMode = i
rv = true
end
end
ImGui.EndCombo(ctx)
end
ImGui.EndDisabled(ctx)

return rv
end

local function start()
local source = reaper.PCM_Source_CreateFromFile(file)
if not source then return end

if reverse then
local section = reaper.PCM_Source_CreateFromType('SECTION')
reaper.CF_PCM_Source_SetSectionInfo(section, source, 0, 0, true)
reaper.PCM_Source_Destroy(source)
source = section
end

if preview then reaper.CF_Preview_Stop(preview) end
preview = reaper.CF_CreatePreview(source)
if outToTrack then
reaper.CF_Preview_SetOutputTrack(preview, outputProj, outputTrack)
else
reaper.CF_Preview_SetValue(preview, 'I_OUTCHAN', outputChan)
end
-- reaper.CF_Preview_SetValue(preview, 'D_POSITION', position)
reaper.CF_Preview_SetValue(preview, 'B_LOOP', loop and 1 or 0)
reaper.CF_Preview_SetValue(preview, 'D_VOLUME', volume)
reaper.CF_Preview_SetValue(preview, 'D_PAN', pan)
reaper.CF_Preview_SetValue(preview, 'D_MEASUREALIGN', measureAlign)
reaper.CF_Preview_SetValue(preview, 'D_PLAYRATE', playRate)
reaper.CF_Preview_SetValue(preview, 'D_PITCH', pitch)
reaper.CF_Preview_SetValue(preview, 'B_PPITCH', preservePitch and 1 or 0)
reaper.CF_Preview_SetValue(preview, 'I_PITCHMODE', pitchMode())
reaper.CF_Preview_SetValue(preview, 'D_FADEINLEN', fadeInLen)
reaper.CF_Preview_SetValue(preview, 'D_FADEOUTLEN', fadeOutLen)
reaper.CF_Preview_Play(preview)
reaper.PCM_Source_Destroy(source)
end

local function window()
local rv
local active, position = reaper.CF_Preview_GetValue(preview, 'D_POSITION')
local length = select(2, reaper.CF_Preview_GetValue(preview, 'D_LENGTH'))

ImGui.PushItemWidth(ctx, -72)
ImGui.AlignTextToFramePadding(ctx)
ImGui.Text(ctx, 'File:')
ImGui.SameLine(ctx)
rv, file = ImGui.InputText(ctx, '##path', file)
ImGui.SameLine(ctx)
if ImGui.Button(ctx, 'Browse...') then
local rv, newFile = reaper.JS_Dialog_BrowseForOpenFiles('Select audio file', '', file, '', false)
if rv and newFile:len() > 0 then
file = newFile
reaper.SetExtState('sws_test', 'preview_file', file, false)
end
end

local time = ('%s / %s'):format(
reaper.format_timestr(position, ''),
reaper.format_timestr(length, ''))
rv, seekPos = ImGui.SliderDouble(ctx, '##position', seekPos or position, 0, length, time)
if ImGui.IsItemDeactivatedAfterEdit(ctx) then
reaper.CF_Preview_SetValue(preview, 'D_POSITION', seekPos)
elseif not ImGui.IsItemActive(ctx) then
seekPos = position
end
ImGui.SameLine(ctx)
rv, loop = ImGui.Checkbox(ctx, 'Loop', loop)
if rv and active then reaper.CF_Preview_SetValue(preview, 'B_LOOP', loop and 1 or 0) end

if outputSelect() then
if outToTrack then
reaper.CF_Preview_SetOutputTrack(preview, outputProj, outputTrack)
else
reaper.CF_Preview_SetValue(preview, 'I_OUTCHAN', outputChan)
end
end

ImGui.SetNextItemWidth(ctx, -340)
rv, volume = ImGui.SliderDouble(ctx, 'Volume', volume, 0, 2,
('%.2fdB'):format(VAL2DB(volume)), ImGui.SliderFlags_Logarithmic())
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_VOLUME', volume) end
ImGui.SameLine(ctx)
ImGui.SetNextItemWidth(ctx, 55)
rv, pan = ImGui.SliderDouble(ctx, 'Pan', pan, -1, 1)
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_PAN', pan) end
ImGui.SameLine(ctx)
local avail_w = ImGui.PushItemWidth(ctx, 40)
rv, fadeInLen = ImGui.InputDouble(ctx, 'Fade in', fadeInLen)
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_FADEINLEN', fadeInLen) end
ImGui.SameLine(ctx)
rv, fadeOutLen = ImGui.InputDouble(ctx, 'Fade out', fadeOutLen)
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_FADEOUTLEN', fadeOutLen) end
ImGui.PopItemWidth(ctx)

ImGui.PushItemWidth(ctx, 50)
rv, playRate = ImGui.InputDouble(ctx, 'Playback rate', playRate)
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_PLAYRATE', playRate) end
ImGui.SameLine(ctx)
rv, pitch = ImGui.InputDouble(ctx, 'Pitch adjust (semitones)', pitch)
if rv and active then reaper.CF_Preview_SetValue(preview, 'D_PITCH', pitch) end
ImGui.PopItemWidth(ctx)
ImGui.SameLine(ctx)
rv, preservePitch = ImGui.Checkbox(ctx, 'Preserve pitch when changing rate', preservePitch)
if rv and active then reaper.CF_Preview_SetValue(preview, 'B_PPITCH', preservePitch and 1 or 0) end

rv = pitchShiftSelect()
if rv and active then
reaper.CF_Preview_SetValue(preview, 'I_PITCHMODE', pitchMode())
end

ImGui.Spacing(ctx)
if ImGui.Button(ctx, 'Start preview') then start() end
ImGui.SameLine(ctx)
ImGui.BeginDisabled(ctx, not active)
if ImGui.Button(ctx, 'Stop') then
reaper.CF_Preview_Stop(preview)
preview = nil
end
ImGui.EndDisabled(ctx)
ImGui.SameLine(ctx, nil, 20)
ImGui.SetNextItemWidth(ctx, 72)
rv, measureAlign = ImGui.InputInt(ctx, 'Align to measure', measureAlign)
if rv and measureAlign < 0 then measureAlign = 0 end
ImGui.SameLine(ctx, nil, 20)
rv, reverse = ImGui.Checkbox(ctx, 'Reverse', reverse)
ImGui.SameLine(ctx, nil, 20)
ImGui.SetNextItemWidth(ctx, -62)
rv, peakChans = ImGui.InputInt(ctx, 'Peaks', peakChans, 1, 1)
if rv then peakChans = math.max(2, math.min(128, peakChans)) end

ImGui.Spacing(ctx)
local avail_w, avail_h = ImGui.GetContentRegionAvail(ctx)
local spacing_x, spacing_h = ImGui.GetStyleVar(ctx, ImGui.StyleVar_ItemSpacing())
local meter_h = math.max(spacing_h, ((avail_h + spacing_h) / peakChans) - spacing_h)
for i = 1, peakChans do
local peak = select(2, reaper.CF_Preview_GetPeak(preview, i))
ImGui.ProgressBar(ctx, peak, -FLT_MIN, meter_h, ' ')
end

ImGui.PopItemWidth(ctx)
end

local function loop()
ImGui.PushFont(ctx, sans_serif)
ImGui.SetNextWindowSizeConstraints(ctx, 590, 240, FLT_MAX, FLT_MAX)
local visible, open = ImGui.Begin(ctx, SCRIPT_NAME, true)
if visible then
window()
ImGui.End(ctx)
end
ImGui.PopFont(ctx)

if open then
reaper.defer(loop)
end
end

reaper.defer(loop)

reaper.atexit(function()
-- EnumProjects(0) to check whether SWS is still loaded
if preview and reaper.EnumProjects(0) then
reaper.CF_Preview_Stop(preview)
end
end)

0 comments on commit a206262

Please sign in to comment.