Explore the Gigatron music engine, using GLCC

Using, learning, programming and modding the Gigatron and anything related.
Forum rules
Be nice. No drama.
denjhang
Posts: 54
Joined: 02 May 2021, 01:25
Location: yuenan
Contact:

Re: Explore the Gigatron music engine, using GLCC

Post by denjhang »

at67 wrote: 15 Mar 2022, 15:50 The issue here is that it is way more powerful than you think, experiment more, lament less...

I should be asleep, but I am annoyed, why am I annoyed? Who the F knows, anyway here is a video of code that I wrote in the last 45minutes that conclusively proves what the Gigatron is capable of, if you refuse to accept this proof, then I give up.

https://www.youtube.com/watch?v=-RmvJREUqcU

Here is the entirety of the source code written in gtBASIC, there is no faking of the waveform, the oscilloscope samples address 0x13, (XOUT), as fast as it can to give a representation of the audio being generated.

Good luck :/

P.S. Yes it uses ROMvX0 instructions, but that is completely irrelevant, the same thing works with any ROM, just much less efficiently in terms of vCPU cycles.

Code: Select all

_runtimePath_ "../runtime"
_runtimeStart_ &hFFFF
_arraysStart_ &hFFFF
_codeRomType_ ROMvX0
_enable6BitAudioEmu_ ON 'experimental

const SMP_A = &h8000
const SMP_N = 64
const SMP_H = 64
const OSC_X = 49
const OSC_Y = (120 - 64)/2 + 4
const OSC_A = &h0800 + OSC_Y*256 + OSC_X

const OFFSCREEN=&h78A0

samplesAddr = SMP_A
def byte(SMP_A,        0, 1, SMP_N) = 0 'buffer A
def byte(SMP_A + &h40, 0, 1, SMP_N) = 0 'buffer B

'alloc offscreen scrolling areas, (<address>, <optional width>, <optional height>, <optional offset>)
alloc OFFSCREEN, 96, 8, &h0100

def byte(&h0702, x, 0.0, 360.0, 64, 4) = sin(x)*31.0 + 32.0


v1=0 : v2=v1 : v3=v2 : v4=v3
s1=1 : s2=s1 : s3=s2 : s4=s3
scrollPos=0 : scrollTmp=scrollPos

gosub initialise

init textScroll
sound off


f = 0

repeat
    wait
    set SOUND_TIMER, 5
    gosub sinewave
    gosub oscilloscope
    
    inc f.lo

    if (f.lo LSR 1) AND 1 then v1 = v1 + s1
    if (f.lo LSR 2) AND 1 then v2 = v2 + s2
    if (f.lo LSR 4) AND 1 then v3 = v3 + s3
    if (f.lo LSR 8) AND 1 then v4 = v4 + s4

    if (v1 > 63) OR (v1 < 0) then s1 = -s1
    if (v2 > 63) OR (v2 < 0) then s2 = -s2
    if (v3 > 63) OR (v3 < 0) then s3 = -s3
    if (v4 > 63) OR (v4 < 0) then s4 = -s4
forever


sinewave:
    sound on, 1, 2000, v1, 2 
    sound on, 2, 2500, v2, 2
    sound on, 3, 3000, v3, 2
    sound on, 4, 3500, v4, 2
return


oscilloscope:
    addrA = samplesAddr
    
    'erase waveform buffer B
    set FG_COLOUR, &h10
    sAddr = samplesAddr XOR &h40
    for i=SMP_N-1 downto 0
        asm
            LD      0x13    'sample waveform
            POKE+   _addrA  'save to buffer
            LDWI    _OSC_A
            OSCPX   _sAddr, _i
        endasm

        'pixelAddr = (((peek(sAddr + i)) AND &hFC) LSR 2) LSL 8
        'poke OSC_A + pixelAddr + i, &h10
    next i
    
    'draw waveform buffer A
    set FG_COLOUR, &h1D
    for i=SMP_N-1 &downto 0
        asm
            LDWI    _OSC_A
            OSCPX   _samplesAddr, _i
        endasm

        'pixelAddr = (((peek(samplesAddr + i)) AND &hFC) LSR 2) LSL 8
        'poke OSC_A + pixelAddr + i, &h1D
    next i
    
    'toggle waveform buffers
    samplesAddr = samplesAddr XOR &h40
return


textScroll:
    asm
        PUSH
        LDWI    &h01E1          'bottom banner
        STW     _scrollTmp
        LDW     _scrollPos
        POKE    _scrollTmp
        INC     _scrollPos
        POP
    endasm
