News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Square SNES SPC Engine  (Read 22616 times)

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #20 on: December 24, 2013, 06:44:36 am »
Short update:

After more than a month, I finally could dedicate a morning to this. Good progress, I think. And, Bregalad: You were right about the four-level-music loop stuff.

Chippy2000

  • Jr. Member
  • **
  • Posts: 76
  • I'm the CREATOR Pokemon Gold Beta/Alpha Revival!
    • View Profile
Re: Square SNES SPC Engine
« Reply #21 on: December 24, 2013, 02:18:58 pm »
I think my brain just exploded from this stuff  :o
COO of The Chippy Cooperation (TCC)

I'm...just here...floating in space and stuff...

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #22 on: March 22, 2014, 12:12:55 pm »
I think my brain just exploded from this stuff  :o

That's for what we are here. :D ;)

Short question to Bregalad (or someone else, but I guess he's the only one who knows it :D):

Most of the time X contains the channel number, multiplicated by 2 (channel 0: 0, c1: 2, c2:4 ... c7:$0E).

1) Do I get it right? X also can be bigger than $10?
2) (Do I get it right?)² Is this the way to tell the program that that channel is used for a Sound Effect if it is bigger than $10? (e. g. $1C = channel 6, playing a Sound Effect)
3) (Do I get it right?)³ All the channel-value-storage-registers (e. g. $FE40, X) are always in a range of $20 bytes, so that for example "$FE40, X" covers the area $FE40 to $FE5F? (Wait, this question has automatically the same answer as question 1... o_O )

Bregalad

  • Hero Member
  • *****
  • Posts: 2755
    • View Profile
Re: Square SNES SPC Engine
« Reply #23 on: March 22, 2014, 03:23:56 pm »
Yeah you got it right, the engines always have 16 logical tracks, the last 8 alias the first 8 and uses the same physical sound channels. The first 8 are typically used for music and the last 8 for sound effects, but who knowns, nothing makes this an absolute rule.

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #24 on: March 22, 2014, 03:46:48 pm »
Yeah you got it right, the engines always have 16 logical tracks, the last 8 alias the first 8 and uses the same physical sound channels. The first 8 are typically used for music and the last 8 for sound effects, but who knowns, nothing makes this an absolute rule.

By the way: On what did you found the comments you added?
Did you really read this directly (and solely) from the code,
or did you experiment ("I force now this value in this registers, and -look!- that happened!") and estimated it's purpose afterwards? Because I'm still struggling with getting out how you came to all the conclusions you wrote in the comments. The funny thing is: Everything I found out in the last months (what was quite... nothing) was based on the fact that I claimed a priori that "everything Bregalad wrote was without a doubt right". Not a very scientific approach, I know ;) ... but the most successful at hand. :D

