mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-31 09:49:06 +03:00
Add MIDI Piano Demo
This commit is contained in:
19
audio/midi_piano/README.md
Normal file
19
audio/midi_piano/README.md
Normal 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
|
||||
|
||||

|
||||
BIN
audio/midi_piano/icon.png
Normal file
BIN
audio/midi_piano/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 807 B |
35
audio/midi_piano/icon.png.import
Normal file
35
audio/midi_piano/icon.png.import
Normal 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
97
audio/midi_piano/piano.gd
Normal 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))
|
||||
33
audio/midi_piano/piano.tscn
Normal file
33
audio/midi_piano/piano.tscn
Normal 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
|
||||
}
|
||||
BIN
audio/midi_piano/piano_keys/A440.wav
Normal file
BIN
audio/midi_piano/piano_keys/A440.wav
Normal file
Binary file not shown.
21
audio/midi_piano/piano_keys/A440.wav.import
Normal file
21
audio/midi_piano/piano_keys/A440.wav.import
Normal 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
|
||||
32
audio/midi_piano/piano_keys/black_piano_key.tscn
Normal file
32
audio/midi_piano/piano_keys/black_piano_key.tscn
Normal 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"]
|
||||
30
audio/midi_piano/piano_keys/piano_key.gd
Normal file
30
audio/midi_piano/piano_keys/piano_key.gd
Normal 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
|
||||
8
audio/midi_piano/piano_keys/piano_key_color.gd
Normal file
8
audio/midi_piano/piano_keys/piano_key_color.gd
Normal 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()
|
||||
29
audio/midi_piano/piano_keys/white_piano_key.tscn
Normal file
29
audio/midi_piano/piano_keys/white_piano_key.tscn
Normal 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"]
|
||||
44
audio/midi_piano/project.godot
Normal file
44
audio/midi_piano/project.godot
Normal 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
|
||||
0
audio/midi_piano/screenshots/.gdignore
Normal file
0
audio/midi_piano/screenshots/.gdignore
Normal file
BIN
audio/midi_piano/screenshots/piano-pressed.png
Normal file
BIN
audio/midi_piano/screenshots/piano-pressed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user