ret


initialise:
    mode 2
    set BG_COLOUR, &h30
    cls
    cls OFFSCREEN, 96, 8
    
    set FG_COLOUR, &h20
    rectf OSC_X - 3, OSC_Y - 3, OSC_X + SMP_N + 2, OSC_Y + SMP_H + 2
    set FG_COLOUR, &h10
    rectf OSC_X, OSC_Y, OSC_X + SMP_N - 1, OSC_Y + SMP_H - 1
    
    set FG_COLOUR, &h3F
    tscroll off
    tclip off
    set FG_COLOUR, &h0F
    at 2, 112 : print "Sinewave Envelopes!.."
return
Haha, you don't need to be angry. It turned out that my feelings were wrong. I never deny the fact that I'm genuinely happy with Gigatron's sound (to be honest, Tetronis.gt1's music really needs improvement). So what is currently certain is that each audio channel of Gigatron can freely change the volume, and there is no fake thing.
I'll probably use gtbasic soon, as it seems to be a lot more functional than GLCC. A lot of the GLCC programs I've written so far cause the Gigatron to freeze for no apparent reason, especially with certain loops or nested calls to functions.
at67
Site Admin
Posts: 647
Joined: 14 May 2018, 08:29

Re: Explore the Gigatron music engine, using GLCC

Post by at67 »

denjhang wrote: 16 Mar 2022, 06:51 be honest, Tetronis.gt1's music really needs improvement). So what is currently certain is that each audio channel of Gigatron can freely change the
I'm quite happy with the Tetronis music to be honest, Tetronis was written really early on in the Gigatron's life when most of the current tools were non existent, i.e. it was the first real test of my assembler and music workflow. One of the reasons most of my games don't have volume in their music is that it makes the memory footprint of the MIDI tracks anywhere from 20% to 35% bigger and given that most of my games have so far pushed the 32KRAM Gigatron to within a few hundred bytes of the limits of it's free RAM, I choose extra functionality over music volume.

You also may be using a very early version of Tetronis that had issues with it's music, there are later versions that have timing, stuttering and noise issues resolved.
denjhang wrote: 16 Mar 2022, 06:51 I'll probably use gtbasic soon, as it seems to be a lot more functional than GLCC. A lot of the GLCC programs I've written so far cause the Gigatron to freeze for no apparent reason, especially with certain loops or nested calls to functions.
I wouldn't give up on GLCC so soon, I imagine it will eventually have the same feature set as gtBASIC, if not more. Also you really should be reporting any bugs or issues to the GLCC thread, I am sure lb3361 would be very interested in looking at them.
lb3361
Posts: 360
Joined: 17 Feb 2021, 23:07

Re: Explore the Gigatron music engine, using GLCC

Post by lb3361 »

I am interested to know about GLCC bugs. Best is to give me a small program that shows the bug. Also the glcc version as printed by "glcc -V".
denjhang
Posts: 54
Joined: 02 May 2021, 01:25
Location: yuenan
Contact:

Re: Explore the Gigatron music engine, using GLCC

Post by denjhang »

lb3361 wrote: 16 Mar 2022, 21:46 I am interested to know about GLCC bugs. Best is to give me a small program that shows the bug. Also the glcc version as printed by "glcc -V".
Both step5.c and step6.c make Gigatron stuck. Neither program played all the notes as expected, they were stuck on the first note or group of notes.
step11.c is what I modified to make the Gigatron play all the notes without getting stuck.
The GLCC version I'm using is the latest.
Attachments
step11_v5a_32k.gt1
(7.67 KiB) Downloaded 82 times
step11.c
(2.37 KiB) Downloaded 93 times
step6.c
(2.51 KiB) Downloaded 84 times
step5.c
(1.97 KiB) Downloaded 84 times
step6_v5a_32k.gt1
(7.67 KiB) Downloaded 76 times
step5_v5a_32k.gt1
(7.52 KiB) Downloaded 90 times
denjhang
Posts: 54
Joined: 02 May 2021, 01:25
Location: yuenan
Contact:

Re: Explore the Gigatron music engine, using GLCC

Post by denjhang »

Code: Select all

#include <stdio.h>
#include <gigatron/sys.h>
#include <gigatron/console.h>
#include <gigatron/libc.h>

int temp_fc,stepCount=0;	
int note_index=0;//note array index

struct Note
{
    int ch,noteoffset,volume,waveform,start_step;	
};