Right now (I mean: Right now!) I am looking at the stuff at $0B68 onwards. Seems to be the big outputter. The one big subroutine that transfers the stuff in the temp stores ($FED0+X et al.) to the actual DSP-registers. But, darn, is that convoluted. :(

March 22, 2014, 04:50:15 pm - (Auto Merged - Double Posts are not allowed before 7 days.)
Sadly, your first comment on the stuff at $0B68 was right, again.

Here is, how far I got tonight:

Code: [Select]
0B68: 7D          MOV      A, X ;Compute and write volume to DSP
0B69: 28 0F       AND      A, #$0F
0B6B: C4 34       MOV      $34, A ; ly: A = 0000.dcba
0B6D: 9F          XCN      A ; ly: A = dcba.0000
0B6E: 5C          LSR      A ; ly: A = 0dcb.a000
0B6F: C4 35       MOV      $35, A
0B71: E4 B2       MOV      A, $B2 ; ly: Active Channel Flag register
0B73: 24 C9       AND      A, $C9
0B75: D0 03       BNE      $0B7A ; ly: Leave out the JMP-command if the active Channel is the one in $C9
0B77: 5F 0A 0C    JMP      $0C0A
0B7A: 8F 80 36    MOV      $36, #$80
0B7D: 03 B1 1C    BBS      B0 $B1, $0B9C ; ly: Leave out the following step if the LSB of $B1 is set
0B80: F5 41 FC    MOV      A, $FC41+X
0B83: 60          CLRC
0B84: 95 E1 FD    ADC      A, $FDE1+X
0B87: 2D          PUSH     A
0B88: F5 E1 FD    MOV      A, $FDE1+X
0B8B: AE          POP      A
0B8C: 30 06       BMI      $0B94 ; ly: Branch if A is between #$80 and #$FF
0B8E: 90 08       BCC      $0B98 ; ly: Branch if A is between #$00 and #$7F and Carry is clear
0B90: E8 FF       MOV      A, #$FF ; ly: This happens if A is between #$00 and #$7F and Carry is set
0B92: 2F 04       BRA      $0B98
0B94: B0 02       BCS      $0B98 ; ly: Branch if A is between #$80 and #$FF and Carry is set
0B96: E8 00       MOV      A, #$00 ; ly: This happens if A is between #$80 and #$FF and Carry is clear
0B98: 48 FF       EOR      A, #$FF ; ly: BEFORE the EOR is executed (the result after the EOR is at the end of the line):
; ly: A is #$00 if it was between #$80 and #$FF + c=0 = #$FF
; ly: A is between #$00 and #$7F if it was between #$00 and #$7F + c=0 = #$80 to #$FF
; ly: A is between #$80 and #$FF if it was between #$80 and #$FF + c=1 = #$00 to #$7F
; ly: A is #$FF if it was between #$00 and #$7F + c=1 = #$00
0B9A: C4 36       MOV      $36, A ; ly: Store this convoluted mess
0B9C: F5 01 FC    MOV      A, $FC01+X
0B9F: FD          MOV      Y, A
0BA0: C4 37       MOV      $37, A
0BA2: F5 01 FD    MOV      A, $FD01+X
0BA5: 1C          ASL      A
0BA6: F0 10       BEQ      $0BB8 ; ly: Branch if $FD01+X = #%x000.0000
0BA8: 90 03       BCC      $0BAD ; ly: Branch if $FD01+X = #%0xxx.xxxx (a positive number)
0BAA: 48 FF       EOR      A, #$FF ; ly: this is only executed if $FD01+X is between #$81 and #$FF
0BAC: BC          INC      A ; ly: This turns a negative number into a positive
0BAD: CF          MUL      YA ; ly: Multiply $FC01+X with two times of the absolute value of $FD01+X
0BAE: B0 08       BCS      $0BB8 ; ly: Branch if $FD01+X = #%1xxx.xxxx (a negative number)
0BB0: DD          MOV      A, Y ; ly: Only the high byte of the result is relevant...?
0BB1: 84 37       ADC      A, $37 ; ly: Add $FC01+X to the result's high byte (see lines $0B9C and $0BA0)
0BB3: 90 02       BCC      $0BB7 ; ly: Branch if the result is lower than #$100
0BB5: E8 FF       MOV      A, #$FF ; ly: If the result of the addition is higher than #$FF, YA= #$FFFF
0BB7: FD          MOV      Y, A ; ly: If the result of the addition was lower, than that byte is now both in Y and A
0BB8: C8 10       MOV      X, #$10
0BBA: B0 0E       BCS      $0BCA ; ly: Branch if the result of the addition was higher than #$FF
0BBC: E4 84       MOV      A, $84
0BBE: CF          MUL      YA
0BBF: E5 88 FF    MOV      A, $FF88
0BC2: 74 5D       CMP      A, $5D+X ; ly: Compare with Instrument Source Number
0BC4: F0 07       BEQ      $0BCD ; ly: Branch if they are the same
0BC6: E4 AC       MOV      A, $AC
0BC8: 2F 02       BRA      $0BCC
0BCA: E8 90       MOV      A, #$90
0BCC: CF          MUL      YA
0BCD: CB 37       MOV      $37, Y
0BCF: C8 10       MOV      X, #$10
0BD1: 90 06       BCC      $0BD9
0BD3: AA B1 60    MOV1     C, $60B1 ; ly: Carry = bit 6 of B1
0BD6: CA 34 00    MOV1     $0034, C ; ly: bit 0 of 34 = Carry
0BD9: E4 36       MOV      A, $36
0BDB: FD          MOV      Y, A
0BDC: E4 37       MOV      A, $37
0BDE: CF          MUL      YA
0BDF: E4 B2       MOV      A, $B2
0BE1: 24 B3       AND      A, $B3
0BE3: F0 02       BEQ      $0BE7 ; ly: Branch if current channel is not in $B3-AND-Mask
0BE5: 8D 00       MOV      Y, #$00 ; ly: If it is, $00CD/E is cleared
0BE7: DD          MOV      A, Y
0BE8: EB 34       MOV      Y, $34
0BEA: D6 CD 00    MOV      $00CD+Y, A ; ly: This changes either CD or CE, depending on bit 6 of B1 (see line $0BD3)
0BED: 5C          LSR      A
0BEE: FD          MOV      Y, A
0BEF: E4 35       MOV      A, $35
0BF1: C8 10       CMP      X, #$10
0BF3: 90 05       BCC      $0BFA
0BF5: 73 B1 02    BBC      B3 $B1, $0BFA ; ly: Branch if Bit 3 of $B1 is clear
0BF8: 48 01       EOR      A, #$01 ; ly: If Bit 3 of $B1 is set, EOR the output address with #$01
0BFA: C4 F2       MOV      $F2, A ; ly: Sets DSP register address
0BFC: CB F3       MOV      $F3, Y ; ly: Put Y into the DSP address that is in A
0BFE: E4 36       MOV      A, $36
0C00: 48 FF       EOR      A, #$FF
0C02: EA 34 00    NOT1     $0034 ; ly: Invert bit 0 of $34
0C05: AB 35       INC      $35
0C07: 33 35 D1    BBC      B1 $35, $0BDB ; ly: Branch if Bit 1 of $35 is clear
0C0A: 22 35       SET1     $35, 1
0C0C: E4 B2       MOV      A, $B2
0C0E: 24 CA       AND      A, $CA
0C10: F0 43       BEQ      $0C55 ; ly: Jump to exit if current Channel isn't the one in $CA

I should re-work this before I go to bed...

Just so far:
Line $0B68: A contains X, X contains the channel number * 2
Line $0B6E: A contains #%0dcb.a000, where dcba are bits 4-0 of X. Thus, a is always = 0 (because of the * 2), and the higher nybble is the number of the active channel (minus the +#$10 for This-Channel-Is-Used-As-A-SFX).
Line $0B6F: This value gets stored in $35 and remains there unchanged for a long while.

Line $0BEF: $35 gets loaded again
Line $0BF5/8: If Bit 3 of $B1 is set, The address in A gets EORed with #$01
This means: Is Bit 3 of $B1 set, this changes the value in the volume for the right side, else it changes the volume for the left side
Line $0BFA/C: Output.



Sadly, you are right, this is only used for DSP-registers $x0 and $x1 for Channel x. :(
« Last Edit: March 22, 2014, 04:50:15 pm by lytron »

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #25 on: March 22, 2014, 11:39:46 pm »
I wish I saw this thread sooner.

I just did a bunch of disassembling with Secret of Mana's music engine (I'm working on a general purpose SPC editor and am starting with that game)

My notes are here:

https://www.dropbox.com/s/mnruanfhptp458o/SoM_spc_disassembly.zip

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #26 on: March 23, 2014, 02:38:10 am »
I wish I saw this thread sooner.

I just did a bunch of disassembling with Secret of Mana's music engine (I'm working on a general purpose SPC editor and am starting with that game)

