This commit is contained in:
lifestorm
2024-08-04 23:54:45 +03:00
parent 8064ba84d8
commit 6a58f406b1
7522 changed files with 4011896 additions and 15 deletions

View File

@@ -0,0 +1,129 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
surface.CreateFont( "MKeyboard_Title", {
size = ScrH() * 0.025, weight = 300, antialias = true, font = "Coolvetica"
} )
surface.CreateFont( "MKeyboard_Key", {
size = ScrH() * 0.02, weight = 300, antialias = true, font = "Coolvetica"
} )
surface.CreateFont( "MKeyboard_Sheet", {
size = ScrH() * 0.022, antialias = true, font = "Roboto"
} )
MKeyboard.colors = {
manual = Color( 245, 163, 108 ),
automated = Color( 196, 0, 226 ),
white = Color( 255, 255, 255, 255 ),
black = Color( 0, 0, 0, 255 ),
frame = Color( 0, 0, 0, 220 ),
panel = Color( 30, 30, 30, 255 )
}
MKeyboard.instruments = MKeyboard.instruments or {}
MKeyboard.layouts = MKeyboard.layouts or {}
MKeyboard.sheets = MKeyboard.sheets or {}
function MKeyboard:RegisterInstrument( name, path, noteMin, noteMax )
self.instruments[#self.instruments + 1] = {
name = name,
path = path,
noteMin = noteMin,
noteMax = noteMax
}
end
function MKeyboard:RegisterLayout( id, label, keys )
self.layouts[#self.layouts + 1] = {
id = id,
label = label,
keys = keys
}
end
function MKeyboard:RegisterSheet( title, layout, sequence )
self.sheets[#self.sheets + 1] = {
title = title,
layout = layout,
sequence = sequence
}
end
net.Receive( "mkeyboard.set_current_keyboard", function()
local ent = net.ReadEntity()
MKeyboard:Shutdown()
if IsValid( ent ) then
MKeyboard:Init( ent )
end
end )
-- Receiver for the button hooks that only run serverside on single-player
if game.SinglePlayer() then
net.Receive( "mkeyboard.key", function()
local button = net.ReadUInt( 8 )
local pressed = net.ReadBool()
if IsValid( MKeyboard.entity ) then
if pressed then
MKeyboard:OnButtonPress( button )
else
MKeyboard:OnButtonRelease( button )
end
end
end )
end
-- Play notes received from the network,
-- while keeping the original timings
local queue = {}
local SysTime = SysTime
local GetKeys = table.GetKeys
hook.Add( "Think", "MKeyboard.ProcessReproductionQueue", function()
local now = SysTime()
local timestamps = GetKeys( queue )
for _, t in ipairs( timestamps ) do
if now > t then
local n = queue[t]
if IsValid( n[1] ) then
n[1]:EmitNote( n[2], n[3], n[4], n[5] )
end
queue[t] = nil
end
end
end )
net.Receive( "mkeyboard.notes", function()
local ent = net.ReadEntity()
if not IsValid( ent ) or not ent.EmitNote then return end
local automated = net.ReadBool()
local noteCount = net.ReadUInt( 5 )
local note, velocity, instrument, timeOffset
local t = SysTime()
for i = 1, noteCount do
note = net.ReadUInt( 7 )
velocity = net.ReadUInt( 7 )
instrument = net.ReadUInt( 6 )
timeOffset = net.ReadFloat()
-- i * 0.01 to prevent overriding stuff already on the queue
queue[t + timeOffset + ( i * 0.01 )] = { ent, note, velocity, instrument, automated }
end
end )

View File

