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:
Sarah Rietkerk 2023-04-17 12:37:03 -07:00 коммит произвёл GitHub
Родитель 5bb91a1caf
Коммит 68cbd2feb6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 138 добавлений и 158 удалений

Двоичные данные
docs/static/libs/audio-recording.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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;
}
}
}

18
libs/audio-recording/shims.d.ts поставляемый
Просмотреть файл

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