module ZXLib::AYSound::Macros

ZXLib::AYSound macros.

The AYSound Macros provide functions to create note tables and some basic routines to interact with the AY-3-891x registers.

To interact with the AY-3-891x registers you may either:

  1. Import labels from the ZXLib::Sys and optionally override the io_ay option of the appropriate routines with either the fuller_io for the Fuller Box or ioT2k for the Timex 2068 I/O ports. The default for io_ay is io128 (ZX Spectrum 128k).

  2. Create a namespace label io_ay with the corresponding sub-labels:

ns :io_ay do
  ay_sel  addr 0xFFFD # out: select a register 0-15.
  ay_inp  addr 0xFFFD # in: read the value of the selected register
  ay_out  addr 0xBFFD # out: write data to the selected register
end

To override a default io_ay for the AYSound Macros create a class method which returns the appropriate namespace label.

def self.io_ay
  case $select_io_ay
  when :fuller then fuller_io
  when :timex then ioT2k
  when :k128 then io128
  else
    # this will return io_ay namespace label defined previously, e.g. by ZXLib::Sys
    define_label :io_ay
  end
end

Example:

require 'zxlib/ay_sound'
require 'zxlib/sys'
require 'zxlib/basic'

class PlayScales
  include Z80
  include Z80::TAP
  include ZXLib

  label_import    Sys
  macro_import    AYSound
  macro_import    MathInt

  NOTES = ay_tone_periods

  start           call mute_sound
                  ay_set_volume 0, 15, bc_const_loaded:true
                  xor  a
  play_loop       push af
                  call get_note_de
                  ay_set_tone_period(0, tph:d, tpl:e, bc_const_loaded:true)
                  ld   a, 10
                  call wait_periods
                  pop  af
                  inc  a
                  cp   NOTES.length
                  jr   C, play_loop
                  call mute_sound
                  ret

  mute_sound      ay_init
                  ret

  ns :wait_periods do
    wloop         halt
                  dec  a
                  jr   NZ, wloop
                  ret
  end

  get_note_de     ld   hl, notes
                  add  a
                  adda_to h,l
                  ld   e, [hl]
                  inc  hl
                  ld   d, [hl]
                  ret

  notes           words NOTES
end

playscales = PlayScales.new 0xC000
puts playscales.debug
program = ZXLib::Basic.parse_source <<-EOC
  10 RANDOMIZE USR #{playscales[:start]}
9998 STOP: GO TO 10
9999 CLEAR #{playscales.org-1}: LOAD ""CODE: RUN
EOC
program.save_tap "playscales", line: 9999
playscales.save_tap "playscales", append: true

Public Instance Methods

ay_expand_notes(notes=hl, octaves:8, half_tones:12) click to toggle source

Creates a routine for expanding the note to AY-3-891x tone period table to a higher number of octaves.

Elements of the table are 2-byte words holding the AY-3-891x tone period values for each consecutive note.

  • notes

    An address of the beginning of the lowest octave of the notes to expand. The memory should be pre-filled with tone period values for the 1st octave. See: Macros.ay_tone_periods.

Options:

  • octaves

    A number of octaves, between 2 and 8, the notes should be expanded to.

  • half_tones

    A number of half-tones per one octave.

T-states: 41 + 98*(octaves-1)*half_tones (default options: 8273)

Example:

           ay_expand_notes(notes)
           ...

notes      dw ay_tone_periods(min_octave:0, max_octave:0)
notes_end  union notes[8*12], 2

Modifies: af, bc, de, hl.

# File lib/zxlib/ay_sound.rb, line 308
def ay_expand_notes(notes=hl, octaves:8, half_tones:12)
  raise ArgumentError, "octaves out of range: #{octaves} (2-8)" unless (2..8).include?(octaves)
  raise ArgumentError, "half_tones should be an Integer" unless Integer === half_tones
  num_notes_to_extend = (octaves-1)*half_tones
  raise ArgumentError, "half_tones out of range" unless (1..256).include?(num_notes_to_extend)
  isolate do
                  ld   hl, notes unless notes==hl
                  ld16 de, hl
                  ld   bc, half_tones*2
                  add  hl, bc
                  ld   a, num_notes_to_extend
    eloop         ex   de, hl
                  ld   c, [hl]
                  inc  hl
                  ld   b, [hl] # bc = notes[i]
                  inc  hl
                  ex   de, hl
                  inc  bc
                  srl  b
                  rr   c       # bc = (bc + 1)/2
                  ld   [hl], c
                  inc  hl
                  ld   [hl], b # notes[i+half_tones] = (notes[i] + 1)/2
                  inc  hl
                  dec  a
                  jr   NZ, eloop
  end
