OPL synthesis on AY (part 2) djnzx48 8-bit samples Now I will explain the output method which I used in this player.If you are familiar with the chipAY,used in Spectrum 128,you know that each channel, of which there are only three, can reproduce only 16 individual volume levels ( 31 for YM, but this is more complicated and requires use─ using envelopes). So, how is this pose─ allows us to output 8-bit samples? We we can do this by combining thunder levels─ bones from three available channels. Since distance between adjacent thunder levels─ dice varies, we can combine them to create intermediate levels and getting an 8-bit value. This method also has disadvantages. One one of these disadvantages is that each giving a sample requires changing three registers─ ditch, not one, but several registers can be changed instantly. Bye Regis─ three are in an intermediate state, unwantedintermediate levels─ lower volumes that cause distortion. More one drawback is that this me─ This only works for monophonic audio output fromAY.To obtain the correct results on stereo requires a separate 4-bit output procedure. For efficiency we can use table that gives the corresponding com─ bination of levels for each 8-bit value─ sample values. I've tried several different ones approaches to creating such a table. Basic the table accepts0 + 0 + 0as the very bottom─ level andf + f + fas the highest - I tried to select the values manually, to select a more suitable range, which introduces less distortion, but I don't I think the results were so good─ shi. I also tried to use the creature─ sound sample as input data─ useful for weighing the readings so that the most─ Most commonly used sample values were closest to their ideal values. Regardless of the method I use─ shaft, some distortion was inevitable, in mainly those caused by intermediate volume levels when switching from one─ from one level to another. If each sample is calculated as a + b + c,then the general difference between the two successive samples can be modeled as |a1 - a2| + |b1 - b2| + |c1 - c2| if the channels are updated sequentially. I found that if one channel (say A ) applied for the largest of three values, another channel (B) with an average value, and the remaining channel (C) with the lowest value─ eat, then the total distance will be minimal─ valid for all possible combinations of values channels. Output program An example of code using the described al─ algorithm for achieving 8-bit sound, close to the one used in the player: setup: ; select AY registers ld bc,#fffd ;register port AY ld a,7 ;register AY out (c),a ; turn off tone and noise generation ld b,#bf ;data port AY ld a,#3f ;turn off tone and noise out (c), a ; ... play_sample: ;assume that the reading is generated and ;stored in register A ;table of volume levels, one byte each ;for three AY channels for each 8-bit ;counting values: ld h,HIGH output_table ld bc,#fffd ;register port AY ld a,8 ;select channel A out (c),a ld b,#bf ;data port AY ld a,(hl) ;take the value for channel A out (c),a ;set the value there inc l ld b,#ff ;port register AY ld a,9 ;select channel B out (c),a ld b,#bf ;data port AY ld a,(hl) ;take the value for channel B out (c),a ;set the value there inc l ld b,#ff ;port register AY ld a,10 ;select channel C out (c), a ld b,#bf ;data port AY ld a,(hl) ;take the value for channel C out (c),a ;set the value there inc l Using this method we can get simple 8-bit output. But this code is not ideal if we are going to withdraw a thousand─ chi samples per second. Please note how should we switch between port register select and data port eachtimes when we record to the channel. These days─ you: 11-- ---- ---- --0- register selection 10-- ---- ---- --0- data Volume levels we send in AY, represent the four minor bits that do not conflict with two bits─ mi used to distinguish between portsAY. So we can encode part port addressesAY in the table values─ tion and get the following: play_sample: ; our count in register A ld h,HIGH output_table ld bc,#fffd ;register port AY ld a,8 ;select channel A out (c),a ld a,(hl) ;take the value for channel A out (#fd),a ;set the value there inc l ld a,9 ;select channel B out (c),a ld a,(hl) ;take the value for channel B out (#fd),a ;set the value there inc l ld a,10 ;select channel C out (c),a ld a,(hl) ;take the value for channel C out (#fd),a ;set the value there inc l We have now eliminated the need for─ load into register B the required port address, saving 38 cycles per count. But there is more one improvement we can make. When we write to port #fffd, to select─ select the desired registerAY for recording, select─ The earlier register is remembered. If we record─ we put it in only one registerAY,this is a hut─ depends on necessitychoose one and the other same register several times. Currently─ We write data to the channels in order A, B, C, A, B, C,which requires choosing a re─ hyster for each new channel. But what, if we alternate the order of channels? Something like this: count 0: output A, B, C count 1: output C, B, A count 2: output A, B, C count 3: output C, B, A count 4: output A, B, C ...and so on. In this case, the last register, selected during each sample, coincident gives with the first register selected during next sample. We can do this having two different inference procedures that we alternate. Like this: play_sample0: ; our count in register A ld a,(hl) ;take the value for channel A out (#fd),a ;set the value there inc l ld a,9 ;select channelB out (c),a ld a,(hl) ;take the value for channel B out (#fd),a ;set the value there inc l ld a,10 ;select channel C out (c),a ld a,(hl) ;take the value for channel C out (#fd),a ;set the value there inc l ; ... play_sample1: ; our count in register A ld a,(hl) ;take the value for channel C out (#fd),a ;set the value there dec l ld a,9 ;select channel B out (c),a ld a,(hl) ;take the value forchannel B out (#fd),a ;set the value there dec l ld a,8 ;select channel A out (c),a ld a,(hl) ;take the value for channel A out (#fd),a ;set the value there dec l Now we have won additionally19ta─ who, with only5OUT per sample, and not6. There is one more advantage: we no longer you need to reload the value table address volume, just increase and decrease pointer. (This is important! If we change the time─ channel dock, but let's read the volume table in the same order for each ots─ couple, we will cause a sharp buzzing, because channelsAandC quickly change their levels.) There's only one last addition left─ connection to our output program. We must get waveform data from buffer to pa─ crumple, and byewe do it, we can too mix drum samples from offsetIY. In an earlier version I copied samples drums into the buffer with expandedLDI,but this turned out to be too slow. Here's the end─ a separate pair of procedures, each of which takes a total of134cycles and25 byte: ;cycles ;bytes sample_out_routine_ay_mono_0: ; get the countdown data ld a,(hl) ;7 / 7 ;1 / 1 inc l ;4 / 11 ;1 / 2 add a,(iy+0) ;19 / 30 ;3 / 5 ld e,a ;4 / 34 ;1 / 6 ; channel A output ld a,(de) ;7 / 41 ;1 / 7 out (#fd),a ;11 / 52 ;2 / 9 inc d ;4 / 56 ;1 / 10 ; вывод канала B ld a,#09 ;7 / 63 ;2 / 12 out (c),a ;12 / 75 ;2 / 14 ld a,(de) ;7 / 82 ;1 / 15 out (#fd),a ;11 / 93 ;2 / 17 inc d ;4 / 97 ;1 / 18 ; вывод канала C ld a,#0a ;7 / 104 ;2 / 20 out (c),a ;12 / 116 ;2 / 22 ld a,(de) ;7 / 123 ;1 / 23 out (#fd),a ;11 / 134 ;2 / 25 sample_out_routine_ay_mono_1: ; получаем данные отсчёта ld a,(hl) ;7 / 7 ;1 / 1 inc l ;4 / 11 ;1 / 2 add a,(iy+0) ;19 /30 ;3 / 5 ld e,a ;4 / 34 ;1 / 6 ; вывод канала C ld a,(de) ;7 / 41 ;1 / 7 out (#fd),a ;11 / 52 ;2 / 9 dec d ;4 / 56 ;1 / 10 ; вывод канала B ld a,#09 ;7 / 63 ;2 / 12 out (c),a ;12 / 75 ;2 / 14 ld a,(de) ;7 / 82 ;1 / 15 out (#fd),a ;11 / 93 ;2 / 17 dec d ;4 / 97 ;1 / 18 ; вывод канала A ld a,#08 ;7 / 104 ;2 / 20 out (c),a ;12 / 116 ;2 / 22 ld a,(de) ;7 / 123 ;1 / 23 out (#fd),a ;11 / 134 ;2 / 25 Other output methods Along with the output method for mono chips AY,I have developed a program that allows you to use─ use other output methods. To them include the output routine forSpecDrum (essentially an 8-bit DAC providing higher output quality), one for one channel of the microcircuitAY (intended to achieve monophonic playback─ division on a stereo chip, where the combination carried─ how many channels no longer work), and also one for the left and right channels─ catching stereo chipAY (tone channels A, B and C on the left and sampling channel D on the right). Each additional output method is expanded─ nut to make it exactly the same length─ we and the execution time are the same as the first me─ Output code,25byte and134cycle. It's easy─ makes it possible to introducechanges in each program─ me in which it is used, during execution (no need to recompile─ lations). Timings To achieve what the player takes strictly constant time, order execution must be carefully planned─ van. Some branches require more bars than others, so inst─ actions that have no other purpose, cro─ I mean, how to waste time, it turned out to be very convenient─ tee. Of all the instructions available onZ80, EX (SP),HL is completed for the longest time time relative to size in bytes (19 cycles to 1 byte), next - EX (SP),IX ( 23 ticks to 2 bytes). The most universal, in my opinion, is inst─ The manual was ADD HL,HL.Among its useful properties: uses only one byte of memory─ ty, executed in 11 cycles, do not change registers, exceptHL, and there is no appeal to pro─ arbitrary memory address. Other instructions that I found on─ were usefulRLDandRRD (18cycles, by2ba─ yta each), arranged in pairs, potencies─ undesirable effects of left shift are canceled by a subsequent shift to the right. Usually I need delays of5cycles, but they could only be obtained with the help conditionalRETwith a false condition. For such cases were thoroughly checked─ ka to make sure that the condition cannot be true! This table shows a list of useful things─ No instructions for synchronization are in order efficiency (measured by the ratio ta─ who to bytes). The clock bytes command spoils efficiency ======= ===== ===== ============= ====== EX (SP),HL 19 1 19 (SP),HL ADD HL,rr 11 1 11 HL,F RRD/RLD 18 2 9 (HL),AF CPI 16 2 8 HL,BC CP (HL) 7 1 7 F LD A,(rr) 7 1 7 A INC rr 6 1 6 rr JR $+2 12 2 6 RET cc 5 1 5 LD A,R 9 2 4.5 AF NOP 4 1 4 Memory usage Along with clocks, memory is also scarce resource, and it needs to be effective─ effectively used to store more several 8-bit audio samples. When speed takes priority, some memory will inevitably be used deployed cycles, but I still found ways to save─ save several hundred bytes in different places. Of all the things that can be spent wasted memory, tables with aligned data─ nym are probably one of the most new.Table aligned to256bytes spendsup to 255 extra bytes of memory, or in average 127.5 bytes. To smooth out the problem I moved all tables whose size was equal256 byte (or a multiple of it) to the beginning of the bank pa─ wrinkle. This allows you to keep them together without wasting extra space. What about aligned tables of length less256 bytes? Placing them next to each other friend creates useless unused space. In my case I wanted to have ability to perform efficient indexing─ tion with the low byte of the address (to avoid─ costly arithmetic), so these the tables had to fit within 256 bytes. However, I realized that none of them doesn't really need alignment tobeginning of the 256-byte segment. Thus, I was able to place these ta─ blitzes more or less anywhere in prog─ frames. The assembler macro issues a warning─ change if the table accidentally crosses 256-byte boundary, this makes it possible know when to look for another place to table placement. The only special one─ ity associated withWith this approach, I conclude The problem is that the indexes in these tables are not completely predictable and not based on the base, equal to 0, but since the assembler generates these indexes are at the compilation stage, then in prin─ Overall this is not a big problem. B in general, leaving tables unaligned there were significant memory savings. Conclusions This project didn't turn out quite like that. how I naively imagined him at the beginning development two years ago, but in some he turned out to be better in relations, and I I learned a lot during the development process. His true potential is yet to come properly used, basically due to the lack of a tracker (music come─ elk manually write in the form of instructions DB ). But sooner or later this may change─ to be. Will there be further experiments with sound for Speccy? Stay tuned and find out! (Or maybe not...)
Share your thoughts about the article