forked from W4D/soundcube-firmware
Compare commits
16 Commits
v.0.0.5
...
sampler-si
| Author | SHA1 | Date | |
|---|---|---|---|
| 962c299e80 | |||
| cef092f494 | |||
| d44210ce59 | |||
| 7418cdfd35 | |||
| ad035e69e2 | |||
| d5ea2377f9 | |||
| b9130dc0ae | |||
| b4f504b21d | |||
| 6bcc37d3ee | |||
| 8858d41806 | |||
| ffdbe9fa6d | |||
| 6ef94390a5 | |||
| 6dd3acac2f | |||
| 4a04e95757 | |||
| 06642bd570 | |||
| 07323ec068 |
70
README.md
70
README.md
@ -13,9 +13,8 @@
|
|||||||
## SD Card contents
|
## SD Card contents
|
||||||
|
|
||||||
|
|
||||||
´´´
|
```
|
||||||
config.txt
|
config.txt
|
||||||
click.wav
|
|
||||||
sound/
|
sound/
|
||||||
├─ 1.wav
|
├─ 1.wav
|
||||||
├─ 2.wav
|
├─ 2.wav
|
||||||
@ -25,8 +24,11 @@ sound/
|
|||||||
├─ 6.wav
|
├─ 6.wav
|
||||||
├─ 7.wav
|
├─ 7.wav
|
||||||
├─ 8.wav
|
├─ 8.wav
|
||||||
|
ui/
|
||||||
|
├─ click.wav
|
||||||
|
├─ beep.wav
|
||||||
|
|
||||||
´´´
|
```
|
||||||
|
|
||||||
### config.txt
|
### config.txt
|
||||||
|
|
||||||
@ -34,45 +36,89 @@ Json formatted config file. For now only edge led color working.
|
|||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
|
"boxid":"",
|
||||||
"edge": {
|
"edge": {
|
||||||
"color":{
|
"color":{
|
||||||
"r":50,
|
"idle":{
|
||||||
"g":0,
|
"r":50,
|
||||||
"b":0
|
"g":0,
|
||||||
|
"b":50
|
||||||
|
},
|
||||||
|
"active":{
|
||||||
|
"r":0,
|
||||||
|
"g":50,
|
||||||
|
"b":50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ring": {
|
||||||
|
"color":{
|
||||||
|
"idle":{
|
||||||
|
"r":0,
|
||||||
|
"g":50,
|
||||||
|
"b":50
|
||||||
|
},
|
||||||
|
"active":{
|
||||||
|
"r":0,
|
||||||
|
"g":80,
|
||||||
|
"b":50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### click.wav
|
## Sound
|
||||||
|
|
||||||
Put a file called `click.wav` (not longer than a few seconds) in the root of the SD card and it will play when you press a button.
|
### UI
|
||||||
|
|
||||||
|
There are two UI sounds - `click.wav` and `beep.wav`.
|
||||||
|
|
||||||
|
- `click.wav` is played when a button is pressed other than the select button
|
||||||
|
- `beep.wav` is played when the rotary encoder select button is pressed
|
||||||
|
|
||||||
|
Put these files in the subfolder `ui` on the SD card.
|
||||||
|
|
||||||
|
#### Wave File Format for UI Sounds
|
||||||
|
|
||||||
|
`click.wav` needs to be **22050Hz (22kHz) 16bit Mono**
|
||||||
|
|
||||||
|
|
||||||
### Sounds
|
### Audio Out or Speakers
|
||||||
|
|
||||||
Put all sounds into the ´/sound´ subfolder. Name them ´1.wav, 2.wav, 3.wav...´
|
Put all sounds into the `/sound` subfolder. Name them `1.wav, 2.wav, 3.wav...`
|
||||||
|
|
||||||
|
|
||||||
## Wave File Format
|
#### Wave File Format for Audio Out and Speakers
|
||||||
|
|
||||||
|
Export all sounds as **48000Hz (48kHz) 16bit Stereo**.
|
||||||
|
|
||||||
Export all sounds except ´click.wav´ as **48000Hz (48kHz) 16bit Stereo**.
|
|
||||||
|
|
||||||
### Audacity
|
### Audacity
|
||||||
|
|
||||||
You can use Audacity to export all soundfiles to WAV format.
|
You can use Audacity to export all soundfiles to WAV format.
|
||||||
|
|
||||||
|
Download it here [Github](https://github.com/audacity/audacity/releases)
|
||||||
|
|
||||||
1. Load file into Audacity
|
1. Load file into Audacity
|
||||||
2. Select Track
|
2. Select Track
|
||||||
3. File -> Export Audio
|
3. File -> Export Audio
|
||||||
|
|
||||||
#### Format options in Audacity
|
#### Format options in Audacity
|
||||||
|
|
||||||
|
**Audio Out**
|
||||||
|
|
||||||
- WAV(Microsoft)
|
- WAV(Microsoft)
|
||||||
- Chanels: Stereo
|
- Chanels: Stereo
|
||||||
- Samplerate: 48000Hz
|
- Samplerate: 48000Hz
|
||||||
- Encoding: Signed 16-bit PCM
|
- Encoding: Signed 16-bit PCM
|
||||||
|
|
||||||
|
**UI**
|
||||||
|
|
||||||
|
- WAV(Microsoft)
|
||||||
|
- Channels: Mono
|
||||||
|
- Samplerate: 22050Hz
|
||||||
|
- Encoding: Signed 16-bit PCM
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class RingBuffer{
|
class RingBuffer{
|
||||||
public:
|
public:
|
||||||
RingBuffer() {}
|
RingBuffer() {}
|
||||||
@ -11,10 +10,10 @@ class RingBuffer{
|
|||||||
}
|
}
|
||||||
|
|
||||||
void begin(){
|
void begin(){
|
||||||
buffer = new T[bufferSize];
|
buffer = new int16_t[bufferSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool push(T data){
|
bool push(int16_t data){
|
||||||
if(counter < bufferSize){
|
if(counter < bufferSize){
|
||||||
buffer[write] = data;
|
buffer[write] = data;
|
||||||
write++; // % bufferSize;
|
write++; // % bufferSize;
|
||||||
@ -25,8 +24,8 @@ class RingBuffer{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
T pop(){
|
int16_t pop(){
|
||||||
T retval = 0;
|
int16_t retval = 0;
|
||||||
if(counter > 0) {
|
if(counter > 0) {
|
||||||
counter--;
|
counter--;
|
||||||
retval = buffer[read];
|
retval = buffer[read];
|
||||||
@ -35,23 +34,73 @@ class RingBuffer{
|
|||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t* getReadPointer(){
|
||||||
|
return &buffer[read];
|
||||||
|
}
|
||||||
|
|
||||||
|
void pointerPop(int nbytes){
|
||||||
|
for(int i=0; i<nbytes / 2; ++i){
|
||||||
|
if(counter > 0) {
|
||||||
|
counter--;
|
||||||
|
read++;// % bufferSize;
|
||||||
|
if(read == bufferSize) read = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushDMA(int32_t *source){
|
||||||
|
if(counter < bufferSize){
|
||||||
|
rp2040.memcpyDMA(&buffer[write], source, 4);
|
||||||
|
write += 2; // % bufferSize;
|
||||||
|
if(write == bufferSize) write = 0;
|
||||||
|
counter += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* getWritePointer(){
|
||||||
|
return &buffer[write];
|
||||||
|
}
|
||||||
|
|
||||||
|
void advance(int nbytes){
|
||||||
|
for(int i = 0; i < nbytes/2; ++i){
|
||||||
|
if(counter < (bufferSize-1)){
|
||||||
|
write++; // % bufferSize;
|
||||||
|
if(write == bufferSize) write = 0;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void popDMA(int32_t *target){
|
||||||
|
if(counter > 1) {
|
||||||
|
counter -= 2;
|
||||||
|
rp2040.memcpyDMA(target, &buffer[read], 4);
|
||||||
|
read += 2;
|
||||||
|
if(read >= bufferSize) read = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isEmpty(){
|
bool isEmpty(){
|
||||||
return counter == 0;
|
return counter == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isFull(){
|
bool isFull(){
|
||||||
return counter == bufferSize;
|
return counter == (bufferSize-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
int size(){
|
int size(){
|
||||||
return counter;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int remains(){
|
||||||
|
return (bufferSize-2) - counter;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t bufferSize = 0;
|
size_t bufferSize = 0;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
int write = 0;
|
int write = 0;
|
||||||
int read = 0;
|
int read = 0;
|
||||||
T *buffer;
|
int16_t *buffer;
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -74,16 +74,53 @@ struct WaveFile{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void readblock(){
|
bool readblock(){
|
||||||
uint8_t samplebyte[blockalign];
|
uint8_t samplebyte[blockalign];
|
||||||
|
|
||||||
file.read(samplebyte, blockalign);
|
file.read(samplebyte, blockalign);
|
||||||
|
|
||||||
if(!file.available() && loop) file.seek(44, SeekSet);
|
if(!file.available() && loop) file.seek(44, SeekSet);
|
||||||
|
if(!file.available() && !loop) {
|
||||||
|
file.seek(44, SeekSet);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for(int i = 0; i < blockalign; i+=2){
|
for(int i = 0; i < blockalign; i+=2){
|
||||||
int16_t sample = (samplebyte[i+1] << 8) + samplebyte[i];
|
int16_t sample = (samplebyte[i+1] << 8) + samplebyte[i];
|
||||||
buffer.push(sample);
|
buffer.push(sample);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readblockDMA(){
|
||||||
|
void *bufferStart = buffer.getWritePointer();
|
||||||
|
|
||||||
|
file.read((uint8_t*)bufferStart, 4);
|
||||||
|
buffer.advance(4);
|
||||||
|
|
||||||
|
if(!file.available() && loop) file.seek(44, SeekSet);
|
||||||
|
if(!file.available() && !loop) {
|
||||||
|
file.seek(44, SeekSet);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readblockSD(){
|
||||||
|
void *bufferStart = buffer.getWritePointer();
|
||||||
|
remains = buffer.remains();
|
||||||
|
|
||||||
|
if(remains > 512){
|
||||||
|
adv = file.readBytes((char*)bufferStart, 512);
|
||||||
|
buffer.advance(adv);
|
||||||
|
|
||||||
|
if(!file.available() && loop) file.seek(44, SeekSet);
|
||||||
|
if(!file.available() && !loop) {
|
||||||
|
file.seek(44, SeekSet);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t get(){
|
int16_t get(){
|
||||||
@ -94,6 +131,10 @@ struct WaveFile{
|
|||||||
file.seek(44, SeekSet);
|
file.seek(44, SeekSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset(){
|
||||||
|
file.seek(44, SeekSet);
|
||||||
|
}
|
||||||
|
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
uint16_t format = 0;
|
uint16_t format = 0;
|
||||||
uint32_t length = 0;
|
uint32_t length = 0;
|
||||||
@ -102,7 +143,10 @@ struct WaveFile{
|
|||||||
uint16_t blockalign = 0;
|
uint16_t blockalign = 0;
|
||||||
uint16_t bitspersample = 0;
|
uint16_t bitspersample = 0;
|
||||||
|
|
||||||
RingBuffer<int16_t> buffer;
|
int adv = 0;
|
||||||
|
int remains = 0;
|
||||||
|
|
||||||
|
RingBuffer buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaveStream{
|
class WaveStream{
|
||||||
@ -110,7 +154,7 @@ class WaveStream{
|
|||||||
WaveStream(){}
|
WaveStream(){}
|
||||||
|
|
||||||
void begin(){
|
void begin(){
|
||||||
wavefile.buffer.setSize(24000);
|
wavefile.buffer.setSize(2048);
|
||||||
wavefile.buffer.begin();
|
wavefile.buffer.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +165,10 @@ class WaveStream{
|
|||||||
|
|
||||||
void toggle(){playing = !playing;}
|
void toggle(){playing = !playing;}
|
||||||
|
|
||||||
void play(){playing = true;}
|
void play(bool reset = false){
|
||||||
|
if(reset) wavefile.reset();
|
||||||
|
playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
void stop(){
|
void stop(){
|
||||||
playing = false;
|
playing = false;
|
||||||
@ -131,19 +178,35 @@ class WaveStream{
|
|||||||
void pause(){playing = false;}
|
void pause(){playing = false;}
|
||||||
|
|
||||||
void stream(){
|
void stream(){
|
||||||
if(!wavefile.buffer.isFull() && playing){
|
int cnt = 0;
|
||||||
int cnt = 0;
|
while (!wavefile.buffer.isFull() && cnt < 1024) {
|
||||||
while (!wavefile.buffer.isFull() && cnt < 6000) {
|
bool ok = wavefile.readblockDMA();
|
||||||
wavefile.readblock();
|
if(!ok) playing = false;
|
||||||
cnt++;
|
cnt += 2;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void streamChunk(){
|
||||||
|
if(!wavefile.buffer.isFull()){
|
||||||
|
bool ok = wavefile.readblockSD();
|
||||||
|
if(!ok) playing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int16_t get(){
|
int16_t get(){
|
||||||
return playing ? wavefile.get() : 0;
|
return playing ? wavefile.get() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t* getPointer(){
|
||||||
|
int16_t* p = wavefile.buffer.getReadPointer();
|
||||||
|
wavefile.buffer.pointerPop(2);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getDMA(int32_t *samples){
|
||||||
|
if(playing) wavefile.buffer.popDMA(samples);
|
||||||
|
}
|
||||||
|
|
||||||
bool isPlaying(){
|
bool isPlaying(){
|
||||||
return playing;
|
return playing;
|
||||||
}
|
}
|
||||||
@ -151,8 +214,9 @@ class WaveStream{
|
|||||||
//private:
|
//private:
|
||||||
|
|
||||||
WaveFile wavefile;
|
WaveFile wavefile;
|
||||||
|
uint32_t cuelist[16];
|
||||||
|
|
||||||
bool playing = false;
|
bool playing = false;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user