Module MusistrataRenderer.Renderer

Render MusiStrata songs and tracks to numpy array.

Expand source code
"""
    Render MusiStrata songs and tracks to numpy array.  
"""

from MusiStrata.Components import Song, Track, Bar, SoundEvent, Note
from SamplesLocator import SamplesLocator

import numpy as np
import librosa

import Settings as SETTINGS


LOCATOR = SamplesLocator()


# here assuming stereo
def RenderSample(targetDuration: float, decayDuration: float, sampleData: np.ndarray, sampleRate: int = 44100) -> np.ndarray:
    """
        Render a sample to a target length with added decay.  
    """
    terminalIndex = int(targetDuration * sampleRate)
    decayTerminalIndex = int((targetDuration + decayDuration) * sampleRate)
    arr = np.zeros((2, decayTerminalIndex), dtype=float)
    arr[:, :decayTerminalIndex] = sampleData[:, :decayTerminalIndex]
    arr[:, terminalIndex:decayTerminalIndex] *= np.arange(decayTerminalIndex - terminalIndex)[::-1] / (decayTerminalIndex - terminalIndex)
    return arr


def RenderTrack(track: Track, tempo: int, beatsPerBar: int = 4, sampleRate: int = 44100, stereo: bool = True) -> np.ndarray:
    """
        Render a Musistrata track by reading all sound events, loading samples and writing to a numpy array.  
    """
    # Determine length
    duration = len(track.Bars) * beatsPerBar
    arr = np.zeros((2, int(duration * sampleRate * 60 / tempo + SETTINGS.SONG_PADDING_SECONDS * sampleRate)))
    for idBar, bar in enumerate(track.Bars):
        for _, soundEvent in enumerate(bar.SoundEvents):
            y = LOCATOR(track.Instrument, soundEvent.Note.Height)
            initialIndex = int((idBar * beatsPerBar + soundEvent.Beat) * 60 / tempo * sampleRate) 
            rendered = RenderSample(soundEvent.Duration * 60 / tempo,  LOCATOR.GetSettingsInstrument(track.Instrument)["Decay"], y) * soundEvent.Velocity / 100
            arr[:, initialIndex:(rendered.shape[1] + initialIndex)] += rendered  # directly using rendered shape: avoid potential rounding errors? Also good if adding effects?
    return arr


def RenderSong(song: Song, sampleRate: int = 44100) -> np.ndarray:
    """
        Render a Musistrata Song to a numpy array by rendering every track of the song to a different array, before summing these individual arrays.
    """
    tracks = []
    for track in song.Tracks:
        tracks.append(
            RenderTrack(track, song.Tempo, song.BeatsPerBar)
        )
    arr = tracks[0]
    for i in range(1, len(tracks)):
        arr += tracks[i]
    return arr

Functions

def RenderSample(targetDuration: float, decayDuration: float, sampleData: numpy.ndarray, sampleRate: int = 44100) ‑> numpy.ndarray

Render a sample to a target length with added decay.

Expand source code
def RenderSample(targetDuration: float, decayDuration: float, sampleData: np.ndarray, sampleRate: int = 44100) -> np.ndarray:
    """
        Render a sample to a target length with added decay.  
    """
    terminalIndex = int(targetDuration * sampleRate)
    decayTerminalIndex = int((targetDuration + decayDuration) * sampleRate)
    arr = np.zeros((2, decayTerminalIndex), dtype=float)
    arr[:, :decayTerminalIndex] = sampleData[:, :decayTerminalIndex]
    arr[:, terminalIndex:decayTerminalIndex] *= np.arange(decayTerminalIndex - terminalIndex)[::-1] / (decayTerminalIndex - terminalIndex)
    return arr
def RenderSong(song: MusiStrata.Components.Structure.Song, sampleRate: int = 44100) ‑> numpy.ndarray

Render a Musistrata Song to a numpy array by rendering every track of the song to a different array, before summing these individual arrays.

Expand source code
def RenderSong(song: Song, sampleRate: int = 44100) -> np.ndarray:
    """
        Render a Musistrata Song to a numpy array by rendering every track of the song to a different array, before summing these individual arrays.
    """
    tracks = []
    for track in song.Tracks:
        tracks.append(
            RenderTrack(track, song.Tempo, song.BeatsPerBar)
        )
    arr = tracks[0]
    for i in range(1, len(tracks)):
        arr += tracks[i]
    return arr
def RenderTrack(track: MusiStrata.Components.Structure.Track, tempo: int, beatsPerBar: int = 4, sampleRate: int = 44100, stereo: bool = True) ‑> numpy.ndarray

Render a Musistrata track by reading all sound events, loading samples and writing to a numpy array.

Expand source code
def RenderTrack(track: Track, tempo: int, beatsPerBar: int = 4, sampleRate: int = 44100, stereo: bool = True) -> np.ndarray:
    """
        Render a Musistrata track by reading all sound events, loading samples and writing to a numpy array.  
    """
    # Determine length
    duration = len(track.Bars) * beatsPerBar
    arr = np.zeros((2, int(duration * sampleRate * 60 / tempo + SETTINGS.SONG_PADDING_SECONDS * sampleRate)))
    for idBar, bar in enumerate(track.Bars):
        for _, soundEvent in enumerate(bar.SoundEvents):
            y = LOCATOR(track.Instrument, soundEvent.Note.Height)
            initialIndex = int((idBar * beatsPerBar + soundEvent.Beat) * 60 / tempo * sampleRate) 
            rendered = RenderSample(soundEvent.Duration * 60 / tempo,  LOCATOR.GetSettingsInstrument(track.Instrument)["Decay"], y) * soundEvent.Velocity / 100
            arr[:, initialIndex:(rendered.shape[1] + initialIndex)] += rendered  # directly using rendered shape: avoid potential rounding errors? Also good if adding effects?
    return arr