-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdemo_chip.c
151 lines (128 loc) · 3.86 KB
/
demo_chip.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* Simple NES-like sound chip emulator that plays back file of logged writes */
#include "blip_buf.h"
#include "wave_writer.h"
#include <stdlib.h>
#include <stdio.h>
static const int sample_rate = 44100; /* 44.1 kHz sample rate*/
static const double clock_rate = 1789772.727; /* 1.78 MHz clock rate */
static blip_buffer_t* blip;
enum { period, volume, timbre }; /* indicies into Chan's regs [] */
typedef struct Chan Chan;
struct Chan
{
void (*run)( Chan*, int end_time );
int gain; /* overall volume of channel */
int regs [3]; /* period (clocks between deltas), volume, timbre */
int time; /* clock time of next delta */
int phase; /* position within waveform */
int amp; /* current amplitude in delta buffer */
};
/* Updates amplitude of waveform in delta buffer */
static void update_amp( Chan* m, int new_amp )
{
int delta = new_amp * m->gain - m->amp;
m->amp += delta;
blip_add_delta( blip, m->time, delta );
}
/* Runs square wave to end_time */
static void run_square( Chan* m, int end_time )
{
for ( ; m->time < end_time; m->time += m->regs [period] )
{
m->phase = (m->phase + 1) % 8;
update_amp( m, (m->phase < m->regs [timbre]) ? 0 : m->regs [volume] );
}
}
/* Runs triangle wave to end_time */
static void run_triangle( Chan* m, int end_time )
{
for ( ; m->time < end_time; m->time += m->regs [period] )
{
/* phase only increments when volume is non-zero (volume is otherwise
ignored)*/
if ( m->regs [volume] != 0 )
{
m->phase = (m->phase + 1) % 32;
update_amp( m, (m->phase < 16 ? m->phase : 31 - m->phase) );
}
}
}
/* Runs noise to end_time */
static void run_noise( Chan* m, int end_time )
{
/* phase is noise LFSR, which must never be zero */
if ( m->phase == 0 )
m->phase = 1;
for ( ; m->time < end_time; m->time += m->regs [period] )
{
m->phase = ((m->phase & 1) * m->regs [timbre]) ^ (m->phase >> 1);
update_amp( m, (m->phase & 1) * m->regs [volume] );
}
}
enum { master_vol = 65536 / 15 };
enum { chan_count = 4 };
static Chan chans [chan_count] =
{
{ run_square, master_vol * 26 / 100, { 10, 0, 0 }, 0, 0, 0 },
{ run_square, master_vol * 26 / 100, { 10, 0, 0 }, 0, 0, 0 },
{ run_triangle, master_vol * 30 / 100, { 10, 0, 0 }, 0, 0, 0 },
{ run_noise, master_vol * 18 / 100, { 10, 0, 0 }, 0, 0, 0 }
};
/* Runs channel to specified time, then writes data to channel's register */
static void write_chan( int time, int chan, int addr, int data )
{
Chan* c = &chans [chan];
c->run( c, time );
c->regs [addr] = data;
}
/* Ends time frame and flushes samples */
static void end_frame( int end_time )
{
int i;
for ( i = 0; i < chan_count; ++i )
{
chans [i].run( &chans [i], end_time );
chans [i].time -= end_time;
}
blip_end_frame( blip, end_time );
while ( blip_samples_avail( blip ) > 0 )
{
enum { temp_size = 1024 };
short temp [temp_size];
/* count is number of samples actually read (in case there
were fewer than temp_size samples actually available) */
int count = blip_read_samples( blip, temp, temp_size, 0 );
wave_write( temp, count );
}
}
static void init_sound( void )
{
blip = blip_new( sample_rate / 10 );
if ( blip == NULL )
exit( EXIT_FAILURE ); /* out of memory */
blip_set_rates( blip, clock_rate, sample_rate );
}
int main( void )
{
/* Open log of writes to sound hardware */
FILE* in = fopen( "demo_log.txt", "r" );
if ( in == NULL )
return EXIT_FAILURE;
init_sound();
/* Play back logged writes and record to wave sound file */
wave_open( sample_rate, "out.wav" );
while ( wave_sample_count() < 120 * sample_rate )
{
/* In an emulator these writes would be generated by the emulated CPU */
int time, chan, addr, data;
if ( fscanf( in, "%d %d %d %d\n", &time, &chan, &addr, &data ) < 4 )
break;
if ( chan < chan_count )
write_chan( time, chan, addr, data );
else
end_frame( time );
}
wave_close();
blip_delete( blip );
return 0;
}