pxt-ev3/libs/music/music.cpp
2018-05-15 11:21:24 -07:00

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);
}
}