module ZXUtils::AYMusic::Macros
AYMusic
engine utilities.¶ ↑
- NOTE
-
Some of the
AYMusic
Macros
requireZ80::MathInt::Macros
and some requireZXLib::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
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 finalret
will be added andbranch_not_finished
option will default toret
.
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
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 theAYMusic
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
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 useret
instruction on finish.
- NOTE
-
This macro requires
Z80::MathInt::Macros
to be also imported ifplay
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
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 thebc
registers' content is preserved since.
io_ay
-
A namespace label containing
ay_sel
,ay_inp
anday_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
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 useret
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