struct Note notes[] = 
{//channel, pitch, volume, waveform, start step
{1,62,62,2,0},
{3,54,62,1,0},
{4,69,62,3,0},
{4,1,0,2,28},
{3,1,0,2,29},

{1,78,30,2,30}, 
{1,78,20,2,35}, 
{1,78,10,2,40}, 
{1,78,5,2,45}, 
{1,64,40,2,60}, 
{1,64,20,2,80},
{1,66,50,2,90}, 
{1,66,30,2,115}, 
{1,0,0,2,120}, 
{1,74,60,2,150}, 
{1,76,40,2,180}, 
{1,78,60,2,210}, 
{1,74,40,2,240}, 
{1,69,60,2,270}, 
{1,69,50,2,280}, 
{1,69,40,2,290}, 
{1,69,20,2,295}, 
{1,0,0,2,310},
};

void init_channel(){
	channel1.keyL=0;
	channel2.keyL=0;
	channel3.keyL=0;
	channel4.keyL=0;
	
	channel1.keyH=0;
	channel2.keyH=0;
	channel3.keyH=0;
	channel4.keyH=0;	
	
}


void setup_channel(int ch, int noteoffset,int volume,int waveform)
{
	int wava=0;
	channel_t *chan = &channel(ch);
	
	if(waveform==2)wava=128-volume;
	else wava=0;
	
	chan->wavA = wava;
	chan->wavX = waveform;
	//Close the channel when noteoffset=0
	chan->keyL = SYS_Lup(notesTable + noteoffset);
	chan->keyH = SYS_Lup(notesTable + noteoffset + 1);
}


int play_note(int note_index,int stepCount){
	int play_stat=0;
	struct Note current_note=notes[note_index];
	struct Note next_note=notes[note_index+1];
	if(stepCount==current_note.start_step){
		setup_channel(current_note.ch,2*(current_note.noteoffset-1),current_note.volume,current_note.waveform);		
		play_stat=1;
	}else play_stat=0;
	if(next_note.start_step==current_note.start_step){		
		play_stat=2;			
	}		
	return 	play_stat;
	
}


int main()
{	


	temp_fc=frameCount=1;
	
	printf("Gigatron step Test\n");
	init_channel();
	while(1){
			if(temp_fc==frameCount);
			else if(frameCount!=temp_fc){
//				setup_channel(ch,2*notes,volume,waveform);
				int flag=0;
				flag=play_note(note_index,stepCount);
				if(flag==1)note_index++;
				while(flag==2){
//				if(flag==2){
					note_index+=2;
//					note_index+=1;
					flag=play_note(note_index-1,stepCount);	
					
//					note_index-=1;
//					note_index+=1;
//					printf("%i\n",note_index);
//				}
				}
				stepCount++;
				temp_fc=frameCount;				
//				printf("%i\n",stepCount);
				soundTimer=1;
			}
		}	
	return 0;
}


Pay attention to this piece of code, it is all the content of step11.c. I extract the repeatedly executed segment in the while loop of the main function out of the main function, and encapsulate it as a function, and then use this function to replace this segment of the main function, Gigatron will only be able to play the first note, followed by All of the notes are stuck.

Code: Select all

void play(int stepCount)
	{
		
		int flag=0;
		flag=play_note(note_index,stepCount);
		if(flag==1)note_index++;
		while(flag==2){
//		if(flag==2){
			note_index+=2;
//			note_index+=1;
			flag=play_note(note_index-1,stepCount);	
			}
		}
	}
	
	
	
int main()
{	


	temp_fc=frameCount=1;
	
	printf("Gigatron step Test\n");
	init_channel();
	while(1){
			if(temp_fc==frameCount);
			else if(frameCount!=temp_fc){
					void play(stepCount);
//					}
				}
				stepCount++;
				temp_fc=frameCount;				
//				printf("%i\n",stepCount);
				soundTimer=1;
			}
		}	
	return 0;
}

lb3361
Posts: 360
Joined: 17 Feb 2021, 23:07

Re: Explore the Gigatron music engine, using GLCC

Post by lb3361 »

First observation: the Gigatron does not hang, but the program loops. You can see that because the blinkenlights keep going .

Second observation: this is not a GLCC bug but a logic error in the program.

In the case of step5.c, the bug is that note_index is not incremented by the number of played record. As a result stepcount becomes greater than the start_step in the current record, and the program loops. A possible way to unblock it is:

Code: Select all

