module ZXUtils::AYMusic::Macros

AYMusic engine utilities.

NOTE

Some of the AYMusic Macros require Z80::MathInt::Macros and some require ZXLib::AYSound::Macros to be imported.

require 'zxutils/ay_music'

class Program
  include Z80
  macro_import    MathInt
  label_import    ZXLib::Sys
  macro_import    ZXLib::AYSound
  macro_import    ZXUtils::AYMusic
  ...
end

Public Instance Methods

ay_music_finished?(music_control, compact:false, subroutine:false, branch_not_finished: :eoc) click to toggle source

Creates a routine that detects if the currently played music is finished.

As a result ZF is 1 if all of the main music track cursors has reached the end.

music_control

A label of the type MusicControl addressing the data structure.

Options:

  • compact

    Pass true for a more compact routine (36..39 vs 61..71 bytes), which is slower and using more registers.

  • subroutine

    Pass true for a subroutine, where a final ret will be added and branch_not_finished option will default to ret.

  • branch_not_finished

    Pass an optional address, possibly as a label, where to branch when music is not finished. Pass :ret to return from a subroutine in this instance.

Modifies: af, hl, (b, de if compact is true).

# File lib/zxutils/ay_music.rb, line 470
def ay_music_finished?(music_control, compact:false, subroutine:false, branch_not_finished: :eoc)
  isolate do |eoc|
    branch_not_finished = :ret if subroutine && branch_not_finished == :eoc
    branch_not_fin = case branch_not_finished
    when :eoc then -> { jr   NZ, eoc }
    when :ret then -> { ret  NZ }
    else
                   -> { jr   NZ, branch_not_finished }
    end
    if compact
                      ld   hl, music_control.chan_a.track.cursor_hi
                      ld   b, 3
      ckloop          ld   d, [hl] # cursor_hi
                      dec  hl      # -> cursor_lo
                      ld   e, [hl] # cursor_lo
                      ld   a, [de]
                      anda a
                      branch_not_fin[]
                      dec  hl      # -> delay_hi
                      ld   d, [hl] # delay_hi
                      dec  hl      # -> delay_lo
                      ld   e, [hl] # delay_lo
                      ld   a, e
                      ora  d
                      branch_not_fin[]
                      dec  hl      # -> track_stack_hi
                      ld   d, [hl] # track_stack_hi
                      dec  hl      # -> track_stack_lo
                      ld   e, [hl] # track_stack_lo
                      ex   de, hl
                      ora  [hl]
                      inc  hl
                      ora  [hl]
                      inc  hl
                      ora  [hl]
                      branch_not_fin[]
                      ex   de, hl
                      ld   de, +ChannelControl + +TrackControl - 1
                      add  hl, de
                      djnz ckloop
    else
                      xor  a
      (0..2).each do |ch|
                      ld   hl, [music_control.chans[ch].track.cursor]
                      cp   [hl]
                      branch_not_fin[]
      end
      (0..2).each do |ch|
                      ld   hl, [music_control.chans[ch].track.delay]
                      ld   a, l
                      ora  h
                      branch_not_fin[]
      end
      (0..2).each_with_index do |ch, i|
                      ld   hl, [music_control.chans[ch].track.track_stack]
                      ora  [hl]
                      inc  hl
                      ora  [hl]
                      inc  hl
                      ora  [hl]
                      branch_not_fin[] unless i == 2 && (branch_not_finished == :eoc || (branch_not_finished == :ret && subroutine))
      end
    end
                      ret if subroutine
  end
end
ay_music_init(track_a, track_b, track_c, index_table:nil, init:self.init, play:self.play, music_control:self.music_control, disable_intr:true, enable_intr:true) click to toggle source

Creates a routine that initializes music tracks and optionally the index lookup table.

Provide addresses of the main tracks as track_a, track_b and track_c.

Options:

  • index_table

    An address of the index lookup table associated with tracks as an integer, a label or a pointer. Required if AYMusic::READ_ONLY_CODE is true.

  • init

    A label addressing the AYMusic.init routine.

  • play

    A label addressing the AYMusic.play routine.

  • music_control

    A label of the type MusicControl addressing the data structure used by the AYMusic routines.

  • disable_intr

    A boolean flag indicating that the routine should disable interrupts. Provide false only if you have already disabled the interrupts.

  • enable_intr

    A boolean flag indicating that the routine should enable interrupts. Provide false if you need to perform more uninterrupted actions.

