Convert ZIP version to git repo
commit
12ca1a43de
|
@ -0,0 +1,275 @@
|
|||
midi = {}
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
|
||||
----------------------------------------
|
||||
-- Registration function
|
||||
----------------------------------------
|
||||
|
||||
midi.registered_instruments = {}
|
||||
|
||||
function midi.register_instrument(program_number, def)
|
||||
midi.registered_instruments[program_number] = {
|
||||
description = def.description,
|
||||
get_sounds = def.get_sounds,
|
||||
}
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- Helper
|
||||
----------------------------------------
|
||||
|
||||
function midi.get_scale(note_number)
|
||||
local scales = {
|
||||
"C-1", "C#-1", "D-1", "D#-1", "E-1", "F-1", "F#-1", "G-1", "G#-1", "A-1", "A#-1", "B-1",
|
||||
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
|
||||
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
|
||||
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
|
||||
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
|
||||
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
|
||||
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
|
||||
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
|
||||
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
|
||||
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
|
||||
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9"
|
||||
}
|
||||
|
||||
local scale = scales[note_number + 1] -- Note number starts at 0, but Lua table index starts at 1.
|
||||
return scale or "None"
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- Loading function
|
||||
----------------------------------------
|
||||
|
||||
-- Merge tracks to single track
|
||||
local function merge_tracks(tracks)
|
||||
local track_merged, timings = {}, {}
|
||||
|
||||
for _, track in ipairs(tracks) do
|
||||
local time = 0 -- Current time
|
||||
local program = 1 -- Instrument number
|
||||
|
||||
for _, message in ipairs(track.messages) do
|
||||
time = time + message.time
|
||||
|
||||
-- Initialize notes
|
||||
if not track_merged[time] then
|
||||
track_merged[time] = {
|
||||
notes = {},
|
||||
tempo_change = nil -- For 'time_to_seconds' function
|
||||
}
|
||||
table.insert(timings, time)
|
||||
end
|
||||
|
||||
if (message.type == "on") or (message.type == "off") then -- Note on/off
|
||||
local note = {
|
||||
type = message.type,
|
||||
number = message.number,
|
||||
velocity = message.velocity or 80,
|
||||
program = program
|
||||
}
|
||||
table.insert(track_merged[time].notes, note)
|
||||
elseif (message.type == "meta") and message.tempo then -- Change tempo
|
||||
track_merged[time].tempo_change = message.tempo
|
||||
elseif (message.type == "program_change") then -- Change program(instrument)
|
||||
program = message.program
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(timings)
|
||||
|
||||
return track_merged, timings
|
||||
end
|
||||
|
||||
-- Convert track to table that has seconds as key
|
||||
local function time_to_seconds(track, timings, timebase)
|
||||
local track_converted = {}
|
||||
|
||||
local tempo = 0.5 -- Dummy
|
||||
local seconds = 0
|
||||
|
||||
for i, time in ipairs(timings) do
|
||||
local data = track[time]
|
||||
|
||||
-- Change tempo
|
||||
if data.tempo_change then
|
||||
tempo = (data.tempo_change / 1000000) -- Micro seconds to seconds
|
||||
end
|
||||
|
||||
-- Convert time to seconds
|
||||
local difftime = timings[i] - (timings[i - 1] or 0)
|
||||
seconds = seconds + (difftime / timebase * tempo)
|
||||
|
||||
for _, note in ipairs(data.notes) do
|
||||
if (note.type == "on") or (note.type == "off") then
|
||||
if not track_converted[seconds] then
|
||||
track_converted[seconds] = {}
|
||||
end
|
||||
table.insert(track_converted[seconds], note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return track_converted
|
||||
end
|
||||
|
||||
local parser = dofile(modpath .. "/lib/parser.lua")
|
||||
function midi.load_midi(midi_path)
|
||||
local midi_parsed = parser(midi_path)
|
||||
|
||||
local tracks = midi_parsed.tracks
|
||||
local track_merged, timings = merge_tracks(tracks)
|
||||
local track = time_to_seconds(track_merged, timings, midi_parsed.timebase)
|
||||
|
||||
return track
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- Playing function
|
||||
----------------------------------------
|
||||
|
||||
function midi.play_midi(name, track, delay)
|
||||
local function note_off(note)
|
||||
local fade = [[
|
||||
if handles[%d] then
|
||||
for i = 1, #handles[%d] do
|
||||
fade(handles[%d][i], %f, 0)
|
||||
end
|
||||
handles[%d] = nil
|
||||
end
|
||||
]]
|
||||
local step = ((note.velocity ~= 0 and note.velocity or 50) / -10)
|
||||
return fade:format(note.number, note.number, note.number, step, note.number)
|
||||
end
|
||||
|
||||
local function note_on(note)
|
||||
local create_handle_list = [[
|
||||
if not handles[%d] then
|
||||
handles[%d] = {}
|
||||
end
|
||||
]]
|
||||
|
||||
local play = [[
|
||||
handles[%d][#handles[%d] + 1] = play("%s", {
|
||||
gain = %f,
|
||||
pitch = %f,
|
||||
to_player = "%s"
|
||||
})
|
||||
]]
|
||||
|
||||
-- Get sounds
|
||||
local instrument = midi.registered_instruments[note.program]
|
||||
if not instrument then
|
||||
return ""
|
||||
end
|
||||
|
||||
local sounds = instrument.get_sounds(table.copy(note))
|
||||
if (#sounds == 0) then
|
||||
return ""
|
||||
end
|
||||
|
||||
local func = ""
|
||||
for _, sound in ipairs(sounds) do
|
||||
if (sound.gain > 0) and (sound.pitch > 0) then
|
||||
func = func .. " " .. play:format(note.number, note.number, sound.name, sound.gain, sound.pitch, name)
|
||||
end
|
||||
end
|
||||
|
||||
local is_func_empty = (func:gsub(" ", "") == "")
|
||||
if is_func_empty then
|
||||
return ""
|
||||
end
|
||||
|
||||
return create_handle_list:format(note.number, note.number) .. func
|
||||
end
|
||||
|
||||
local function note_release(note)
|
||||
local play = [[
|
||||
play("%s", {
|
||||
gain = %f,
|
||||
pitch = %f,
|
||||
to_player = "%s"
|
||||
})
|
||||
]]
|
||||
|
||||
-- Get sounds
|
||||
local instrument = midi.registered_instruments[note.program]
|
||||
if not instrument then
|
||||
return ""
|
||||
end
|
||||
|
||||
local sounds = instrument.get_sounds(table.copy(note))
|
||||
if (#sounds == 0) then
|
||||
return ""
|
||||
end
|
||||
|
||||
local func = ""
|
||||
for _, sound in ipairs(sounds) do
|
||||
if (sound.gain > 0) and (sound.pitch > 0) then
|
||||
func = func .. " " .. play:format(sound.name, sound.gain, sound.pitch, name)
|
||||
end
|
||||
end
|
||||
|
||||
return func
|
||||
end
|
||||
|
||||
local handles = {}
|
||||
for seconds, notes in pairs(track) do
|
||||
local function_string = ""
|
||||
|
||||
for _, note in ipairs(notes) do
|
||||
if (note.velocity == 0) then
|
||||
note.type = "off"
|
||||
end
|
||||
|
||||
if (note.type == "on") then
|
||||
function_string = function_string .. " " .. note_on(note)
|
||||
elseif (note.type == "off") then
|
||||
function_string = note_off(note) .. " " .. function_string
|
||||
function_string = function_string .. " " .. note_release(note)
|
||||
end
|
||||
end
|
||||
|
||||
local is_func_empty = (function_string:gsub(" ", "") == "")
|
||||
if not is_func_empty then
|
||||
local func = [[
|
||||
return function(handles, play, fade)
|
||||
%s
|
||||
end
|
||||
]]
|
||||
minetest.after((seconds + delay), loadstring(func:format(function_string))(), handles, minetest.sound_play, minetest.sound_fade)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- Chatcommand
|
||||
----------------------------------------
|
||||
|
||||
minetest.register_chatcommand("midi", {
|
||||
description = "Play midi",
|
||||
params = "<midiname> [delay]",
|
||||
func = function(name, param)
|
||||
local params = param:split(" ")
|
||||
|
||||
local midi_name = params[1]
|
||||
if not midi_name then
|
||||
return false, "midiname required"
|
||||
end
|
||||
|
||||
local flag, ret = pcall(function()
|
||||
local midi_path = (modpath .. "/midi/" .. midi_name)
|
||||
return midi.load_midi(midi_path)
|
||||
end)
|
||||
|
||||
if not flag then
|
||||
return false, ret
|
||||
end
|
||||
|
||||
local delay = tonumber(params[2]) or 1
|
||||
midi.play_midi(name, ret, delay)
|
||||
end
|
||||
})
|
|
@ -0,0 +1,434 @@
|
|||
--[[
|
||||
midiParser for Lua
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2016 Yutaka Obuchi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
The original version is located at:
|
||||
https://github.com/FMS-Cat/Lua_midiParser
|
||||
--]]
|
||||
|
||||
|
||||
local MThd = {77, 84, 104, 100}
|
||||
local MTrk = {77, 84, 114, 107}
|
||||
|
||||
local function Parser(filepath)
|
||||
local result = {}
|
||||
|
||||
------------------
|
||||
-- Prepare file --
|
||||
------------------
|
||||
|
||||
if not filepath then
|
||||
error("Path is nil")
|
||||
end
|
||||
|
||||
local midi do
|
||||
local file = io.open(filepath, "rb")
|
||||
if not file then
|
||||
error("Not found: " .. filepath)
|
||||
end
|
||||
|
||||
midi = file:read("*all")
|
||||
midi:gsub("\r\n", "\n")
|
||||
|
||||
file:close()
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- Some functions --
|
||||
--------------------
|
||||
|
||||
local function byteArray(start, length)
|
||||
local tbl = {}
|
||||
for i = 1, length do
|
||||
tbl[i] = midi:byte(i + start - 1)
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
local function bytesToNumber(start, length)
|
||||
local n = 0
|
||||
for i = 1, length do
|
||||
n = n + midi:byte(i + start - 1) * math.pow(256, length - i)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Variable-length quantity
|
||||
local function vlq(start)
|
||||
local n = 0
|
||||
local head = 0
|
||||
local byte = 0
|
||||
|
||||
repeat
|
||||
byte = midi:byte(start + head)
|
||||
n = n * 128 + (byte - math.floor(byte / 128) * 128)
|
||||
head = head + 1
|
||||
until math.floor(byte / 128) ~= 1
|
||||
|
||||
return n, head
|
||||
end
|
||||
|
||||
local function isSameTable(a, b)
|
||||
for i, v in ipairs(a) do
|
||||
if v ~= b[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, v in ipairs(b) do
|
||||
if v ~= a[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Check format --
|
||||
------------------
|
||||
|
||||
local head = 1
|
||||
|
||||
do -- Check "MThd"
|
||||
local MThd_LENGTH = 4
|
||||
|
||||
assert(isSameTable(byteArray(head, MThd_LENGTH), MThd),
|
||||
"Input file is not midi")
|
||||
|
||||
head = head + MThd_LENGTH
|
||||
end
|
||||
|
||||
do -- Header chunk length
|
||||
local HEADER_LEN_LENGTH = 4
|
||||
|
||||
local header_length = bytesToNumber(head, HEADER_LEN_LENGTH)
|
||||
result.header_length = header_length
|
||||
|
||||
head = head + HEADER_LEN_LENGTH
|
||||
end
|
||||
|
||||
do -- Check midi format
|
||||
local FORMAT_LENGTH = 2
|
||||
|
||||
local format = bytesToNumber(head, FORMAT_LENGTH)
|
||||
result.format = format
|
||||
assert((format == 0 or format == 1),
|
||||
"Not supported format " .. format .. " of midi")
|
||||
|
||||
head = head + FORMAT_LENGTH
|
||||
end
|
||||
|
||||
do -- Track count
|
||||
local TRACK_COUNT_LENGTH = 2
|
||||
|
||||
local track_count = bytesToNumber(head, TRACK_COUNT_LENGTH)
|
||||
result.track_count = track_count
|
||||
|
||||
head = head + TRACK_COUNT_LENGTH
|
||||
end
|
||||
|
||||
do -- Timebase
|
||||
local TIMEBASE_LENGTH = 2
|
||||
|
||||
local timebase = bytesToNumber(head, TIMEBASE_LENGTH)
|
||||
result.timebase = timebase
|
||||
|
||||
head = head + TIMEBASE_LENGTH
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Fight against midi --
|
||||
------------------------
|
||||
|
||||
result.tracks = {}
|
||||
|
||||
while (#midi > head) do
|
||||
local is_MTrk = (function() -- Check MTrk
|
||||
local MTrk_LENGTH = 4
|
||||
|
||||
local is_MTrk = isSameTable(byteArray(head, MTrk_LENGTH), MTrk)
|
||||
head = head + MTrk_LENGTH
|
||||
|
||||
return is_MTrk
|
||||
end)()
|
||||
|
||||
local chunk_length = (function() -- Chunk length
|
||||
local CHUNK_LEN_LENGTH = 4
|
||||
|
||||
local chunk_length = bytesToNumber(head, CHUNK_LEN_LENGTH)
|
||||
head = head + CHUNK_LEN_LENGTH
|
||||
|
||||
return chunk_length
|
||||
end)()
|
||||
|
||||
if not is_MTrk then
|
||||
-- Skip unknown chunk
|
||||
head = head + chunk_length
|
||||
else
|
||||
local track = {messages = {}}
|
||||
table.insert(result.tracks, track)
|
||||
|
||||
local status = 0
|
||||
|
||||
local chunk_start = head
|
||||
while (chunk_start + chunk_length) > head do
|
||||
|
||||
local deltaTime, deltaHead = vlq(head)
|
||||
head = head + deltaHead
|
||||
|
||||
local tempStatus = bytesToNumber(head, 1)
|
||||
if math.floor(tempStatus / 128) == 1 then -- event, running status
|
||||
head = head + 1
|
||||
status = tempStatus
|
||||
end
|
||||
|
||||
local event = math.floor(status / 16)
|
||||
local channel = status - event * 16
|
||||
|
||||
if event == 8 then -- Note off
|
||||
|
||||
local data = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert(track.messages, {
|
||||
type = "off",
|
||||
time = deltaTime,
|
||||
channel = channel,
|
||||
number = data[1],
|
||||
velocity = data[2]
|
||||
})
|
||||
|
||||
elseif event == 9 then -- Note on
|
||||
|
||||
local data = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert(track.messages, {
|
||||
type = "on",
|
||||
time = deltaTime,
|
||||
channel = channel,
|
||||
number = data[1],
|
||||
velocity = data[2]
|
||||
})
|
||||
|
||||
elseif event == 10 then -- Polyphonic keypressure
|
||||
|
||||
local data = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert(track.messages, {
|
||||
time = deltaTime
|
||||
})
|
||||
|
||||
elseif event == 11 then -- Control change
|
||||
|
||||
local data = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert(track.messages, {
|
||||
time = deltaTime
|
||||
})
|
||||
|
||||
elseif event == 12 then -- Program change
|
||||
|
||||
local data = byteArray(head, 1)
|
||||
head = head + 1
|
||||
|
||||
table.insert(track.messages, {
|
||||
type = "program_change",
|
||||
time = deltaTime,
|
||||
channel = channel,
|
||||
program = tonumber(data[1])
|
||||
})
|
||||
|
||||
elseif event == 13 then -- Channel pressure
|
||||
|
||||
local data = byteArray(head, 1)
|
||||
head = head + 1
|
||||
|
||||
table.insert(track.messages, {
|
||||
time = deltaTime
|
||||
})
|
||||
|
||||
elseif event == 14 then -- Pitch bend
|
||||
|
||||
local data = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert(track.messages, {
|
||||
time = deltaTime
|
||||
})
|
||||
|
||||
elseif status == 255 then -- Meta event
|
||||
|
||||
local metaType = bytesToNumber(head, 1)
|
||||
head = head + 1
|
||||
|
||||
local metaLength, metaHead = vlq(head)
|
||||
|
||||
--[[if metaType == 0 then -- sequence number
|
||||
elseif metaType == 1 then -- text
|
||||
elseif metaType == 2 then -- licence
|
||||
else]]if metaType == 3 then -- track name
|
||||
|
||||
head = head + metaHead
|
||||
track.name = midi:sub(head, head + metaLength - 1)
|
||||
head = head + metaLength
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Track Name",
|
||||
text = track.name
|
||||
})
|
||||
|
||||
elseif metaType == 4 then -- instrument name
|
||||
|
||||
head = head + metaHead
|
||||
track.instrument = midi:sub(head, head + metaLength - 1)
|
||||
head = head + metaLength
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Instrument Name",
|
||||
text = track.instrument
|
||||
} )
|
||||
|
||||
elseif metaType == 5 then -- lyric
|
||||
head = head + metaHead
|
||||
track.lyric = string.sub( midi, head, head + metaLength - 1 )
|
||||
head = head + metaLength
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Lyric",
|
||||
text = track.lyric
|
||||
} )
|
||||
|
||||
--elseif metaType == 6 then -- marker
|
||||
--elseif metaType == 7 then -- queue point
|
||||
elseif metaType == 8 then -- program name or sound name
|
||||
head = head + metaHead
|
||||
local v = string.sub( midi, head, head + metaLength - 1 )
|
||||
head = head + metaLength
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Meta8",
|
||||
text = v
|
||||
} )
|
||||
|
||||
elseif metaType == 9 then -- device name or spundfont name
|
||||
head = head + metaHead
|
||||
local v = string.sub( midi, head, head + metaLength - 1 )
|
||||
head = head + metaLength
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Meta9",
|
||||
text = v
|
||||
} )
|
||||
--elseif metaType == 32 then -- midi channel prefix
|
||||
--elseif metaType == 33 then -- select port
|
||||
elseif metaType == 47 then -- end of track
|
||||
head = head + 1
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "End of Track"
|
||||
} )
|
||||
|
||||
break
|
||||
|
||||
elseif metaType == 81 then -- tempo
|
||||
head = head + 1
|
||||
|
||||
local micros = bytesToNumber( head, 3 )
|
||||
head = head + 3
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Set Tempo",
|
||||
tempo = micros
|
||||
} )
|
||||
|
||||
--elseif metaType == 84 then -- SMPTE offset
|
||||
elseif metaType == 88 then -- time signature
|
||||
head = head + 1
|
||||
|
||||
local sig = byteArray( head, 4 )
|
||||
head = head + 4
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Time Signature",
|
||||
signature = sig
|
||||
} )
|
||||
|
||||
elseif metaType == 89 then -- key signature
|
||||
|
||||
head = head + 1
|
||||
local sig = byteArray(head, 2)
|
||||
head = head + 2
|
||||
|
||||
table.insert( track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Key Signature",
|
||||
signature = sig
|
||||
} )
|
||||
|
||||
--elseif metaType == 127 then -- sequencer specific event
|
||||
else -- comment
|
||||
|
||||
head = head + metaHead
|
||||
local text = midi:sub(head, head + metaLength - 1)
|
||||
head = head + metaLength
|
||||
|
||||
table.insert(track.messages, {
|
||||
time = deltaTime,
|
||||
type = "meta",
|
||||
meta = "Unknown Text: ",
|
||||
text = text
|
||||
})
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return Parser
|
|
@ -0,0 +1,23 @@
|
|||
License of source code
|
||||
----------------------
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2017 Rui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1 @@
|
|||
Put your midi files in this folder.
|
|
@ -0,0 +1 @@
|
|||
name = midi
|
|
@ -0,0 +1 @@
|
|||
midi
|
|
@ -0,0 +1,140 @@
|
|||
----------------------------------------
|
||||
-- Pitches
|
||||
----------------------------------------
|
||||
|
||||
local pitch_down, pitch_up = (0.5 ^ (1 / 12)), (2 ^ (1 / 12))
|
||||
local pitches = {
|
||||
Gs = {scale = "A", pitch = pitch_down},
|
||||
As = {scale = "A", pitch = pitch_up},
|
||||
D = {scale = "Ds", pitch = pitch_down},
|
||||
E = {scale = "Ds", pitch = pitch_up},
|
||||
F = {scale = "Fs", pitch = pitch_down},
|
||||
G = {scale = "Fs", pitch = pitch_up},
|
||||
B = {scale = "C", pitch = pitch_down},
|
||||
Cs = {scale = "C", pitch = pitch_up}
|
||||
}
|
||||
|
||||
----------------------------------------
|
||||
-- Getting sound functions
|
||||
----------------------------------------
|
||||
|
||||
local function select_velocity_number(velocity)
|
||||
local function range(v, lovel, hivel)
|
||||
if hivel then
|
||||
return (v >= lovel) and (v <= hivel)
|
||||
else
|
||||
return (v >= lovel)
|
||||
end
|
||||
end
|
||||
|
||||
return (range(velocity, 1, 26) and 1)
|
||||
or (range(velocity, 27, 34) and 2)
|
||||
or (range(velocity, 35, 36) and 3)
|
||||
or (range(velocity, 37, 43) and 4)
|
||||
or (range(velocity, 44, 46) and 5)
|
||||
or (range(velocity, 47, 50) and 6)
|
||||
or (range(velocity, 51, 56) and 7)
|
||||
or (range(velocity, 57, 64) and 8)
|
||||
or (range(velocity, 65, 72) and 9)
|
||||
or (range(velocity, 73, 80) and 10)
|
||||
or (range(velocity, 81, 88) and 11)
|
||||
or (range(velocity, 89, 96) and 12)
|
||||
or (range(velocity, 97, 104) and 13)
|
||||
or (range(velocity, 105, 112) and 14)
|
||||
or (range(velocity, 113, 120) and 15)
|
||||
or (range(velocity, 121) and 16)
|
||||
or 1 -- Else
|
||||
end
|
||||
|
||||
local function calc_gain(velocity, amp_veltrack)
|
||||
local gain = 20 * math.log(velocity, 10)
|
||||
return gain + (gain * (amp_veltrack / 100))
|
||||
end
|
||||
|
||||
local function get_noteon_sounds(sounds, note)
|
||||
-- Note
|
||||
do
|
||||
local scale_with_pitch = midi.get_scale(note.number):gsub("#", "s")
|
||||
local scale, scalenumber = scale_with_pitch:match("(.+)(%d)")
|
||||
local pitch = 1
|
||||
|
||||
local scaledef = pitches[scale]
|
||||
if scaledef then
|
||||
if (scale == "B") then
|
||||
scalenumber = scalenumber + 1
|
||||
end
|
||||
|
||||
scale_with_pitch = scaledef.scale .. scalenumber
|
||||
pitch = scaledef.pitch
|
||||
end
|
||||
|
||||
local amp_veltrack = 73
|
||||
|
||||
local sound = scale_with_pitch .. "v" .. select_velocity_number(note.velocity)
|
||||
local gain = calc_gain(note.velocity, amp_veltrack)
|
||||
|
||||
table.insert(sounds, {name = ("midi_instrument_salamander_" .. sound), gain = gain, pitch = pitch})
|
||||
end
|
||||
|
||||
-- HammerNoise
|
||||
if minetest.settings:get_bool("midi.salamander.hammernoise") then
|
||||
if (note.number >= 21) and (note.number <= 108) then
|
||||
local volume = -37
|
||||
local amp_veltrack = 82 * (100 / (volume + 150))
|
||||
|
||||
local sound = "rel" .. (note.number - 20)
|
||||
local gain = calc_gain(note.velocity, amp_veltrack) / 500
|
||||
|
||||
table.insert(sounds, {name = ("midi_instrument_salamander_" .. sound), gain = gain, pitch = 1})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_noteoff_sounds(sounds, note)
|
||||
-- Pedal
|
||||
if minetest.settings:get_bool("midi.salamander.pedal") then
|
||||
-- Pedal 1
|
||||
do
|
||||
local volume = -20
|
||||
local amp_veltrack = 100 / (volume + 150)
|
||||
|
||||
local sound = "pedalD" .. math.random(1, 2)
|
||||
local gain = calc_gain(note.velocity, amp_veltrack)
|
||||
table.insert(sounds, {name = ("midi_instrument_salamander_" .. sound), gain = gain, pitch = 1})
|
||||
end
|
||||
|
||||
-- Pedal 2
|
||||
do
|
||||
local volume = -19
|
||||
local amp_veltrack = 100 / (volume + 150)
|
||||
|
||||
local sound = "pedalU" .. math.random(1, 2)
|
||||
local gain = calc_gain(note.velocity, amp_veltrack)
|
||||
|
||||
table.insert(sounds, {name = ("midi_instrument_salamander_" .. sound), gain = gain, pitch = 1})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_sounds(note)
|
||||
local sounds = {}
|
||||
|
||||
if (note.type == "on") then
|
||||
get_noteon_sounds(sounds, note)
|
||||
elseif (note.type == "off") then
|
||||
get_noteoff_sounds(sounds, note)
|
||||
end
|
||||
|
||||
return sounds
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- Register
|
||||
----------------------------------------
|
||||
|
||||
for i = 0, 7 do -- 0 ~ 7: Piano
|
||||
midi.register_instrument(i, {
|
||||
description = "Salamander",
|
||||
get_sounds = get_sounds
|
||||
})
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
License of source code
|
||||
----------------------
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2017 Rui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
License of sounds
|
||||
-----------------
|
||||
|
||||
Attribution 4.0 International (CC BY 4.0)
|
||||
Copyright (C) 2015 Alexander Holm
|
||||
|
||||
For more details:
|
||||
http://creativecommons.org/licenses/by/4.0/
|
|
@ -0,0 +1 @@
|
|||
name = midi_instrument_salamander
|
|
@ -0,0 +1,5 @@
|
|||
# Enable HammerNoise Sound
|
||||
midi.salamander.hammernoise (HammerNoise) bool false
|
||||
|
||||
# Enable Pedal Sound
|
||||
midi.salamander.pedal (Pedal) bool false
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue