-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ULPAudio.h
225 lines (190 loc) · 6.22 KB
/
ULPAudio.h
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// https://github.com/charlierobson/lasertag/blob/master/lt2/Audio.h
#pragma once
#ifdef ESP32
#include <esp32/ulp.h>
#include <driver/rtc_io.h>
#include <driver/dac.h>
#include <soc/rtc.h>
#include "assets.wav.h"
// THANKS BITLUNI!
// https://github.com/bitluni/ULPSoundESP32/tree/master/ULPSoundMonoSamples
unsigned char* samplePointers[2];
unsigned int sampleLengths[2];
enum Sounds {
HECTOR_SOUND
};
class SFX {
public:
SFX() :
_lastFilledWord(0),
_sampleDataLen(0) {
samplePointers[HECTOR_SOUND] = (unsigned char*)hector_wav_raw;
sampleLengths[HECTOR_SOUND] = hector_wav_raw_len;
}
void begin() {
//calculate the actual ULP clock
unsigned long rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 1000);
unsigned long rtc_fast_freq_hz = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;
//initialize DACs
dac_output_enable(DAC_CHANNEL_1);
dac_output_enable(DAC_CHANNEL_2);
dac_output_voltage(DAC_CHANNEL_1, 128);
dac_output_voltage(DAC_CHANNEL_2, 128);
int retAddress1 = 13;
int loopCycles = 84;
Serial.print("Real RTC clock: ");
Serial.println(rtc_fast_freq_hz);
int dt = (rtc_fast_freq_hz / samplingRate) - loopCycles;
if(dt < 0)
Serial.println("Sampling rate too high");
Serial.print("dt: ");
Serial.println(dt);
const ulp_insn_t mono[] = {
//reset offset register
I_MOVI(R3, 0),
//delay to get the right sampling rate
I_DELAY(dt), // 6 + dt
//reset sample index
I_MOVI(R0, 0), // 6
//write the index back to memory for the main cpu
I_ST(R0, R3, indexAddress), // 8
//divide index by two since we store two samples in each dword
I_RSHI(R2, R0, 1), // 6
//load the samples
I_LD(R1, R2, bufferStart), // 8
//get if odd or even sample
I_ANDI(R2, R0, 1), // 6
//multiply by 8
I_LSHI(R2, R2, 3), // 6
//shift the bits to have the right sample in the lower 8 bits
I_RSHR(R1, R1, R2), // 6
//mask the lower 8 bits
I_ANDI(R1, R1, 255), // 6
//multiply by 2
I_LSHI(R1, R1, 1), // 6
//add start position
I_ADDI(R1, R1, dacTableStart1),// 6
//jump to the dac opcode
I_BXR(R1), // 4
//here we get back from writing a sample
//increment the sample index
I_ADDI(R0, R0, 1), // 6
//if reached end of the buffer, jump relative to index reset
I_BGE(-13, totalSamples), // 4
//wait to get the right sample rate (2 cycles more to compensate the index reset)
I_DELAY((unsigned int)dt + 2), // 8 + dt
//if not, jump absolute to where index is written to memory
I_BXI(3)}; // 4
size_t load_addr = 0;
size_t size = sizeof(mono)/sizeof(ulp_insn_t);
ulp_process_macros_and_load(load_addr, mono, &size);
// this is how to get the opcodes
// for(int i = 0; i < size; i++)
// Serial.println(RTC_SLOW_MEM[i], HEX);
//create DAC opcode tables
for(int i = 0; i < 256; i++)
{
RTC_SLOW_MEM[dacTableStart1 + i * 2] = 0x1D4C0121 | (i << 10); //dac0
RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = 0x80000000 + retAddress1 * 4;
}
//set all samples to 128 (silence)
for(int i = 0; i < totalSampleWords; i++)
RTC_SLOW_MEM[bufferStart + i] = 0x8080;
//start
RTC_SLOW_MEM[indexAddress] = 0;
ulp_run(0);
while(RTC_SLOW_MEM[indexAddress] == 0)
delay(1);
}
void playSound(int soundID) {
_sampleDataPtr = samplePointers[soundID];
_sampleDataLen = sampleLengths[soundID];
}
void toggleMute() {
muted = !muted;
if( muted ) {
dac_output_disable(DAC_CHANNEL_1);
dac_output_disable(DAC_CHANNEL_2);
} else {
dac_output_enable(DAC_CHANNEL_1);
dac_output_enable(DAC_CHANNEL_2);
}
}
void playSoundSync(int soundID) {
playSound(soundID);
while(_sampleDataLen) {
update();
}
}
void flush() {
for(int i = 0; i < 400; ++i) {
update();
delay(1);
}
}
void update() {
int currentSample = RTC_SLOW_MEM[indexAddress] & 0xffff;
int currentWord = currentSample >> 1;
while(_lastFilledWord != currentWord)
{
unsigned int w = nextSample();
w |= nextSample() << 8;
RTC_SLOW_MEM[bufferStart + _lastFilledWord] = w;
_lastFilledWord++;
if(_lastFilledWord == totalSampleWords) {
_lastFilledWord = 0;
}
}
}
unsigned long samplelen = 0;
private:
unsigned char* _sampleDataPtr;
unsigned int _sampleDataLen;
int _lastFilledWord;
const unsigned long samplingRate = 44100;
const int opcodeCount = 17;
const int dacTableStart1 = 2048 - 512;
const int dacTableStart2 = dacTableStart1 - 512;
const int totalSampleWords = 2048 - 512 - (opcodeCount + 1);
const int totalSamples = totalSampleWords * 2;
const int indexAddress = opcodeCount;
const int bufferStart = indexAddress + 1;
bool muted = false;
unsigned long startsample;
unsigned long endsample;
unsigned char nextSample() {
if (!_sampleDataLen) return 0x80;
static long pos = 0;
if(pos >= _sampleDataLen) {
endsample = millis();
samplelen = endsample - startsample;
pos = 0;
}
if(pos == 0) {
startsample = millis();
}
return (unsigned char)((int)_sampleDataPtr[pos++] + 128);
}
/*
unsigned char nextSample()
{
if (!_sampleDataLen) return 0x80;
--_sampleDataLen;
return *_sampleDataPtr++;
}*/
};
#else
class SFX {
public:
void begin() {
}
void playSound(int sample) {
}
void playSoundSync(int sample) {
}
void flush() {
}
void update() {
}
};
#endif