class ZXUtils::MusicBox::Song::SongModule

MusicBox Song SongModule

An instance of this class can be created by calling Song.to_module instance method of the compiled song.

The instance of this class can produce an instance of PlayerModule or a Z80::Program.

Example:

require 'zxutils/ay_music'
require 'zxlib/basic'
require_relative 'examples/test_music'

class Music
  include Z80
  include Z80::TAP
  include ZXUtils

  MusicData = TestMusic.new.to_program

  macro_import        MathInt
  macro_import        Utils::SinCos
  import              ZXLib::Sys, macros: true
  macro_import        ZXLib::AYSound
  macro_import        AYMusic

  with_saved :start, :exx, hl, ret: :after_ei do
                      call make_sincos
                      ay_expand_notes( music.notes, octaves:8 )
                      ay_music_tone_progress_table_factory( music.fine_tones )
                      ay_music_note_to_fine_tone_cursor_table_factory( music.note_to_cursor, play: music.play )
                      di
                      call music.init
                      dw   track_a, track_b, track_c
    forever           ei
                      halt
                      di
                      push iy
                      call music.play
                      pop  iy
                      key_pressed?
                      jr   Z, forever
                      ay_init
  end

  make_sincos         create_sincos_from_sintable music.sincos, sintable:sintable
  sintable            bytes   neg_sintable256_pi_half_no_zero_lo

  song                import MusicData
  song_end            label

  import              AYMusic, :music, override: { index_table: index_table }
  music_end           label

  NOTES = ay_tone_periods(min_octave:0, max_octave:0)
                      dw NOTES[11]*2
  notes               dw NOTES
end

music = Music.new 0x8000
puts music.debug
program = ZXLib::Basic.parse_source <<-EOC
  10 RANDOMIZE USR #{music[:start]}
9998 STOP: GO TO 10
9999 CLEAR #{music.org-1}: LOAD ""CODE: RUN
EOC
puts program.to_source escape_keywords: true
program.save_tap "music", line: 9999
music.save_tap "music", append: true
Z80::TAP.parse_file('music.tap') do |hb|
    puts hb.to_s
end

Attributes

code[R]

A compiled song module body as a binary string.

index_items[R]

An array containing descriptors of each of the indexed items.

index_offsets[R]

An array containing offset ranges determining the position within the SongModule.code each of the indexed items.

track_offsets[R]

An array containing offset ranges determining the position within the SongModule.code each of the three channel tracks.

Public Instance Methods

to_player_module() click to toggle source

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

# File lib/zxutils/music_box/song.rb, line 345
def to_player_module
        PlayerModule.new(*@track_offsets.map(&:first), @index_offsets.map(&:first), @code)
end
to_program() click to toggle source

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

The returned program exports the following labels:

  • track_a

  • track_b

  • track_c

  • index_table

That can be passed to the the ZXUtils::AYMusic engine. For the complete example, see SongModule.

Additionally these label namespaces are also being exported:

  • track

  • instrument

  • envelope

  • chord

  • mask

And each song's item is being sub-labeled in the according namespaces, so you can identify them in the debug output or use it for some other purpose.

# File lib/zxutils/music_box/song.rb, line 367
def to_program
        code = @code
        track_offsets = @track_offsets
        index_offsets = @index_offsets
        index_items = @index_items
        index_labels = Array.new(index_offsets.length)
        Class.new do
                include Z80
                include Z80::TAP

                export :auto
                track_a       data code.slice(track_offsets[0])
                track_b       data code.slice(track_offsets[1])
                track_c       data code.slice(track_offsets[2])
                index_items.zip(index_offsets).
                sort_by {|item, _| "#{item.type}.#{item.name}"}.
                chunk {|item, _| item.type}.
                each do |type, items|
                        isolate type do
                                items.each do |item, offsets|
                                        index_labels[item.index - 1] = define_label item.name, data(code.slice(offsets))
                                end
                        end
                end
                raise "sanity error" if index_labels.length != index_offsets.length or index_labels.any?(&:nil?)
                index_table   words index_labels
        end
end