Interactive Midi
InteractiveMidi is a module that takes the buffer of a midi file and allows you to play music with them. It comes with a Parser, Player, Visualizer, and Note Player.
Preview
Test Place
Methods:
Player
Read Only
Player.IsPlayer: boolean
Player.IsPlaying: boolean
Player.TimeLength: boolean
Editable Values
Player.Loop: boolean
Player.TimeMultiplier: boolean
Functions
Player:GetSongLength() -- To get the TimeLength you do Player.TimeLength after doing this
Player:Load(MidiClass)
Player:FireEvent(EventName, ...)
Player:SetTempo(tempo: number)
Player:HeartbeatStep(MidiClass, heartBeatDeltaTime: number)
Player:SetTimePosition(timePosition: number)
Player:Stop()
Player:Pause()
Player:Play()
Parser
Read Only
IsParser: boolean
ParseType: boolean
Editable Values
Parser.Events: {}
Functions
Parser:Load(Midi)
Parser:Parse(ParseType: string)
Midi
Read Only
IsMidi: boolean
Raw: {}
Header: {}
Editable Values
None
Functions
Midi.new(...)
Midi:Load(Buffer)
Binder
Read Only
IsBinder: boolean
BindingOrder: {}
Editable Values
None
Functions
Binder:Bind(...)
Binder:Load(Buffer)
Notator
Read Only
None
Editable Values
Instruments: {}
Percussion: {}
Functions
Notator:AddInstruments(instruments: { { SoundId: string, Offset: number } })
Notator:IsPercussion(noteNumber)
Notator:CreateSound(
instrument: string,
settings: {
NoteNumber: number,
Velocity: number,
Offset: number, -- Optional
CheckForPercussion: boolean
}
)
Visualizer
Read Only
None
Editable Values
None
Functions
Visualizer:Basic(
settings: {
NoteNumber: number,
Length: number,
CenterCFrame: CFrame,
Size: number,
Z: number,
Sharp: number,
C: number
}
) : BlockData
Editable Values (BlockData)
NoteLetter: number
Block: Instance
Functions (BlockData)
BlockData:Stop()
BlockData:SmoothHide(tweenTime)
BlockData:SmoothVisibility(tweenTime)
BlockData:StopGrow()
BlockData:MoveUp(height: number, tweenTime: number, grows: boolean)
How to use:
Parse Midi
local InteractiveMidi = require(script.InteractiveMidi) -- Require the InteractiveMidi Module
local MidiUrl = "https://github.com/Wyvern-Dev/Wyvern/raw/main/betterviva.mid" -- URL to Midi File, this is mine to Viva la Vida
local MidiBuffer = game:GetService("HttpService"):GetAsync(MidiUrl)
local Midi, Parser = InteractiveMidi.new("Midi", "Parser")
Midi:Load(MidiBuffer) -- Load The Buffer of The Midi File
Parser:Load(Midi) -- Load the Midi (getting it ready to parse)
Parser:Parse("Summation") -- Parses the Midi, there is only "Summation" currently, so you're kinda forced to put it.
print(Parser.Instructions) -- Print the Instructions
Shortened Version
local InteractiveMidi = require(script.InteractiveMidi) -- Require the InteractiveMidi Module
local MidiUrl = "https://github.com/Wyvern-Dev/Wyvern/raw/main/betterviva.mid" -- URL to Midi File, this is mine to Viva la Vida
local MidiBuffer = game:GetService("HttpService"):GetAsync(MidiUrl)
local Midi, Parser, Binder = InteractiveMidi.new("Midi", "Parser", "Binder")
Binder:Bind(Midi, { Parser, "Parse", "Summation" })
Binder:Load(MidiBuffer)
print(Parser.Instructions) -- Print the Instructions
Midi Player
local InteractiveMidi = require(script.InteractiveMidi) -- Require the InteractiveMidi Module
local MidiUrl = "https://github.com/Wyvern-Dev/Wyvern/raw/main/betterviva.mid" -- URL to Midi File, this is mine to Viva la Vida
local MidiBuffer = game:GetService("HttpService"):GetAsync(MidiUrl)
local Midi, Parser, Player, Notator = InteractiveMidi.new("Midi", "Parser", "Player", "Notator")
Midi:Load(MidiBuffer) -- Load The Buffer of The Midi File
Parser:Load(Midi) -- Load the Midi (getting it ready to parse)
Parser:Parse("Summation") -- Parses the Midi, there is only "Summation" currently, so you're kinda forced to put it.
Player:Load(Parser) -- Load The Parser into the Player
Player.Events.NoteOn = function(Instruction) -- Listen for When a note is played
local IsPercussion = Instruction.Channel == 9 -- 9 is for Percussion notes
local Sound: Sound = Notator:CreateSound("Piano", {
NoteNumber = Instruction.NoteNumber,
Velocity = Instruction.Velocity,
CheckForPercussion = IsPercussion
})
Sound.Parent = workspace
Sound:Play()
game:GetService("Debris"):AddItem(Sound, 1)
end
Player:Play() -- Play the Midi
Shortened Version
local InteractiveMidi = require(script.InteractiveMidi) -- Require the InteractiveMidi Module
local MidiUrl = "https://github.com/Wyvern-Dev/Wyvern/raw/main/betterviva.mid" -- URL to Midi File, this is mine to Viva la Vida
local MidiBuffer = game:GetService("HttpService"):GetAsync(MidiUrl)
local Midi, Parser, Player, Notator, Binder = InteractiveMidi.new("Midi", "Parser", "Player", "Notator", "Binder")
Binder:Bind(Midi, { Parser, "Parse", "Summation" }, Player):Load(MidiBuffer)
Player.Events.NoteOn = function(Instruction) -- Listen for When a note is played
local IsPercussion = Instruction.Channel == 9
Instruction.CheckForPercussion = Instruction.Channel == 9
local Sound: Sound = Notator:CreateSound("Piano", Instruction)
Sound.Parent = workspace
Sound:Play()
game:GetService("Debris"):AddItem(Sound, 2)
end
Player:Play() -- Play the Midi
Visualizer
local InteractiveMidi = require(script.InteractiveMidi) -- Require the InteractiveMidi Module
local MidiUrl = "https://github.com/Wyvern-Dev/Wyvern/raw/main/betterviva.mid" -- URL to Midi File, this is mine to Viva la Vida
local MidiBuffer = game:GetService("HttpService"):GetAsync(MidiUrl)
local Midi, Parser, Player, Notator, Binder, Visualizer = InteractiveMidi.new("Midi", "Parser", "Player", "Notator", "Binder", "Visualizer")
Binder:Bind(Midi, { Parser, "Parse", "Summation" }, Player):Load(MidiBuffer)
Player.Events.NoteOn = function(Instruction) -- Listen for When a note is played
local IsPercussion = Instruction.Channel == 9
Instruction.CheckForPercussion = Instruction.Channel == 9
local Sound: Sound = Notator:CreateSound("Piano", Instruction)
Sound.Parent = workspace
Sound:Play()
game:GetService("Debris"):AddItem(Sound, 2)
Instruction.Length = 1
Instruction.CenterCFrame = CFrame.new(0, 10, 0)
Instruction.Size = 1
Instruction.Z = 0.1
Instruction.Sharp = 0.5
Instruction.C = 1
local BlockData = Visualizer:Basic(Instruction):SmoothVisibility(0.1):MoveUp(100, 10, false)
local Block = BlockData.Block
Block.Material = Enum.Material.Neon
Block.Color = Color3.fromRGB(255, 0, 0):Lerp(Color3.fromRGB(49, 193, 255), Instruction.NoteNumber/127)
Block.Parent = workspace
task.delay(9, BlockData.SmoothHide, BlockData, 0.5)
game:GetService("Debris"):AddItem(Block, 10)
end
Player:Play() -- Play the Midi
Midi
All Events
InstructionEventData
InteractiveMidi.References.InstructionEventData = {
SystemExclusive = { "Text" },
TuneRequest = {},
SongSelect = {},
SongPosition = { "LSB", "MSB" },
SequencerSpecific = { "Text" },
KeySignature = { "Signature", "RelativeKey" },
TimeSignature = { "Numerator", "Denominator", "ClockInMetronome", "Notated32ndNotesInQuarter" },
SMPTEOffset = { "Hours", "Minutes", "Seconds", "Frames", "FractionalFrames" },
SetTempo = { "Tempo" },
SetSequenceNumber = { "SequenceNumber" },
EndOfTrack = {},
ChannelPrefix = { "Channel" },
TextEvent = { "Text" },
CopyrightText = { "Text" },
TrackName = { "Text" },
InstrumentName = { "Text" },
Lyric = { "Text" },
Marker = { "Text" },
CuePoint = { "Text" },
NoteOn = { "Channel", "NoteNumber", "Velocity" },
NoteOff = { "Channel", "NoteNumber", "Velocity" },
AfterTouchPoly = { "NoteNumber", "Touch" },
ProgramChange = { "Channel", "PatchNumber" },
ControlChange = { "ControllerNumber", "ControlChange" },
AfterTouchChannel = { "Pressure" },
PitchWheel = { "PitchBendLSB", "PitchBendMSB" },
}
InstructionEventBytes
InteractiveMidi.References.InstructionEventBytes = {
-- Meta-Events --
["F7"] = { "SystemExclusive", { "LENGTH" } },
["F6"] = { "TuneRequest", {} },
["F3"] = { "SongSelect", {} },
["F2"] = { "SongPosition", { 2, 2 } },
["F0"] = { "SystemExclusive", { "LENGTH" } },
["7F"] = { "SequencerSpecific", { "LENGTH" } },
["59"] = { "KeySignature", { 2, 2 } },
["58"] = { "TimeSignature", { 2, 2, 2, 2 } },
["54"] = { "SMPTEOffset", { 2, 2, 2, 2, 2 } },
["51"] = { "SetTempo", { 6 } },
["00"] = { "SetSequenceNumber", { 2 } },
["2F"] = { "EndOfTrack", {} },
["20"] = { "ChannelPrefix", { 2 } },
["21"] = { "CopyrightText", { "LENGTH" } },
-- Text Events --
["01"] = { "TextEvent", { "LENGTH" } },
["02"] = { "CopyrightText", { "LENGTH" } },
["03"] = { "TrackName", { "LENGTH" } },
["04"] = { "InstrumentName", { "LENGTH" } },
["05"] = { "Lyric", { "LENGTH" } },
["06"] = { "Marker", { "LENGTH" } },
["07"] = { "CuePoint", { "LENGTH" } },
-- Reserved/Unassigned Text Events --
["08"] = { "TextEvent", { "LENGTH" } },
["09"] = { "TextEvent", { "LENGTH" } },
["0A"] = { "TextEvent", { "LENGTH" } },
["0B"] = { "TextEvent", { "LENGTH" } },
["0C"] = { "TextEvent", { "LENGTH" } },
["0D"] = { "TextEvent", { "LENGTH" } },
["0E"] = { "TextEvent", { "LENGTH" } },
["0F"] = { "TextEvent", { "LENGTH" } },
-- Midi-Events --
["9"] = { "NoteOn", { 2, 2 } },
["8"] = { "NoteOff", { 2, 2 } },
["A"] = { "AfterTouchPoly", { 2, 2 } },
["B"] = { "ControlChange", { 2, 2 } },
["C"] = { "ProgramChange", { 2 } },
["D"] = { "AfterTouchChannel", { 2 } },
["E"] = { "PitchWheel", { 2, 2 } }
}
Get InteractiveMidi Here (Latest)
InteractiveMidi Model
Or
Version of this Post
InteractiveMidi.rbxm (11.3 KB)
Demo Place:
DemoPlace.rbxl (119.8 KB)
There are many things that I haven’t even shown you could do with it, but the Demo places shows some cool things you can do. I might still update the model, probably adding a syncing feature. So keep a look out for that.
This is my first module and, I created this module because I have no idea how other midi parsers/players work on Roblox and it was very hard to edit and read code. So I made my own.
I couldn’t find a tutorial on how to read the raw midi files and execute it into instructions. It was really hard and took me around a week just the finally get here. Feel free to read the code, the main stuff is going on in the Midi Parser.
Should I make it so you can create a midi file?
Similar to this
- Yes
- No
0 voters