Modifies: af, bc, de, hl, ix.

# File lib/zxutils/ay_music.rb, line 274
def ay_music_init(track_a, track_b, track_c, index_table:nil, init:self.init, play:self.play, music_control:self.music_control, disable_intr:true, enable_intr:true)
  raise ArgumentError unless address?(track_a) and !pointer?(track_a) and
                             address?(track_b) and !pointer?(track_b) and
                             address?(track_c) and !pointer?(track_c) and
                             (index_table.nil? or address?(index_table)) and
                             label?(init) and label?(play) and label?(music_control)
  raise ArgumentError, "index_table is required with READ_ONLY_CODE" if AYMusic::READ_ONLY_CODE and index_table.nil?
  isolate do
                      di if disable_intr
                      call init
                      dw   track_a, track_b, track_c
                      ei if enable_intr
    if index_table
                      ld   hl, index_table unless index_table == hl
      if AYMusic::READ_ONLY_CODE
                      ld   [music_control.index_table], hl
      else
                      ld   [play.index_table_p], hl
      end
    end
  end
end
ay_music_note_to_fine_tone_cursor_table_factory(note_to_cursor, play:nil, num_notes:AYMusic::MAX_NOTES_COUNT, subroutine:false) click to toggle source

Creates a routine that builds a note-to-fine tones index table.

note_to_cursor

An address of the table to be built (max 192 bytes).

Options:

  • play

    An optional label addressing the AYMusic player's iteration routine (code re-use optimisation).

  • num_notes

    An optional number of notes to cover which is by default its maximum value: 96.

  • subroutine

    Pass true to use ret instruction on finish.

NOTE

This macro requires Z80::MathInt::Macros to be also imported if play option is not provided.

Modifies: af, bc, de, hl and 1 level of stack.

# File lib/zxutils/ay_music.rb, line 336
def ay_music_note_to_fine_tone_cursor_table_factory(note_to_cursor, play:nil, num_notes:AYMusic::MAX_NOTES_COUNT, subroutine:false)
  raise ArgumentError, "num_notes should be between 1 and #{AYMusic::MAX_NOTES_COUNT}" unless (1..AYMusic::MAX_NOTES_COUNT).include?(num_notes)
  isolate do |eoc|
                      ld   bc, (0<<8)|12
                      ld   hl, note_to_cursor
    floop             ld   a, b         # index = note * 256 * 32 / 12
                      cp   num_notes
    if subroutine
                      ret  NC
    else
                      jr   NC, eoc
    end
                      push bc
                      3.times { rrca }  # note * 32
                      ld   d, a         # 000ddddd eee00000
                      anda 0b11100000
                      ld   e, a
                      xor  d
                      ld   d, a
                      ex   de, hl
    if play.nil?                        # note * 32 / 12
                      divmod h, c, check0:false, check1:false, optimize: :size
                      divmod l, c, clrrem:false, optimize: :size
    else
                      call play.divmod_hl_c
    end
                      ld   h, l         # note * 256 * 32 / 12
                      ld   l, 0
    if play.nil?
                      divmod l, c, clrrem:false, optimize: :size
    else
                      call play.divmod_hl_c.divmod_rem_l_c
    end
                      ex   de, hl
                      ld   [hl], e
                      inc  hl
                      ld   [hl], d
                      inc  hl
                      pop  bc
                      inc  b
                      jr   floop
  end
