Skip to content

File: Pianola/Pianola.py

Pianola.Pianola

PianolaConfig

Source code in Projects/Pianola/Pianola/Pianola.py
15
16
17
18
19
20
21
22
23
24
25
@define
class PianolaConfig:
    class SoundFont(BrokenEnum):
        Salamander = "https://freepats.zenvoid.org/Piano/SalamanderGrandPiano/SalamanderGrandPiano-SF2-V3+20200602.tar.xz"
        """# Note: Salamander Grand Piano, Licensed under CC BY 3.0, by Alexander Holm"""

    class Songs(BrokenEnum):
        TheEntertainer = "https://bitmidi.com/uploads/28765.mid"

    soundfont: SoundFont = SoundFont.Salamander.field()
    midi:      Songs     = Songs.TheEntertainer.field()

SoundFont

Bases: BrokenEnum

Source code in Projects/Pianola/Pianola/Pianola.py
17
18
19
class SoundFont(BrokenEnum):
    Salamander = "https://freepats.zenvoid.org/Piano/SalamanderGrandPiano/SalamanderGrandPiano-SF2-V3+20200602.tar.xz"
    """# Note: Salamander Grand Piano, Licensed under CC BY 3.0, by Alexander Holm"""
Salamander
1
Salamander = "https://freepats.zenvoid.org/Piano/SalamanderGrandPiano/SalamanderGrandPiano-SF2-V3+20200602.tar.xz"
Note: Salamander Grand Piano, Licensed under CC BY 3.0, by Alexander Holm

Songs

Bases: BrokenEnum

Source code in Projects/Pianola/Pianola/Pianola.py
21
22
class Songs(BrokenEnum):
    TheEntertainer = "https://bitmidi.com/uploads/28765.mid"
TheEntertainer
1
TheEntertainer = 'https://bitmidi.com/uploads/28765.mid'

soundfont

1
soundfont: SoundFont = SoundFont.Salamander.field()

midi

1
midi: Songs = Songs.TheEntertainer.field()

PianolaScene

Bases: ShaderScene

Basic piano roll

Source code in Projects/Pianola/Pianola/Pianola.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@define
class PianolaScene(ShaderScene):
    """Basic piano roll"""
    __name__ = "Pianola"

    config: PianolaConfig = Factory(PianolaConfig)

    # Todo: Think better ways of presetting Scenes in general
    def input(self,
        midi:      Annotated[str,  Option("--midi",      "-m", help="Midi file to load")],
        audio:     Annotated[str,  Option("--audio",     "-a", help="Pre-rendered Audio File of the Input Midi")]=None,
        normalize: Annotated[bool, Option("--normalize", "-n", help="Normalize the velocities of the Midi")]=False,
    ):
        ...

    def build(self):
        self.soundfont_file = next(BrokenPath.get_external(self.config.soundfont).rglob("*.sf2"))

        # Define scene inputs
        self.midi_file  = BrokenPath.get_external(self.config.midi)
        self.audio_file = "/path/to/your/midis/audio.ogg"

        # Make modules
        self.audio = ShaderAudio(scene=self, name="Audio")
        self.piano = ShaderPiano(scene=self)
        self.piano.normalize_velocities(100, 100)
        self.piano.fluid_load(self.soundfont_file)
        self.shader.fragment = (PIANOLA.RESOURCES.SHADERS/"Pianola.frag")
        self.load_midi(self.midi_file)

    def load_midi(self, path: Path):
        self.piano.fluid_all_notes_off()
        self.piano.clear()
        self.piano.load_midi(path)
        self.time = (-1 * self.piano.roll_time)
        self.set_duration(self.piano.duration)

    def handle(self, message: ShaderMessage):
        ShaderScene.handle(self, message)

        if isinstance(message, ShaderMessage.Window.FileDrop):
            file = BrokenPath.get(message.files[0])

            if (file.suffix == ".mid"):
                self.load_midi(file)

            elif (file.suffix == ".sf2"):
                self.piano.fluid_load(file)

            elif (file.suffix in (".png", ".jpg", ".jpeg")):
                log.warning("No background image support yet")

    def setup(self):

        # Midi -> Audio if rendering or input audio doesn't exist
        if (self.exporting) and not BrokenPath.get(self.audio.file):
            self.audio.file = self.piano.fluid_render(soundfont=self.soundfont_file, midi=self.midi_file)

    def update(self):

        # Mouse drag time scroll to match piano roll size
        self._mouse_drag_time_factor = (self.piano.roll_time/(self.piano.height - 1))*self.camera.zoom.value

