module ZXUtils::MusicBox::Song

MusicBox Song

A song is a special Multitrack that also organizes other multi-tracks, sub-tracks, instruments, envelopes, masks and chords.

To create a custom song you need to include the Song module in your class.

class MySong
    include ZXUtils::MusicBox::Song
    #... song commands follow
end

To compile a song just instantiate it:

mysong = MySong.new
puts mysong.channel_tracks.map(&:ticks_counter)
puts mysong.channel_tracks.map(&:max_recursion_depth)

To validate recursion depth of tracks and instruments do:

mysong.validate_recursion_depth!

Convert to a program class:

require 'z80'
puts mysong.to_program.new(0x8000).debug

Or save as a player module:

mysong.to_player_module.save_tap('mysong')

Commands

For the description of available commands see MultitrackCommands and SongCommands.

Attributes

item_table[R]

A hash containing compiled and used items as keys and item descriptors as values.

Public Class Methods

new() click to toggle source

Creates and instance of the song.

Calls superclass method ZXUtils::MusicBox::Multitrack::new
# File lib/zxutils/music_box/song.rb, line 170
def initialize
        @item_table = {}
        pargs = self.class.module_eval do
                { track_klasses:      @tracks,
                        multitrack_klasses: @multitracks,
                        instrument_klasses: @instruments,
                        envelopes:          @envelopes,
                        chords:             @chords,
                        masks:              @masks }
        end
        super(Resolver.new @item_table, **pargs)
end

Public Instance Methods

instruments() click to toggle source

Returns a hash of instruments used in a song. Keys are instrument names and values are Instrument instances.

# File lib/zxutils/music_box/song.rb, line 209
def instruments
        resolver.instruments
end
to_module() click to toggle source

Returns an instance of the SongModule from the compiled Song instance.

# File lib/zxutils/music_box/song.rb, line 235
def to_module
        body = ''
        track_offsets = @channel_tracks.map do |track|
                offset = body.bytesize
                body << track.code << Command::TERMINATOR
                offset...body.bytesize
        end
        index_offsets = @item_table.each_key.map do |item|
                offset = body.bytesize
                body << item.code << Command::TERMINATOR
                offset...body.bytesize
        end
        SongModule.new track_offsets, index_offsets, item_table.values, body
end
to_player_module() click to toggle source

Returns an instance of the PlayerModule from the compiled Song instance.

# File lib/zxutils/music_box/song.rb, line 224
def to_player_module
        to_module.to_player_module
end
to_program() click to toggle source

Returns an ad-hoc Z80::Program class containing the compiled Song. See SongModule.to_program.

# File lib/zxutils/music_box/song.rb, line 230
def to_program
        to_module.to_program
end
unused_item_names() click to toggle source

Returns a hash with unused item names in each of the item category.

# File lib/zxutils/music_box/song.rb, line 214
def unused_item_names
        { multitracks: resolver.unused_multitrack_names,
                tracks: resolver.unused_track_names,
                instruments: resolver.unused_instrument_names,
                envelopes: resolver.unused_envelope_names,
                chords: resolver.unused_chord_names,
                masks: resolver.unused_mask_names }
end
validate_recursion_depth!(track_stack_depth=20) click to toggle source

Checks if maximal recursion depth of tracks and instruments is not exceeding the given threshold.

Provide the maximum allowed track_stack_depth. You may want to use ZXUtils::AYMusic::TRACK_STACK_DEPTH constant.

Raises an error when recursion depth is exceeding track_stack_depth.

# File lib/zxutils/music_box/song.rb, line 188
def validate_recursion_depth!(track_stack_depth=20)
        max_recursion_depth = 0
        check_level = proc do |track, &block|
                max_recursion_depth = track.max_recursion_depth if track.max_recursion_depth > max_recursion_depth
                block.call if max_recursion_depth > track_stack_depth
        end
        channel_tracks.each_with_index do |track, ch_num|
                check_level.call(track) do
                        raise "too many recursions on track_#{Resolver::CHANNEL_NAMES[ch_num]} depth: #{max_recursion_depth} > #{track_stack_depth}"
                end
        end
        instruments.each do |name, instrument|
                check_level.call(instrument) do
                        raise "too many recursions on instrument :#{name} depth: #{max_recursion_depth} > #{track_stack_depth}"
                end
        end
        max_recursion_depth
end