int play_note(int note_index,int stepCount){
	int play_stat=0;
	struct Note current_note=notes[note_index];
	struct Note next_note=notes[note_index+1];
	if(stepCount>=current_note.start_step){        // Note >= instead of ==
           ...
However the correct way is to count how many notes were played to increment node_index by the right amount. Note that recursion is problematic on a 32k machine because the stack space is limited. The stack grows downwards from 0x5fc because this is the largest continguous space available. It is always better to have a direct loop. However this is not the problem here. It seems to me that step6.c has the same problem, made more complicated by having two variants of play_note.

Anyway, here is a simple way to address the problem:

Code: Select all

struct Note *play_note(struct Note *note, int stepCount){
        while (stepCount == note->start_step) {
          channel_t *chan = &channel(note->ch);
          int off = note->noteoffset * 2;
          chan->wavA = 128 - note->volume;
          chan->wavX = note->waveform;
          chan->keyL = SYS_Lup(notesTable + off - 2);
          chan->keyH = SYS_Lup(notesTable + off - 1);
          note += 1;
        }
        return note;
}

int main()
{
  register struct Note *note = notes;
  register int tmpfc = frameCount;
  register int stepCount = 0;
  init_channel();
  while(note->ch){ // using ch==0 to mark the end
    while (frameCount == tmpfc)
      { /* wait */ }
    tmpfc = frameCount;
    note = play_note(note, stepCount);
    soundTimer = 1;
    stepCount += 1;
  }
}

denjhang
Posts: 54
Joined: 02 May 2021, 01:25
Location: yuenan
Contact:

Re: Explore the Gigatron music engine, using GLCC

Post by denjhang »

lb3361 wrote: 18 Mar 2022, 13:44 First observation: the Gigatron does not hang, but the program loops. You can see that because the blinkenlights keep going .

Second observation: this is not a GLCC bug but a logic error in the program.

In the case of step5.c, the bug is that note_index is not incremented by the number of played record. As a result stepcount becomes greater than the start_step in the current record, and the program loops. A possible way to unblock it is:

Code: Select all

int play_note(int note_index,int stepCount){
	int play_stat=0;
	struct Note current_note=notes[note_index];
	struct Note next_note=notes[note_index+1];
	if(stepCount>=current_note.start_step){        // Note >= instead of ==
           ...
However the correct way is to count how many notes were played to increment node_index by the right amount. Note that recursion is problematic on a 32k machine because the stack space is limited. The stack grows downwards from 0x5fc because this is the largest continguous space available. It is always better to have a direct loop. However this is not the problem here. It seems to me that step6.c has the same problem, made more complicated by having two variants of play_note.

Anyway, here is a simple way to address the problem:

Code: Select all

struct Note *play_note(struct Note *note, int stepCount){
        while (stepCount == note->start_step) {
          channel_t *chan = &channel(note->ch);
          int off = note->noteoffset * 2;
          chan->wavA = 128 - note->volume;
          chan->wavX = note->waveform;
          chan->keyL = SYS_Lup(notesTable + off - 2);
          chan->keyH = SYS_Lup(notesTable + off - 1);
          note += 1;
        }
        return note;
}

int main()
{
  register struct Note *note = notes;
  register int tmpfc = frameCount;
  register int stepCount = 0;
  init_channel();
  while(note->ch){ // using ch==0 to mark the end
    while (frameCount == tmpfc)
      { /* wait */ }
    tmpfc = frameCount;
    note = play_note(note, stepCount);
    soundTimer = 1;
    stepCount += 1;
  }
}


Thanks to your help, my music engine is now better. It seems at67 is right. So the music part of gigatron is very similar to a four-channel PSG, like the SN76489 with four preset waveforms
Attachments
step14.c
(1.62 KiB) Downloaded 84 times
step14_v5a_32k.gt1
(7.31 KiB) Downloaded 92 times
lb3361
Posts: 360
Joined: 17 Feb 2021, 23:07

Re: Explore the Gigatron music engine, using GLCC

Post by lb3361 »

I guess one could also try rudimentary enveloppe control. One could also write the waveforms on the fly, just in time, but there wouldn't be much cpu time left for anything else. At67 has really spent a lot of time working on sound. He probably has thought of all this already...
at67
Site Admin
Posts: 647
Joined: 14 May 2018, 08:29

Re: Explore the Gigatron music engine, using GLCC

Post by at67 »

lb3361 wrote: 19 Mar 2022, 01:17 I guess one could also try rudimentary enveloppe control. One could also write the waveforms on the fly, just in time, but there wouldn't be much cpu time left for anything else. At67 has really spent a lot of time working on sound. He probably has thought of all this already...
I do full envelope control at 60Hz, (or 30Hz depending on game), in PucMon and Invader to generate the sound effects. The audio was by far the most challenging part of those projects to get to a level I was satisfied with.

The way that I do it is that I analyse the waveform I am trying to replicate in a couple of steps:

1) I edit one of the Gigatron's internal 64byte waveform to as faithfully as possible, (sometimes this is impossible), replicate the repetitive high frequency components of that sound effect.

2) I use 30Hz/60Hz ADSR, (usually in a simple time sliced state machine), to as faithfully as possible replicate the low frequency components of the sound effect.

3) This is non trivial to do, but also a lot of fun, especially when your efforts get close to your goal.

4) See PucMon and Invader source, (it's pretty well commented), to see how I do it.

5) In some cases I come up with my own effect, e.g. the mother ship explosion in Invader; for this sound effect I took the original 2 channel warble/siren/alarm, composited it with a 2 channel explosion effect and then decreased the frequency of the warble to simulate the slowing down of the exploding mother ship's rotation whilst simultaneously fading it's and the explosions volume to zero.