My notes are here:

https://www.dropbox.com/s/mnruanfhptp458o/SoM_spc_disassembly.zip

Cool stuff! Am I allowed to unite your notes with ours? I guess, we would be pretty much done, then. :)

March 23, 2014, 03:16:35 am - (Auto Merged - Double Posts are not allowed before 7 days.)
Oh boy! You're missing a lot of code (but you know already).
Code goes to $1417, the first non-code data starts at $1418

Here a intermediate result-excerpt of my ROM map (of Bregalad's log file):

Code: [Select]
; $0C5E-$0CAE - FUNCTION: (?) Trembolo-/Vibrato-Related
; $0CAF-$0CC6 - FUNCTION: REACT TO 5A22: Do something or not?
; $0CC7-$0CF0 - FUNCTION: REACT TO 5A22: Case decider, jump to Sub-Function
; $0CF1-$0????- FUNCTION: REACT TO 5A22:

; $0FF9-$100E - FUNCTION: REACT TO 5A22:

; $11EA-$1213 - FUNCTION: REACT TO 5A22: Wait-/Transfer-Commands
; $1214-$1236 - FUNCTION: 11EA-FUNCTION: Transfer three bytes at once
; $1237-$1250 - FUNCTION: 11EA-FUNCTION: Transfer two bytes at once
; $1251-$1262 - FUNCTION: 11EA-FUNCTION: Transfer one byte at once
; $1263-$126F - FUNCTION: 11EA-FUNCTION: Wait
; $1270-$12AB - FUNCTION: 11EA-FUNCTION: Internal SPC RAM transfer

; $12DE-$132E - FUNCTION: Echo Initialisation (+ Changing of the Echo Buffer address [= size]), Starting Timer 1 & 2
; $132F-$138D - FUNCTION: Clears Echo Buffer, waits until new Echo Buffer is accepted by Hardware, transfer Echo FIR Filter Coefficient set
; $138E-$13E4 - FUNCTION: Echo the multiplication results of four OUTX-registers back to the 5A22

; $1418-$1427 - Jump Addresses (16-bit-values) for $11EA

And here some disassembled code with my comments so far (could not post all, max. 15000 characters per post ;)):

Code: [Select]
0CAF: F8 F4       MOV      X, $F4 ;Communication with main CPU
0CB1: F0 3D       BEQ      $0CF0 ; ly: If $F4 (this is $2140 on the 65816/5A22 side) is empty, exit

0CB3: BA F6       MOVW     YA, $F6 ; ly: Else, store the content of the four I/O registers in $B4-$B7
0CB5: DA B6       MOVW     $B6, YA
0CB7: BA F4       MOVW     YA, $F4
0CB9: DA B4       MOVW     $B4, YA
0CBB: 68 83       CMP      A, #$83 ; ly: A can contain the value of either $F4 or $F5, I expect it to be $F4
0CBD: D0 02       BNE      $0CC1
0CBF: CB F5       MOV      $F5, Y ; ly: The value of Y is only given back to $F5 if $F4 = #$83
0CC1: C4 F4       MOV      $F4, A
0CC3: 64 F4       CMP      A, $F4
0CC5: F0 FC       BEQ      $0CC3 ;Wait responce from main CPU ; ly: Wait until the 5A22 gives back the value stored in $F4

; ly: JUMP TABLE:
; ly: (X = A = value in $F4/$2140)
; ly: X = #$01: $0CF1
; ly: X = #$02: $0E10
; ly: X = #$FE: $11EA
: ly: Everything else between X = #$00 and #$0F: $0CF0 (EXIT)
: ly: Everything else between X = #$10 and #$1F: $0EF6
: ly: Everything else between X = #$20 and #$7F: $0CF0 (EXIT)
: ly: Everything else between X = #$80 and #$8F: $0FF9
: ly: Everything else between X = #$90 and #$EF: $0FF9 (But does nothing instantly exit)
: ly: Everything else between X = #$F0 and #$FF: $0FF9
0CC7: 5D          MOV      X, A
0CC8: 10 0D       BPL      $0CD7 ; ly: Branch if A/X is between #$00 and #$7F
0CCA: C8 FE       CMP      X, #$FE ; ly: A/X is between #$80 and #$FF
0CCC: D0 03       BNE      $0CD1
0CCE: 5F EA 11    JMP      $11EA ;?
0CD1: 8F 00 F4    MOV      $F4, #$00
0CD4: 5F F9 0F    JMP      $0FF9 ;?
0CD7: C8 01       CMP      X, #$01 ; ly: A/X is between #$00 and #$7F
0CD9: F0 16       BEQ      $0CF1
0CDB: 8F 00 F4    MOV      $F4, #$00
0CDE: C8 02       CMP      X, #$02
0CE0: D0 03       BNE      $0CE5
0CE2: 5F 10 0E    JMP      $0E10
0CE5: C8 10       CMP      X, #$10
0CE7: 90 07       BCC      $0CF0
0CE9: C8 20       CMP      X, #$20
0CEB: B0 03       BCS      $0CF0
0CED: 5F F6 0E    JMP      $0EF6
0CF0: 6F          RET

; ly: The following command has to receive two double-bytes through $2142/$2143
; ly: In the first double-byte, $2142 has to contain the Echo Delay value. The ...
; ly: ... bits 4-7 of this value will be removed.
; ly: The second double-byte is used when the subroutine at $132F is called.
; ly: $2142 gets stored in $C7 (is the Echo FIR filter coefficient set, only Bits 0 & 1 are relevant.
; ly: $2143 gets stored in $C8 (Echo Feedback value, DSP-Register $0D).

; ly: Before anything gets done with these value, all channels get a key-off and ...
; ly: ... the SPC waits 0.008 sec (= one tick on a 125 Hz timer) for this key-off to happen.
0CF1: E8 FF       MOV      A, #$FF ; ly: Key Off all voices
0CF3: 8D 5C       MOV      Y, #$5C
0CF5: 3F 1B 06    CALL     $061B
0CF8: 8F 00 F1    MOV      $F1, #$00 ; ly: Disable all Timers
0CFB: 8F 40 FA    MOV      $FA, #$40 ; ly: Set Timer 0 divider to #$40 (125 Hz)
0CFE: 8F 01 F1    MOV      $F1, #$01 ; ly: Enable Timer 0
0D01: E4 FD       MOV      A, $FD ; ly: ... useless line?
0D03: E4 FD       MOV      A, $FD
0D05: F0 FC       BEQ      $0D03 ; ly: Wait until the first tick of Timer 0
0D07: 8F 00 F1    MOV      $F1, #$00 ; ly: Disable all Timers
0D0A: 8F 24 FA    MOV      $FA, #$24 ; ly: Set Timer 0 divider to #$24 (222 Hz)
0D0D: 8F 01 F1    MOV      $F1, #$01 ; ly: Enable Timer 0
0D10: F8 F4       MOV      X, $F4
0D12: E4 F6       MOV      A, $F6
0D14: 28 0F       AND      A, #$0F ; ly: $DF = Lower nybble of $F6(/$2142)
0D16: C4 DF       MOV      $DF, A
0D18: E4 F7       MOV      A, $F7
0D1A: C4 A7       MOV      $A7, A ; ly: $A7 = $F7(/$2143) (New Echo volume for left and right side)
0D1C: D8 F4       MOV      $F4, X
0D1E: 3E F4       CMP      X, $F4
0D20: F0 FC       BEQ      $0D1E ; ly: Wait until the 5A22 responses
0D22: F8 F4       MOV      X, $F4 ; ly: LOAD SECOND DOUBLE-BYTE FROM $F6/7
0D24: BA F6       MOVW     YA, $F6
0D26: C4 C8       MOV      $C8, A ; ly: $C8 = $F6
0D28: CB C7       MOV      $C7, Y ; ly: $C7 = $F7
0D2A: D8 F4       MOV      $F4, X
0D2C: 3E F4       CMP      X, $F4
0D2E: F0 FC       BEQ      $0D2C ; ly: Wait until the 5A22 responses
0D30: 3F DE 12    CALL     $12DE ; ly: Setup new Echo Buffer size according to $DF
0D33: 3F EA 11    CALL     $11EA
0D36: E8 00       MOV      A, #$00
0D38: FD          MOV      Y, A
0D39: DA AD       MOVW     $AD, YA ; ly: Clear the double-byte that gets transfered through $F6/$F7 to the 5A22
0D3B: DA BC       MOVW     $BC, YA ; ly:
0D3D: DA BE       MOVW     $BE, YA ; ly:
0D3F: DA C0       MOVW     $C0, YA ; ly:
0D41: C4 B8       MOV      $B8, A ; ly: Clear Echo enable
0D43: C4 B9       MOV      $B9, A ; ly: Clear Noise enable
0D45: C4 BA       MOV      $BA, A ; ly: Clear Pitch modulation
0D47: 32 B1       CLR1     $B1, 1
0D49: DA AF       MOVW     $AF, YA ; ly: Clear Key on/Key off
0D4B: C4 90       MOV      $90, A
0D4D: E2 90       SET1     $90, 7 ; ly: Sets global song tempo ($90) to #$80
0D4F: C4 97       MOV      $97, A
0D51: C4 9C       MOV      $9C, A
0D53: E2 9C       SET1     $9C, 7
0D55: C4 A3       MOV      $A3, A
0D57: DA C9       MOVW     $C9, YA ; ly:
0D59: DA C2       MOVW     $C2, YA
0D5B: C4 C4       MOV      $C4, A
0D5D: DA CB       MOVW     $CB, YA
0D5F: 8F 01 7D    MOV      $7D, #$01
0D62: 8F FF 7E    MOV      $7E, #$FF
0D65: 8F FF AC    MOV      $AC, #$FF
0D68: E3 B1 02    BBS      B7 $B1, $0D6D ; ly: Branch if Bit 7 of $B1 is set
0D6B: C4 B3       MOV      $B3, A
0D6D: 3F 2F 13    CALL     $132F ; ly: New Echo buffer initialization
0D70: 73 04 03    BBC      B3 $04, $0D76 ; ly: If Bit 3 of $04 is clear, branch
0D73: 5F DD 0D    JMP      $0DDD ; ly: Reset

0D76: CD 10       MOV      X, #$10
0D78: F5 01 1A    MOV      A, $1A01+X ; ly: Transfer Channel Data pointers from $1A02 to $1A11
0D7B: D4 0B       MOV      $0B+X, A ; ly: Bytewise transfer loop
0D7D: 1D          DEC      X
0D7E: D0 F8       BNE      $0D78 ; ly: This loop is left when X reaches 0, so $1A01 is never read and it is never something stored in $0B in this loop

0D80: E5 00 1A    MOV      A, $1A00 ; ly: Transfer $1A00 to $06
0D83: C4 06       MOV      $06, A
0D85: E5 01 1A    MOV      A, $1A01 ; ly: Transfer $1A01 to $07
0D88: C4 07       MOV      $07, A
0D8A: E8 14       MOV      A, #$14
0D8C: 8D 1A       MOV      Y, #$1A
0D8E: 9A 06       SUBW     YA, $06 ; ly: YA = #$1A14 - value of $06
0D90: DA 06       MOVW     $06, YA
0D92: CD 0E       MOV      X, #$0E ; ly: Setting up the downwards counter for the loop from $D9F to $DBC
0D94: 8F 80 B2    MOV      $B2, #$80 ; ly: Setting up a "bitwise counter". This can be decremented through...
; ly: ... Logical Shift Right eight times, before it is = 0 (see lines $DBA and $DBC)
0D97: E5 12 1A    MOV      A, $1A12
0D9A: EC 13 1A    MOV      Y, $1A13
0D9D: DA 34       MOVW     $34, YA
0D9F: F4 0C       MOV      A, $0C+X
0DA1: FB 0D       MOV      Y, $0D+X
0DA3: 5A 34       CMPW     YA, $34 ; ly: Compare $34/5 with $0C/D+X (Channel's Song Data Pointer)
0DA5: F0 11       BEQ      $0DB8 ; ly: If equal: Skip this channel
0DA7: 09 B2 AD    OR       $B2, $AD
0DAA: 7A 06       ADDW     YA, $06
0DAC: D4 0C       MOV      $0C+X, A
0DAE: DB 0D       MOV      $0D+X, Y
0DB0: E8 FF       MOV      A, #$FF
0DB2: C5 88 FF    MOV      $FF88, A
0DB5: 3F E5 0D    CALL     $0DE5
0DB8: 1D          DEC      X ; ly: Decrement X twice: Next channel
0DB9: 1D          DEC      X
0DBA: 4B B2       LSR      $B2 ; ly: $B2 was set to #$80 in line $0D94, so this loop gets repeated eight times
0DBC: D0 E1       BNE      $0D9F

0DBE: E4 B7       MOV      A, $B7
0DC0: 28 F0       AND      A, #$F0
0DC2: 9F          XCN      A
0DC3: 8D 11       MOV      Y, #$11
0DC5: CF          MUL      YA
0DC6: C4 84       MOV      $84, A
0DC8: E4 B6       MOV      A, $B6
0DCA: 28 F0       AND      A, #$F0
0DCC: C4 B5       MOV      $B5, A
0DCE: E4 B7       MOV      A, $B7
0DD0: 28 0F       AND      A, #$0F
0DD2: 04 B5       OR       A, $B5
0DD4: C4 B5       MOV      $B5, A
0DD6: E8 81       MOV      A, #$81
0DD8: C4 B4       MOV      $B4, A
0DDA: 3F 11 10    CALL     $1011
0DDD: CD FF       MOV      X, #$FF ; ly: Reset stack
0DDF: BD          MOV      SP, X
0DE0: E4 FD       MOV      A, $FD ; ly: Empty Timer 0 tick counter
0DE2: 5F 79 02    JMP      $0279 ; ly: Jump back to the beginning of the Main Loop

0DE5: 7D          MOV      A, X ; ly: Setting up the repition level counter
0DE6: 1C          ASL      A ; ly: $5C (channel 0) = #$00, $5E (channel 1) = #$04
0DE7: D4 5C       MOV      $5C+X, A ; ly: $60 (channel 2) = #$08 and so on
0DE9: E8 00       MOV      A, #$00
0DEB: D5 20 01    MOV      $0120+X, A
0DEE: D5 80 FD    MOV      $FD80+X, A
0DF1: D5 81 FD    MOV      $FD81+X, A
0DF4: D5 21 01    MOV      $0121+X, A
0DF7: D5 40 01    MOV      $0140+X, A
0DFA: D5 41 01    MOV      $0141+X, A
0DFD: D5 E0 FD    MOV      $FDE0+X, A
0E00: D5 E1 FD    MOV      $FDE1+X, A
0E03: D5 40 FD    MOV      $FD40+X, A
0E06: D5 61 FD    MOV      $FD61+X, A
0E09: D5 80 FC    MOV      $FC80+X, A
0E0C: BC          INC      A
0E0D: D4 3C       MOV      $3C+X, A
0E0F: 6F          RET

0E10: FA B5 2C    MOV      $2C, $B5 ; ly: value of $F5
0E13: 8F 00 2D    MOV      $2D, #$00
0E16: 0B 2C       ASL      $2C ; ly: The value in $2C/D gets multiplicated by 4
0E18: 2B 2D       ROL      $2D
0E1A: 0B 2C       ASL      $2C
0E1C: 2B 2D       ROL      $2D
0E1E: E8 00       MOV      A, #$00 ; ly: Add #$2C00 to value in $2C/D
0E20: 8D 2C       MOV      Y, #$2C
0E22: 7A 2C       ADDW     YA, $2C
0E24: DA 2C       MOVW     $2C, YA ; ly: Store the result in $2C/D
0E26: CD 00       MOV      X, #$00
0E28: 8D 01       MOV      Y, #$01
0E2A: F7 2C       MOV      A, [$2C]+Y
0E2C: D0 09       BNE      $0E37
0E2E: 8D 03       MOV      Y, #$03
0E30: F7 2C       MOV      A, [$2C]+Y
0E32: F0 17       BEQ      $0E4B ; ly: Exit If both addresses are empty
0E34: DC          DEC      Y
0E35: 2F 08       BRA      $0E3F
0E37: 8D 03       MOV      Y, #$03
0E39: F7 2C       MOV      A, [$2C]+Y
0E3B: D0 0F       BNE      $0E4C ; ly: Execute stuff beneath if both addresses are not empty
0E3D: 8D 00       MOV      Y, #$00 ; ly: If the first address was not empty but the second...
0E3F: F7 2C       MOV      A, [$2C]+Y
0E41: C4 30       MOV      $30, A ; ly: ... load $2C/$2C+1 into $30/1...
0E43: FC          INC      Y
0E44: F7 2C       MOV      A, [$2C]+Y
0E46: C4 31       MOV      $31, A
0E48: 5F 11 0F    JMP      $0F11 ; ly: ... and Execute $F11
0E4B: 6F          RET
« Last Edit: March 23, 2014, 03:16:35 am by lytron »

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #27 on: March 23, 2014, 08:42:24 am »
Quote
Cool stuff! Am I allowed to unite your notes with ours? I guess, we would be pretty much done, then.

Absolutely.  You're welcome to do whatever you want with it.

Quote
Oh boy! You're missing a lot of code (but you know already).

Yeah.  I just played one of the SPCs through a sort of code/data logger and only disassembled the code that this particular song executed.  So I'm missing a bunch of other stuff.

I sort of stopped with it once I got enough data to decode the bulk of the music format.

I don't know how much of that you already had figured out, but I figure it's best to share what I can.  =P

Bregalad

  • Hero Member
  • *****
  • Posts: 2755
    • View Profile
Re: Square SNES SPC Engine
« Reply #28 on: March 23, 2014, 09:00:13 am »
Back then I figured out the music format first, without doing any disassembly. Only then I've disassembled the engine.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #29 on: March 24, 2014, 12:35:09 am »
I have a few holes in my score outline if you can fill them in.  There's like 8 or so commands I didn't quite figure out.

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #30 on: March 24, 2014, 07:05:45 am »
I have a few holes in my score outline if you can fill them in.  There's like 8 or so commands I didn't quite figure out.

I'll pastebin and PM you my current status of Bregalad's file in about six hours.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #31 on: March 24, 2014, 12:31:56 pm »
Cool.

No rush, though.  I'm at work and won't be able to check it until tonight anyway.  And I probably won't even be able to apply the information for at least another week.

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #32 on: March 24, 2014, 03:32:56 pm »
No rush, though.  I'm at work and won't be able to check it until tonight anyway.  And I probably won't even be able to apply the information for at least another week.

Hehe... I could not have sent it to you on another time today, because then I was at work. ;)
I won't have the time to do anything on this, too, till the next weekend. And I don't think that I will have a lot time then, too. I hope we won't do the same work twice if we both work on this... :(

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #33 on: March 24, 2014, 04:32:43 pm »
Quote
I hope we won't do the same work twice if we both work on this

What are you doing with it?

I've moved passed the disassembling and am working on applying the info.

My current project is a generic SPC editor (like a SPC tracker) that is game-agnostic.  Game specific details are going to be stored externally in Lua scripts, so adding support for other games can be done (and importing/exporting stuff between games would be feasible).

Secret of Mana is just the starter game.

JCE3000GT

  • Sr. Member
  • ****
  • Posts: 433
    • View Profile
Re: Square SNES SPC Engine
« Reply #34 on: March 24, 2014, 05:19:19 pm »
I'm so looking forward to this.   :beer: 

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #35 on: March 25, 2014, 01:47:32 am »
What are you doing with it?

I've moved passed the disassembling and am working on applying the info.

Ah, okay. I thought you would keep disassembling (as I do, because I am not confident enough to work with this in an ASM-program).

My current project is a generic SPC editor (like a SPC tracker) that is game-agnostic.  Game specific details are going to be stored externally in Lua scripts, so adding support for other games can be done (and importing/exporting stuff between games would be feasible).

Secret of Mana is just the starter game.

Cool project, but this might be a really big project: You have to disassemble each game's sound engine from scratch and rack your brain how to transfer everything.

But, BTW, I think SoM is a good game to start with and to use as the fundament, because it is drafted to use all 8 sound channels; all of SD3s music, for example, is only 6 channels. I expect the sound engine to only be able to play music on these 6 channels (and the remaining two are reserved for sound effects), so porting an 8-channel-song into that engine might blow it up. ;)
I think you should prepare your program this way, like: "This game has only 6 channels and your song has 8 channels. Which should be left out?"

Furthermore: I have to do the sound engine of Ihatovo Monogatari someday, and I expect it to be (for the most part) the same as of other Hect-games (timeless gaming jewels such as... Throroughbred Breeder, Part I - MCCIX). As soon as that is done, I'll let you know.

Bregalad

  • Hero Member
  • *****
  • Posts: 2755
    • View Profile
Re: Square SNES SPC Engine
« Reply #36 on: March 25, 2014, 03:01:13 am »
Quote
My current project is a generic SPC editor (like a SPC tracker) that is game-agnostic.
Sounds like almost impossible to me, considering the already enormous differences between just the diferent versions of Square's engine.

Also, Seiken Densestu 3 (as well as Bahamut Lagoon and Super Mario RPG) uses a completely different engine which is not related to the main series of Square's sound engines, it was probably outsourced.

Disch

  • Hero Member
  • *****
  • Posts: 2814
  • NES Junkie
    • View Profile
Re: Square SNES SPC Engine
« Reply #37 on: March 25, 2014, 11:06:45 am »
@Bregalad:

The similarities/differences in the underlying music engine really don't matter.

All music engines boil down to the same general idea:

- you have a sequence of "note/rest" commands
- intertwined with jump/loop commands
- with the occasional configuration command (switch instrument, change volume, etc)