end
ay_expand_notes_faster(notes=hl, octaves:8, half_tones:12, save_sp:true, disable_intr:true, enable_intr:true) click to toggle source

Creates a routine for expanding the note to AY-3-891x tone period table to a higher number of octaves. This is a faster version of the Macros.ay_expand_notes that uses a stack pointer register for reading notes, so it requires that the interrupts are disabled while it's being run.

Elements of the table are 2-byte words holding the AY-3-891x tone period values for each consecutive note.

  • notes

    An address of the beginning of the lowest octave of the notes to expand. The memory should be pre-filled with tone period values for the 1st octave. See: Macros.ay_tone_periods.

Options:

  • octaves

    A number of octaves, between 2 and 8, the notes should be expanded to.

  • half_tones

    A number of half-tones per one octave.

  • save_sp

    A boolean flag indicating that the sp register should be saved and restored. Otherwise sp will point to the beginning of the last octave of the table.

  • 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.

NOTE

Restoring the sp register uses self-modifying code.

T-states: 39 + 71*(octaves-1)*half_tones + 30*save_sp + 4*disable_intr + 4*enable_intr (default options: 6041)

Example:

           ay_expand_notes_faster(notes)
           ...

notes      dw ay_tone_periods(min_octave:0, max_octave:0)
notes_end  union notes[8*12], 1

Modifies: af, b, de, hl and optionally sp.

# File lib/zxlib/ay_sound.rb, line 370
def ay_expand_notes_faster(notes=hl, octaves:8, half_tones:12, save_sp:true, disable_intr:true, enable_intr:true)
  raise ArgumentError, "can't enable interrupts without restoring the sp register first" if enable_intr and !save_sp
  raise ArgumentError, "octaves out of range: #{octaves} (2-8)" unless (2..8).include?(octaves)
  raise ArgumentError, "half_tones should be an Integer" unless Integer === half_tones
  num_notes_to_extend = (octaves-1)*half_tones
  raise ArgumentError, "half_tones out of range" unless (1..256).include?(num_notes_to_extend)
  isolate do
                  ld   b, num_notes_to_extend
                  ld   hl, notes unless notes==hl
                  ld   [restore_sp + 1], sp if save_sp
                  di if disable_intr
                  ld   sp, hl
                  ld   de, half_tones*2
                  add  hl, de
    eloop         pop  de      # de = notes[i]
                  inc  de
                  srl  d
                  rr   e       # de = (de + 1)/2
                  ld   [hl], e
                  inc  hl
                  ld   [hl], d # notes[i+half_tones] = (notes[i] + 1)/2
                  inc  hl
                  djnz eloop
    restore_sp    ld   sp, 0 if save_sp
                  ei if enable_intr
  end
