def __init__( self, sound_fonts_path: Union[str, Path] = DEFAULT_SOUND_FONTS, audio_driver: Union[str, None] = None, instrument: Union[str, int] = "Acoustic Grand Piano", ) -> None: self.__fluid_synth_sequencer = FluidSynthSequencer() self._sound_fonts_path = Path(sound_fonts_path) # Set variable to track if sound fonts are loaded self._sound_fonts_loaded = False self.load_sound_fonts(self._sound_fonts_path) # Audio output is lazily loaded when self.play method is called the first time without recording self._current_audio_driver = audio_driver # Set a variable to track if audio output is currently active self._audio_driver_is_active = False # Set instrument self.instrument = instrument self.load_instrument(self.instrument) # Initialize a piano keyboard self.keyboard = PianoKeyboard()
import random import math from mingus.containers import NoteContainer, Note, Bar, Track from mingus.midi.fluidsynth import FluidSynthSequencer from mingus.midi import fluidsynth, midi_file_out from threading import Thread import latestmusic import time from pydub import AudioSegment import os import itertools from mingus.containers.instrument import MidiInstrument import finalpercussion from midi2audio import FluidSynth from mingus.containers.mt_exceptions import NoteFormatError snareplayer = FluidSynthSequencer() baseplayer = FluidSynthSequencer() highhatplayer = FluidSynthSequencer() cymbalplayer = FluidSynthSequencer() tomplayer = FluidSynthSequencer() lefthandplayer = FluidSynthSequencer() righthandplayer = FluidSynthSequencer() basse = MidiInstrument() basse.instrument_nr = 32 guitar = MidiInstrument() guitar.instrument_nr = 25 string = MidiInstrument() string.instrument_nr = 48
class Piano(object): """Class representing a Piano with 88 keys based on mingus Class to programmatically play piano via audio output or record music to a wav file. Abstraction layer on top of mingus.midi.fluidsynth.FluidSynthSequencer. Attributes sound_fonts_path: Optional string or Path object pointing to a *.sf2 files. PyPiano ships sound fonts by default audio_driver: Optional argument specifying audio driver to use. Following audio drivers could be used: (None, "alsa", "oss", "jack", "portaudio", "sndmgr", "coreaudio","Direct Sound", "dsound", "pulseaudio"). Not all drivers will be available for every platform instrument: Optional argument to set the instrument that should be used. If default sound fonts are used you can choose one of the following pianos sounds: ("Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavi"). If different sound fonts are provided you should also pass an integer with the instrument number """ def __init__( self, sound_fonts_path: Union[str, Path] = DEFAULT_SOUND_FONTS, audio_driver: Union[str, None] = None, instrument: Union[str, int] = "Acoustic Grand Piano", ) -> None: self.__fluid_synth_sequencer = FluidSynthSequencer() self._sound_fonts_path = Path(sound_fonts_path) # Set variable to track if sound fonts are loaded self._sound_fonts_loaded = False self.load_sound_fonts(self._sound_fonts_path) # Audio output is lazily loaded when self.play method is called the first time without recording self._current_audio_driver = audio_driver # Set a variable to track if audio output is currently active self._audio_driver_is_active = False # Set instrument self.instrument = instrument self.load_instrument(self.instrument) # Initialize a piano keyboard self.keyboard = PianoKeyboard() def load_sound_fonts(self, sound_fonts_path: Union[str, Path]) -> None: """Load sound fonts from a given path""" logger.debug("Attempting to load sound fonts from {file}".format( file=sound_fonts_path)) if self._sound_fonts_loaded: self._unload_sound_fonts() if not self.__fluid_synth_sequencer.load_sound_font( str(sound_fonts_path)): raise Exception("Could not load sound fonts from {file}".format( file=sound_fonts_path)) self._sound_fonts_loaded = True self._sound_fonts_path = Path(sound_fonts_path) logger.debug( "Successfully initialized sound fonts from {file_path}".format( file_path=sound_fonts_path)) def _unload_sound_fonts(self) -> None: """Unload a given sound font file Safely unload current sound font file. Method controls if a sound font file is already loaded via self._sound_fonts_loaded. """ logger.debug( "Unloading current active sound fonts from file: {0}".format( self._sound_fonts_path)) if self._sound_fonts_loaded: self.__fluid_synth_sequencer.fs.sfunload( self.__fluid_synth_sequencer.sfid) self._sound_fonts_loaded = False self._sound_fonts_path = None else: logger.debug("No active sound fonts") def _start_audio_output(self) -> None: """Private method to start audio output This method in conjunction with self._stop_audio_output should be used to safely start and stop audio output, for example when there is switch between audio output and recording audio to a file (check doc string of self._stop_audio_output for more details why this necessary). This method replaces mingus.midi.fluidsynth.FluidSynthSequencer """ logger.debug("Starting audio output using driver: {driver}".format( driver=self._current_audio_driver)) # That is actually already done by the low level method and is included here again for transparency if self._current_audio_driver not in VALID_AUDIO_DRIVERS: raise ValueError( "{driver} is not a valid audio driver. Must be one of: {allowed_drivers}" .format( driver=self._current_audio_driver, allowed_drivers=VALID_AUDIO_DRIVERS, )) if not self._audio_driver_is_active: self.__fluid_synth_sequencer.start_audio_output( self._current_audio_driver) # It seems to be necessary to reset the program after starting audio output # mingus.midi.pyfluidsynth.program_reset() is calling fluidsynth fluid_synth_program_reset() # https://www.fluidsynth.org/api/group__midi__messages.html#ga8a0e442b5013876affc685b88a6e3f49 self.__fluid_synth_sequencer.fs.program_reset() self._audio_driver_is_active = True else: logger.debug("Audio output seems to be already active") def _stop_audio_output(self) -> None: """Private method to stop audio output Method is used to safely stop audio output via deleting an active audio driver, for example if there is a switch between audio output and recording. This method should be used in conjunction with self._start_audio_output(). It is a thin wrapper around the mingus.midi.pyfluidsynth.delete_fluid_audio_driver and ensures that mingus.midi.pyfluidsynth.delete_fluid_audio_driver is not called twice because this seems to result in segmentation fault: [1] 4059 segmentation fault python3 Tracking is done via checking and setting self._audio_driver_is_active attribute. This method basically replaces mingus.midi.pyfluidsynth.delete() (which is also basically a wrapper for mingus.midi.pyfluidsynth.delete_fluid_audio_driver), because the delete method from the mingus package seems not safe to use and results in a crash if for some reason is called after an audio driver was already deleted and there isn't currently an active one. Despite the mingus.midi.pyfluidsynth.delete method seems to attempt to check if an audio driver is present and tries to avoid such a scenario via checking mingus.midi.pyfluidsynth.audio_driver argument for None. However once an audio driver was initialized the audio_driver argument seems to be never set back to None and therefore it seems you can't rely on checking that argument to know if an audio is active. I am not sure if it is a good way to do it that way and if it has any side effects, but it seems to work so far and enables switching between recording to a file and playing audio output without initializing a new object. """ if self._audio_driver_is_active: globalfs.delete_fluid_audio_driver( self.__fluid_synth_sequencer.fs.audio_driver) # It seems to be necessary to reset the program after starting audio output # mingus.midi.pyfluidsynth.program_reset() is calling fluidsynth fluid_synth_program_reset() # https://www.fluidsynth.org/api/group__midi__messages.html#ga8a0e442b5013876affc685b88a6e3f49 self.__fluid_synth_sequencer.fs.program_reset() self._audio_driver_is_active = False else: logger.debug("Audio output seems to be already inactive") def load_instrument(self, instrument: Union[str, int]) -> None: """Method to change the piano instrument Load an instrument that should be used for playing or recording music. If PyPiano default sound fonts are used you can choose one of the following instruments: ("Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavi") Args instrument: String with the name of the instrument to be used for default sound founts. If different sound fonts are used an integer with the instrument number should be provided. """ logger.info("Setting instrument: {0}".format(instrument)) # If default sound fonts are used, check if the provided instrument string is contained in the valid # instruments. If different sound fonts are provided, checks are disabled if self._sound_fonts_path == DEFAULT_SOUND_FONTS: if isinstance(instrument, int): raise TypeError( "When using default sound fonts you must pass a string for instrument parameter" ) if instrument not in tuple(DEFAULT_INSTRUMENTS.keys()): raise ValueError( "Unknown instrument parameter. Instrument must be one of: {instrument}" .format(instrument=tuple(DEFAULT_INSTRUMENTS.keys()))) self.__fluid_synth_sequencer.set_instrument( channel=1, instr=DEFAULT_INSTRUMENTS[instrument], bank=0) self.instrument = instrument else: if isinstance(instrument, str): raise TypeError( "When using non default sound fonts you must pass an integer for instrument parameter" ) self.__fluid_synth_sequencer.set_instrument(channel=1, instr=instrument, bank=0) self.instrument = instrument def play( self, music_container: Union[str, int, Note, NoteContainer, Bar, Track, PianoKey], recording_file: Union[str, None] = None, record_seconds: int = 4, ) -> None: """Function to play a provided music container and control recording settings Central user facing method of Piano class to play or record a given music container. Handles setting up audio output or recording to audio file and handles switching between playing audio and recording to wav file. Args music_container: A music container such as Notes, NoteContainers, etc. describing a piece of music recording_file: Path to a wav file where audio should be saved to. If passed music_container will be recorded record_seconds: The duration of recording in seconds """ # Check a given music container for invalid notes. See docstring of self._lint_music_container for more details self._lint_music_container(music_container) if recording_file is None: logger.info( "Playing music container: {music_container} via audio".format( music_container=music_container)) self._start_audio_output() self._play_music_container(music_container) else: logger.info( "Recording music container: {music_container} to file {recording_file}" .format(music_container=music_container, recording_file=recording_file)) self._stop_audio_output() self.__fluid_synth_sequencer.start_recording(recording_file) self._play_music_container(music_container) WAV_SAMPLE_FREQUENCY = 44100 samples = globalfs.raw_audio_string( self.__fluid_synth_sequencer.fs.get_samples( int(record_seconds * WAV_SAMPLE_FREQUENCY))) self.__fluid_synth_sequencer.wav.writeframes(bytes(samples)) self.__fluid_synth_sequencer.wav.close() # It seems we have to delete the wav attribute after recording in order to enable switching between # audio output and recording for all music containers. The # mingus.midi.fluidsynth.FluidSynthSequencer.play_Bar and # mingus.midi.fluidsynth.FluidSynthSequencer.play_Track use the # mingus.midi.fluidsynth.FluidSynthSequencer.sleep methods internally which is for some reason also used # to record in mingus. # See also my issue in the mingus repository: https://github.com/bspaans/python-mingus/issues/77 # When wav attribute is present sleep tries to write to the wave file and if not the method just sleeps. # If we do not delete the wav attribute it is still there as None and play_Bar tries to write to the file # resulting in AttributeError: 'NoneType' object has no attribute 'write' delattr(self.__fluid_synth_sequencer, "wav") logger.info("Finished recording to {recording_file}".format( recording_file=recording_file)) def _play_music_container( self, music_container: Union[str, int, Note, NoteContainer, Bar, Track, PianoKey], ) -> None: """Private method to call the appropriate low level play method for given music container class mingus.midi.fluidsynth exposes a few different methods to play different music containers, such as Notes or NoteContainers, etc. This should be abstracted for the user and this function calls the appropriate low level play method from mingus.midi.fluidsynth Args music_container: A music container such as Notes, NoteContainers, etc. describing a piece of music """ logger.debug( "Attempting to play music container: {music_container} of type: {container_type}" .format( music_container=music_container, container_type=str(type(music_container)), )) if isinstance(music_container, str): self.__fluid_synth_sequencer.play_Note(Note(music_container)) elif isinstance(music_container, int): # FIX ME: Added another type check to fix mypy error piano_key = self.keyboard[music_container] if isinstance(piano_key, int): raise TypeError("This should not happen") self.__fluid_synth_sequencer.play_Note(piano_key.first_note) elif isinstance(music_container, Note): self.__fluid_synth_sequencer.play_Note(music_container) elif isinstance(music_container, NoteContainer): self.__fluid_synth_sequencer.play_NoteContainer(music_container) elif isinstance(music_container, Bar): self.__fluid_synth_sequencer.play_Bar(music_container) elif isinstance(music_container, Track): self.__fluid_synth_sequencer.play_Track(music_container) logger.debug( "Done playing music container: {music_container} of type: {container_type}" .format( music_container=music_container, container_type=str(type(music_container)), )) def _lint_music_container( self, music_container: Union[str, Note, NoteContainer, Bar, Track]) -> None: """Check a music container for invalid notes Method checks a given music container like mingus.containers.Note or more complex containers like Tracks, etc. for notes that can't be found on a piano with 88 keys. In case a string is passed it also checks whether it can be parsed as a mingus.containers.Note. Args music_container: A music container such as Notes, NoteContainers, etc. describing a piece of music Raises ValueError: If illegal notes in given music container are found """ logger.debug( "Checking music container: {container} of class {container_type} for invalid notes" .format(container=music_container, container_type=str(type(music_container)))) if isinstance(music_container, str): note = Note(music_container) distinct_notes_in_container = {note_to_string(note)} elif isinstance(music_container, Note): distinct_notes_in_container = {note_to_string(music_container)} elif isinstance(music_container, NoteContainer): distinct_notes_in_container = set( note_container_to_note_string_list(music_container)) elif isinstance(music_container, Bar): distinct_notes_in_container = set( bar_to_note_string_list(music_container)) elif isinstance(music_container, Track): distinct_notes_in_container = set( track_to_note_string_list(music_container)) else: raise Exception("Unexpected Error") diff = distinct_notes_in_container - self.keyboard.distinct_key_names if len(diff) > 0: raise ValueError( "Found notes that are not on a piano with 88 keys. Invalid notes in container: {0}" .format(diff)) logger.debug( "Music container: {container} of class {container_type} looks good" .format(container=music_container, container_type=str(type(music_container)))) @staticmethod def pause(seconds: int) -> None: """Pause further execution for a given time Args duration: Time to pause further execution in seconds """ time.sleep(seconds)
12.5, 12.75, 13.0, 13.25, 13.5, 13.75, 14.0, 14.25, 14.5, 14.75, 15.0, 15.25, 15.5, 15.75, 16.0, 16.25, 16.5, 16.75, 17.0, 17.25, 17.5, 17.75, 18.0, 18.25, 18.5, 18.75, 19.0, 19.25, 19.5, 19.75, 20.0, 20.25, 20.5, 20.75, 21.0, 21.25, 21.5, 21.75, 22.0, 22.25, 22.5, 22.75, 23.0, 23.25, 23.5, 23.75, 24.0, 24.25, 24.5, 24.75, 25.0, 25.25, 25.5, 25.75, 26.0, 26.25, 26.5, 26.75, 27.0, 27.25, 27.5, 27.75, 28.0, 28.25, 28.5, 28.75, 29.0, 29.25, 29.5, 29.75, 30.0, 30.25, 30.5, 30.75, 31.0, 31.25, 31.5, 31.75 ] downbeatpossiblevalues = [ 0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0 ] possiblenote = ["C", "D", "E", "F", "G", "A", "B"] initialized = False uiplayer = FluidSynthSequencer() fluidsynth.init('goodtry.sf2', uiplayer) cymbal = Note() cymbal.from_int(37) snare = Note() snare.from_int(26) highhat = Note() highhat.from_int(30) base = NoteContainer() basenote = Note() basenote.from_int(23) base.add_note(basenote) soundtrack = Track() starttrack = Track() starttrack.add_notes(highhat, 1) starttrack.add_notes(highhat, 1)
import random import math from mingus.containers import NoteContainer, Note, Bar, Track from mingus.midi.fluidsynth import FluidSynthSequencer from mingus.midi import fluidsynth from threading import Thread import musicdeterminationdemo import time from mingus.containers.instrument import MidiInstrument otherhandmusicthinny = FluidSynthSequencer() otherhandmusicthinny.init(volume=1.75) lefthandmusicthinny = FluidSynthSequencer() lefthandmusicthinny.init(volume=1) speed = 150 offbeat = False first_time = True firstbar = True lastbar = False highorlow = random.randint(0,1) musicpiece1 = "" musicpiece2 = "" musicpiece3 = "" musicpiece4 = "" musicpiece5 = "" musicpiece6 = "" musicpiece7 = "" musicpiece8 = "" part1 = [] part2 = [] part3 = [] part4 = []
import os from string import upper from mingus.containers import Track from mingus.midi import fluidsynth from mingus.midi.fluidsynth import FluidSynthSequencer speed1 = 100 speed2 = 240 # 1 second per beat. lefthandmusicthinny = FluidSynthSequencer() # notes = ['E-6|1.25', 'D-7|1.25', 'E-7|1.50', 'C-7|1.25', 'C-7|1.25', 'A-6|1.50', 'C-7|1.25', 'B-6|1.25', 'A-6|1.50', # 'A-6|1.25', 'B-6|1.25', 'A-6|1.50', 'B-6|1.25', 'C-7|1.25', 'C-7|1.50', 'B-6|1.25', 'A-6|1.25', 'A-6|1.50', # 'A-6|1.25', 'D-7|1.25', 'C-7|1.50', 'C-7|1.25', 'C-7|1.25', 'B-6|1.50'] snaretrack2 = Track() # i = 0 # for note in notes: # notePair = note.split("|") # snaretrack2.add_notes(notePair[0], float(notePair[1])) # # i += 1 # # print notes # print snaretrack2 # # # snaretrack2.add_notes('C--4') # snaretrack2.add_notes('D4') # snaretrack2.add_notes('E-4') # snaretrack2.add_notes('F-4')
import random import math from mingus.containers import NoteContainer, Note, Bar, Track from mingus.midi.fluidsynth import FluidSynthSequencer from mingus.midi import fluidsynth from threading import Thread import musicdeterminationdemo import time from mingus.containers.instrument import MidiInstrument lefthandmusicthinny = FluidSynthSequencer() lefthandmusicthinny.init() fluidsynth.init("goodtry.sf2",lefthandmusicthinny) lefthandmusicthinny.main_volume(channel=0,value=100) lefthandmusicthinny.main_volume(channel=1,value=75) lefthandmusicthinny.main_volume(channel=9,value=75) reserved = "" speed1 = 100 speed2 = 140 offbeat = False first_time = True firstbar = True lastbar = False highorlow = random.randint(0,1) musicpiece1 = "" musicpiece2 = "" musicpiece3 = "" musicpiece4 = "" musicpiece5 = "" musicpiece6 = "" musicpiece7 = "" musicpiece8 = ""