[Aviutl] 稍微提高制作音MAD的PV的效率的方法

写了一个让Aviutl能通过midi生成PV的脚本,目前还在测试中。

只需要需要在Aviutl安装目录下的Scripts文件夹新建两个文件:

1. midi.obj

--file:

if not videoFile then
    videoFile = {file}
else
    obj.setfont("", "40")
    obj.load("text", "OK")
end

2. midi.anm

--dialog:Import alpha channel/chk, alpha=0;MIDI File, midiPath="";MIDI Track, midiTrack="";Flip horizontal/chk, flipH=1;Flip vertical/chk, flipV=0;Other effects, effects={};
function getByteValue(str, begin)
    local pos, value = begin, 0
    while string.byte(str, pos, pos) > 128 do
        value = value * 128 + string.byte(str, pos, pos) % 128
        pos = pos + 1
    end
    value = value * 128 + string.byte(str, pos, pos)
    pos = pos + 1
    return value, pos
end
function getValue(str, begin, last)
    local i, value = begin, 0
    while i <= last do
        value = value + string.byte(str, i, i) * 256 ^ (last-i)
        i = i + 1
    end
    return value
end
function parseMidi(filePath)
    -- Open midi file
    local midiFile = io.open(filePath, "rb")
    if midiFile == nil then
        obj.load("text", "The file does not exist")
        return false
    end
    -- Load content
    local midiContent = midiFile:read("*all")
    local midiContentLength = string.len(midiContent)
    if string.sub(midiContent, 1, 4) ~= "MThd" then
        obj.load("text", "This is not a midi file")
        midiFile:close()
        return false
    end
    -- Parse content
    midiType        = getValue(midiContent,  9, 10)
    midiTracksCount = getValue(midiContent, 11, 12)
    midiDeltaTime   = getValue(midiContent, 13, 14)
    notes           = {}

    -- Parse MIDI tracks
    local i, trackId=15, 0
    while i < midiContentLength do
        if string.sub(midiContent, i, i+3) ~= "MTrk" then
            break
        end
        local trackLength       = getValue(midiContent, i+4, i+7)
        local trackNotesCount   = 0
        trackId = trackId + 1
        -- Parse MIDI Events
        local j = i + 8
        local extraOffset = 0
        notes[trackId] = {}
        while j < i + trackLength + 8 do
            local eventOffset, nextPos  = getByteValue(midiContent, j)
            j = nextPos
            local eventType = getValue(midiContent, j, j)
            if math.floor(eventType / 16) == 8 then         -- End of a note
                extraOffset = extraOffset + eventOffset
                j = j + 3
            elseif math.floor(eventType / 16) == 9 then     -- Beginning of a note
                trackNotesCount = trackNotesCount + 1
                notes[trackId][trackNotesCount] = (eventOffset+extraOffset)/midiDeltaTime
                -- print(trackNotesCount, (eventOffset+extraOffset)/midiDeltaTime)
                extraOffset = 0
                j = j + 3
            elseif math.floor(eventType / 16) == 10 then    -- Key after touch
                extraOffset = extraOffset + eventOffset
                j = j + 3
            elseif math.floor(eventType / 16) == 11 then    -- Controller
                extraOffset = extraOffset + eventOffset
                j = j + 3
            elseif math.floor(eventType / 16) == 12 then    -- Change the instrument
                extraOffset = extraOffset + eventOffset
                j = j + 2
            elseif math.floor(eventType / 16) == 13 then    -- Track after touch??
                extraOffset = extraOffset + eventOffset
                j = j + 2
            elseif math.floor(eventType / 16) == 14 then    -- Portamento
                extraOffset = extraOffset + eventOffset
                j = j + 3
            elseif eventType == 240 then        -- System code?
                extraOffset = extraOffset + eventOffset
                local eventLength, nextPos  = getByteValue(midiContent, j+1)
                j = nextPos + eventLength
            elseif eventType == 255 then        -- Information
                local informType                = getValue(midiContent, j+1, j+1)
                local informLength, nextPos = getByteValue(midiContent, j+2)
                if informType == 3 then     -- Track's name
                    trackName = string.sub(midiContent, nextPos, nextPos + informLength - 1)
                elseif informType == 81 then
                    midiTempo   = 60*1000000/getValue(midiContent, nextPos, nextPos+2)
                elseif informType == 88 then
                    beatNum = getValue(midiContent, nextPos  , nextPos  )
                    beatDen = getValue(midiContent, nextPos+1, nextPos+1)
                    beatClk = getValue(midiContent, nextPos+2, nextPos+2)
                    beatCnt = getValue(midiContent, nextPos+3, nextPos+3)
                end
                j = nextPos + informLength
            end
        end
        -- print(trackId,trackName, trackNotesCount)
        i = i + trackLength + 8
    end

    -- Succeeded
    midiFile:close()
    return true
end
function convertFrame(frame, frameRate)
    return frame/frameRate*midiTempo/60
end
function getNoteId(pos, trackId)
    local totalTime = 0
    for i=1,#notes[trackId]-1 do
        totalTime = totalTime + notes[trackId][i]
        if totalTime + notes[trackId][i+1] > pos then
            return i, totalTime
        end
    end
    return #notes[trackId], totalTime+notes[trackId][#notes[trackId]]
end

if videoFile then
    if midiPath then
        parseMidi(midiPath)
        local id, lastTime = getNoteId(convertFrame(obj.frame+2, obj.framerate), tonumber(midiTrack))
        obj.load("movie", videoFile[1], obj.time-lastTime/(midiTempo/60), alpha)
        if id % 2 == 0 then
            if flipH==1 then
                obj.effect("Reversal", "Flip horizontal", 1)
            end
            if flipV==1 then
                obj.effect("Reversal", "Flip vertical", 1)
            end
            if #effects~=0 then
                for i=1,#effects do
                    obj.effect(unpack(effects[i]))
                end
            end
        end
    end
end

目前已知的bug:

  1. 多音符会出现奇怪的效果

  2. 更换midi后无法及时更新画面

发表评论

电子邮件地址不会被公开。 必填项已用*标注