diff --git a/audio/midi_piano/README.md b/audio/midi_piano/README.md new file mode 100644 index 00000000..f9f813b5 --- /dev/null +++ b/audio/midi_piano/README.md @@ -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) diff --git a/audio/midi_piano/icon.png b/audio/midi_piano/icon.png new file mode 100644 index 00000000..c8f14924 Binary files /dev/null and b/audio/midi_piano/icon.png differ diff --git a/audio/midi_piano/icon.png.import b/audio/midi_piano/icon.png.import new file mode 100644 index 00000000..a4c02e6e --- /dev/null +++ b/audio/midi_piano/icon.png.import @@ -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 diff --git a/audio/midi_piano/piano.gd b/audio/midi_piano/piano.gd new file mode 100644 index 00000000..085ac74c --- /dev/null +++ b/audio/midi_piano/piano.gd @@ -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)) diff --git a/audio/midi_piano/piano.tscn b/audio/midi_piano/piano.tscn new file mode 100644 index 00000000..1fc5a317 --- /dev/null +++ b/audio/midi_piano/piano.tscn @@ -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 +} diff --git a/audio/midi_piano/piano_keys/A440.wav b/audio/midi_piano/piano_keys/A440.wav new file mode 100644 index 00000000..09ad34b1 Binary files /dev/null and b/audio/midi_piano/piano_keys/A440.wav differ diff --git a/audio/midi_piano/piano_keys/A440.wav.import b/audio/midi_piano/piano_keys/A440.wav.import new file mode 100644 index 00000000..762c76a2 --- /dev/null +++ b/audio/midi_piano/piano_keys/A440.wav.import @@ -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 diff --git a/audio/midi_piano/piano_keys/black_piano_key.tscn b/audio/midi_piano/piano_keys/black_piano_key.tscn new file mode 100644 index 00000000..2d4bfc64 --- /dev/null +++ b/audio/midi_piano/piano_keys/black_piano_key.tscn @@ -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"] diff --git a/audio/midi_piano/piano_keys/piano_key.gd b/audio/midi_piano/piano_keys/piano_key.gd new file mode 100644 index 00000000..6c9807a5 --- /dev/null +++ b/audio/midi_piano/piano_keys/piano_key.gd @@ -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 diff --git a/audio/midi_piano/piano_keys/piano_key_color.gd b/audio/midi_piano/piano_keys/piano_key_color.gd new file mode 100644 index 00000000..da43720c --- /dev/null +++ b/audio/midi_piano/piano_keys/piano_key_color.gd @@ -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() diff --git a/audio/midi_piano/piano_keys/white_piano_key.tscn b/audio/midi_piano/piano_keys/white_piano_key.tscn new file mode 100644 index 00000000..e612a269 --- /dev/null +++ b/audio/midi_piano/piano_keys/white_piano_key.tscn @@ -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"] diff --git a/audio/midi_piano/project.godot b/audio/midi_piano/project.godot new file mode 100644 index 00000000..1e5178d0 --- /dev/null +++ b/audio/midi_piano/project.godot @@ -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 diff --git a/audio/midi_piano/screenshots/.gdignore b/audio/midi_piano/screenshots/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/audio/midi_piano/screenshots/piano-pressed.png b/audio/midi_piano/screenshots/piano-pressed.png new file mode 100644 index 00000000..28d00758 Binary files /dev/null and b/audio/midi_piano/screenshots/piano-pressed.png differ