end
ay_get_register_value(regn=a, regv=e, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that reads a specific AY-3-891x register's value.

  • regn

    A AY-3-891x register index as an integer or a 8-bit CPU register.

  • regv

    An 8-bit CPU register to receive a value from a AY-3-891x register.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc and regv.

# File lib/zxlib/ay_sound.rb, line 517
def ay_get_register_value(regn=a, regv=e, bc_const_loaded:false, io_ay:self.io_ay)
  raise ArgumentError unless register?(regv)
  isolate do
    if Integer === regn || [a,b,c].include?(regn)
                  ld   a, regn unless regn == a
    end
    if bc_const_loaded
                  ay_io_swap2sel_bc(io_ay)
    else
                  ld   bc, io_ay.ay_sel
    end
    if Integer === regn || [a,b,c].include?(regn)
                  out (c), a
    else
                  out (c), regn
    end
                  ay_io_swap2inp_bc(io_ay)
                  inp regv, (c)
  end
end
ay_get_set_env_shape(sinp=a, sout=sinp, bc_const_loaded:false, io_ay:self.io_ay) { |eoc| ... } click to toggle source

Creates a routine that gets the AY-3-891x envelope shape's value applies a block of code and sets the value back. The block of code should not modify bc register pair.

  • sinp

    An 8-bit CPU register to receive the shape's value.

  • sout

    An 8-bit CPU register that will contain a value which will be written back to the register.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc and sinp.

# File lib/zxlib/ay_sound.rb, line 772
def ay_get_set_env_shape(sinp=a, sout=sinp, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do |eoc|
                  ay_get_register_value(AYSound::ENV_SHAPE, sinp, bc_const_loaded:bc_const_loaded, io_ay:io_ay)
                  yield eoc
                  ay_io_swap2out_bc(io_ay)
                  out (c), sout
  end
end
ay_get_set_mixer(vinp=a, vout=vinp, bc_const_loaded:false, io_ay:self.io_ay) { |eoc| ... } click to toggle source

Creates a routine that gets the AY-3-891x mixer's value applies a block of code and sets the mixer value back. The block of code should not modify bc register pair.

  • vinp

    An 8-bit CPU register to receive the mixer's value.

  • vout

    An 8-bit CPU register that will contain a value which will be written back to the mixer.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Example:

ay_get_set_mixer(a) do |eoc|
  bit  0, a
  # jumping to the +eoc+ label will cancel writing mixer's value back.
  jr   NZ, eoc
  ora  0b00000001
end

Modifies: af, bc and vinp.

# File lib/zxlib/ay_sound.rb, line 630
def ay_get_set_mixer(vinp=a, vout=vinp, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do |eoc|
                  ay_get_register_value(AYSound::MIXER, vinp, bc_const_loaded:bc_const_loaded, io_ay:io_ay)
                  yield eoc
                  ay_io_swap2out_bc(io_ay)
                  out (c), vout
  end
end
ay_hz2tp(hz, clock_hz:AYSound::CLOCK_HZ) click to toggle source

Converts a frequency given in Hz to AY-3-891x tone period value.

Options:

  • clock_hz

    AY-3-891x clock frequency in Hz.

# File lib/zxlib/ay_sound.rb, line 262
def ay_hz2tp(hz, clock_hz:AYSound::CLOCK_HZ)
  (clock_hz / (16.0 * hz)).round
end
ay_init(t:e, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that sets volume of all AY-3-891x sound channels to 0, disables noise on all channels and enables tone output on all channels.

This effectively mutes all sound.

Options:

  • t

    A temporary 8-bit register, one of: d, e, h or l.

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, t, bc.

# File lib/zxlib/ay_sound.rb, line 593
def ay_init(t:e, bc_const_loaded:false, io_ay:self.io_ay)
  raise ArgumentError unless register?(t) and [d,e,h,l].include?(t)
  isolate do
                  ld   a, AYSound::VOLUME_C
                  ay_io_load_const_reg_bc(io_ay) unless bc_const_loaded
                  ld   t, 0
    vol_res_loop  ay_set_register_value(a, t, bc_const_loaded:true, io_ay:io_ay)
                  dec  a
                  cp   AYSound::MIXER
                  jr   NZ, vol_res_loop
                  ay_get_register_value(a, a, bc_const_loaded:true, io_ay:io_ay)
                  anda 0b11000000
                  ora  0b00111000
                  ay_io_swap2out_bc(io_ay)
                  out  (c), a
  end
end
ay_io_load_const_reg_bc(io_ay=self.io_ay) click to toggle source

Creates a routine that loads a constant 8-bit part of the AY-3-891x I/O addresses into b or c register.

Run this routine before running one of:

to get a full AY-3-891x I/O address of the specific chip function into bc register pair.

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: b or c.

# File lib/zxlib/ay_sound.rb, line 412
def ay_io_load_const_reg_bc(io_ay=self.io_ay)
  ay_reg_combined = ((io_ay.ay_out ^ io_ay.ay_sel) | (io_ay.ay_inp ^ io_ay.ay_sel))
  isolate do
    select(ay_reg_combined & 0xFF00, &:zero?).then do |eoc|
          ld   b, io_ay.ay_sel >> 8
    end.else_select(ay_reg_combined & 0x00FF, &:zero?).then do |eoc|
          ld   c, io_ay.ay_sel & 0x00FF
    end.else do
      raise ArgumentError, "ay_out, ay_inp and ay_sel should have different only 8-bit msb or lsb"
    end
  end
end
ay_io_swap2inp_bc(io_ay=self.io_ay) click to toggle source

Creates a routine that loads a specific 8-bit part of the AY-3-891x input addresses into b or c register.

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Example:

ay_io_load_const_reg_bc
...
ay_io_swap2sel_bc
ld   a, ZXLib::AYSound::VOLUME_A
out  (c), a        # select VOLUME_A register
ay_io_swap2inp_bc
inp  e, (c)        # get a value from the selected register

Modifies: b or c.

# File lib/zxlib/ay_sound.rb, line 466
def ay_io_swap2inp_bc(io_ay=self.io_ay)
  ay_reg_combined = ((io_ay.ay_out ^ io_ay.ay_sel) | (io_ay.ay_inp ^ io_ay.ay_sel))
  select(ay_reg_combined & 0xFF00, &:zero?).then do |eoc|
        ld   c, io_ay.ay_inp & 0x00FF
  end.else_select(ay_reg_combined & 0x00FF, &:zero?).then do |eoc|
        ld   b, io_ay.ay_inp >> 8
  end.else do
    raise ArgumentError, "ay_out, ay_inp and ay_sel should have different only 8-bit msb or lsb"
  end
end
ay_io_swap2out_bc(io_ay=self.io_ay) click to toggle source

Creates a routine that loads a specific 8-bit part of the AY-3-891x output addresses into b or c register.

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Example:

ay_io_load_const_reg_bc
...
ay_io_swap2sel_bc
ld   a, ZXLib::AYSound::VOLUME_A
out  (c), a        # select VOLUME_A register
ay_io_swap2out_bc
out  (c), e        # set a value of the selected register

Modifies: b or c.

# File lib/zxlib/ay_sound.rb, line 440
def ay_io_swap2out_bc(io_ay=self.io_ay)
  ay_reg_combined = ((io_ay.ay_out ^ io_ay.ay_sel) | (io_ay.ay_inp ^ io_ay.ay_sel))
  select(ay_reg_combined & 0xFF00, &:zero?).then do |eoc|
        ld   c, io_ay.ay_out & 0x00FF
  end.else_select(ay_reg_combined & 0x00FF, &:zero?).then do |eoc|
        ld   b, io_ay.ay_out >> 8
  end.else do
    raise ArgumentError, "ay_out, ay_inp and ay_sel should have different only 8-bit msb or lsb"
  end
end
ay_io_swap2sel_bc(io_ay=self.io_ay) click to toggle source

Creates a routine that loads a specific 8-bit part of the AY-3-891x select addresses into b or c register.

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Example:

ay_io_load_const_reg_bc
...
ay_io_swap2sel_bc
ld   a, ZXLib::AYSound::MIXER
out  (c), a        # select MIXER register
ay_io_swap2inp_bc
inp  a, (c)        # get a value from the selected register
anda 0b11000000
ora  0b00111000    # apply mask
ay_io_swap2out_bc
out  (c), a        # set a value of the selected register

Modifies: b or c.

# File lib/zxlib/ay_sound.rb, line 496
def ay_io_swap2sel_bc(io_ay=self.io_ay)
  ay_reg_combined = ((io_ay.ay_out ^ io_ay.ay_sel) | (io_ay.ay_inp ^ io_ay.ay_sel))
  select(ay_reg_combined & 0xFF00, &:zero?).then do |eoc|
        ld   c, io_ay.ay_sel & 0x00FF
  end.else_select(ay_reg_combined & 0x00FF, &:zero?).then do |eoc|
        ld   b, io_ay.ay_sel >> 8
  end.else do
    raise ArgumentError, "ay_out, ay_inp and ay_sel should have different only 8-bit msb or lsb"
  end
end
ay_set_envelope_duration(dh=d, dl=e, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that sets a AY-3-891x envelope duration.

  • dh

    The most significant 8 bits of the 16-bit envelope duration value as an integer or an 8-bit CPU register except b, c or a. This value is known as the envelope coarse duration value.

  • dl

    The least significant 8 bits of the 16-bit envelope duration value as an integer or an 8-bit CPU register except b, c or a. This value is known as the envelope fine duration value.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc.

# File lib/zxlib/ay_sound.rb, line 752
def ay_set_envelope_duration(dh=d, dl=e, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do
                  ld   a, AYSound::ENV_PER_FINE
                  ay_set_register_value(a, dl, bc_const_loaded:bc_const_loaded, io_ay:io_ay)
                  inc  a
                  ay_set_register_value(a, dh, bc_const_loaded:true, io_ay:io_ay)
  end
end
ay_set_noise_pitch(pitch=e, pitch_8bit:false, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that sets a AY-3-891x noise pitch.

  • pitch

    A pitch level or an 8-bit register except b, c or a.

Options:

  • pitch_8bit

    True if the pitch is in the range: 0..255. False if the pitch is in the range: 0..31.

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc.

# File lib/zxlib/ay_sound.rb, line 719
def ay_set_noise_pitch(pitch=e, pitch_8bit:false, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do
    ay_set_register_value(AYSound::NOISE_PERIOD, pitch, bc_const_loaded:bc_const_loaded, io_ay:io_ay) do |_|
      if Integer === pitch
        if pitch_8bit
                  ld   a, (pitch>>3) & 0x1F
        else
                  ld   a, pitch & 0x1F
        end
                  out  (c), a
      elsif pitch_8bit
                  ld   a, pitch
                  3.times { rrca }
                  out  (c), a
      else
                  out  (c), pitch
      end
    end
  end
end
ay_set_register_value(regn=a, regv=e, bc_const_loaded:false, io_ay:self.io_ay) { |eoc| ... } click to toggle source

Creates a routine that writes a value to a specific AY-3-891x register.

If the block is given, the code it creates will be evaluated after the AY-3-891x register has been selected and bc registers has been loaded with an “output” AY-3-891x I/O address. The regv value will not be output in this instance and the regv argument will be ignored. Instead the code created by the block should perform writing out the value with out (c), regv.

  • regn

    A AY-3-891x register index as an integer or a 8-bit CPU register.

  • regv

    A value to write to a AY-3-891x register as an integer or an 8-bit CPU register except b, c or a.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc.

# File lib/zxlib/ay_sound.rb, line 554
def ay_set_register_value(regn=a, regv=e, bc_const_loaded:false, io_ay:self.io_ay)
  raise ArgumentError if [b,c].include?(regv) or (regv == a and regn != 0)
  isolate do |eoc|
    if Integer === regn || [a,b,c].include?(regn)
                  ld   a, regn unless regn == a
    end
    if bc_const_loaded
                  ay_io_swap2sel_bc(io_ay)
    else
                  ld   bc, io_ay.ay_sel
    end
    if Integer === regn || [a,b,c].include?(regn)
                  out (c), a
    else
                  out (c), regn
    end
                  ay_io_swap2out_bc(io_ay)
    if block_given?
                  yield eoc
    elsif Integer === regv
                  ld   a, regv
                  out (c), a
    else
                  out (c), regv
    end
  end
end
ay_set_tone_period(ch=a, tph:d, tpl:e, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that sets a AY-3-891x channel's tone period.

  • ch

    A channel number 0..2 as an integer, label, or an 8-bit CPU register.

  • tph

    The most significant 4 bits of the 12-bit tone period value as an integer or an 8-bit CPU register except b, c or a. This value is known as the coarse tone period value.

  • tpl

    The least significant 8 bits of the 12-bit tone period value as an integer or an 8-bit CPU register except b, c or a. This value is known as the fine tone period value.

Options:

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc.

# File lib/zxlib/ay_sound.rb, line 652
def ay_set_tone_period(ch=a, tph:d, tpl:e, bc_const_loaded:false, io_ay:self.io_ay)
  isolate do
    if Integer === ch
      if ch.zero?
                  xor  a
      else
                  ld   a, ch << 1
      end
    else
                  ld   a, ch unless ch == a
                  add  a
    end
                  ay_set_register_value(a, tpl, bc_const_loaded:bc_const_loaded, io_ay:io_ay)
                  inc  a
                  ay_set_register_value(a, tph, bc_const_loaded:true, io_ay:io_ay)
  end
end
ay_set_volume(ch=a, vol=e, vol_8bit:false, bc_const_loaded:false, io_ay:self.io_ay) click to toggle source

Creates a routine that sets a AY-3-891x channel's volume level.

  • ch

    A channel number 0..2 as an integer, label, or an 8-bit CPU register.

  • vol

    A volume level or an 8-bit register except b, c or a.

Options:

  • vol_8bit

    True if the volume is in the range: 0..255. False if the volume is in the range: 0..15.

  • bc_const_loaded

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

  • io_ay

    A label with ay_sel, ay_inp and ay_out sub-labels addressing the AY-3-891x I/O bus.

Modifies: af, bc.

# File lib/zxlib/ay_sound.rb, line 681
def ay_set_volume(ch=a, vol=e, vol_8bit:false, bc_const_loaded:false, io_ay:self.io_ay)
  raise ArgumentError if [a,b,c].include?(vol)
  isolate do
    if Integer === ch
                  ld   a, ch + AYSound::VOLUME_A
    else
                  ld   a, ch unless ch == a
                  add  AYSound::VOLUME_A
    end
    ay_set_register_value(a, vol, bc_const_loaded:bc_const_loaded, io_ay:io_ay) do |_|
      if Integer === vol
        if vol_8bit
                  ld   a, (vol>>4) & 0x0F
        else
                  ld   a, vol & 0x0F
        end
      else
                  ld   a, vol
        if vol_8bit
                  4.times { rrca }
        end
                  anda 0x0F
      end
                  out  (c), a
    end
  end
end
ay_tone_periods(min_octave:0, max_octave:7, notes_hz:self.equal_tempered_scale_notes_hz, clock_hz:AYSound::CLOCK_HZ) click to toggle source

Returns a tone period array for the AY-3-891x chip.

Options:

  • min_octave

    A minimal octave number, 0-based.

  • max_octave

    A maximal octave number, 0-based.

  • notes_hz

    An array of tone frequencies (in Hz) in the 5th octave (0-based: 4). By default equal_tempered_scale_notes_hz is being used to generate frequencies.

  • clock_hz

    The AY-3-891x clock frequency in Hz.

# File lib/zxlib/ay_sound.rb, line 274
def ay_tone_periods(min_octave:0, max_octave:7, notes_hz:self.equal_tempered_scale_notes_hz, clock_hz:AYSound::CLOCK_HZ)
  (min_octave..max_octave).map do |octave|
    notes_hz.map do |hz|
      hz = (hz * 2.0**(octave-4))
      tp = ay_hz2tp(hz, clock_hz:clock_hz)
      raise ArgumentError, "tone period out of range: #{tp} (#{hz} Hz)" unless (1..4095).include?(tp)
      tp
    end
  end.flatten
end
equal_tempered_scale_notes_hz(hz:440, n0:0, steps:12) click to toggle source

Returns an array of equal tempered scale frequencies from a given base frequency.

See

pages.mtu.edu/~suits/NoteFreqCalcs.html

Options:

  • hz

    base frequency in Hz.

  • n0

    an index from the base frequency to the first note in the table: 0 is for “A”, -9 for “C”.

  • steps

    how many half tones, 12 is the default.

# File lib/zxlib/ay_sound.rb, line 254
def equal_tempered_scale_notes_hz(hz:440, n0:0, steps:12)
  (0...steps).map {|n| hz.to_f * 2.0**((n + n0).to_f/steps.to_f) }
end