The details of how that is stored in a game may drastically differ from game to game... but that's basically the bottom line of how they all work.  The offsets, and storage format of that data are just details that can be scripted externally.

Writing new Lua scripts to add new game support will certainly not be a cakewalk.  It'll definitely take some considerable work and effort... but it'll be much easier than writing a full editor.


@lytron:

Awesome.  Maybe once I get this editor off the ground we can tag team.  You can discover music formats in games, and I'll write the Lua scripts to support them in the editor  ;P  hahaha

We'll see.  Baby steps.  Let's see if I can finish this editor first.

Bregalad

  • Hero Member
  • *****
  • Posts: 2755
    • View Profile
Re: Square SNES SPC Engine
« Reply #38 on: March 25, 2014, 11:26:29 am »
Well, it's good news if that's a "detail", but it doesn't quite sounds like so. Without those "details" we would only be able to make music at a constant volume, without enveloppe nor panning nor vibratoes nor any other extra effect. It'd sound quite crude.

For instance, FF4 and Romancing Saga does feature "software enveloppes", something that they decided to drop later. FF4 is the only Square game to feature partially incomplete note (they'll turn to "release" state slightly before their end). The later revisions supported native encoding for drums, and Chrono Trigger even features an instrument with auto-pulse width modulation (the PWM frequency is constant (done by changing the BRR sample) and doesn't follow the note's pitch).

Those are certainly not "details that can easily be scrapped/abstracted", as the FF4 engine would be unable to replicate Chrono Trigger's music accurately and vice-versa (for example), despite both belonging to the same series of sound engines. If you switch among totally different sound engines it'll only get worse.

lytron

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
    • pantalytron.com
Re: Square SNES SPC Engine
« Reply #39 on: March 25, 2014, 11:30:38 am »
You can test if it is so easy: :P ;)
Take this and make the Green Brinstar Music appear at Gaia's Navel!

I'm really interested how you plan to do the stuff with porting an 8-channel-song in a 6-channel-engine (as mentioned above).
Furthermore: Will you add some sample changing / adding stuff?