Audio Blocks: Adding Sample Rate (#5041)
* Changed block colors and casing for dropdowns * Audio blocks: added sample rate, played with values to improve sound quality (this needs more work still). commented out uses of init in ts for initial refactoring * Enums following convention, more tweaking of mic gain since they were way too high * Added block ids, got rid of the sample rate getter to match patterns in the extension * Fixed setting both sample rate bug, got rid of get sample rate * Fixed bug where output playback wouldn't be set if the recording was already initialized, fixed booleans * Got rid of TS logic that's no longer needed, played with gain more * Got rid of playable.ts, fixed a typo * Updated the extension thumbnail
This commit is contained in:
Родитель
5bb91a1caf
Коммит
68cbd2feb6
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 10 KiB После Ширина: | Высота: | Размер: 7.1 KiB |
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"record": "Functions to operate the v2 on-board microphone and speaker.",
|
||||
"record.audioDuration": "Get how long the recorded audio clip is",
|
||||
"record.audioEvent": "Do something based on what the audio is doing",
|
||||
"record.audioIsPlaying": "Get whether the playback is active",
|
||||
"record.audioIsRecording": "Get whether the microphone is listening",
|
||||
"record.audioIsStopped": "Get whether the board is recording or playing back",
|
||||
|
@ -10,8 +9,12 @@
|
|||
"record.play": "Play the audio clip that is saved in the buffer",
|
||||
"record.playAudio": "Play recorded audio",
|
||||
"record.record": "Record an audio clip",
|
||||
"record.setBothSamples": "Set the sample rate for both input and output",
|
||||
"record.setInputSampleRate": "Change the sample rate of the splitter channel (audio input)",
|
||||
"record.setMicGain": "Change how sensitive the microphone is. This changes the recording quality!",
|
||||
"record.setMicrophoneGain": "Set sensitity of the microphone input",
|
||||
"record.setOutputSampleRate": "Change the sample rate of the mixer channel (audio output)",
|
||||
"record.setSampleRate": "Set the sample frequency for recording, playback, or both (default)\n* @param hz The sample frequency, in Hz",
|
||||
"record.startRecording": "Record an audio clip for a maximum of 3 seconds",
|
||||
"record.stop": "Stop recording"
|
||||
}
|
|
@ -3,17 +3,23 @@
|
|||
"record.AudioEvent.StartedRecording|block": "starts recording",
|
||||
"record.AudioEvent.StoppedPlaying|block": "stops playing",
|
||||
"record.AudioEvent.StoppedRecording|block": "stops recording",
|
||||
"record.AudioLevels.High|block": "high",
|
||||
"record.AudioLevels.Low|block": "low",
|
||||
"record.AudioLevels.Medium|block": "medium",
|
||||
"record.AudioRecordingMode.Playing|block": "playing",
|
||||
"record.AudioRecordingMode.Recording|block": "recording",
|
||||
"record.AudioRecordingMode.Stopped|block": "stopped",
|
||||
"record.AudioSampleRateScope.Everything|block": "everything",
|
||||
"record.AudioSampleRateScope.Playback|block": "playback",
|
||||
"record.AudioSampleRateScope.Recording|block": "recording",
|
||||
"record.AudioStatus.BufferFull|block": "full",
|
||||
"record.AudioStatus.Playing|block": "playing",
|
||||
"record.AudioStatus.Recording|block": "recording",
|
||||
"record.AudioStatus.Stopped|block": "stopped",
|
||||
"record.audioEvent|block": "on audio $eventType",
|
||||
"record.audioStatus|block": "audio is $status",
|
||||
"record.playAudio|block": "play recording",
|
||||
"record.playAudio|block": "play audio clip",
|
||||
"record.setMicGain|block": "set microphone sensitivity to $gain",
|
||||
"record.setSampleRate|block": "set sample rate to $hz || for $scope",
|
||||
"record.startRecording|block": "record audio clip",
|
||||
"record|block": "Record",
|
||||
"{id:category}Record": "Record"
|
||||
|
|
|
@ -29,6 +29,8 @@ using namespace pxt;
|
|||
namespace record {
|
||||
|
||||
static StreamRecording *recording = NULL;
|
||||
static SplitterChannel *splitterChannel = NULL;
|
||||
static MixerChannel *channel = NULL;
|
||||
|
||||
void enableMic() {
|
||||
uBit.audio.activateMic();
|
||||
|
@ -40,21 +42,28 @@ void disableMic() {
|
|||
uBit.audio.deactivateMic();
|
||||
}
|
||||
|
||||
void checkEnv() {
|
||||
|
||||
void checkEnv(int sampleRate = -1) {
|
||||
if (recording == NULL) {
|
||||
if (sampleRate == -1)
|
||||
sampleRate = 11000;
|
||||
MicroBitAudio::requestActivation();
|
||||
|
||||
recording = new StreamRecording(*uBit.audio.splitter);
|
||||
splitterChannel = uBit.audio.splitter->createChannel();
|
||||
|
||||
MixerChannel *channel = uBit.audio.mixer.addChannel(*recording, 22000);
|
||||
recording = new StreamRecording(*splitterChannel);
|
||||
|
||||
// By connecting to the mic channel, we activate it automatically, so shut it down again.
|
||||
disableMic();
|
||||
channel = uBit.audio.mixer.addChannel(*recording, sampleRate);
|
||||
|
||||
channel->setVolume(100.0);
|
||||
channel->setVolume(75.0);
|
||||
uBit.audio.mixer.setVolume(1000);
|
||||
uBit.audio.setSpeakerEnabled(true);
|
||||
}
|
||||
|
||||
if (recording != NULL && sampleRate != -1) {
|
||||
channel = uBit.audio.mixer.addChannel(*recording, sampleRate);
|
||||
channel->setVolume(75.0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,13 +113,13 @@ void erase() {
|
|||
void setMicrophoneGain(int gain) {
|
||||
switch (gain) {
|
||||
case 1:
|
||||
uBit.audio.processor->setGain(0.1);
|
||||
uBit.audio.processor->setGain(0.079f);
|
||||
break;
|
||||
case 2:
|
||||
uBit.audio.processor->setGain(0.5);
|
||||
uBit.audio.processor->setGain(0.2f);
|
||||
break;
|
||||
case 3:
|
||||
uBit.audio.processor->setGain(1);
|
||||
uBit.audio.processor->setGain(0.4f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -146,4 +155,32 @@ bool audioIsRecording() {
|
|||
bool audioIsStopped() {
|
||||
return recording->isStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the sample rate of the splitter channel (audio input)
|
||||
*/
|
||||
//%
|
||||
void setInputSampleRate(int sampleRate) {
|
||||
checkEnv();
|
||||
splitterChannel->requestSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change the sample rate of the mixer channel (audio output)
|
||||
*/
|
||||
//%
|
||||
void setOutputSampleRate(int sampleRate) {
|
||||
checkEnv(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sample rate for both input and output
|
||||
*/
|
||||
//%
|
||||
void setBothSamples(int sampleRate) {
|
||||
checkEnv(sampleRate);
|
||||
splitterChannel->requestSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
} // namespace record
|
|
@ -38,12 +38,24 @@ namespace record {
|
|||
StoppedRecording
|
||||
}
|
||||
|
||||
export enum AudioGain {
|
||||
export enum AudioLevels {
|
||||
//% block="low"
|
||||
Low = 1,
|
||||
//% block="medium"
|
||||
Medium,
|
||||
//% block="high"
|
||||
High
|
||||
}
|
||||
|
||||
export enum AudioSampleRateScope {
|
||||
//% block="everything"
|
||||
Everything,
|
||||
//% block="playback"
|
||||
Playback,
|
||||
//% block="recording"
|
||||
Recording
|
||||
}
|
||||
|
||||
export enum AudioRecordingMode {
|
||||
//% block="stopped"
|
||||
Stopped,
|
||||
|
@ -63,196 +75,93 @@ namespace record {
|
|||
//% block="full"
|
||||
BufferFull,
|
||||
}
|
||||
const AUDIO_EVENT_ID = 0xFF000
|
||||
const AUDIO_VALUE_OFFSET = 0x10
|
||||
|
||||
// Expressed in samples, as we can have varying recording and playback rates!
|
||||
const MAX_SAMPLES = 55000
|
||||
const INTERVAL_STEP = 100
|
||||
|
||||
// Shim state
|
||||
let _moduleMode: AudioRecordingMode = AudioRecordingMode.Stopped
|
||||
let _recordingFreqHz = 22000
|
||||
let _playbackFreqHz = 22000
|
||||
let _micGain: AudioGain = AudioGain.Medium
|
||||
|
||||
// Track if we have a simulator tick timer to use...
|
||||
let _isSetup: boolean = false
|
||||
let _memoryFill: number = 0
|
||||
let _playbackHead: number = 0
|
||||
|
||||
function _init(): void {
|
||||
if (_isSetup)
|
||||
return
|
||||
_isSetup = true
|
||||
|
||||
_moduleMode = AudioRecordingMode.Stopped
|
||||
_recordingFreqHz = 22000
|
||||
_playbackFreqHz = 22000
|
||||
_micGain = AudioGain.Medium
|
||||
music._onStopSound(stopRecording);
|
||||
|
||||
|
||||
control.runInParallel(() => {
|
||||
while (true) {
|
||||
|
||||
switch (_moduleMode) {
|
||||
case AudioRecordingMode.Playing:
|
||||
if (_playbackHead >= _memoryFill) {
|
||||
_playbackHead = 0
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
}
|
||||
else {
|
||||
_playbackHead += _playbackFreqHz / (1000 / INTERVAL_STEP)
|
||||
}
|
||||
break
|
||||
|
||||
case AudioRecordingMode.Recording:
|
||||
if (_memoryFill >= MAX_SAMPLES) {
|
||||
_memoryFill = MAX_SAMPLES
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
}
|
||||
else {
|
||||
_memoryFill += _recordingFreqHz / (1000 / INTERVAL_STEP)
|
||||
}
|
||||
break
|
||||
case AudioRecordingMode.Stopped:
|
||||
if (_memoryFill > 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
basic.pause(INTERVAL_STEP)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function emitEvent(type: AudioEvent): void {
|
||||
control.raiseEvent(AUDIO_EVENT_ID, AUDIO_VALUE_OFFSET + type, EventCreationMode.CreateAndFire)
|
||||
}
|
||||
|
||||
function _setMode(mode: AudioRecordingMode): void {
|
||||
switch (mode) {
|
||||
case AudioRecordingMode.Stopped:
|
||||
if (_moduleMode == AudioRecordingMode.Recording) {
|
||||
_moduleMode = AudioRecordingMode.Stopped
|
||||
return emitEvent(AudioEvent.StoppedRecording)
|
||||
}
|
||||
|
||||
if (_moduleMode == AudioRecordingMode.Playing) {
|
||||
_moduleMode = AudioRecordingMode.Stopped
|
||||
return emitEvent(AudioEvent.StoppedPlaying)
|
||||
}
|
||||
|
||||
_moduleMode = AudioRecordingMode.Stopped
|
||||
return
|
||||
|
||||
case AudioRecordingMode.Playing:
|
||||
if (_moduleMode !== AudioRecordingMode.Stopped) {
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
}
|
||||
|
||||
_moduleMode = AudioRecordingMode.Playing
|
||||
return emitEvent(AudioEvent.StartedPlaying)
|
||||
|
||||
case AudioRecordingMode.Recording:
|
||||
if (_moduleMode !== AudioRecordingMode.Stopped) {
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
}
|
||||
|
||||
_moduleMode = AudioRecordingMode.Recording
|
||||
return emitEvent(AudioEvent.StartedRecording)
|
||||
}
|
||||
}
|
||||
let _recordingPresent: boolean = false;
|
||||
|
||||
/**
|
||||
* Record an audio clip for a maximum of 3 seconds
|
||||
*/
|
||||
//% block="record audio clip"
|
||||
//% blockId="record_startRecording"
|
||||
//% weight=70
|
||||
export function startRecording(): void {
|
||||
_init()
|
||||
eraseRecording();
|
||||
record();
|
||||
_setMode(AudioRecordingMode.Recording)
|
||||
_recordingPresent = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Play recorded audio
|
||||
*/
|
||||
//% block="play recording"
|
||||
//% block="play audio clip"
|
||||
//% blockId="record_playAudio"
|
||||
//% weight=60
|
||||
//% shim=record::play
|
||||
export function playAudio(): void {
|
||||
_init()
|
||||
_playbackHead = 0
|
||||
if (!isEmpty()) {
|
||||
_setMode(AudioRecordingMode.Playing)
|
||||
play();
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//% shim=record::stop
|
||||
export function stopRecording(): void {
|
||||
_init()
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
_playbackHead = 0
|
||||
stop();
|
||||
return
|
||||
}
|
||||
|
||||
export function eraseRecording(): void {
|
||||
_init()
|
||||
_setMode(AudioRecordingMode.Stopped)
|
||||
_playbackHead = 0
|
||||
_memoryFill = 0
|
||||
_recordingPresent = false;
|
||||
erase();
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something based on what the audio is doing
|
||||
*/
|
||||
//% block="on audio $eventType"
|
||||
//% weight=10
|
||||
export function audioEvent(eventType: AudioEvent, handler: () => void): void {
|
||||
_init()
|
||||
control.onEvent(AUDIO_EVENT_ID, AUDIO_VALUE_OFFSET + eventType, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test what the audio is doing
|
||||
*/
|
||||
//% block="audio is $status"
|
||||
//% blockId="record_audioStatus"
|
||||
export function audioStatus(status: AudioStatus): boolean {
|
||||
_init();
|
||||
switch (status) {
|
||||
case AudioStatus.Playing:
|
||||
return _moduleMode === AudioRecordingMode.Playing;
|
||||
return audioIsPlaying();
|
||||
case AudioStatus.Recording:
|
||||
return _moduleMode === AudioRecordingMode.Recording;
|
||||
return audioIsRecording();
|
||||
case AudioStatus.Stopped:
|
||||
return _moduleMode === AudioRecordingMode.Stopped;
|
||||
return audioIsStopped();
|
||||
case AudioStatus.BufferFull:
|
||||
return _memoryFill > 0;
|
||||
return _recordingPresent;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmpty(): boolean {
|
||||
_init()
|
||||
return _memoryFill <= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Change how sensitive the microphone is. This changes the recording quality!
|
||||
*/
|
||||
//% block="set microphone sensitivity to $gain"
|
||||
//% gain.defl=Medium
|
||||
//% weight=40
|
||||
export function setMicGain(gain: AudioGain): void {
|
||||
_init()
|
||||
_micGain = gain
|
||||
//% blockId="record_setMicGain"
|
||||
//% weight=30
|
||||
export function setMicGain(gain: AudioLevels): void {
|
||||
setMicrophoneGain(gain);
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sample frequency for recording, playback, or both (default)
|
||||
*
|
||||
* @param hz The sample frequency, in Hz
|
||||
*/
|
||||
//% block="set sample rate to $hz || for $scope"
|
||||
//% blockId="record_setSampleRate"
|
||||
//% hz.min=1000 hz.max=22000 hz.defl=11000
|
||||
//% expandableArgumentMode="enabled"
|
||||
//% weight=40
|
||||
export function setSampleRate(hz: number, scope?: AudioSampleRateScope): void {
|
||||
switch (scope) {
|
||||
case AudioSampleRateScope.Playback:
|
||||
setOutputSampleRate(hz);
|
||||
break;
|
||||
|
||||
case AudioSampleRateScope.Recording:
|
||||
setInputSampleRate(hz);
|
||||
break;
|
||||
case AudioSampleRateScope.Everything:
|
||||
default:
|
||||
setBothSamples(hz);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,24 @@ declare namespace record {
|
|||
*/
|
||||
//% shim=record::audioIsStopped
|
||||
function audioIsStopped(): boolean;
|
||||
|
||||
/**
|
||||
* Change the sample rate of the splitter channel (audio input)
|
||||
*/
|
||||
//% shim=record::setInputSampleRate
|
||||
function setInputSampleRate(sampleRate: int32): void;
|
||||
|
||||
/**
|
||||
* Change the sample rate of the mixer channel (audio output)
|
||||
*/
|
||||
//% shim=record::setOutputSampleRate
|
||||
function setOutputSampleRate(sampleRate: int32): void;
|
||||
|
||||
/**
|
||||
* Set the sample rate for both input and output
|
||||
*/
|
||||
//% shim=record::setBothSamples
|
||||
function setBothSamples(sampleRate: int32): void;
|
||||
}
|
||||
|
||||
// Auto-generated. Do not edit. Really.
|
||||
|
|
|
@ -37,4 +37,11 @@ namespace pxsim.record {
|
|||
export function audioIsStopped(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function setSampleRate(rate: number): void {
|
||||
}
|
||||
|
||||
export function getSampleRate(): number {
|
||||
return 0;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче