204 lines
4.7 KiB
C++
204 lines
4.7 KiB
C++
#include "pxt.h"
|
|
#include "ev3const.h"
|
|
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#define NOTE_PAUSE 20
|
|
|
|
namespace music {
|
|
|
|
uint8_t currVolume = 50;
|
|
uint8_t *lmsSoundMMap;
|
|
|
|
int writeDev(void *data, int size) {
|
|
int fd = open("/dev/lms_sound", O_WRONLY);
|
|
int res = write(fd, data, size);
|
|
close(fd);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Set the output volume of the sound synthesizer.
|
|
* @param volume the volume 0...100, eg: 50
|
|
*/
|
|
//% weight=96
|
|
//% blockId=synth_set_volume block="set volume %volume"
|
|
//% parts="speaker" blockGap=8
|
|
//% volume.min=0 volume.max=100
|
|
//% help=music/set-volume
|
|
//% weight=1
|
|
void setVolume(int volume) {
|
|
currVolume = max(0, min(100, volume));
|
|
}
|
|
|
|
#define SOUND_CMD_BREAK 0
|
|
#define SOUND_CMD_TONE 1
|
|
#define SOUND_CMD_PLAY 2
|
|
#define SOUND_CMD_REPEAT 3
|
|
#define SOUND_CMD_SERVICE 4
|
|
|
|
struct ToneCmd {
|
|
uint8_t cmd;
|
|
uint8_t vol;
|
|
uint16_t freq;
|
|
uint16_t duration;
|
|
};
|
|
|
|
static void _stopSound() {
|
|
uint8_t cmd = SOUND_CMD_BREAK;
|
|
writeDev(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static void _playTone(uint16_t frequency, uint16_t duration, uint8_t volume) {
|
|
ToneCmd cmd;
|
|
cmd.cmd = SOUND_CMD_TONE;
|
|
cmd.vol = volume;
|
|
cmd.freq = frequency;
|
|
cmd.duration = duration;
|
|
// (*SoundInstance.pSound).Busy = TRUE;
|
|
writeDev(&cmd, sizeof(cmd));
|
|
}
|
|
|
|
static bool pumpMusicThreadRunning;
|
|
static pthread_mutex_t pumpMutex;
|
|
static Buffer currentSample;
|
|
static int samplePtr;
|
|
static pthread_cond_t sampleDone;
|
|
|
|
static void pumpMusic() {
|
|
if (currentSample == NULL) {
|
|
if (samplePtr > 0 && *lmsSoundMMap == 0) {
|
|
samplePtr = 0;
|
|
pthread_cond_broadcast(&sampleDone);
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint8_t buf[250]; // max size supported by hardware
|
|
buf[0] = SOUND_CMD_SERVICE;
|
|
int len = min((int)sizeof(buf) - 1, currentSample->length - samplePtr);
|
|
if (len == 0) {
|
|
decrRC(currentSample);
|
|
currentSample = NULL;
|
|
return;
|
|
}
|
|
|
|
memcpy(buf + 1, currentSample->data + samplePtr, len);
|
|
int rc = writeDev(buf, len + 1);
|
|
if (rc > 0) {
|
|
samplePtr += len;
|
|
}
|
|
}
|
|
|
|
static void *pumpMusicThread(void *dummy) {
|
|
while (true) {
|
|
sleep_core_us(10000);
|
|
pthread_mutex_lock(&pumpMutex);
|
|
pumpMusic();
|
|
pthread_mutex_unlock(&pumpMutex);
|
|
}
|
|
}
|
|
|
|
void playSample(Buffer buf) {
|
|
if (lmsSoundMMap == NULL) {
|
|
int fd = open("/dev/lms_sound", O_RDWR);
|
|
lmsSoundMMap = (uint8_t *)mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
close(fd);
|
|
}
|
|
|
|
if (!pumpMusicThreadRunning) {
|
|
pumpMusicThreadRunning = true;
|
|
pthread_t pid;
|
|
pthread_create(&pid, NULL, pumpMusicThread, NULL);
|
|
pthread_detach(pid);
|
|
}
|
|
|
|
stopUser();
|
|
pthread_mutex_lock(&pumpMutex);
|
|
*lmsSoundMMap = 1; // BUSY
|
|
uint8_t cmd[] = {SOUND_CMD_PLAY, (uint8_t)((currVolume / 33) + 1)};
|
|
writeDev(cmd, 2);
|
|
decrRC(currentSample);
|
|
currentSample = buf;
|
|
incrRC(buf);
|
|
samplePtr = 44; // WAV header is 44 bytes
|
|
pumpMusic();
|
|
pthread_cond_wait(&sampleDone, &pumpMutex);
|
|
pthread_mutex_unlock(&pumpMutex);
|
|
startUser();
|
|
}
|
|
|
|
/**
|
|
* Play a tone through the speaker for some amount of time.
|
|
* @param frequency pitch of the tone to play in Hertz (Hz), eg: Note.C
|
|
* @param ms tone duration in milliseconds (ms), eg: BeatFraction.Half
|
|
*/
|
|
//% help=music/play-tone
|
|
//% blockId=music_play_note block="play tone|at %note=device_note|for %duration=device_beat"
|
|
//% parts="headphone" async
|
|
//% blockNamespace=music
|
|
//% weight=76 blockGap=8
|
|
void playTone(int frequency, int ms) {
|
|
if (frequency <= 0) {
|
|
_stopSound();
|
|
if (ms >= 0)
|
|
sleep_ms(ms);
|
|
} else {
|
|
if (ms > 0) {
|
|
int d = max(1, ms - NOTE_PAUSE); // allow for short rest
|
|
int r = max(1, ms - d);
|
|
_playTone(frequency, d, currVolume);
|
|
sleep_ms(d + r);
|
|
} else {
|
|
// ring
|
|
_playTone(frequency, 0, currVolume);
|
|
}
|
|
}
|
|
sleep_ms(1);
|
|
}
|
|
|
|
/**
|
|
* Stop all sounds.
|
|
*/
|
|
//% help=music/stop-all-sounds
|
|
//% blockId=music_stop_all_sounds block="stop all sounds"
|
|
//% parts="headphone"
|
|
//% blockNamespace=music
|
|
//% weight=97
|
|
void stopAllSounds() {
|
|
if (currentSample) {
|
|
samplePtr = currentSample->length;
|
|
}
|
|
_stopSound();
|
|
}
|
|
|
|
/** Makes a sound bound to a buffer in WAV format. */
|
|
//%
|
|
Sound fromWAV(Buffer buf) {
|
|
incrRC(buf);
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
//% fixedInstances
|
|
namespace SoundMethods {
|
|
|
|
/** Returns the underlaying Buffer object. */
|
|
//% property
|
|
Buffer buffer(Sound snd) {
|
|
incrRC(snd);
|
|
return snd;
|
|
}
|
|
|
|
/** Play sound. */
|
|
//% promise
|
|
void play(Sound snd) {
|
|
music::playSample(snd);
|
|
}
|
|
} |