__name__

1
__name__ = 'Pianola'

config

1
config: PianolaConfig = Factory(PianolaConfig)

input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
input(
    midi: Annotated[
        str, Option(--midi, -m, help="Midi file to load")
    ],
    audio: Annotated[
        str,
        Option(
            --audio,
            -a,
            help="Pre-rendered Audio File of the Input Midi",
        ),
    ] = None,
    normalize: Annotated[
        bool,
        Option(
            --normalize,
            -n,
            help="Normalize the velocities of the Midi",
        ),
    ] = False,
)
Source code in Projects/Pianola/Pianola/Pianola.py
38
39
40
41
42
43
def input(self,
    midi:      Annotated[str,  Option("--midi",      "-m", help="Midi file to load")],
    audio:     Annotated[str,  Option("--audio",     "-a", help="Pre-rendered Audio File of the Input Midi")]=None,
    normalize: Annotated[bool, Option("--normalize", "-n", help="Normalize the velocities of the Midi")]=False,
):
    ...

build

1
build()
Source code in Projects/Pianola/Pianola/Pianola.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def build(self):
    self.soundfont_file = next(BrokenPath.get_external(self.config.soundfont).rglob("*.sf2"))

    # Define scene inputs
    self.midi_file  = BrokenPath.get_external(self.config.midi)
    self.audio_file = "/path/to/your/midis/audio.ogg"

    # Make modules
    self.audio = ShaderAudio(scene=self, name="Audio")
    self.piano = ShaderPiano(scene=self)
    self.piano.normalize_velocities(100, 100)
    self.piano.fluid_load(self.soundfont_file)
    self.shader.fragment = (PIANOLA.RESOURCES.SHADERS/"Pianola.frag")
    self.load_midi(self.midi_file)

load_midi

1
load_midi(path: Path)
Source code in Projects/Pianola/Pianola/Pianola.py
60
61
62
63
64
65
def load_midi(self, path: Path):
    self.piano.fluid_all_notes_off()
    self.piano.clear()
    self.piano.load_midi(path)
    self.time = (-1 * self.piano.roll_time)
    self.set_duration(self.piano.duration)

handle

1
handle(message: ShaderMessage)
Source code in Projects/Pianola/Pianola/Pianola.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def handle(self, message: ShaderMessage):
    ShaderScene.handle(self, message)

    if isinstance(message, ShaderMessage.Window.FileDrop):
        file = BrokenPath.get(message.files[0])

        if (file.suffix == ".mid"):
            self.load_midi(file)

        elif (file.suffix == ".sf2"):
            self.piano.fluid_load(file)

        elif (file.suffix in (".png", ".jpg", ".jpeg")):
            log.warning("No background image support yet")

setup

1
setup()
Source code in Projects/Pianola/Pianola/Pianola.py
82
83
84
85
86
def setup(self):

    # Midi -> Audio if rendering or input audio doesn't exist
    if (self.exporting) and not BrokenPath.get(self.audio.file):
        self.audio.file = self.piano.fluid_render(soundfont=self.soundfont_file, midi=self.midi_file)

update

1
update()
Source code in Projects/Pianola/Pianola/Pianola.py
88
89
90
91
def update(self):

    # Mouse drag time scroll to match piano roll size
    self._mouse_drag_time_factor = (self.piano.roll_time/(self.piano.height - 1))*self.camera.zoom.value