-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDaisy_SPC.cpp
More file actions
144 lines (120 loc) · 3.33 KB
/
Daisy_SPC.cpp
File metadata and controls
144 lines (120 loc) · 3.33 KB
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
#include "daisy_seed.h"
#include "lib/snes_spc/snes_spc/spc.h"
#ifdef BOOT_APP
// In BOOT_QSPI builds, libDaisy's qspi linker script does not map .qspiflash_data
// explicitly. Neutralize DSY_QSPI_DATA so song bytes live in normal app sections.
#ifdef DSY_QSPI_DATA
#undef DSY_QSPI_DATA
#endif
#define DSY_QSPI_DATA
#endif
#include "assets/song.h"
using namespace daisy;
DaisySeed hw;
SNES_SPC* g_spc = nullptr;
bool g_spc_ready = false;
namespace
{
enum ErrorCode
{
kErrorNone = 0,
kErrorNotLoaded = 1,
kErrorOther = 2,
};
volatile ErrorCode g_last_error_code = kErrorNone;
constexpr size_t kMaxBlock = 64;
constexpr float kSpcGain = 1.0f;
constexpr float kPcmScale = 1.0f / 32768.0f;
void WriteSilence(AudioHandle::OutputBuffer out, size_t size)
{
for(size_t i = 0; i < size; ++i)
{
out[0][i] = 0.0f;
out[1][i] = 0.0f;
}
}
float ClampAudio(float sample)
{ return sample > 1.0f ? 1.0f : (sample < -1.0f ? -1.0f : sample); }
void UpdateErrorBlinker()
{
static bool led_state = false;
static uint32_t last_toggle_ms = 0;
constexpr uint32_t kBlinkIntervalMs = 1000;
if(g_last_error_code == kErrorNone)
{
led_state = false;
hw.SetLed(false);
return;
}
const uint32_t now = System::GetNow();
if((now - last_toggle_ms) >= kBlinkIntervalMs)
{
last_toggle_ms = now;
led_state = !led_state;
}
hw.SetLed(led_state);
}
} // namespace
void AudioCallback(AudioHandle::InputBuffer in,
AudioHandle::OutputBuffer out,
size_t size)
{
(void)in;
if(!g_spc_ready || !g_spc || size > kMaxBlock)
{
WriteSilence(out, size);
return;
}
short pcm[2 * kMaxBlock] = {0};
if(spc_play(g_spc, static_cast<int>(size * 2), pcm) != nullptr)
{
// Runtime failure during spc_play, such as invalid memory access or similar.
g_last_error_code = kErrorOther;
WriteSilence(out, size);
return;
}
for(size_t i = 0; i < size; ++i)
{
const int sample_left = static_cast<int>(pcm[2 * i]);
const int sample_right = static_cast<int>(pcm[2 * i + 1]);
float left = sample_left * kPcmScale * kSpcGain;
float right = sample_right * kPcmScale * kSpcGain;
out[0][i] = ClampAudio(left);
out[1][i] = ClampAudio(right);
}
}
int main(void)
{
hw.Init();
hw.SetAudioBlockSize(16);
hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_32KHZ);
g_spc = spc_new();
g_spc_ready = false;
g_last_error_code = kErrorNotLoaded;
if(!g_spc)
{
// Allocation/init failed before an SPC could be loaded.
g_last_error_code = kErrorOther;
}
else
{
const char* load_err = spc_load_spc(g_spc, rawData, sizeof(rawData));
if(load_err == nullptr)
{
spc_set_tempo(g_spc, spc_tempo_unit);
spc_clear_echo(g_spc);
g_spc_ready = true;
g_last_error_code = kErrorNone;
}
else
{
// SPC data was not accepted by the loader.
g_last_error_code = kErrorNotLoaded;
}
}
hw.StartAudio(AudioCallback);
while(1)
{
UpdateErrorBlinker();
}
}