I also have made a couple of examples of procedurally generated sound, (i.e on the fly), using ROMvX0 instructions and I was able to produce reasonable facsimiles of the two ByteBeat classics, Crowd and FourtyTwo and still have vCPU time left over to display the audio waveform on a SW oscilloscope.

My version of Crowd:
https://www.youtube.com/watch?v=PG_Sct_zBoE

Bytebeat history:
http://canonical.org/~kragen/bytebeat/
denjhang
Posts: 54
Joined: 02 May 2021, 01:25
Location: yuenan
Contact:

Re: Explore the Gigatron music engine, using GLCC

Post by denjhang »

lb3361 wrote: 19 Mar 2022, 01:17 I guess one could also try rudimentary enveloppe control. One could also write the waveforms on the fly, just in time, but there wouldn't be much cpu time left for anything else. At67 has really spent a lot of time working on sound. He probably has thought of all this already...
I want to make a vibrato-like effect, which requires the gigatron to emit audio frequencies that don't exist in the ROM's note table. These frequency values may lie between two adjacent pitches in the note table. So I made some small changes to store relative pitch changes in my note array. The increased and decreased pitch values are then written to keyH and keyL, but I find that my pitch adjustment algorithm seems to be somewhat imprecise. I'm not sure if keyL will exceed 100.

Code: Select all

struct Note
{
    int ch,noteoffset,volume,waveform,relative_pitch,start_step;	
};

struct Note notes[] = 
{//channel, pitch, volume, waveform,relative_pitch, start_step
{1, 62, 64, 2, 10, 0},
{1, 62, 64, 2, 20, 10},
{1, 62, 64, 2, 30, 20},
{1, 62, 64, 2, 20, 30},
{1, 62, 64, 2, 10, 40},
{1, 0, 0, 2, 0, 45},
{1, 62, 64, 2, -10, 50},
{1, 62, 64, 2, -20, 60},
{1, 62, 64, 2, -30, 70},
{1, 62, 64, 2, -20, 80},
{1, 62, 64, 2, -10, 90},
{1, 0, 0, 2, 0, 100},
};

struct Note *play_note(struct Note *note, int stepCount){
        while (stepCount == note->start_step) {
          channel_t *chan = &channel(note->ch);
          int off = note->noteoffset * 2;
		  int keyL = SYS_Lup(notesTable + off - 2);
		  int keyH = SYS_Lup(notesTable + off - 1);
		  int key_sum,keyL_new,keyH_new,key_sum_new;
		  
          chan->wavA = 128 - note->volume;
          chan->wavX = note->waveform;
		  
		  if(note->relative_pitch==0){
          chan->keyL = SYS_Lup(notesTable + off - 2);
          chan->keyH = SYS_Lup(notesTable + off - 1);			  			  
		  }else{
			  if(keyL>=100)key_sum=keyH*1000+keyL;
			  else key_sum=keyH*100+keyL;
			key_sum_new=key_sum+note->relative_pitch;
	
			keyL_new=key_sum_new%100;
			keyH_new=key_sum_new/100;
			
			chan->keyL = keyL_new;
		    chan->keyH = keyH_new;		

		  }
		printf("keyH=%i,keyL=%i\n",chan->keyH,chan->keyL);		 		  
        note += 1;
        }
        return note;
}
Attachments
step15.c
(2.12 KiB) Downloaded 76 times
Post Reply