end
ay_music_preserve_io_ports_state(music_control, play, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that reads a state of the I/O ports from the AY-3-891x chip and stores it into the player's register mirror.

Execute this code before each play iteration if you care about preserving the AY general purpose I/O ports' state.

music_control

A label of the type MusicControl addressing the data structure.

play

A label addressing the AYMusic player's iteration routine.

Options:

  • bc_const_loaded

    If ZXLib::AYSound::Macros.ay_io_load_const_reg_bc has been already run and the bc registers' content is preserved since.

  • io_ay

    A namespace label containing ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x output and select/input I/O bus.

NOTE

This macro requires ZXLib::AYSound::Macros to be also imported.

Modifies: af, bc, de.

# File lib/zxutils/ay_music.rb, line 315
def ay_music_preserve_io_ports_state(music_control, play, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do
                      ay_get_register_value(AYMusic::MIXER, a, bc_const_loaded:bc_const_loaded, io_ay:io_ay)
                      ld   de, music_control.ay_registers.mixer
                      ld   b, 0b00111111
                      call play.apply_mask_de
  end
end
ay_music_tone_progress_table_factory(fine_tones, hz: 440, clock_hz: ZXLib::AYSound::CLOCK_HZ, subroutine:false) click to toggle source

Creates a routine that builds a fine tones to AY-3-891x tone periods table.

fine_tones

An address of the fine tones table to be built (512 bytes).

Options:

  • hz

    base (middle) frequency in Hz.

  • clock_hz

    AY-3-891x clock frequency in Hz.

  • subroutine

    Pass true to use ret instruction on finish.

NOTE

This macro requires Z80::MathInt::Macros to be also imported.

Modifies: af, af', hl, hl', b, bc', de, de'.

# File lib/zxutils/ay_music.rb, line 392
def ay_music_tone_progress_table_factory(fine_tones, hz: 440, clock_hz: ZXLib::AYSound::CLOCK_HZ, subroutine:false)
  # CLOCK_HZ=3.5469/2 * 1_000_000
  # STEPS=256
  # aaa=->(notes, steps) {2**(notes.to_f/steps.to_f/12.0)}
  # a=aaa[-12,STEPS]
  #
  # x0=(CLOCK_HZ / (16.0 * 440.0/STEPS.to_f)).round
  # xf0=(CLOCK_HZ / (16.0 * 440.0/(STEPS.to_f/8)))
  # fc=->(steps){[2**(-1.0/steps.to_f),(CLOCK_HZ/(16.0*440.0/steps.to_f)).round,(CLOCK_HZ/(16.0*440.0/(steps.to_f/8)))]}
  # a,x0,xf0 = fc[STEPS]
  # a0=(a*65536).round
  # d1=->(fi){delta=0;(STEPS+1).times.inject(x0){|x,i| puts "#{i.to_s.rjust(4)}:#{x1=x>>3} #{x2=(xf0*a**i).round} #{delta+=(x2-x1).abs}"; ((x*a0)+fi)>>16};delta}
  # dd=->(fi){delta=0;(STEPS+1).times.inject(x0){|x,i| x1=x>>3; x2=(xf0*a**i).round; delta+=(x2-x1).abs; ((x*a0)+fi)>>16}; delta }
  # dd[16384+8192-673]
  # (-1024..1024).map{|i| [i,dd[16384+8192+i]]}
  #
  # a=2**(1.0/steps.to_f)
  # a0=((a-1.0)*65536).round
  # x0=(CLOCK_HZ / (16.0 * 440.0/STEPS.to_f)/2.0).round
  # xf0=(CLOCK_HZ / (16.0 * 440.0/(STEPS.to_f/8))/2.0)
  # d1=->(fi){delta=0;(STEPS+1).times.inject(x0){|x,i| puts "#{i.to_s.rjust(4)}:#{x1=(x+4)>>3} #{x2=(xf0*a**i).round} #{delta+=(x2-x1).abs}"; x+(((x*a0)+fi)>>16)};delta}
  # dd=->(fi){delta=0;(STEPS+1).times.inject(x0){|x,i| x1=(x+4)>>3; x2=(xf0*a**i).round; delta+=(x2-x1).abs; x+(((x*a0)+fi)>>16)};delta}
  # (0..1024).map{|i| [i,dd[16384+2048+i]]}
  # d1[16384+2048+594]
  steps = 256
  af = 2**(-1.0/steps.to_f)
  a0 = (af*65536).round
  fi = 16384+8192-673 # calculated numerically for given steps
  # xf = (clock_hz/(16.0*hz.to_f/(steps.to_f/8.0)))
  x0 = (clock_hz/(16.0*hz.to_f/steps.to_f)).round
  # x=((x*a0)+fi)>>16; x>>3
  isolate do |eoc|
                      ld   b, steps
                      exx
                      ld   de, fine_tones
                      ld   hl, x0
    mloop             ld16 bc, hl
                      ld   a, 3
    adjloop           srl  b
                      rr   c
                      dec  a
                      jr   NZ, adjloop
                      ex   de, hl
                      ld   [hl], c
                      inc  hl
                      ld   [hl], b
                      inc  hl
                      ex   de, hl
                      exx
                      dec  b
    if subroutine
                      ret  Z
    else
                      jr   Z, eoc
    end
                      exx
                      ld   bc, a0
                      mul16_32 bc, tt:de, clrhlhl:fi, optimize: :size
                      jr   mloop
  end
end