@@ -0,0 +1,813 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local shortcuts = {
[KEY_TAB] = function()
if IsValid( MKeyboard.entity ) then
RunConsoleCommand( "musical_keyboard_leave", MKeyboard.entity:EntIndex() )
end
end,
[KEY_SPACE] = function()
MKeyboard:ToggleMenu()
end,
[KEY_LEFT] = function()
MKeyboard:SwitchInstrument( -1 )
end,
[KEY_RIGHT] = function()
MKeyboard:SwitchInstrument( 1 )
end,
[KEY_UP] = function()
MKeyboard:SetTranspose( MKeyboard.settings.transpose + 1 )
end,
[KEY_DOWN] = function()
MKeyboard:SetTranspose( MKeyboard.settings.transpose - 1 )
end
}
local dontBlockBinds = {
["+attack"] = true,
["+attack2"] = true,
["+duck"] = true
}
local transmitQueue = {}
local queueTimer, queueStart
MKeyboard.layoutName = ""
MKeyboard.isMenuOpen = false
MKeyboard.lastNoteWasAutomated = false
function MKeyboard:Init( ent )
self.entity = ent
self.blockInputTimer = RealTime() + 0.3
self:LoadSettings()
self:OpenInterface()
self:SetInstrument()
self:SetTranspose()
self:SetLayoutByIndex()
self.piano.OnNoteOn = function( note, velocity, instrument, automated )
instrument = instrument or self.settings.instrument
local data = self.instruments[instrument] or self.instruments[1]
if note < data.noteMin or note > data.noteMax then return end
self.entity:EmitNote( note, velocity, instrument, automated )
self.lastNoteWasAutomated = automated
-- Remember when we started putting notes
-- on the queue, and when we should send them
local t = SysTime()
if not queueTimer then
queueTimer = t + 0.4
queueStart = t
end
-- Add notes to the queue unless the limit was reached
local noteCount = #transmitQueue
if noteCount < self.NET_MAX_NOTES then
transmitQueue[noteCount + 1] = {
note, velocity, instrument, t - queueStart
}
end
end
hook.Add( "Think", "MKeyboard.ProcessLocalKeyboard", function()
if not IsValid( self.entity ) then
self:Shutdown()
return
end
local t = SysTime()
-- If the queued notes are ready to be sent...
if queueTimer and t > queueTimer then
net.Start( "mkeyboard.notes", false )
net.WriteEntity( self.entity )
net.WriteBool( self.lastNoteWasAutomated )
net.WriteUInt( #transmitQueue, 5 )
for _, params in ipairs( transmitQueue ) do
net.WriteUInt( params[1], 7 ) -- note
net.WriteUInt( params[2], 7 ) -- velocity
net.WriteUInt( params[3], 6 ) -- instrument
net.WriteFloat( params[4] ) -- time offset
end
net.SendToServer()
table.Empty( transmitQueue )
queueTimer = nil
end
end )
hook.Add( "PlayerButtonDown", "MKeyboard.DetectButtonPress", function( ply, button )
if ply == LocalPlayer() and IsFirstTimePredicted() then
self:OnButtonPress( button )
end
end )
hook.Add( "PlayerButtonUp", "MKeyboard.DetectButtonRelease", function( ply, button )
if ply == LocalPlayer() and IsFirstTimePredicted() then
self:OnButtonRelease( button )
end
end )
hook.Add( "PlayerBindPress", "MKeyboard.BlockBinds", function( _, bind )
if not dontBlockBinds[bind] then return true end
end )
-- Custom Chat compatibility
hook.Add( "CustomChatBlockInput", "MKeyboard.PreventOpeningChat", function()
return true
end )
if not self.isMIDIAvailable then return end
timer.Create( "MKeyboard.CheckMIDI", 1, 0, function()
self:CheckMIDI()
end )
end
function MKeyboard:Shutdown()
self.entity = nil
transmitQueue = {}
queueTimer = nil
self:CloseInterface()
timer.Remove( "MKeyboard.CheckMIDI" )
if self.isMIDIAvailable then
self:CloseMIDI()
end
hook.Remove( "Think", "MKeyboard.ProcessLocalKeyboard" )
hook.Remove( "PlayerButtonDown", "MKeyboard.DetectButtonPress" )
hook.Remove( "PlayerButtonUp", "MKeyboard.DetectButtonRelease" )
hook.Remove( "PlayerBindPress", "MKeyboard.BlockBinds" )
hook.Remove( "CustomChatBlockInput", "MKeyboard.PreventOpeningChat" )
end
local settings = MKeyboard.settings
local colors = MKeyboard.colors
function MKeyboard:OnButtonPress( button )
if RealTime() > self.blockInputTimer then
self.lastNoteWasAutomated = false
self.piano:PressButton( button, settings.velocity, settings.instrument, colors.manual )
end
end
function MKeyboard:OnButtonRelease( button )
if shortcuts[button] then
shortcuts[button]()
return
end
self.piano:ReleaseButton( button )
end
function MKeyboard:SetInstrument( index )
index = index or settings.instrument
local instrument = self.instruments[index]
if instrument then
self.piano:SetNoteRange( instrument.noteMin, instrument.noteMax )
settings.instrument = index
end
end
function MKeyboard:SetTranspose( transpose )
transpose = transpose or settings.transpose
self.piano:ReleaseAllNotes()
self.piano.transpose = transpose
settings.transpose = transpose
end
function MKeyboard:SetLayoutByIndex( index )
index = index or settings.layout
if self.layouts[index] then
settings.layout = index
local layout = self.layouts[index]
self.piano:SetLayout( layout.keys )
self.layoutName = language.GetPhrase( layout.label )
self:UpdateSheetsList()
end
end
function MKeyboard:SwitchInstrument( to )
local newInstrument = settings.instrument + to
if newInstrument < 1 then
newInstrument = #self.instruments
elseif newInstrument > #self.instruments then
newInstrument = 1
end
self:SetInstrument( newInstrument )
local line = self.instrumentList:GetLine( newInstrument )
self.instrumentList:ClearSelection()
self.instrumentList:SelectItem( line )
self.instrumentList.VBar:AnimateTo( line:GetY() - self.instrumentList:GetTall() * 0.5, 0.25, 0, -1 )
self:SaveSettings()
end
function MKeyboard:CloseInterface()
self.isMenuOpen = false
if IsValid( self.frame ) then
self.frame:Close()
end
if IsValid( self.frameDevices ) then
self.frameDevices:Close()
end
if IsValid( self.frameChannels ) then
self.frameChannels:Close()
end
end
function MKeyboard:ToggleMenu()
self.isMenuOpen = not self.isMenuOpen
-- I might have found a bug here. Basically, cant get the panel position
-- after calling LerpPositions without playing the animation first
-- local x = self.frame:GetX()
local x = ( ScrW() - ScrW() * 0.7 ) * 0.5
local tall = self.frame:GetTall()
if self.isMenuOpen then
self.frame:SetPos( x, ScrH() - tall )
self.frame:MakePopup()
else
self.frame:SetPos( x, ScrH() - tall * 0.49 )
self.frame:SetMouseInputEnabled( false )
self.frame:SetKeyboardInputEnabled( false )
end
end
local function CreatePanel( title, parent, dock, wide, help )
local panel = vgui.Create( "DPanel", parent )
panel:DockPadding( 8, 8, 8, 8 )
panel:DockMargin( 4, 0, 4, 0 )
panel:Dock( dock )
panel:SetBackgroundColor( colors.panel )
if wide then
panel:SetWide( wide )
end
local panelHeader = vgui.Create( "DPanel", panel )
panelHeader:Dock( TOP )
panelHeader:SetPaintBackground( false )
local labelTitle = vgui.Create( "DLabel", panelHeader )
labelTitle:SetFont( "MKeyboard_Key" )
labelTitle:SetText( language.GetPhrase( title ) )
labelTitle:SizeToContents()
labelTitle:Dock( LEFT )
labelTitle:SetTextColor( colors.white )
if help then
local labelHelp = vgui.Create( "DLabel", panelHeader )
labelHelp:SetFont( "MKeyboard_Key" )
labelHelp:SetText( language.GetPhrase( help ) )
labelHelp:SizeToContents()
labelHelp:Dock( RIGHT )
labelHelp:SetTextColor( colors.white )
end
return panel
end
local SetColor = surface.SetDrawColor
local DrawRect = surface.DrawRect
local DrawText = draw.SimpleText
local Ceil = math.ceil
local function DrawSheet( index, x, y )
local data = MKeyboard.sheets[index]
if not data then return end
local borderSize = Ceil( ScrW() * 0.002 )
local titleBarSize = ScrH() * 0.028
surface.SetFont( "MKeyboard_Sheet" )
local sheetW, sheetH = surface.GetTextSize( data.sequence )
local w, h = math.max( ScrW() * 0.3, sheetW + borderSize * 2 ), sheetH + titleBarSize * 0.5
local oldClipping = DisableClipping( true )
x = x - w * 0.5
y = y - h
SetColor( 0, 0, 0, 254 )
DrawRect( x, y, w, h )
draw.DrawText( data.sequence, "MKeyboard_Sheet", x + w * 0.5, y + titleBarSize, nil, TEXT_ALIGN_CENTER )
DrawText( data.title, "MKeyboard_Title", x + w * 0.5, y + borderSize, colors.white, TEXT_ALIGN_CENTER )
DisableClipping( oldClipping )
end
function MKeyboard:OpenInterface()
if not self.selectedMIDIPort then
self:ShowDevicesDialog()
end
local wide = ScrW() * 0.7
local tall = ScrH() * 0.5
self.frame = vgui.Create( "DFrame" )
self.frame:SetPos( ( ScrW() - wide ) * 0.5, ScrH() - tall * 0.5 )
self.frame:SetSize( wide, tall )
self.frame:SetTitle( "" )
self.frame:SetDeleteOnClose( true )
self.frame:SetDraggable( false )
self.frame:SetSizable( false )
self.frame:ShowCloseButton( false )
self.frame:LerpPositions( 1, true )
-- Passthrough button events while this panel is focused
self.frame.OnKeyCodePressed = function( _, key )
self:OnButtonPress( key )
end
self.frame.OnKeyCodeReleased = function( _, key )
self:OnButtonRelease( key )
end
local helpMessage = {
[false] = language.GetPhrase( "musicalk.help.open" ),
[true] = language.GetPhrase( "musicalk.help.close" )
}
self.frame.Paint = function( _, w, h )
draw.RoundedBoxEx( 8, 0, h * 0.4, w, h * 0.6, colors.frame, true, true, false, false )
if self.midiPortName then
DrawText( self.midiPortName, "MKeyboard_Title", w - ( w * 0.01 ), h * 0.43,
colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP )
DrawText( helpMessage[self.isMenuOpen], "MKeyboard_Title", w * 0.01, h * 0.43,
colors.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP )
else
DrawText( helpMessage[self.isMenuOpen], "MKeyboard_Title", w * 0.5, h * 0.43,
colors.white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP )
end
if settings.sheet > 0 then
DrawSheet( settings.sheet, w * 0.5, 0 )
end
local pianoW = self.piano.w
local pianoH = h * 0.3
local x = ( w * 0.5 ) - ( pianoW * 0.5 )
local y = 6
draw.RoundedBox( 4, x - 6, y - 6, pianoW + 12, pianoH + 12, colors.panel )
-- Draw layout, instrument name & transpose
local transpose = settings.transpose
local offset = 0
DrawText( self.layoutName, "MKeyboard_Key", x, y, colors.white )
if transpose ~= 0 then
offset = DrawText(
transpose > 0 and "+" .. transpose or transpose,
"MKeyboard_Key",
x + pianoW, y, colors.manual,
TEXT_ALIGN_RIGHT
)
offset = offset + 6
end
local data = self.instruments[settings.instrument]
local _, th = DrawText( data.name, "MKeyboard_Key", x + pianoW - offset, y, colors.white, TEXT_ALIGN_RIGHT )
th = th + 4
self.piano:Draw( x, y + th, pianoH - th )
end
local mainPanel = vgui.Create( "DPanel", self.frame )
mainPanel:SetPos( wide * 0.01, tall * 0.52 )
mainPanel:SetSize( wide * 0.98, tall * 0.45 )
mainPanel:DockPadding( 0, 0, 0, 0 )
mainPanel:SetPaintBackground( false )
---------- Settings panel ----------
local panelSettings = CreatePanel( "musicalk.settings", mainPanel, LEFT, wide * 0.3 )
local settingsScroll = vgui.Create( "DScrollPanel", panelSettings )
settingsScroll:Dock( FILL )
settingsScroll:DockMargin( 0, 8, 0, 0 )
local function AddRow( text, propertyClass )
local parent = vgui.Create( "DPanel", settingsScroll )
parent:SetWide( wide * 0.3 )
parent:SetTall( 28 )
parent:DockPadding( 4, 4, 4, 4 )
parent:DockMargin( 0, 0, 0, 2 )
parent:Dock( TOP )
parent:SetBackgroundColor( colors.white )
local label = vgui.Create( "DLabel", parent )
label:SetText( language.GetPhrase( text ) )
label:SetTextColor( colors.black )
label:SetWide( parent:GetWide() )
label:Dock( FILL )
if not propertyClass then
label:SetTextColor( colors.white )
label:SetContentAlignment( 5 )
parent:SetBackgroundColor( colors.black )
parent:SetTall( 28 )
return
end
local property = vgui.Create( propertyClass, parent )
if propertyClass == "DNumSlider" then
property.TextArea:SetWide( 30 )
function property:PerformLayout()
self.Label:SetWide( 0 )
end
end
property:Dock( RIGHT )
property:SetWide( 150 )
return property
end
AddRow( "musicalk.vkeys" )
-- Layouts list
local rowLayouts = AddRow( "musicalk.layout", "DComboBox" )
for i, v in ipairs( self.layouts ) do
rowLayouts:AddChoice( language.GetPhrase( v.label ), nil, i == settings.layout )
end
rowLayouts.OnSelect = function( _, index )
settings.sheet = 0
self:SetLayoutByIndex( index )
self:SaveSettings()
end
-- Should draw labels?
local rowDrawLabels = AddRow( "musicalk.vkeys.labels", "DImageButton" )
rowDrawLabels:SetImage( settings.drawKeyLabels and "icon16/accept.png" or "icon16/cancel.png" )
rowDrawLabels:SetStretchToFit( false )
rowDrawLabels:SetPaintBackground( true )
rowDrawLabels.DoClick = function()
settings.drawKeyLabels = not settings.drawKeyLabels
self:SaveSettings()
rowDrawLabels:SetIcon( settings.drawKeyLabels and "icon16/accept.png" or "icon16/cancel.png" )
end
-- Velocity
local rowVelocity = AddRow( "musicalk.vkeys.velocity", "DNumSlider" )
rowVelocity:SetMin( 0 )
rowVelocity:SetMax( 127 )
rowVelocity:SetDecimals( 0 )
rowVelocity:SetValue( settings.velocity )
rowVelocity.Label:SetTextColor( colors.black )
rowVelocity.OnValueChanged = function( _, value )
settings.velocity = Ceil( value )
end
-- Transpose
local rowTranspose = AddRow( "musicalk.vkeys.transpose", "DNumSlider" )
rowTranspose:SetMin( -24 )
rowTranspose:SetMax( 42 )
rowTranspose:SetDecimals( 0 )
rowTranspose:SetValue( settings.transpose )
rowTranspose.Label:SetTextColor( colors.black )
rowTranspose.Label:SetWide( 30 )
rowTranspose.Slider:SetWide( 300 )
self.rowTranspose = rowTranspose
rowTranspose.OnValueChanged = function( _, value )
if value < 0 then
value = Ceil( value )
else
value = math.floor( value )
end
self:SetTranspose( value )
self:SaveSettings()
end
AddRow( "MIDI" )
-- MIDI devices
local rowDevices = AddRow( "musicalk.midi.device", "DButton" )
rowDevices:SetText( language.GetPhrase( "musicalk.midi.device.choose" ) )
if self.isMIDIAvailable then
if table.Count( midi.GetPorts() ) == 0 then
rowDevices:SetText( language.GetPhrase( "musicalk.midi.nodevices" ) )
rowDevices:SetEnabled( false )
end
rowDevices.DoClick = function()
self:ShowDevicesDialog()
end
local rowChannels = AddRow( "musicalk.midi.channels", "DButton" )
rowChannels:SetText( language.GetPhrase( "musicalk.midi.channels.setup" ) )
rowChannels.DoClick = function()
self:ShowChannelsDialog()
end
local rowMIDITranspose = AddRow( "musicalk.vkeys.transpose", "DNumSlider" )
rowMIDITranspose:SetMin( -48 )
rowMIDITranspose:SetMax( 48 )
rowMIDITranspose:SetDecimals( 0 )
rowMIDITranspose:SetValue( settings.midiTranspose )
rowMIDITranspose.Label:SetTextColor( colors.black )
rowMIDITranspose.Label:SetWide( 30 )
rowMIDITranspose.Slider:SetWide( 300 )
rowMIDITranspose.OnValueChanged = function( _, value )
if value < 0 then
settings.midiTranspose = Ceil( value )
else
settings.midiTranspose = math.floor( value )
end
self:SaveSettings()
self.piano:ReleaseAllNotes()
end
else
rowDevices:SetText( language.GetPhrase( "musicalk.midi.nomodule" ) )
rowDevices:SetEnabled( false )
local rowInstallHelp = AddRow( "musicalk.midi.guide", "DButton" )
rowInstallHelp:SetText( language.GetPhrase( "musicalk.midi.guide" ) )
rowInstallHelp.DoClick = function()
gui.OpenURL( self.URL_MIDI_GUIDE )
self:ToggleMenu()
end
end
---------- Instruments panel ----------
local panelInstruments = CreatePanel( "musicalk.instruments", mainPanel, FILL, nil, "musicalk.instruments.help" )
self.instrumentList = vgui.Create( "DListView", panelInstruments )
self.instrumentList:Dock( FILL )
self.instrumentList:DockMargin( 0, 8, 0, 0 )
self.instrumentList:AddColumn( language.GetPhrase( "musicalk.instruments" ) )
self.instrumentList:SetMultiSelect( false )
self.instrumentList:SetHideHeaders( true )
self.instrumentList:SetSortable( false )
for i, v in ipairs( self.instruments ) do
self.instrumentList:AddLine( i .. " - " .. v.name )
end
self.instrumentList:SelectItem( self.instrumentList:GetLine( settings.instrument ) )
self.instrumentList.OnRowSelected = function( _, index )
self:SetInstrument( index )
self:SaveSettings()
end
---------- Layouts Panel ----------
local panelSheets = CreatePanel( "musicalk.sheets", mainPanel, RIGHT, wide * 0.3 )
self.sheetList = vgui.Create( "DListView", panelSheets )
self.sheetList:Dock( FILL )
self.sheetList:DockMargin( 0, 8, 0, 0 )
self.sheetList:AddColumn( language.GetPhrase( "musicalk.sheets" ) )
self.sheetList:SetMultiSelect( false )
self.sheetList:SetHideHeaders( true )
self.sheetList:SetSortable( false )
self:UpdateSheetsList()
self.sheetList.OnRowSelected = function( _, _, line )
settings.sheet = line._sheetIndex
self:SaveSettings()
end
end
function MKeyboard:UpdateSheetsList()
local layoutId = self.layouts[settings.layout].id
self.sheetList:Clear()
local selectedItem = self.sheetList:AddLine( language.GetPhrase( "musicalk.sheets.hidden" ) )
selectedItem._sheetIndex = 0
for i, v in ipairs( self.sheets ) do
if v.layout == layoutId then
local line = self.sheetList:AddLine( v.title )
line._sheetIndex = i
if i == settings.sheet then
selectedItem = line
end
end
end
self.sheetList:SelectItem( selectedItem )
end
function MKeyboard:ShowDevicesDialog()
if IsValid( self.frameDevices ) then
self.frameDevices:Close()
end
if not self.isMIDIAvailable then return end
local midiPorts = midi.GetPorts()
if table.Count( midiPorts ) == 0 then return end
if midi.IsOpened() then
self:CloseMIDI()
end
self.selectedMIDIPort = nil
self.frameDevices = vgui.Create( "DFrame" )
self.frameDevices:SetSize( 300, 130 )
self.frameDevices:SetTitle( language.GetPhrase( "musicalk.midi.device.choose" ) )
self.frameDevices:SetVisible( true )
self.frameDevices:SetDraggable( true )
self.frameDevices:ShowCloseButton( true )
self.frameDevices:SetDeleteOnClose( true )
self.frameDevices:Center()
self.frameDevices:MakePopup()
local startTime = SysTime()
local oldPaint = self.frameDevices.Paint
self.frameDevices.Paint = function( s, w, h )
Derma_DrawBackgroundBlur( s, startTime )
oldPaint( s, w, h )
end
local labelHelp = vgui.Create( "DLabel", self.frameDevices )
labelHelp:SetPos( 10, 40 )
labelHelp:SetSize( 280, 40 )
labelHelp:SetText( string.format(
language.GetPhrase( "musicalk.midi.found" ),
tostring( table.Count( midiPorts ) )
) )
local comboDevices = vgui.Create( "DComboBox", self.frameDevices )
comboDevices:SetPos( 10, 90 )
comboDevices:SetSize( 280, 20 )
comboDevices:SetValue( language.GetPhrase( "musicalk.midi.select" ) )
for k, v in pairs( midiPorts ) do
comboDevices:AddChoice( "[" .. k .. "] " .. v )
end
comboDevices.OnSelect = function( _, index )
self.selectedMIDIPort = index - 1
self.frameDevices:Close()
end
end
function MKeyboard:ShowChannelsDialog()
if IsValid( self.frameChannels ) then
self.frameChannels:Close()
return
end
local tall = math.min( 610, ScrH() * 0.6 )
self.frameChannels = vgui.Create( "DFrame" )
self.frameChannels:SetSize( 400, tall )
self.frameChannels:SetTitle( language.GetPhrase( "musicalk.channels" ) )
self.frameChannels:SetVisible( true )
self.frameChannels:SetSizable( true )
self.frameChannels:SetDraggable( true )
self.frameChannels:ShowCloseButton( true )
self.frameChannels:SetDeleteOnClose( true )
self.frameChannels:SetPos( ScrW() - self.frameChannels:GetWide(), 0 )
self.frameChannels:MakePopup()
-- Passthrough button events while this panel is focused
self.frameChannels.OnKeyCodePressed = function( _, key )
self:OnButtonPress( key )
end
self.frameChannels.OnKeyCodeReleased = function( _, key )
self:OnButtonRelease( key )
end
local scrollChannels = vgui.Create( "DScrollPanel", self.frameChannels )
scrollChannels:Dock( FILL )
local function PaintChannel( s, w, h )
SetColor( 0, 0, 0, 255 )
DrawRect( 0, 0, w, h )
SetColor( 50, 50, 50, 255 )
DrawRect( 4, 4, 8, h - 8 )
self.channelState[s.channel] = Lerp( FrameTime() * 8, self.channelState[s.channel], 0 )
SetColor( colors.automated.r, colors.automated.g, colors.automated.b, 255 * self.channelState[s.channel] )
DrawRect( 4, 4, 8, h - 8 )
end
local function OnSelectInstrument( s, _, _, idx )
if idx == -1 then
settings.channelInstruments[s.channel] = nil
else
settings.channelInstruments[s.channel] = idx
end
self:SaveSettings()
end
for i = 0, 15 do
self.channelState[i] = 0
local panelChannel = vgui.Create( "DPanel", scrollChannels )
panelChannel:SetSize( wide, 32 )
panelChannel:Dock( TOP )
panelChannel:DockMargin( 0, 0, 0, 4 )
panelChannel.channel = i
panelChannel.Paint = PaintChannel
local labelIndex = vgui.Create( "DLabel", panelChannel )
labelIndex:SetFont( "Trebuchet24" )
labelIndex:SetTextColor( colors.white )
labelIndex:SetText( "#" .. ( i + 1 ) )
labelIndex:SetWide( 50 )
labelIndex:Dock( LEFT )
labelIndex:DockMargin( 20, 0, 0, 0 )
local combo = vgui.Create( "DComboBox", panelChannel )
combo:SetSortItems( false )
combo:AddChoice( language.GetPhrase( "musicalk.channels.usecurrent" ), -1, true )
combo:AddChoice( language.GetPhrase( "musicalk.channels.mute" ), 0 )
combo:AddSpacer()
combo:Dock( FILL )
local myInstrument = settings.channelInstruments[i] or -1
for idx, v in ipairs( self.instruments ) do
combo:AddChoice( v.name, idx, idx == myInstrument )
end
combo.channel = i
combo.OnSelect = OnSelectInstrument
end
end

View File

@@ -0,0 +1,97 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
if util.IsBinaryModuleInstalled( "midi" ) then
require( "midi" )
end
if not midi then return end
MKeyboard.isMIDIAvailable = true
MKeyboard.channelState = {}
function MKeyboard:SetMIDIPortName( name )
self.midiPortName = nil
if not name then return end
if string.len( name ) > 28 then
name = string.sub( name, 1, 25 ) .. "..."
end
self.midiPortName = string.format( language.GetPhrase( "musicalk.midi.connected" ), name )
end
function MKeyboard:OpenMIDI( port )
if midi.IsOpened() then
self:CloseMIDI()
end
local portName = midi.GetPorts()[port]
if not portName then
print( "Could not find MIDI port: " .. port )
return
end
print( "Opening MIDI port: " .. portName )
local success, err = pcall( midi.Open, port )
if success then
self:SetMIDIPortName( portName )
else
print( "Failed to open MIDI port: " .. err )
end
end
function MKeyboard:CloseMIDI()
self:SetMIDIPortName()
if midi and midi.IsOpened() then
print( "Closing MIDI port." )
midi.Close()
end
end
function MKeyboard:CheckMIDI()
if midi.IsOpened() then
if self.selectedMIDIPort and not midi.GetPorts()[self.selectedMIDIPort] then
print( "MIDI port no longer exists." )
self.selectedMIDIPort = nil
self:CloseMIDI()
end
elseif self.selectedMIDIPort then
self:OpenMIDI( self.selectedMIDIPort )
end
end
function MKeyboard:OnMIDIEvent( code, p1, p2 )
local cmd = midi.GetCommandName( code )
local transpose = self.settings.midiTranspose
if cmd == "NOTE_ON" and p2 > 0 then
local channel = midi.GetCommandChannel( code )
local instrument = self.settings.channelInstruments[channel]
self.lastNoteWasAutomated = true
self.piano:PressNote( p1 + transpose, p2, instrument, self.colors.automated, true )
self.channelState[channel] = 1
elseif cmd == "NOTE_OFF" then
self.piano:ReleaseNote( p1 + transpose )
end
end
hook.Add( "MIDI", "MKeyboard.CaptureMIDIEvents", function( _, code, p1, p2 )
if code and IsValid( MKeyboard.entity ) then
MKeyboard:OnMIDIEvent( code, p1, p2 )
end
end )

View File

@@ -0,0 +1,216 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- Piano renderer
local Piano = {
keys = {},
whiteCount = 0,
transpose = 0,
noteMin = 0,
noteMax = 128,
w = 16,
keyW = 22
}
MKeyboard.piano = Piano
local keyNameOverride = {
[KEY_SEMICOLON] = ";"
}
local function GetButtonName( button )
return language.GetPhrase( keyNameOverride[button] or input.GetKeyName( button ) or "NONE" )
end
function Piano:SetLayout( layout )
self.keys = {}
self.whiteCount = 0
for _, key in ipairs( layout ) do
self.keys[#self.keys + 1] = {
button = key[1],
altButton = key[5],
label = GetButtonName( key[1] ),
note = key[2],
isBlack = key[3],
needsShift = key[4],
isPressed = false
}
if not key[3] then
self.whiteCount = self.whiteCount + 1
end
end
self.w = self.keyW * self.whiteCount
end
--- Set the note limits. Keys with notes
--- outside this range will be grayed out.
function Piano:SetNoteRange( min, max )
self.noteMin, self.noteMax = min, max
end
--- Find the normal and shifted keys linked to this button.
function Piano:FindKeys( button )
local normal, shifted
for _, key in ipairs( self.keys ) do
if key.button == button or key.altButton == button then
if key.needsShift then
shifted = key
else
normal = key
end
end
end
return normal, shifted
end
--- Find the key linked to this note.
function Piano:FindKeyByNote( note )
for _, key in ipairs( self.keys ) do
if key.note == note then
return key
end
end
end
--- Press the key linked to this note.
function Piano:PressNote( note, velocity, instrument, color, automated )
local key = self:FindKeyByNote( note )
if key then
key.color = color
key.isPressed = true
end
self.OnNoteOn( note, velocity, instrument, automated )
end
--- Release the key linked to this note.
function Piano:ReleaseNote( note )
local key = self:FindKeyByNote( note )
if key then
key.color = nil
key.isPressed = false
end
end
--- Releases all notes.
function Piano:ReleaseAllNotes()
for _, key in ipairs( self.keys ) do
key.color = nil
key.isPressed = false
end
end
--- Press the key linked to this button.
function Piano:PressButton( button, velocity, instrument, color )
velocity = velocity or 127
local normal, shifted = self:FindKeys( button )
if input.IsShiftDown() then
if shifted then
shifted.color = color
shifted.isPressed = true
self.OnNoteOn( shifted.note + self.transpose, velocity, instrument )
end
else
if normal then
normal.color = color
normal.isPressed = true
self.OnNoteOn( normal.note + self.transpose, velocity, instrument )
end
end
end
--- Release the key linked to this button.
function Piano:ReleaseButton( button )
-- Always release both normal and shifted keys, to handle a key that was
-- pressed with shift previously but does not have shift pressed now.
local normal, shifted = self:FindKeys( button )
if normal and normal.isPressed then
normal.color = nil
normal.isPressed = false
end
if shifted and shifted.isPressed then
shifted.color = nil
shifted.isPressed = false
end
end
local SetColor = surface.SetDrawColor
local DrawRect = surface.DrawRect
local DrawRoundedBox = draw.RoundedBoxEx
local DrawText = draw.SimpleText
local COLOR_WHITE = Color( 255, 255, 255 )
local COLOR_BLACK = Color( 0, 0, 0 )
local ALIGN_CENTER = TEXT_ALIGN_CENTER
local ALIGN_BOTTOM = TEXT_ALIGN_BOTTOM
local settings = MKeyboard.settings
function Piano:Draw( x, y, h )
local min, max = self.noteMin, self.noteMax
local transpose = self.transpose
local labels = settings.drawKeyLabels
local w = self.keyW
local labelY = y + h - 4
local blackLabelY = y + ( h * 0.6 ) - 4
local blackW = w * 0.7
local note, deferKey
for _, key in ipairs( self.keys ) do
note = key.note + transpose
if note < min or note > max then
-- empty space
if not key.isBlack then
x = x + w
end
elseif key.isBlack then
-- draw it on top of the next one
deferKey = key
else
SetColor( key.color or COLOR_WHITE )
DrawRect( x, y, w - 1, h )
if labels then
DrawText( key.label, "MKeyboard_Key", x + w * 0.5, labelY, COLOR_BLACK, ALIGN_CENTER, ALIGN_BOTTOM )
end
if deferKey then
DrawRoundedBox( 4, x - blackW * 0.5, y, blackW, h * 0.6, deferKey.color or COLOR_BLACK, false, false, true, true )
if labels then
DrawText( deferKey.label, "MKeyboard_Key", x, blackLabelY, COLOR_WHITE, ALIGN_CENTER, ALIGN_BOTTOM )
end
deferKey = nil
end
x = x + w
end
end
end

View File

@@ -0,0 +1,94 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
MKeyboard.settings = MKeyboard.settings or {
layout = 1,
instrument = 1,
sheet = 0,
velocity = 127,
transpose = 0,
midiTranspose = 0,
drawKeyLabels = true,
channelInstruments = {}
}
local function ValidateInteger( n, min, max )
return math.Round( math.Clamp( tonumber( n ), min, max ) )
end
function MKeyboard:LoadSettings()
local rawData = file.Read( self.SETTINGS_FILE, "DATA" )
if not rawData then return end
local data = util.JSONToTable( rawData ) or {}
local instrumentCount = #self.instruments
local settings = self.settings
-- last layout that was used on the keyboard
if data.layout then
settings.layout = ValidateInteger( data.layout, 1, #self.layouts )
end
-- last instrument that was used on the keyboard
if data.instrument then
settings.instrument = ValidateInteger( data.instrument, 1, instrumentCount )
end
-- last selected sheet
if data.sheet then
settings.sheet = ValidateInteger( data.sheet, 0, #self.sheets )
end
-- last used velocity
if data.velocity then
settings.velocity = ValidateInteger( data.velocity, 1, 127 )
end
-- last used transpose
if data.transpose then
settings.transpose = ValidateInteger( data.transpose, -48, 48 )
end
-- last transpose that was used with midi
if data.midiTranspose then
settings.midiTranspose = ValidateInteger( data.midiTranspose, -48, 48 )
end
-- draw labels for keys
settings.drawKeyLabels = Either( isbool( data.drawKeyLabels ), tobool( data.drawKeyLabels ), true )
-- links between instruments and MIDI channels
if data.channelInstruments and type( data.channelInstruments ) == "table" then
for c, i in pairs( data.channelInstruments ) do
local channel = ValidateInteger( c, 0, 15 )
local instrument = ValidateInteger( i, 1, instrumentCount )
settings.channelInstruments[channel] = instrument
end
end
end
function MKeyboard:SaveSettings()
local settings = self.settings
file.Write(
self.SETTINGS_FILE,
util.TableToJSON( {
layout = settings.layout,
instrument = settings.instrument,
sheet = settings.sheet,
velocity = settings.velocity,
transpose = settings.transpose,
midiTranspose = settings.midiTranspose,
channelInstruments = settings.channelInstruments,
drawKeyLabels = settings.drawKeyLabels
}, true )
)
end