Add MIDI Piano Demo

This commit is contained in:
Aaron Franke
2021-12-27 02:11:55 -08:00
parent 75505ca79f
commit be43d1644b
14 changed files with 348 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
# MIDI Piano
This demo shows how to use
[InputEventMIDI](https://docs.godotengine.org/en/latest/classes/class_inputeventmidi.html)
by creating a piano that can be controlled by a MIDI device.
This is known to work with a Yamaha MX88.
The piano can also be controlled by clicking on the keys, or by
manually calling the activate and deactivate methods on each key.
Note that MIDI output is not yet supported in Godot, only input works.
Language: GDScript
Renderer: GLES 2
## Screenshots
![Screenshot](screenshots/piano-pressed.png)

BIN
audio/midi_piano/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

97
audio/midi_piano/piano.gd Normal file
View File

@@ -0,0 +1,97 @@
extends Control
# A standard piano with 88 keys has keys from 21 to 108.
# To get a different set of keys, modify these numbers.
# A maximally extended 108-key piano goes from 12 to 119.
# A 76-key piano goes from 23 to 98, 61-key from 36 to 96,
# 49-key from 36 to 84, 37-key from 41 to 77, and 25-key
# from 48 to 72. Middle C is pitch number 60, A440 is 69.
const START_KEY = 21
const END_KEY = 108
const WhiteKeyScene = preload("res://piano_keys/white_piano_key.tscn")
const BlackKeyScene = preload("res://piano_keys/black_piano_key.tscn")
var piano_key_dict := Dictionary()
onready var white_keys = $WhiteKeys
onready var black_keys = $BlackKeys
func _ready():
# Sanity checks.
if _is_note_index_sharp(_pitch_index_to_note_index(START_KEY)):
printerr("The start key can't be a sharp note (limitation of this piano-generating algorithm). Try 21.")
return
for i in range(START_KEY, END_KEY + 1):
piano_key_dict[i] = _create_piano_key(i)
if white_keys.get_child_count() != black_keys.get_child_count():
_add_placeholder_key(black_keys)
OS.open_midi_inputs()
print(OS.get_connected_midi_inputs())
func _input(input_event):
if not (input_event is InputEventMIDI):
return
var midi_event: InputEventMIDI = input_event
if midi_event.pitch < START_KEY or midi_event.pitch > END_KEY:
# The given pitch isn't on the on-screen keyboard, so return.
return
_print_midi_info(midi_event)
var key: PianoKey = piano_key_dict[midi_event.pitch]
if midi_event.message == MIDI_MESSAGE_NOTE_ON:
key.activate()
else:
key.deactivate()
func _add_placeholder_key(container):
var placeholder = Control.new()
placeholder.size_flags_horizontal = SIZE_EXPAND_FILL
placeholder.mouse_filter = Control.MOUSE_FILTER_IGNORE
placeholder.name = "Placeholder"
container.add_child(placeholder)
func _create_piano_key(pitch_index):
var note_index = _pitch_index_to_note_index(pitch_index)
var piano_key: PianoKey
if _is_note_index_sharp(note_index):
piano_key = BlackKeyScene.instance()
black_keys.add_child(piano_key)
else:
piano_key = WhiteKeyScene.instance()
white_keys.add_child(piano_key)
if _is_note_index_lacking_sharp(note_index):
_add_placeholder_key(black_keys)
piano_key.setup(pitch_index)
return piano_key
func _is_note_index_lacking_sharp(note_index: int):
# B and E, because no B# or E#
return note_index in [2, 7]
func _is_note_index_sharp(note_index: int):
# A#, C#, D#, F#, and G#
return note_index in [1, 4, 6, 9, 11]
func _pitch_index_to_note_index(pitch: int):
pitch += 3
return pitch % 12
func _print_midi_info(midi_event: InputEventMIDI):
print(midi_event)
print("Channel: " + str(midi_event.channel))
print("Message: " + str(midi_event.message))
print("Pitch: " + str(midi_event.pitch))
print("Velocity: " + str(midi_event.velocity))
print("Instrument: " + str(midi_event.instrument))
print("Pressure: " + str(midi_event.pressure))
print("Controller number: " + str(midi_event.controller_number))
print("Controller value: " + str(midi_event.controller_value))

View File

@@ -0,0 +1,33 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://piano.gd" type="Script" id=1]
[node name="Piano" type="ColorRect"]
anchor_right = 1.0
anchor_bottom = 1.0
rect_min_size = Vector2( 200, 20 )
color = Color( 0, 0, 0, 1 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="WhiteKeys" type="HBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
rect_min_size = Vector2( 200, 0 )
mouse_filter = 2
custom_constants/separation = 0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="BlackKeys" type="HBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 0.6
rect_min_size = Vector2( 100, 0 )
mouse_filter = 2
custom_constants/separation = 0
__meta__ = {
"_edit_use_anchors_": false
}

Binary file not shown.

View File

@@ -0,0 +1,21 @@
[remap]
importer="wav"
type="AudioStreamSample"
path="res://.import/A440.wav-f6ecb7d5a329df710ac81894c403bbb6.sample"
[deps]
source_file="res://piano_keys/A440.wav"
dest_files=[ "res://.import/A440.wav-f6ecb7d5a329df710ac81894c403bbb6.sample" ]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop=false
compress/mode=0

View File

@@ -0,0 +1,32 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://piano_keys/piano_key.gd" type="Script" id=1]
[ext_resource path="res://piano_keys/piano_key_color.gd" type="Script" id=2]
[node name="PianoKey" type="Control"]
margin_right = 20.0
margin_bottom = 200.0
mouse_filter = 2
size_flags_horizontal = 3
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Key" type="ColorRect" parent="."]
anchor_left = 0.5
anchor_right = 1.5
anchor_bottom = 1.0
margin_left = 2.0
margin_right = -2.0
size_flags_horizontal = 3
color = Color( 0, 0, 0, 1 )
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ColorTimer" type="Timer" parent="."]
one_shot = true
[connection signal="timeout" from="ColorTimer" to="." method="deactivate"]

View File

@@ -0,0 +1,30 @@
class_name PianoKey
extends Control
var pitch_scale: float
onready var key: ColorRect = $Key
onready var start_color: Color = key.color
onready var color_timer: Timer = $ColorTimer
func setup(pitch_index: int):
name = "PianoKey" + str(pitch_index)
var exponent := (pitch_index - 69.0) / 12.0
pitch_scale = pow(2, exponent)
func activate():
key.color = (Color.yellow + start_color) / 2
var audio := AudioStreamPlayer.new()
add_child(audio)
audio.stream = preload("res://piano_keys/A440.wav")
audio.pitch_scale = pitch_scale
audio.play()
color_timer.start()
yield(get_tree().create_timer(8.0), "timeout")
audio.queue_free()
func deactivate():
key.color = start_color

View File

@@ -0,0 +1,8 @@
extends ColorRect
onready var parent = get_parent()
# Yes, this script exists just for this one method.
func _gui_input(input_event):
if input_event is InputEventMouseButton and input_event.pressed:
parent.activate()

View File

@@ -0,0 +1,29 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://piano_keys/piano_key.gd" type="Script" id=1]
[ext_resource path="res://piano_keys/piano_key_color.gd" type="Script" id=2]
[node name="PianoKey" type="Control"]
margin_right = 20.0
margin_bottom = 200.0
size_flags_horizontal = 3
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Key" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 1.0
margin_right = -1.0
size_flags_horizontal = 3
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ColorTimer" type="Timer" parent="."]
one_shot = true
[connection signal="timeout" from="ColorTimer" to="." method="deactivate"]

View File

@@ -0,0 +1,44 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ {
"base": "Control",
"class": "PianoKey",
"language": "GDScript",
"path": "res://piano_keys/piano_key.gd"
} ]
_global_script_class_icons={
"PianoKey": ""
}
[application]
config/name="MIDI Piano Demo"
run/main_scene="res://piano.tscn"
config/icon="res://icon.png"
[display]
window/size/width=1280
window/size/height=200
[network]
limits/debugger_stdout/max_chars_per_second=2048000
[physics]
common/enable_pause_aware_picking=true
[rendering]
quality/driver/driver_name="GLES2"
vram_compression/import_etc=true
vram_compression/import_etc2=false

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB